diff --git a/.gn b/.gn
index 3533e142..8954741 100644
--- a/.gn
+++ b/.gn
@@ -67,6 +67,7 @@
   "//headless:*",  # 107 errors
   "//ppapi/proxy:ipc_sources",  # 13 errors
   "//remoting/host/security_key:*",  # 10 errors
+  "//remoting/host/win:*",  # 43 errors
   "//sandbox/win:*",  # 7 errors
 
   "//third_party/icu/*",
diff --git a/DEPS b/DEPS
index 85e20da..85a95468 100644
--- a/DEPS
+++ b/DEPS
@@ -196,7 +196,7 @@
   # luci-go CIPD package version.
   # Make sure the revision is uploaded by infra-packagers builder.
   # https://ci.chromium.org/p/infra-internal/g/infra-packagers/console
-  'luci_go': 'git_revision:d51ccb6b754bd6f40127bf0d811e3c4510e254e9',
+  'luci_go': 'git_revision:467ab48f5ed9f3ef32ae17f5b73a117e0c86566b',
 
   # This can be overridden, e.g. with custom_vars, to build clang from HEAD
   # instead of downloading the prebuilt pinned revision.
@@ -240,15 +240,15 @@
   # 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': '10d50f649863aa166cd2e4734a7020bf8c0342f1',
+  'angle_revision': '7c072f7ed7ab788109f849e87026236e490d5875',
   # 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': '15f39445da55d75c72c6d5539e68fe00b5879fd0',
+  'swiftshader_revision': 'e4b7794ddbee1c3fa9a8199779ab6e605faa48c3',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'c716d47fba24d8da451d3db291ede1eb9d596bcb',
+  'pdfium_revision': '4d2a36f310de4a269a1838174f3c076550d9bd6d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -347,7 +347,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '149bd0feb33449801a54d63b6f90860a2a449043',
+  'dawn_revision': '1ee777fcd578efeef72cdc06678975eba4e33bd9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -528,7 +528,7 @@
     'packages': [
       {
         'package': 'chromium/chrome/test/data/autofill/captured_sites',
-        'version': 'gyjv5y8wWOcCtUIhGK9DmMItGKth1Xrb2Hqz31RaEDwC',
+        'version': 'T1tIsFMGPMQ9YL3t92oEL1r4wxGz5RHTSr45M0H9GwkC',
       }
     ],
     'condition': 'checkout_chromium_autofill_test_dependencies',
@@ -595,7 +595,7 @@
   },
 
     'src/ios/third_party/edo/src': {
-      'url': Var('chromium_git') + '/external/github.com/google/eDistantObject.git' + '@' + '3afaeda34eedf9aa833c56a499a0949881371b3a',
+      'url': Var('chromium_git') + '/external/github.com/google/eDistantObject.git' + '@' + 'f516fcc95fc870ea4c6e22af145c8ee3668f8444',
       'condition': 'checkout_ios',
   },
 
@@ -767,7 +767,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': '7SRcTc0Q-9zsf_C8ZCp3cocioXKHPErhPjYQCp-KdDAC',
+          'version': '6YnvOFZqQbSfmq9Bknb9CSKuND84c-TqnEATwNlvhqwC',
       },
     ],
     'condition': 'checkout_android',
@@ -1003,7 +1003,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '7a262eba201a30fbb82c4a034f5cebe21734ffa4',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '0a4dd4181ae2d3f2b77bb9f4ea4451d9209c8159',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1372,7 +1372,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '3dd5b80bc4f172dd82925bb259cb7c82348409c5',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + 'b9be95eef079e1c04520a05a3bd057bec59b77a3',
+    Var('chromium_git') + '/openscreen' + '@' + 'a26944325289323558d134b248610857fe84a2d1',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + 'bf21ccb1007bb531b45d9978919a56ea5059c245',
@@ -1389,7 +1389,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'a81ecdff94f3c7f8e111ad5d3804a70a334aa3bf',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '00e6f338d036b5d1ad547b979b612ec008fe3165',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1478,7 +1478,7 @@
       'packages': [
           {
               'package': 'fuchsia/third_party/aemu/linux-amd64',
-              'version': 'pwQrKzu72NdWp0pJ1seKkyxCFkbmgu1lFXdvWjQcDMkC'
+              'version': 'Nw0OOp4j9l4Sj0WpOmaRhNeJ137UfsLg0P1YrF8uzKwC'
           },
       ],
       'condition': 'host_os == "linux" and checkout_fuchsia',
@@ -1621,7 +1621,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '26da66e2987afd8fb426c8b83f7b96d500c3fb01',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '06bb4649dc60598f2bfb32604d7d6f7d2dfe3b3e',
+    Var('webrtc_git') + '/src.git' + '@' + '363b1745670c15ee85b952cb63ef94b36deef133',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1648,7 +1648,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/linux-amd64',
-          'version': 'Pc3OHV2QqhJ1IA5WQWr_kZ72iUVitbAX-iQN7j8sfcQC',
+          'version': 'H5Ax0_QmNml9Df-r61hCscndRQDsCim7j6-ST_Bqc7QC',
         },
       ],
       'dep_type': 'cipd',
@@ -1658,7 +1658,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/windows-amd64',
-          'version': 'XfCGeDXrOWGLHwo_HFFlERuqKHW2zZGUGnF1DzCyCP0C',
+          'version': 'P8MltnujOo50R-Kl3rxhuKLOYs02wz_YJUkzOruEDYwC',
         },
       ],
       'dep_type': 'cipd',
@@ -1668,7 +1668,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-amd64',
-          'version': 'KASmGKR0ayqtBOEyjhnO0Bt_YP_U602cxGcJOOjhAPoC',
+          'version': 'VP9yR_72z50Efyz4lp6uXyLhZowfvEScnoGf8RMWlHAC',
         },
       ],
       'dep_type': 'cipd',
@@ -1682,7 +1682,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@36a401c8f73fcab0b4bf7f5606a401e78c118b74',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@b6a1efa8ab3e84318cb837af17b4b7187afb21b5',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/WATCHLISTS b/WATCHLISTS
index 7be716f..8de8a43f 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -2748,7 +2748,8 @@
                                 'kayce@chromium.org',
                                 'lighthouse-eng-external+watch-speed-metrics@google.com',
                                 'lighthouse-eng+watch-speed-metrics@google.com',
-                                'rviscomi@chromium.org',
+                                'rviscomi@google.com',
+                                'web-devrel-vitals-team@google.com',
                                 'sullivan@chromium.org'],
     'spellcheck': ['rlp+watch@chromium.org',
                    'rouslan+spell@chromium.org',
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
index 463d7bfa..51cd297 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumFactoryProvider.java
@@ -448,7 +448,7 @@
                 SystemClock.uptimeMillis() - startTime);
 
         /* TODO(torne): re-enable this once the API change is sorted out
-        if (BuildInfo.isAtLeastS()) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
             // TODO: Use the framework constants as indices in timestamps array.
             startTime = mWebViewDelegate.getTimestamps()[0];
             RecordHistogram.recordTimesHistogram(
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index c0739ca..8ebaac4a0 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1091,6 +1091,8 @@
     "system/message_center/message_center_ui_delegate.h",
     "system/message_center/message_center_utils.cc",
     "system/message_center/message_center_utils.h",
+    "system/message_center/message_view_factory.cc",
+    "system/message_center/message_view_factory.h",
     "system/message_center/metrics_utils.cc",
     "system/message_center/metrics_utils.h",
     "system/message_center/notification_swipe_control_view.cc",
diff --git a/ash/capture_mode/capture_mode_controller.cc b/ash/capture_mode/capture_mode_controller.cc
index 70d3c8b..5d49fedc 100644
--- a/ash/capture_mode/capture_mode_controller.cc
+++ b/ash/capture_mode/capture_mode_controller.cc
@@ -21,6 +21,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/system/message_center/message_view_factory.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/bind.h"
@@ -52,7 +53,6 @@
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/notification.h"
 #include "ui/message_center/public/cpp/notification_delegate.h"
-#include "ui/message_center/views/message_view_factory.h"
 #include "ui/snapshot/snapshot.h"
 
 namespace ash {
@@ -345,14 +345,14 @@
           &CaptureModeController::RecordAndResetScreenshotsTakenInLastWeek,
           weak_ptr_factory_.GetWeakPtr()));
 
-  DCHECK(!message_center::MessageViewFactory::HasCustomNotificationViewFactory(
+  DCHECK(!MessageViewFactory::HasCustomNotificationViewFactory(
       kScreenShotNotificationType));
-  DCHECK(!message_center::MessageViewFactory::HasCustomNotificationViewFactory(
+  DCHECK(!MessageViewFactory::HasCustomNotificationViewFactory(
       kScreenRecordingNotificationType));
-  message_center::MessageViewFactory::SetCustomNotificationViewFactory(
+  MessageViewFactory::SetCustomNotificationViewFactory(
       kScreenShotNotificationType,
       base::BindRepeating(&CaptureModeNotificationView::CreateForImage));
-  message_center::MessageViewFactory::SetCustomNotificationViewFactory(
+  MessageViewFactory::SetCustomNotificationViewFactory(
       kScreenRecordingNotificationType,
       base::BindRepeating(&CaptureModeNotificationView::CreateForVideo));
 
@@ -364,9 +364,9 @@
   chromeos::PowerManagerClient::Get()->RemoveObserver(this);
   Shell::Get()->session_controller()->RemoveObserver(this);
   // Remove the custom notification view factories.
-  message_center::MessageViewFactory::ClearCustomNotificationViewFactory(
+  MessageViewFactory::ClearCustomNotificationViewFactory(
       kScreenShotNotificationType);
-  message_center::MessageViewFactory::ClearCustomNotificationViewFactory(
+  MessageViewFactory::ClearCustomNotificationViewFactory(
       kScreenRecordingNotificationType);
 
   DCHECK_EQ(g_instance, this);
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index bb41989..34a8515a 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -215,6 +215,10 @@
 const base::Feature kButtonARCNetworkDiagnostics{
     "ButtonARCNetworkDiagnostics", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enable or disable calendar view from the system tray.
+const base::Feature kCalendarView{"CalendarView",
+                                  base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Controls whether the camera privacy switch toasts and notification should be
 // displayed.
 const base::Feature kCameraPrivacySwitchNotifications{
@@ -1102,11 +1106,6 @@
 const base::Feature kVirtualKeyboardApi{"VirtualKeyboardApi",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Enable or disable using the floating virtual keyboard as the default option
-// on Chrome OS.
-const base::Feature kVirtualKeyboardFloatingDefault{
-    "VirtualKeyboardFloatingDefault", base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Enable or disable bordered key for virtual keyboard on Chrome OS.
 const base::Feature kVirtualKeyboardBorderedKey{
     "VirtualKeyboardBorderedKey", base::FEATURE_DISABLED_BY_DEFAULT};
@@ -1247,6 +1246,10 @@
   return base::FeatureList::IsEnabled(kBluetoothRevamp);
 }
 
+bool IsCalendarViewEnabled() {
+  return base::FeatureList::IsEnabled(kCalendarView);
+}
+
 bool IsCellularActivationUiEnabled() {
   return base::FeatureList::IsEnabled(kUpdatedCellularActivationUi);
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 2e8d3f6..be7a918d 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -93,6 +93,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kButtonARCNetworkDiagnostics;
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const base::Feature kCalendarView;
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kCameraPrivacySwitchNotifications;
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kCellularAllowPerNetworkRoaming;
@@ -414,8 +416,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kVirtualKeyboardApi;
 COMPONENT_EXPORT(ASH_CONSTANTS)
-extern const base::Feature kVirtualKeyboardFloatingDefault;
-COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kVirtualKeyboardBorderedKey;
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kVirtualKeyboardMultipaste;
diff --git a/ash/public/cpp/external_arc/BUILD.gn b/ash/public/cpp/external_arc/BUILD.gn
index 75ab55f4..98ee0e5 100644
--- a/ash/public/cpp/external_arc/BUILD.gn
+++ b/ash/public/cpp/external_arc/BUILD.gn
@@ -35,6 +35,7 @@
   ]
   defines = [ "ASH_PUBLIC_IMPLEMENTATION" ]
   deps = [
+    "//ash",
     "//ash/constants",
     "//ash/public/cpp",
     "//base",
diff --git a/ash/public/cpp/external_arc/message_center/DEPS b/ash/public/cpp/external_arc/message_center/DEPS
index 39592af..1eaaffd 100644
--- a/ash/public/cpp/external_arc/message_center/DEPS
+++ b/ash/public/cpp/external_arc/message_center/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+ash/system/message_center/message_view_factory.h",
   "+components/arc/mojom/notifications.mojom.h",
   "+components/arc/metrics/arc_metrics_constants.h",
   "+components/arc/session/connection_holder.h",
diff --git a/ash/public/cpp/external_arc/message_center/arc_notification_content_view_unittest.cc b/ash/public/cpp/external_arc/message_center/arc_notification_content_view_unittest.cc
index 52afa80..868e472e 100644
--- a/ash/public/cpp/external_arc/message_center/arc_notification_content_view_unittest.cc
+++ b/ash/public/cpp/external_arc/message_center/arc_notification_content_view_unittest.cc
@@ -19,6 +19,7 @@
 #include "ash/public/cpp/external_arc/message_center/mock_arc_notification_item.h"
 #include "ash/public/cpp/message_center/arc_notification_constants.h"
 #include "ash/shell.h"
+#include "ash/system/message_center/message_view_factory.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/system/status_area_widget_test_helper.h"
 #include "ash/system/unified/unified_system_tray.h"
@@ -43,7 +44,6 @@
 #include "ui/events/test/event_generator.h"
 #include "ui/message_center/public/cpp/message_center_constants.h"
 #include "ui/message_center/public/cpp/notification.h"
-#include "ui/message_center/views/message_view_factory.h"
 #include "ui/message_center/views/notification_control_buttons_view.h"
 #include "ui/message_center/views/padded_button.h"
 #include "ui/views/test/button_test_api.h"
@@ -137,7 +137,7 @@
 
     surface_manager_ = std::make_unique<ArcNotificationSurfaceManagerImpl>();
 
-    message_center::MessageViewFactory::ClearCustomNotificationViewFactory(
+    MessageViewFactory::ClearCustomNotificationViewFactory(
         kArcNotificationCustomViewType);
     ArcNotificationManager::SetCustomNotificationViewFactory();
   }
@@ -184,7 +184,7 @@
   CreateNotificationView(const Notification& notification) {
     std::unique_ptr<ArcNotificationView> notification_view(
         static_cast<ArcNotificationView*>(
-            message_center::MessageViewFactory::Create(notification)));
+            MessageViewFactory::Create(notification)));
 
     views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
     params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
@@ -344,13 +344,13 @@
 TEST_F(ArcNotificationContentViewTest, CloseButtonInMessageCenterView) {
   std::string notification_key("notification id");
 
-  message_center::MessageViewFactory::ClearCustomNotificationViewFactory(
+  MessageViewFactory::ClearCustomNotificationViewFactory(
       kArcNotificationCustomViewType);
 
   // Override MessageView factory to capture the created notification view in
   // |notification_view|.
   ArcNotificationView* notification_view = nullptr;
-  message_center::MessageViewFactory::SetCustomNotificationViewFactory(
+  MessageViewFactory::SetCustomNotificationViewFactory(
       kArcNotificationCustomViewType,
       base::BindLambdaForTesting(
           [&notification_view](const message_center::Notification& notification)
diff --git a/ash/public/cpp/external_arc/message_center/arc_notification_manager.cc b/ash/public/cpp/external_arc/message_center/arc_notification_manager.cc
index 44909e54a..2bdabc95 100644
--- a/ash/public/cpp/external_arc/message_center/arc_notification_manager.cc
+++ b/ash/public/cpp/external_arc/message_center/arc_notification_manager.cc
@@ -14,6 +14,7 @@
 #include "ash/public/cpp/external_arc/message_center/arc_notification_view.h"
 #include "ash/public/cpp/message_center/arc_notification_constants.h"
 #include "ash/public/cpp/message_center/arc_notification_manager_delegate.h"
+#include "ash/system/message_center/message_view_factory.h"
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/containers/contains.h"
@@ -22,7 +23,6 @@
 #include "ui/message_center/lock_screen/lock_screen_controller.h"
 #include "ui/message_center/message_center_impl.h"
 #include "ui/message_center/message_center_observer.h"
-#include "ui/message_center/views/message_view_factory.h"
 
 using arc::ConnectionHolder;
 using arc::MojoChannel;
@@ -133,7 +133,7 @@
 
 // static
 void ArcNotificationManager::SetCustomNotificationViewFactory() {
-  message_center::MessageViewFactory::SetCustomNotificationViewFactory(
+  MessageViewFactory::SetCustomNotificationViewFactory(
       kArcNotificationCustomViewType,
       base::BindRepeating(&CreateCustomMessageView));
 }
@@ -656,7 +656,7 @@
 
   instance_owner_->holder()->SetHost(this);
   instance_owner_->holder()->AddObserver(this);
-  if (!message_center::MessageViewFactory::HasCustomNotificationViewFactory(
+  if (!MessageViewFactory::HasCustomNotificationViewFactory(
           kArcNotificationCustomViewType)) {
     SetCustomNotificationViewFactory();
   }
diff --git a/ash/public/cpp/external_arc/message_center/arc_notification_view_unittest.cc b/ash/public/cpp/external_arc/message_center/arc_notification_view_unittest.cc
index f69232a..d88b9b0 100644
--- a/ash/public/cpp/external_arc/message_center/arc_notification_view_unittest.cc
+++ b/ash/public/cpp/external_arc/message_center/arc_notification_view_unittest.cc
@@ -12,6 +12,7 @@
 #include "ash/public/cpp/external_arc/message_center/mock_arc_notification_surface.h"
 #include "ash/public/cpp/message_center/arc_notification_constants.h"
 #include "ash/shell.h"
+#include "ash/system/message_center/message_view_factory.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/desks/desks_util.h"
 #include "base/bind.h"
@@ -29,7 +30,6 @@
 #include "ui/events/test/event_generator.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/notification.h"
-#include "ui/message_center/views/message_view_factory.h"
 #include "ui/message_center/views/notification_control_buttons_view.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/button/image_button.h"
@@ -69,9 +69,9 @@
 
     item_ = std::make_unique<MockArcNotificationItem>(kDefaultNotificationKey);
 
-    message_center::MessageViewFactory::ClearCustomNotificationViewFactory(
+    MessageViewFactory::ClearCustomNotificationViewFactory(
         kArcNotificationCustomViewType);
-    message_center::MessageViewFactory::SetCustomNotificationViewFactory(
+    MessageViewFactory::SetCustomNotificationViewFactory(
         kArcNotificationCustomViewType,
         base::BindRepeating(
             &ArcNotificationViewTest::CreateCustomMessageViewForTest,
@@ -81,7 +81,7 @@
 
     std::unique_ptr<ArcNotificationView> notification_view(
         static_cast<ArcNotificationView*>(
-            message_center::MessageViewFactory::Create(*notification)));
+            MessageViewFactory::Create(*notification)));
     notification_view_ = notification_view.get();
     surface_ =
         std::make_unique<MockArcNotificationSurface>(kDefaultNotificationKey);
diff --git a/ash/system/message_center/ash_message_popup_collection.cc b/ash/system/message_center/ash_message_popup_collection.cc
index 4c82db0..89ce7fe1 100644
--- a/ash/system/message_center/ash_message_popup_collection.cc
+++ b/ash/system/message_center/ash_message_popup_collection.cc
@@ -13,6 +13,7 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/system/message_center/fullscreen_notification_blocker.h"
+#include "ash/system/message_center/message_view_factory.h"
 #include "ash/system/message_center/metrics_utils.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_utils.h"
@@ -199,6 +200,15 @@
   }
 }
 
+message_center::MessagePopupView* AshMessagePopupCollection::CreatePopup(
+    const message_center::Notification& notification) {
+  bool a11_feedback_on_init =
+      notification.rich_notification_data()
+          .should_make_spoken_feedback_for_popup_updates;
+  return new message_center::MessagePopupView(
+      MessageViewFactory::Create(notification), this, a11_feedback_on_init);
+}
+
 void AshMessagePopupCollection::OnSlideOut(const std::string& notification_id) {
   metrics_utils::LogClosedByUser(notification_id, /*is_swipe=*/true,
                                  /*is_popup=*/true);
diff --git a/ash/system/message_center/ash_message_popup_collection.h b/ash/system/message_center/ash_message_popup_collection.h
index eabc3a3..2b37abe 100644
--- a/ash/system/message_center/ash_message_popup_collection.h
+++ b/ash/system/message_center/ash_message_popup_collection.h
@@ -69,6 +69,8 @@
   void NotifyPopupClosed(message_center::MessagePopupView* popup) override;
   void AnimationStarted() override;
   void AnimationFinished() override;
+  message_center::MessagePopupView* CreatePopup(
+      const message_center::Notification& notification) override;
 
   // Returns the current tray bubble height or 0 if there is no bubble.
   int tray_bubble_height_for_test() const { return tray_bubble_height_; }
diff --git a/ui/message_center/views/message_view_factory.cc b/ash/system/message_center/message_view_factory.cc
similarity index 73%
rename from ui/message_center/views/message_view_factory.cc
rename to ash/system/message_center/message_view_factory.cc
index 145c863..a5d7efe 100644
--- a/ui/message_center/views/message_view_factory.cc
+++ b/ash/system/message_center/message_view_factory.cc
@@ -1,8 +1,8 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
+// Copyright 2021 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ui/message_center/views/message_view_factory.h"
+#include "ash/system/message_center/message_view_factory.h"
 
 #include <vector>
 
@@ -11,7 +11,7 @@
 #include "ui/message_center/public/cpp/notification_types.h"
 #include "ui/message_center/views/notification_view_md.h"
 
-namespace message_center {
+namespace ash {
 
 namespace {
 
@@ -21,8 +21,8 @@
 base::LazyInstance<MessageViewCustomFactoryMap>::Leaky g_custom_view_factories =
     LAZY_INSTANCE_INITIALIZER;
 
-std::unique_ptr<MessageView> GetCustomNotificationView(
-    const Notification& notification) {
+std::unique_ptr<message_center::MessageView> GetCustomNotificationView(
+    const message_center::Notification& notification) {
   MessageViewCustomFactoryMap* factories = g_custom_view_factories.Pointer();
   auto iter = factories->find(notification.custom_view_type());
   DCHECK(iter != factories->end());
@@ -32,17 +32,18 @@
 }  // namespace
 
 // static
-MessageView* MessageViewFactory::Create(const Notification& notification) {
-  MessageView* notification_view = nullptr;
+message_center::MessageView* MessageViewFactory::Create(
+    const message_center::Notification& notification) {
+  message_center::MessageView* notification_view = nullptr;
   switch (notification.type()) {
-    case NOTIFICATION_TYPE_BASE_FORMAT:
-    case NOTIFICATION_TYPE_IMAGE:
-    case NOTIFICATION_TYPE_MULTIPLE:
-    case NOTIFICATION_TYPE_SIMPLE:
-    case NOTIFICATION_TYPE_PROGRESS:
+    case message_center::NOTIFICATION_TYPE_BASE_FORMAT:
+    case message_center::NOTIFICATION_TYPE_IMAGE:
+    case message_center::NOTIFICATION_TYPE_MULTIPLE:
+    case message_center::NOTIFICATION_TYPE_SIMPLE:
+    case message_center::NOTIFICATION_TYPE_PROGRESS:
       // Rely on default construction after the switch.
       break;
-    case NOTIFICATION_TYPE_CUSTOM:
+    case message_center::NOTIFICATION_TYPE_CUSTOM:
       notification_view = GetCustomNotificationView(notification).release();
       break;
     default:
@@ -58,7 +59,7 @@
   }
 
   if (!notification_view)
-    notification_view = new NotificationViewMD(notification);
+    notification_view = new message_center::NotificationViewMD(notification);
 
   return notification_view;
 }
@@ -85,4 +86,4 @@
   g_custom_view_factories.Get().erase(custom_view_type);
 }
 
-}  // namespace message_center
+}  // namespace ash
diff --git a/ui/message_center/views/message_view_factory.h b/ash/system/message_center/message_view_factory.h
similarity index 63%
rename from ui/message_center/views/message_view_factory.h
rename to ash/system/message_center/message_view_factory.h
index 02823f5..6bfcc7dc 100644
--- a/ui/message_center/views/message_view_factory.h
+++ b/ash/system/message_center/message_view_factory.h
@@ -1,11 +1,11 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
+// Copyright 2021 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UI_MESSAGE_CENTER_VIEWS_MESSAGE_VIEW_FACTORY_H_
-#define UI_MESSAGE_CENTER_VIEWS_MESSAGE_VIEW_FACTORY_H_
+#ifndef ASH_SYSTEM_MESSAGE_CENTER_MESSAGE_VIEW_FACTORY_H_
+#define ASH_SYSTEM_MESSAGE_CENTER_MESSAGE_VIEW_FACTORY_H_
 
-#include "ui/message_center/message_center_export.h"
+#include "ash/ash_export.h"
 
 #include <memory>
 #include <string>
@@ -13,23 +13,24 @@
 #include "base/callback_forward.h"
 
 namespace message_center {
-
 class MessageView;
 class Notification;
+}  // namespace message_center
+
+namespace ash {
 
 // Creates appropriate MessageViews for notifications depending on the
-// notification type. A notification is top level if it needs to be rendered
-// outside the browser window. No custom shadows are created for top level
-// notifications on Linux with Aura.
-class MESSAGE_CENTER_EXPORT MessageViewFactory {
+// notification type.
+class ASH_EXPORT MessageViewFactory {
  public:
   // A function that creates MessageView for a NOTIFICATION_TYPE_CUSTOM
   // notification.
   using CustomMessageViewFactoryFunction =
-      base::RepeatingCallback<std::unique_ptr<MessageView>(
-          const Notification&)>;
+      base::RepeatingCallback<std::unique_ptr<message_center::MessageView>(
+          const message_center::Notification&)>;
 
-  static MessageView* Create(const Notification& notification);
+  static message_center::MessageView* Create(
+      const message_center::Notification& notification);
 
   // Sets the function that will be invoked to create a custom notification view
   // for a specific |custom_view_type|. This should be a repeating callback.
@@ -45,6 +46,6 @@
       const std::string& custom_view_type);
 };
 
-}  // namespace message_center
+}  // namespace ash
 
-#endif  // UI_MESSAGE_CENTER_VIEWS_MESSAGE_VIEW_FACTORY_H_
+#endif  // ASH_SYSTEM_MESSAGE_CENTER_MESSAGE_VIEW_FACTORY_H_
diff --git a/ash/system/message_center/unified_message_list_view.cc b/ash/system/message_center/unified_message_list_view.cc
index 8e9a7f41..92e28a1 100644
--- a/ash/system/message_center/unified_message_list_view.cc
+++ b/ash/system/message_center/unified_message_list_view.cc
@@ -8,6 +8,7 @@
 #include "ash/public/cpp/metrics_util.h"
 #include "ash/system/message_center/message_center_style.h"
 #include "ash/system/message_center/message_center_utils.h"
+#include "ash/system/message_center/message_view_factory.h"
 #include "ash/system/message_center/metrics_utils.h"
 #include "ash/system/message_center/notification_swipe_control_view.h"
 #include "ash/system/message_center/unified_message_center_view.h"
@@ -23,7 +24,6 @@
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/notification_types.h"
 #include "ui/message_center/views/message_view.h"
-#include "ui/message_center/views/message_view_factory.h"
 #include "ui/views/background.h"
 #include "ui/views/border.h"
 #include "ui/views/layout/box_layout.h"
@@ -558,7 +558,7 @@
 
 MessageView* UnifiedMessageListView::CreateMessageView(
     const Notification& notification) {
-  auto* view = message_center::MessageViewFactory::Create(notification);
+  auto* view = MessageViewFactory::Create(notification);
   view->SetIsNested();
   view->AddObserver(this);
   message_center_view_->ConfigureMessageView(view);
diff --git a/ash/system/phonehub/phone_hub_notification_controller.cc b/ash/system/phonehub/phone_hub_notification_controller.cc
index 67a5230..ccd7472 100644
--- a/ash/system/phonehub/phone_hub_notification_controller.cc
+++ b/ash/system/phonehub/phone_hub_notification_controller.cc
@@ -9,6 +9,7 @@
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/system/message_center/message_view_factory.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/phonehub/phone_hub_metrics.h"
 #include "ash/system/tray/tray_popup_utils.h"
@@ -29,7 +30,6 @@
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/notification.h"
 #include "ui/message_center/public/cpp/notification_delegate.h"
-#include "ui/message_center/views/message_view_factory.h"
 #include "ui/message_center/views/notification_header_view.h"
 #include "ui/message_center/views/notification_view_md.h"
 #include "ui/views/controls/textfield/textfield.h"
@@ -207,11 +207,11 @@
 };
 
 PhoneHubNotificationController::PhoneHubNotificationController() {
-  if (message_center::MessageViewFactory::HasCustomNotificationViewFactory(
+  if (MessageViewFactory::HasCustomNotificationViewFactory(
           kNotificationCustomViewType))
     return;
 
-  message_center::MessageViewFactory::SetCustomNotificationViewFactory(
+  MessageViewFactory::SetCustomNotificationViewFactory(
       kNotificationCustomViewType,
       base::BindRepeating(
           &PhoneHubNotificationController::CreateCustomNotificationView,
diff --git a/ash/webui/shimless_rma/DEPS b/ash/webui/shimless_rma/DEPS
index d38ebf42..af020047 100644
--- a/ash/webui/shimless_rma/DEPS
+++ b/ash/webui/shimless_rma/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
   "+ash/grit/ash_shimless_rma_resources.h",
   "+chromeos/dbus/rmad",
+  "+chromeos/dbus/util",
   "+ui/resources",
   "+ui/web_dialogs",
   "+ui/chromeos/strings",
diff --git a/ash/webui/shimless_rma/backend/BUILD.gn b/ash/webui/shimless_rma/backend/BUILD.gn
index 215672b..4b0b671 100644
--- a/ash/webui/shimless_rma/backend/BUILD.gn
+++ b/ash/webui/shimless_rma/backend/BUILD.gn
@@ -15,6 +15,7 @@
     "//ash/webui/shimless_rma/mojom",
     "//chromeos/dbus/rmad",
     "//chromeos/dbus/rmad:rmad_proto",
+    "//chromeos/dbus/util",
     "//chromeos/services/network_config/public/mojom:mojom",
     "//components/qr_code_generator",
   ]
diff --git a/ash/webui/shimless_rma/backend/shimless_rma_service.cc b/ash/webui/shimless_rma/backend/shimless_rma_service.cc
index 10a14ee..ae907c53 100644
--- a/ash/webui/shimless_rma/backend/shimless_rma_service.cc
+++ b/ash/webui/shimless_rma/backend/shimless_rma_service.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "chromeos/dbus/rmad/rmad.pb.h"
 #include "chromeos/dbus/rmad/rmad_client.h"
+#include "chromeos/dbus/util/version_loader.h"
 #include "components/qr_code_generator/qr_code_generator.h"
 
 using chromeos::network_config::mojom::ConnectionStateType;
@@ -121,8 +122,9 @@
 
 void ShimlessRmaService::GetCurrentOsVersion(
     GetCurrentOsVersionCallback callback) {
-  // TODO(gavindodd): Get actual Chrome version.
-  std::move(callback).Run("Chrome OS 0.0.0.0");
+  // TODO(gavindodd): Decide whether to use full or short Chrome version.
+  std::move(callback).Run(chromeos::version_loader::GetVersion(
+      chromeos::version_loader::VERSION_FULL));
 }
 
 void ShimlessRmaService::CheckForOsUpdates(CheckForOsUpdatesCallback callback) {
diff --git a/ash/wm/desks/autotest_desks_api.cc b/ash/wm/desks/autotest_desks_api.cc
index 680abf1..9e534f61 100644
--- a/ash/wm/desks/autotest_desks_api.cc
+++ b/ash/wm/desks/autotest_desks_api.cc
@@ -140,7 +140,9 @@
   if (!DesksController::Get()->CanCreateDesks())
     return false;
 
-  DesksController::Get()->NewDesk(DesksCreationRemovalSource::kButton);
+  // Use |kKeyboard| as a source instead of |kButton| so the new desk's name is
+  // set to default.
+  DesksController::Get()->NewDesk(DesksCreationRemovalSource::kKeyboard);
   return true;
 }
 
diff --git a/base/android/build_info.cc b/base/android/build_info.cc
index 5d8a138e..be91430 100644
--- a/base/android/build_info.cc
+++ b/base/android/build_info.cc
@@ -86,11 +86,5 @@
   return Singleton<BuildInfo, BuildInfoSingletonTraits >::get();
 }
 
-// static
-bool BuildInfo::IsAtLeastS() {
-  JNIEnv* env = AttachCurrentThread();
-  return Java_BuildInfo_isAtLeastS(env);
-}
-
 }  // namespace android
 }  // namespace base
diff --git a/base/android/build_info.h b/base/android/build_info.h
index f75e8cf..2f154bbc 100644
--- a/base/android/build_info.h
+++ b/base/android/build_info.h
@@ -35,6 +35,7 @@
   SDK_VERSION_P = 28,
   SDK_VERSION_Q = 29,
   SDK_VERSION_R = 30,
+  SDK_VERSION_S = 31,
 };
 
 // BuildInfo is a singleton class that stores android build and device
@@ -51,10 +52,6 @@
   // should only be one instance of BuildInfo ever created.
   static BuildInfo* GetInstance();
 
-  // Checks if the device is running on a pre-release version of Android S or a
-  // release version of Android S or newer.
-  bool IsAtLeastS();
-
   // Const char* is used instead of std::strings because these values must be
   // available even if the process is in a crash state. Sadly
   // std::string.c_str() doesn't guarantee that memory won't be allocated when
diff --git a/base/android/java/src/org/chromium/base/BuildInfo.java b/base/android/java/src/org/chromium/base/BuildInfo.java
index 0fddbf24..d2ef543 100644
--- a/base/android/java/src/org/chromium/base/BuildInfo.java
+++ b/base/android/java/src/org/chromium/base/BuildInfo.java
@@ -239,7 +239,6 @@
      *
      * @return {@code true} if S APIs are available for use, {@code false} otherwise
      */
-    @CalledByNative
     @ChecksSdkIntAtLeast(api = 31, codename = "S")
     public static boolean isAtLeastS() {
         return VERSION.SDK_INT >= 31 || isAtLeastPreReleaseCodename("S", VERSION.CODENAME);
diff --git a/base/android/java/src/org/chromium/base/IntentUtils.java b/base/android/java/src/org/chromium/base/IntentUtils.java
index c87a38c0..4e3fcfb5 100644
--- a/base/android/java/src/org/chromium/base/IntentUtils.java
+++ b/base/android/java/src/org/chromium/base/IntentUtils.java
@@ -515,7 +515,7 @@
     public static int getPendingIntentMutabilityFlag(boolean mutable) {
         if (!mutable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             return ApiHelperForM.getPendingIntentImmutableFlag();
-        } else if (mutable && BuildInfo.isAtLeastS()) {
+        } else if (mutable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
             return FLAG_MUTABLE;
         }
         return 0;
diff --git a/build/config/c++/BUILD.gn b/build/config/c++/BUILD.gn
index 09a2722c..e7ed69e 100644
--- a/build/config/c++/BUILD.gn
+++ b/build/config/c++/BUILD.gn
@@ -102,13 +102,6 @@
     # does not match ours.
     defines += [ "_LIBCPP_NO_AUTO_LINK" ]
 
-    if (is_component_build) {
-      # TODO(crbug.com/1090975): Disable the exclude_from_explicit_instantiation
-      # to work around compiler bugs in the interaction between it and
-      # dllimport/dllexport.
-      defines += [ "_LIBCPP_HIDE_FROM_ABI=_LIBCPP_HIDDEN" ]
-    }
-
     # Add a debug visualizer for Microsoft's debuggers so that they can display
     # libc++ types well.
     if (libcxx_natvis_include) {
diff --git a/build/config/chromeos/ui_mode.gni b/build/config/chromeos/ui_mode.gni
index 208969e..aff59af 100644
--- a/build/config/chromeos/ui_mode.gni
+++ b/build/config/chromeos/ui_mode.gni
@@ -21,6 +21,10 @@
   # Don't set this unless you're sure you want it, because it'll double
   # your build time.
   also_build_ash_chrome = false
+
+  # Setting this to true when building ash-chrome will cause it to
+  # *also* build lacros-chrome in a subdirectory using an alternate toolchain.
+  also_build_lacros_chrome = false
 }
 
 # is_chromeos_{ash,lacros} is used to specify that it is specific to either
@@ -31,3 +35,6 @@
 # toolchains.
 is_chromeos_ash = is_chromeos && !chromeos_is_browser_only
 is_chromeos_lacros = is_chromeos && chromeos_is_browser_only
+
+# also_build_ash_chrome and also_build_lacros_chrome cannot be both true.
+assert(!(also_build_ash_chrome && also_build_lacros_chrome))
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 1e92da8..758d373 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -547,6 +547,13 @@
         ldflags += [ "-Wl,-mllvm,-instcombine-lower-dbg-declare=0" ]
       }
     }
+
+    # TODO(crbug.com/1235145): Investigate why/if this should be needed.
+    if (is_win) {
+      cflags += [ "/clang:-ffp-contract=off" ]
+    } else {
+      cflags += [ "-ffp-contract=off" ]
+    }
   }
 
   # C11/C++11 compiler flags setup.
@@ -1600,10 +1607,8 @@
         # Currently goma can not handle case sensitiveness for windows well.
         cflags += [ "-Wno-nonportable-include-path" ]
 
-        if (llvm_force_head_revision) {
-          # Warns in ATL headers; see https://crbug.com/1208419.
-          cflags += [ "-Wno-null-pointer-subtraction" ]
-        }
+        # Warns in ATL headers; see https://crbug.com/1208419.
+        cflags += [ "-Wno-null-pointer-subtraction" ]
       }
 
       if (current_toolchain == host_toolchain || !use_xcode_clang) {
diff --git a/build/toolchain/android/BUILD.gn b/build/toolchain/android/BUILD.gn
index 3299dceb..728885e 100644
--- a/build/toolchain/android/BUILD.gn
+++ b/build/toolchain/android/BUILD.gn
@@ -79,12 +79,14 @@
     # TODO(crbug.com/865376)
     use_clang_coverage = false
 
-    # This turns off all of the LaCrOS-specific flags. A LaCrOS build may use
-    # |ash_clang_x64| toolchain, which is a chromeos toolchain, to build
-    # Ash-Chrome in a subdirectory, and because chromeos toolchain uses android
-    # toolchain, which eventually resulted in that android toolchains being used
-    # inside a LaCrOS build.
+    # This turns off all of the LaCrOS-specific flags. A LaCrOS related build
+    # may use |ash_clang_x64| or |lacros_clang_x64| toolchain, which are
+    # chromeos toolchains, to build Ash-Chrome or Lacros-Chrome in a
+    # subdirectory, and because chromeos toolchain uses android toolchain, which
+    # eventually resulted in that android toolchains being used inside a LaCrOS
+    # build.
     also_build_ash_chrome = false
+    also_build_lacros_chrome = false
     chromeos_is_browser_only = false
     ozone_platform = ""
     ozone_platform_wayland = false
@@ -116,12 +118,14 @@
     # TODO(crbug.com/865376)
     use_clang_coverage = false
 
-    # This turns off all of the LaCrOS-specific flags. A LaCrOS build may use
-    # |ash_clang_x64| toolchain, which is a chromeos toolchain, to build
-    # Ash-Chrome in a subdirectory, and because chromeos toolchain uses android
-    # toolchain, which eventually resulted in that android toolchains being used
-    # inside a LaCrOS build.
+    # This turns off all of the LaCrOS-specific flags. A LaCrOS related build
+    # may use |ash_clang_x64| or |lacros_clang_x64| toolchain, which are
+    # chromeos toolchains, to build Ash-Chrome or Lacros-Chrome in a
+    # subdirectory, and because chromeos toolchain uses android toolchain, which
+    # eventually resulted in that android toolchains being used inside a LaCrOS
+    # build.
     also_build_ash_chrome = false
+    also_build_lacros_chrome = false
     chromeos_is_browser_only = false
     ozone_platform = ""
     ozone_platform_wayland = false
diff --git a/build/toolchain/linux/BUILD.gn b/build/toolchain/linux/BUILD.gn
index ee494546..db82d72 100644
--- a/build/toolchain/linux/BUILD.gn
+++ b/build/toolchain/linux/BUILD.gn
@@ -188,6 +188,22 @@
   }
 }
 
+# In an ash build, this toolchain is intended to be used as an alternate
+# toolchain to build lacros-Chrome in a subdirectory.
+clang_toolchain("lacros_clang_x64") {
+  toolchain_args = {
+    # This turns the toolchain into the "Lacros" build
+    current_os = "chromeos"
+    target_os = "chromeos"
+    current_cpu = current_cpu
+
+    # This turns on the LaCrOS-specific flag.
+    also_build_lacros_chrome = false
+    chromeos_is_browser_only = true
+    use_clang_coverage = false
+  }
+}
+
 gcc_toolchain("x64") {
   cc = "gcc"
   cxx = "g++"
diff --git a/build/toolchain/toolchain.gni b/build/toolchain/toolchain.gni
index 74f3a7c..6d8f162 100644
--- a/build/toolchain/toolchain.gni
+++ b/build/toolchain/toolchain.gni
@@ -38,12 +38,7 @@
 }
 
 declare_args() {
-  if (llvm_force_head_revision) {
-    clang_version = "14.0.0"
-  } else {
-    # TODO(crbug.com/1233845): Remove in the next Clang roll.
-    clang_version = "13.0.0"
-  }
+  clang_version = "14.0.0"
 }
 
 # Check target_os here instead of is_ios as this file is loaded for secondary
diff --git a/chrome/VERSION b/chrome/VERSION
index 96f61c8f..db70b3d 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=94
 MINOR=0
-BUILD=4598
+BUILD=4599
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index b932289..f8ce0b3 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -569,6 +569,7 @@
     "//third_party/android_deps:protobuf_lite_runtime_java",
     "//third_party/android_media:android_media_java",
     "//third_party/android_swipe_refresh:android_swipe_refresh_java",
+    "//third_party/androidx:androidx_activity_activity_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
     "//third_party/androidx:androidx_appcompat_appcompat_resources_java",
     "//third_party/androidx:androidx_browser_browser_java",
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java
index 9e8afba..dc0abb8 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/AutofillKeyboardAccessoryIntegrationTest.java
@@ -134,7 +134,9 @@
         if (enabledFeature == EnabledFeature.PORTALS) {
             mHelper.loadTestPage(PORTAL_TEST_PAGE, false, false, keyboardDelegate);
             SwapWebContentsObserver observer = new SwapWebContentsObserver();
-            mActivityTestRule.getActivity().getActivityTab().addObserver(observer);
+            TestThreadUtils.runOnUiThreadBlocking(() -> {
+                mActivityTestRule.getActivity().getActivityTab().addObserver(observer);
+            });
             DOMUtils.clickNode(mHelper.getWebContents(), "ACTIVATE");
             CriteriaHelper.pollUiThread(
                     () -> { return observer.mCallbackHelper.getCallCount() == 1; });
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
index d584d77..7b04cab 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -1234,27 +1234,7 @@
                     TabProperties.PAGE_INFO_ICON_DRAWABLE_ID, mSearchChipIconDrawableId);
         }
 
-        if (mMode == TabListMode.GRID && pseudoTab.hasRealTab() && !pseudoTab.isIncognito()) {
-            if (PriceTrackingUtilities.isTrackPricesOnTabsEnabled()
-                    && isUngroupedTab(pseudoTab.getId())) {
-                mModel.get(index).model.set(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER,
-                        new ShoppingPersistedTabDataFetcher(
-                                pseudoTab.getTab(), mPriceWelcomeMessageController));
-            } else {
-                mModel.get(index).model.set(
-                        TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER, null);
-            }
-            if (StoreTrackingUtilities.isStoreHoursOnTabsEnabled()
-                    && isUngroupedTab(pseudoTab.getId())) {
-                mModel.get(index).model.set(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER,
-                        new StorePersistedTabDataFetcher(pseudoTab.getTab()));
-            } else {
-                mModel.get(index).model.set(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER, null);
-            }
-        } else {
-            mModel.get(index).model.set(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER, null);
-            mModel.get(index).model.set(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER, null);
-        }
+        setupPersistedTabDataFetcherForTab(pseudoTab, index);
 
         updateFaviconForTab(pseudoTab, null);
         boolean forceUpdate = isSelected && !quickMode;
@@ -1523,6 +1503,8 @@
             mModel.add(index, new SimpleRecyclerViewAdapter.ListItem(mUiType, tabInfo));
         }
 
+        setupPersistedTabDataFetcherForTab(pseudoTab, index);
+
         updateFaviconForTab(pseudoTab, null);
 
         if (mThumbnailProvider != null && mVisible) {
@@ -1670,6 +1652,30 @@
         return mModel.indexFromId(tabId);
     }
 
+    private void setupPersistedTabDataFetcherForTab(PseudoTab pseudoTab, int index) {
+        if (mMode == TabListMode.GRID && pseudoTab.hasRealTab() && !pseudoTab.isIncognito()) {
+            if (PriceTrackingUtilities.isTrackPricesOnTabsEnabled()
+                    && isUngroupedTab(pseudoTab.getId())) {
+                mModel.get(index).model.set(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER,
+                        new ShoppingPersistedTabDataFetcher(
+                                pseudoTab.getTab(), mPriceWelcomeMessageController));
+            } else {
+                mModel.get(index).model.set(
+                        TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER, null);
+            }
+            if (StoreTrackingUtilities.isStoreHoursOnTabsEnabled()
+                    && isUngroupedTab(pseudoTab.getId())) {
+                mModel.get(index).model.set(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER,
+                        new StorePersistedTabDataFetcher(pseudoTab.getTab()));
+            } else {
+                mModel.get(index).model.set(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER, null);
+            }
+        } else {
+            mModel.get(index).model.set(TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER, null);
+            mModel.get(index).model.set(TabProperties.STORE_PERSISTED_TAB_DATA_FETCHER, null);
+        }
+    }
+
     @VisibleForTesting
     void updateFaviconForTab(PseudoTab pseudoTab, @Nullable Bitmap icon) {
         int modelIndex = mModel.indexFromId(pseudoTab.getId());
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/suggestions/TabSuggestionsClientFetcherTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/suggestions/TabSuggestionsClientFetcherTest.java
index 893cdd8..5a6186e 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/suggestions/TabSuggestionsClientFetcherTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/suggestions/TabSuggestionsClientFetcherTest.java
@@ -21,11 +21,11 @@
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.Callback;
+import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.testing.local.LocalRobolectricTestRunner;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -36,12 +36,15 @@
  * Test TabSuggestionsClientFetcher
  */
 @SuppressWarnings("ResultOfMethodCallIgnored")
-@RunWith(LocalRobolectricTestRunner.class)
+@RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 public class TabSuggestionsClientFetcherTest {
     @Rule
     public TestRule mProcessor = new Features.JUnitProcessor();
 
+    @Rule
+    public TestRule mCommandLineFlagsRule = CommandLineFlags.getTestRule();
+
     @Mock
     TabContext mTabContext;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
index cb8d08d..94ef8af 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
@@ -9,6 +9,7 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
+import android.os.Build;
 import android.os.Bundle;
 
 import androidx.annotation.CallSuper;
@@ -16,7 +17,6 @@
 import androidx.annotation.StyleRes;
 import androidx.appcompat.app.AppCompatActivity;
 
-import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
@@ -187,7 +187,7 @@
         // are not synchronized. Typically the first time overscroll is enabled, the following will
         // use the old value and then content will pick up the enabled value, causing one execution
         // of inconsistency.
-        if (BuildInfo.isAtLeastS()
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
                 && !CachedFeatureFlags.isEnabled(ChromeFeatureList.ELASTIC_OVERSCROLL)) {
             setTheme(R.style.ThemeOverlay_DisableOverscroll);
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index a34cfc7..4a1759a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -1593,13 +1593,22 @@
     @Override
     protected RootUiCoordinator createRootUiCoordinator() {
         return new TabbedRootUiCoordinator(this, this::onOmniboxFocusChanged,
-                mIntentMetadataOneshotSupplier, getShareDelegateSupplier(),
-                getActivityTabProvider(), mEphemeralTabCoordinatorSupplier,
-                mTabModelProfileSupplier, mBookmarkBridgeSupplier,
-                getOverviewModeBehaviorSupplier(), this::getContextualSearchManager,
+                getShareDelegateSupplier(), getActivityTabProvider(), mTabModelProfileSupplier,
+                mBookmarkBridgeSupplier, this::getContextualSearchManager,
                 getTabModelSelectorSupplier(), mStartSurfaceSupplier,
-                mLayoutStateProviderOneshotSupplier, mStartSurfaceParentTabSupplier,
-                getBrowserControlsManager(), getWindowAndroid(), mJankTracker);
+                mIntentMetadataOneshotSupplier, mLayoutStateProviderOneshotSupplier,
+                mStartSurfaceParentTabSupplier, getBrowserControlsManager(), getWindowAndroid(),
+                mJankTracker, getLifecycleDispatcher(), getLayoutManagerSupplier(),
+                /* menuOrKeyboardActionController= */ this, this::getActivityThemeColor,
+                getModalDialogManagerSupplier(), /* appMenuBlocker= */ this, this::supportsAppMenu,
+                this::supportsFindInPage, getTabCreatorManagerSupplier(), getFullscreenManager(),
+                getCompositorViewHolderSupplier(), getTabContentManagerSupplier(),
+                getOverviewModeBehaviorSupplier(), this::getSnackbarManager, getActivityType(),
+                this::isInOverviewMode, this::isWarmOnResume,
+                /* appMenuDelegate= */ this, /* statusBarColorProvider= */ this,
+                mEphemeralTabCoordinatorSupplier, getIntentRequestTracker(),
+                getControlContainerHeightResource(), getInsetObserverView(),
+                this::backShouldCloseTab);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/DEPS
index 4269da0..1b5dcd6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/DEPS
@@ -29,9 +29,6 @@
   "BaseCustomTabActivity\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
-  "BaseCustomTabRootUiCoordinator\.java": [
-    "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
-  ],
   "DisplayCutoutTabHelper\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
@@ -74,9 +71,6 @@
   "TrustedCdn\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
-  "TabbedRootUiCoordinator\.java": [
-    "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
-  ],
   "ChromeTabCreator\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
@@ -89,9 +83,6 @@
   "ViewShiftingActionBarDelegate\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
-  "RootUiCoordinator\.java": [
-    "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
-  ],
   "ArCompositorDelegateImpl\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
index 8c06113..8f39d2d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
@@ -42,7 +42,6 @@
 import org.chromium.base.ActivityState;
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ApplicationStatus;
-import org.chromium.base.BuildInfo;
 import org.chromium.base.BundleUtils;
 import org.chromium.base.Callback;
 import org.chromium.base.CommandLine;
@@ -318,7 +317,6 @@
     private SnackbarManager mSnackbarManager;
 
     private UpdateNotificationController mUpdateNotificationController;
-    private StatusBarColorController mStatusBarColorController;
 
     // Timestamp in ms when initial layout inflation begins
     private long mInflateInitialLayoutBeginMs;
@@ -495,7 +493,15 @@
                 new OneshotSupplierImpl<>(), new OneshotSupplierImpl<>(),
                 new OneshotSupplierImpl<>(),
                 () -> null, mBrowserControlsManagerSupplier.get(), getWindowAndroid(),
-                new DummyJankTracker());
+                new DummyJankTracker(), getLifecycleDispatcher(), getLayoutManagerSupplier(),
+                 /* menuOrKeyboardActionController= */ this, this::getActivityThemeColor,
+                getModalDialogManagerSupplier(), /* appMenuBlocker= */ this,
+                this::supportsAppMenu, this::supportsFindInPage, mTabCreatorManagerSupplier,
+                getFullscreenManager(), mCompositorViewHolderSupplier,
+                getTabContentManagerSupplier(), getOverviewModeBehaviorSupplier(),
+                this::getSnackbarManager, getActivityType(), this::isInOverviewMode,
+                this::isWarmOnResume, /* appMenuDelegate= */ this,
+                /* statusBarColorProvider= */ this);
         // clang-format on
     }
 
@@ -516,9 +522,9 @@
                         this::getSnackbarManager, mActivityTabProvider, getTabContentManager(),
                         getWindowAndroid(), mCompositorViewHolderSupplier, this,
                         this::getCurrentTabCreator, this::isCustomTab,
-                        getStatusBarColorController(), ScreenOrientationProvider.getInstance(),
-                        this::getNotificationManagerProxy, getTabContentManagerSupplier(),
-                        this::getActivityTabStartupMetricsTracker,
+                        mRootUiCoordinator.getStatusBarColorController(),
+                        ScreenOrientationProvider.getInstance(), this::getNotificationManagerProxy,
+                        getTabContentManagerSupplier(), this::getActivityTabStartupMetricsTracker,
                         /* CompositorViewHolder.Initializer */ this,
                         /* ChromeActivityNativeDelegate */ this, getModalDialogManagerSupplier(),
                         getBrowserControlsManager(), this::getSavedInstanceState,
@@ -532,9 +538,9 @@
                         getLifecycleDispatcher(), this::getSnackbarManager, mActivityTabProvider,
                         getTabContentManager(), getWindowAndroid(), mCompositorViewHolderSupplier,
                         this, this::getCurrentTabCreator, this::isCustomTab,
-                        getStatusBarColorController(), ScreenOrientationProvider.getInstance(),
-                        this::getNotificationManagerProxy, getTabContentManagerSupplier(),
-                        this::getActivityTabStartupMetricsTracker,
+                        mRootUiCoordinator.getStatusBarColorController(),
+                        ScreenOrientationProvider.getInstance(), this::getNotificationManagerProxy,
+                        getTabContentManagerSupplier(), this::getActivityTabStartupMetricsTracker,
                         /* CompositorViewHolder.Initializer */ this,
                         /* ChromeActivityNativeDelegate */ this, getModalDialogManagerSupplier(),
                         getBrowserControlsManager(), this::getSavedInstanceState,
@@ -751,11 +757,12 @@
     protected void onInitialLayoutInflationComplete() {
         mInflateInitialLayoutEndMs = SystemClock.elapsedRealtime();
 
-        getStatusBarColorController().updateStatusBarColor();
+        mRootUiCoordinator.getStatusBarColorController().updateStatusBarColor();
 
         ViewGroup rootView = (ViewGroup) getWindow().getDecorView().getRootView();
         mCompositorViewHolderSupplier.set(
                 (CompositorViewHolder) findViewById(R.id.compositor_view_holder));
+
         // If the UI was inflated on a background thread, then the CompositorView may not have been
         // fully initialized yet as that may require the creation of a handler which is not allowed
         // outside the UI thread. This call should fully initialize the CompositorView if it hasn't
@@ -794,7 +801,7 @@
 
         mTabModelSelectorSupplier.set(tabModelSelector);
         mActivityTabProvider.setTabModelSelector(tabModelSelector);
-        getStatusBarColorController().setTabModelSelector(tabModelSelector);
+        mRootUiCoordinator.getStatusBarColorController().setTabModelSelector(tabModelSelector);
 
         Pair<? extends TabCreator, ? extends TabCreator> tabCreators = createTabCreators();
         mTabCreatorManagerSupplier.set(
@@ -1037,27 +1044,6 @@
     }
 
     /**
-     * @return The {@link StatusBarColorController} that adjusts the status bar color.
-     */
-    public final StatusBarColorController getStatusBarColorController() {
-        // TODO(https://crbug.com/943371): Initialize in SystemUiCoordinator. This requires
-        // SystemUiCoordinator to be created before WebappActivty#onResume().
-        if (mStatusBarColorController == null) {
-            // Context is ready, but AsyncInitializationActivity#isTablet won't be ready until
-            // after #createComponent() is called. Using
-            // DeviceFormFactor.isNonMultiDisplayContextOnTablet(...) directly instead.
-            mStatusBarColorController = new StatusBarColorController(getWindow(),
-                    DeviceFormFactor.isNonMultiDisplayContextOnTablet(/* Context */ this),
-                    getResources(),
-                    /* StatusBarColorProvider */ this, getOverviewModeBehaviorSupplier(),
-                    getLifecycleDispatcher(), getActivityTabProvider(),
-                    mRootUiCoordinator.getTopUiThemeColorProvider());
-        }
-
-        return mStatusBarColorController;
-    }
-
-    /**
      * Returns theme color which should be used when:
      * - Web page does not provide a custom theme color.
      * AND
@@ -1148,7 +1134,7 @@
         ChromeSessionState.setIsInMultiWindowMode(
                 MultiWindowUtils.getInstance().isInMultiWindowMode(this));
 
-        if (BuildInfo.isAtLeastS()) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
             ensurePictureInPictureController();
         }
         if (mPictureInPictureController != null) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
index 509966b..b65aab3c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
@@ -148,14 +148,21 @@
 
     @Override
     protected RootUiCoordinator createRootUiCoordinator() {
+        // clang-format off
         return new BaseCustomTabRootUiCoordinator(this, getShareDelegateSupplier(),
-                ()
-                        -> mToolbarCoordinator,
-                ()
-                        -> mNavigationController,
                 getActivityTabProvider(), mTabModelProfileSupplier, mBookmarkBridgeSupplier,
                 this::getContextualSearchManager, getTabModelSelectorSupplier(),
-                getBrowserControlsManager(), getWindowAndroid());
+                getBrowserControlsManager(), getWindowAndroid(), getLifecycleDispatcher(),
+                getLayoutManagerSupplier(),
+                /* menuOrKeyboardActionController= */ this, this::getActivityThemeColor,
+                getModalDialogManagerSupplier(), /* appMenuBlocker= */ this, this::supportsAppMenu,
+                this::supportsFindInPage, getTabCreatorManagerSupplier(), getFullscreenManager(),
+                getCompositorViewHolderSupplier(), getTabContentManagerSupplier(),
+                getOverviewModeBehaviorSupplier(), this::getSnackbarManager, getActivityType(),
+                this::isInOverviewMode, this::isWarmOnResume,
+                /* appMenuDelegate= */ this, /* statusBarColorProvider= */ this,
+                () -> mToolbarCoordinator, () -> mNavigationController);
+        // clang-format on
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
index 8a77e733..8b3ebbd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
@@ -4,28 +4,45 @@
 
 package org.chromium.chrome.browser.customtabs;
 
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+
 import org.chromium.base.jank_tracker.DummyJankTracker;
+import org.chromium.base.supplier.BooleanSupplier;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.OneShotCallback;
+import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.ActivityTabProvider;
-import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.app.reengagement.ReengagementActivity;
 import org.chromium.chrome.browser.bookmarks.BookmarkBridge;
+import org.chromium.chrome.browser.compositor.CompositorViewHolder;
+import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl;
+import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
+import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
 import org.chromium.chrome.browser.customtabs.content.CustomTabActivityNavigationController;
 import org.chromium.chrome.browser.customtabs.features.toolbar.CustomTabToolbarCoordinator;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
 import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.chrome.browser.fullscreen.BrowserControlsManager;
+import org.chromium.chrome.browser.fullscreen.FullscreenManager;
+import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.reengagement.ReengagementNotificationController;
 import org.chromium.chrome.browser.share.ShareDelegate;
+import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.ui.RootUiCoordinator;
+import org.chromium.chrome.browser.ui.appmenu.AppMenuBlocker;
+import org.chromium.chrome.browser.ui.appmenu.AppMenuDelegate;
+import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
+import org.chromium.chrome.browser.ui.system.StatusBarColorController.StatusBarColorProvider;
+import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController;
 import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.ui.base.ActivityWindowAndroid;
+import org.chromium.ui.modaldialog.ModalDialogManager;
 
 /**
  * A {@link RootUiCoordinator} variant that controls UI for {@link BaseCustomTabActivity}.
@@ -34,20 +51,83 @@
     private final Supplier<CustomTabToolbarCoordinator> mToolbarCoordinator;
     private final Supplier<CustomTabActivityNavigationController> mNavigationController;
 
-    public BaseCustomTabRootUiCoordinator(ChromeActivity activity,
-            ObservableSupplier<ShareDelegate> shareDelegateSupplier,
-            Supplier<CustomTabToolbarCoordinator> customTabToolbarCoordinator,
-            Supplier<CustomTabActivityNavigationController> customTabNavigationController,
-            ActivityTabProvider tabProvider, ObservableSupplier<Profile> profileSupplier,
-            ObservableSupplier<BookmarkBridge> bookmarkBridgeSupplier,
-            Supplier<ContextualSearchManager> contextualSearchManagerSupplier,
-            ObservableSupplier<TabModelSelector> tabModelSelectorSupplier,
-            BrowserControlsManager browserControlsManager, ActivityWindowAndroid windowAndroid) {
+    /**
+     * Construct a new BaseCustomTabRootUiCoordinator.
+     * @param activity The activity whose UI the coordinator is responsible for.
+     * @param shareDelegateSupplier Supplies the {@link ShareDelegate}.
+     * @param tabProvider The {@link ActivityTabProvider} to get current tab of the activity.
+     * @param profileSupplier Supplier of the currently applicable profile.
+     * @param bookmarkBridgeSupplier Supplier of the bookmark bridge for the current profile.
+     * @param contextualSearchManagerSupplier Supplier of the {@link ContextualSearchManager}.
+     * @param tabModelSelectorSupplier Supplies the {@link TabModelSelector}.
+     * @param browserControlsManager Manages the browser controls.
+     * @param windowAndroid The current {@link WindowAndroid}.
+     * @param activityLifecycleDispatcher Allows observation of the activity lifecycle.
+     * @param layoutManagerSupplier Supplies the {@link LayoutManager}.
+     * @param menuOrKeyboardActionController Controls the menu or keyboard action controller.
+     * @param activityThemeColorSupplier Supplies the activity color theme.
+     * @param modalDialogManagerSupplier Supplies the {@link ModalDialogManager}.
+     * @param appMenuBlocker Controls the app menu blocking.
+     * @param supportsAppMenuSupplier Supplies the support state for the app menu.
+     * @param supportsFindInPage Supplies the support state for find in page.
+     * @param tabCreatorManagerSupplier Supplies the {@link TabCreatorManager}.
+     * @param fullscreenManager Manages the fullscreen state.
+     * @param compositorViewHolderSupplier Supplies the {@link CompositorViewHolder}.
+     * @param tabContentManagerSupplier Supplies the {@link TabContentManager}.
+     * @param overviewModeBehaviorSupplier Supplier of the overview mode manager.
+     * @param snackbarManagerSupplier Supplies the {@link SnackbarManager}.
+     * @param activityType The {@link ActivityType} for the activity.
+     * @param isInOverviewModeSupplier Supplies whether the app is in overview mode.
+     * @param isWarmOnResumeSupplier Supplies whether the app was warm on resume.
+     * @param appMenuDelegate The app menu delegate.
+     * @param statusBarColorProvider Provides the status bar color.
+     * @param customTabToolbarCoordinator Coordinates the custom tab toolbar.
+     * @param customTabNavigationController Controls the custom tab navigation.
+     */
+    public BaseCustomTabRootUiCoordinator(@NonNull AppCompatActivity activity,
+            @NonNull ObservableSupplier<ShareDelegate> shareDelegateSupplier,
+            @NonNull ActivityTabProvider tabProvider,
+            @NonNull ObservableSupplier<Profile> profileSupplier,
+            @NonNull ObservableSupplier<BookmarkBridge> bookmarkBridgeSupplier,
+            @NonNull Supplier<ContextualSearchManager> contextualSearchManagerSupplier,
+            @NonNull ObservableSupplier<TabModelSelector> tabModelSelectorSupplier,
+            @NonNull BrowserControlsManager browserControlsManager,
+            @NonNull ActivityWindowAndroid windowAndroid,
+            @NonNull ActivityLifecycleDispatcher activityLifecycleDispatcher,
+            @NonNull ObservableSupplier<LayoutManagerImpl> layoutManagerSupplier,
+            @NonNull MenuOrKeyboardActionController menuOrKeyboardActionController,
+            @NonNull Supplier<Integer> activityThemeColorSupplier,
+            @NonNull ObservableSupplier<ModalDialogManager> modalDialogManagerSupplier,
+            @NonNull AppMenuBlocker appMenuBlocker,
+            @NonNull BooleanSupplier supportsAppMenuSupplier,
+            @NonNull BooleanSupplier supportsFindInPage,
+            @NonNull Supplier<TabCreatorManager> tabCreatorManagerSupplier,
+            @NonNull FullscreenManager fullscreenManager,
+            @NonNull Supplier<CompositorViewHolder> compositorViewHolderSupplier,
+            @NonNull Supplier<TabContentManager> tabContentManagerSupplier,
+            @NonNull OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            @NonNull Supplier<SnackbarManager> snackbarManagerSupplier,
+            @ActivityType int activityType, @NonNull Supplier<Boolean> isInOverviewModeSupplier,
+            @NonNull Supplier<Boolean> isWarmOnResumeSupplier,
+            @NonNull AppMenuDelegate appMenuDelegate,
+            @NonNull StatusBarColorProvider statusBarColorProvider,
+            @NonNull Supplier<CustomTabToolbarCoordinator> customTabToolbarCoordinator,
+            @NonNull Supplier<CustomTabActivityNavigationController>
+                    customTabNavigationController) {
+        // clang-format off
         super(activity, null, shareDelegateSupplier, tabProvider, profileSupplier,
                 bookmarkBridgeSupplier, contextualSearchManagerSupplier, tabModelSelectorSupplier,
                 new OneshotSupplierImpl<>(), new OneshotSupplierImpl<>(),
-                new OneshotSupplierImpl<>(),
-                () -> null, browserControlsManager, windowAndroid, new DummyJankTracker());
+                new OneshotSupplierImpl<>(), () -> null,
+                browserControlsManager, windowAndroid, new DummyJankTracker(),
+                activityLifecycleDispatcher, layoutManagerSupplier, menuOrKeyboardActionController,
+                activityThemeColorSupplier, modalDialogManagerSupplier, appMenuBlocker,
+                supportsAppMenuSupplier, supportsFindInPage, tabCreatorManagerSupplier,
+                fullscreenManager, compositorViewHolderSupplier, tabContentManagerSupplier,
+                overviewModeBehaviorSupplier, snackbarManagerSupplier, activityType,
+                isInOverviewModeSupplier, isWarmOnResumeSupplier, appMenuDelegate,
+                statusBarColorProvider);
+        // clang-format on
         mToolbarCoordinator = customTabToolbarCoordinator;
         mNavigationController = customTabNavigationController;
     }
@@ -76,17 +156,13 @@
 
     @Override
     protected boolean shouldAllowThemingInNightMode() {
-        @ActivityType
-        int activityType = mActivity.getActivityType();
-        return activityType == ActivityType.TRUSTED_WEB_ACTIVITY
-                || activityType == ActivityType.WEB_APK;
+        return mActivityType == ActivityType.TRUSTED_WEB_ACTIVITY
+                || mActivityType == ActivityType.WEB_APK;
     }
 
     @Override
     protected boolean shouldAllowBrightThemeColors() {
-        @ActivityType
-        int activityType = mActivity.getActivityType();
-        return activityType == ActivityType.TRUSTED_WEB_ACTIVITY
-                || activityType == ActivityType.WEB_APK;
+        return mActivityType == ActivityType.TRUSTED_WEB_ACTIVITY
+                || mActivityType == ActivityType.WEB_APK;
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index 7967647..3d4a69ad 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -113,7 +113,7 @@
 
         FontPreloader.getInstance().onPostInflationStartupCustomTabActivity();
 
-        getStatusBarColorController().updateStatusBarColor();
+        mRootUiCoordinator.getStatusBarColorController().updateStatusBarColor();
 
         // Properly attach tab's InfoBarContainer to the view hierarchy if the tab is already
         // attached to a ChromeActivity, as the main tab might have been initialized prior to
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureController.java b/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureController.java
index 95e5575..0483baff 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureController.java
@@ -17,7 +17,6 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 
-import org.chromium.base.BuildInfo;
 import org.chromium.base.Callback;
 import org.chromium.base.Log;
 import org.chromium.base.MathUtils;
@@ -124,7 +123,7 @@
         mActivityTabProvider = activityTabProvider;
         mFullscreenManager = fullscreenManager;
 
-        mListenForAutoEnterability = BuildInfo.isAtLeastS();
+        mListenForAutoEnterability = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
         if (mListenForAutoEnterability) addObserversIfNeeded();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sharing/SharingNotificationUtil.java b/chrome/android/java/src/org/chromium/chrome/browser/sharing/SharingNotificationUtil.java
index ea047489..c9c72336 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sharing/SharingNotificationUtil.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sharing/SharingNotificationUtil.java
@@ -9,13 +9,13 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.os.Build;
 
 import androidx.annotation.DrawableRes;
 import androidx.annotation.Nullable;
 import androidx.core.app.NotificationCompat;
 
 import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
@@ -72,7 +72,7 @@
                         .setDefaults(Notification.DEFAULT_ALL);
 
         if (contentIntent != null) {
-            if (startsActivity && BuildInfo.isAtLeastS()) {
+            if (startsActivity && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                 // We can't use the NotificationIntentInterceptor to start Activities starting in
                 // Android S. Use the unmodified PendingIntent directly instead.
                 builder.setContentIntent(contentIntent.getPendingIntent());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sharing/click_to_call/ClickToCallMessageHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/sharing/click_to_call/ClickToCallMessageHandler.java
index e618067..4889cc2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sharing/click_to_call/ClickToCallMessageHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sharing/click_to_call/ClickToCallMessageHandler.java
@@ -15,7 +15,6 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.IntentUtils;
 import org.chromium.base.annotations.CalledByNative;
@@ -126,7 +125,7 @@
     private static PendingIntentProvider getContentIntentProvider(String phoneNumber) {
         Context context = ContextUtils.getApplicationContext();
 
-        if (BuildInfo.isAtLeastS()) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
             // We can't use the TapReceiver broadcast to start the dialer Activity starting in
             // Android S. Use the dial intent directly instead.
             return PendingIntentProvider.getActivity(context, /*requestCode=*/0,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
index 80d7ec24..4938716 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
@@ -10,12 +10,15 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.appcompat.app.AppCompatActivity;
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Callback;
 import org.chromium.base.CommandLine;
+import org.chromium.base.Function;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.jank_tracker.JankTracker;
+import org.chromium.base.supplier.BooleanSupplier;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.OneshotSupplier;
@@ -25,7 +28,6 @@
 import org.chromium.chrome.browser.ActivityTabProvider.ActivityTabTabObserver;
 import org.chromium.chrome.browser.ApplicationLifetime;
 import org.chromium.chrome.browser.SwipeRefreshHandler;
-import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.banners.AppBannerInProductHelpController;
 import org.chromium.chrome.browser.banners.AppBannerInProductHelpControllerFactory;
 import org.chromium.chrome.browser.bookmarks.BookmarkBridge;
@@ -34,6 +36,7 @@
 import org.chromium.chrome.browser.compositor.bottombar.ephemeraltab.EphemeralTabCoordinator;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
+import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
 import org.chromium.chrome.browser.continuous_search.ContinuousSearchContainerCoordinator;
 import org.chromium.chrome.browser.continuous_search.ContinuousSearchContainerCoordinator.HeightObserver;
@@ -42,9 +45,11 @@
 import org.chromium.chrome.browser.feed.webfeed.WebFeedFollowIntroController;
 import org.chromium.chrome.browser.findinpage.FindToolbarObserver;
 import org.chromium.chrome.browser.firstrun.FirstRunStatus;
+import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.fullscreen.BrowserControlsManager;
+import org.chromium.chrome.browser.fullscreen.FullscreenManager;
 import org.chromium.chrome.browser.gesturenav.BackActionDelegate;
 import org.chromium.chrome.browser.gesturenav.HistoryNavigationCoordinator;
 import org.chromium.chrome.browser.gesturenav.NavigationSheet;
@@ -53,6 +58,7 @@
 import org.chromium.chrome.browser.language.AppLanguagePromoDialog;
 import org.chromium.chrome.browser.language.LanguageAskPrompt;
 import org.chromium.chrome.browser.layouts.LayoutStateProvider;
+import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.merchant_viewer.MerchantTrustMetrics;
 import org.chromium.chrome.browser.merchant_viewer.MerchantTrustSignalsCoordinator;
@@ -75,6 +81,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabAssociatedApp;
 import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tasks.tab_management.PriceTrackingUtilities;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
@@ -82,8 +89,12 @@
 import org.chromium.chrome.browser.toolbar.ToolbarIntentMetadata;
 import org.chromium.chrome.browser.ui.RootUiCoordinator;
 import org.chromium.chrome.browser.ui.TabObscuringHandler;
+import org.chromium.chrome.browser.ui.appmenu.AppMenuBlocker;
+import org.chromium.chrome.browser.ui.appmenu.AppMenuDelegate;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
 import org.chromium.chrome.browser.ui.default_browser_promo.DefaultBrowserPromoUtils;
+import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
+import org.chromium.chrome.browser.ui.system.StatusBarColorController.StatusBarColorProvider;
 import org.chromium.chrome.browser.ui.tablet.emptybackground.EmptyBackgroundViewWrapper;
 import org.chromium.chrome.browser.version.ChromeVersionInfo;
 import org.chromium.chrome.browser.vr.VrModuleProvider;
@@ -95,11 +106,15 @@
 import org.chromium.chrome.features.start_surface.StartSurfaceState;
 import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
 import org.chromium.components.browser_ui.util.ComposedBrowserControlsVisibilityDelegate;
+import org.chromium.components.browser_ui.widget.InsetObserverView;
+import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController;
 import org.chromium.components.browser_ui.widget.TouchEventObserver;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
 import org.chromium.components.messages.MessageDispatcherProvider;
 import org.chromium.ui.base.ActivityWindowAndroid;
 import org.chromium.ui.base.DeviceFormFactor;
+import org.chromium.ui.base.IntentRequestTracker;
+import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.util.TokenHolder;
 
 /**
@@ -135,6 +150,10 @@
     private FindToolbarObserver mContinuousSearchFindToolbarObserver;
     private MerchantTrustSignalsCoordinator mMerchantTrustSignalsCoordinator;
     private CommerceSubscriptionsService mCommerceSubscriptionsService;
+    private final IntentRequestTracker mIntentRequestTracker;
+    private final int mControlContainerHeightResource;
+    private final InsetObserverView mInsetObserverView;
+    private final Function<Tab, Boolean> mBackButtonShouldCloseTabFn;
 
     private int mStatusIndicatorHeight;
     private int mContinuousSearchHeight;
@@ -176,46 +195,104 @@
      * @param activity The activity whose UI the coordinator is responsible for.
      * @param onOmniboxFocusChangedListener callback to invoke when Omnibox focus
      *         changes.
-     * @param intentMetadataOneshotSupplier Supplier with information about the launching intent.
-     * @param shareDelegateSupplier
+     * @param shareDelegateSupplier Supplies the {@link ShareDelegate}.
      * @param tabProvider The {@link ActivityTabProvider} to get current tab of the activity.
      * @param profileSupplier Supplier of the currently applicable profile.
-     * @param bookmarkBridgeSupplier Supplier of the bookmark bridge for the current profile.
-     * @param overviewModeBehaviorSupplier Supplier of the overview mode manager.
+     * @param bookmarkBridgeSupplier Supplier of the bookmark  bridge for the current profile.
      * @param contextualSearchManagerSupplier Supplier of the {@link ContextualSearchManager}.
+     * @param tabModelSelectorSupplier Supplies the {@link TabModelSelector}.
      * @param startSurfaceSupplier Supplier of the {@link StartSurface}.
+     * @param intentMetadataOneshotSupplier Supplier with information about the launching intent.
      * @param layoutStateProviderOneshotSupplier Supplier of the {@link LayoutStateProvider}.
      * @param startSurfaceParentTabSupplier Supplies the parent tab for the StartSurface.
      * @param browserControlsManager Manages the browser controls.
      * @param windowAndroid The current {@link WindowAndroid}.
+     * @param jankTracker Tracks the jank in the app.
+     * @param activityLifecycleDispatcher Allows observation of the activity lifecycle.
+     * @param layoutManagerSupplier Supplies the {@link LayoutManager}.
+     * @param menuOrKeyboardActionController Controls the menu or keyboard action controller.
+     * @param activityThemeColorSupplier Supplies the activity color theme.
+     * @param modalDialogManagerSupplier Supplies the {@link ModalDialogManager}.
+     * @param appMenuBlocker Controls the app menu blocking.
+     * @param supportsAppMenuSupplier Supplies the support state for the app menu.
+     * @param supportsFindInPage Supplies the support state for find in page.
+     * @param tabCreatorManagerSupplier Supplies the {@link TabCreatorManager}.
+     * @param fullscreenManager Manages the fullscreen state.
+     * @param compositorViewHolderSupplier Supplies the {@link CompositorViewHolder}.
+     * @param tabContentManagerSupplier Supplies the {@link TabContentManager}.
+     * @param overviewModeBehaviorSupplier Supplier of the overview mode manager.
+     * @param snackbarManagerSupplier Supplies the {@link SnackbarManager}.
+     * @param activityType The {@link ActivityType} for the activity.
+     * @param isInOverviewModeSupplier Supplies whether the app is in overview mode.
+     * @param isWarmOnResumeSupplier Supplies whether the app was warm on resume.
+     * @param appMenuDelegate The app menu delegate.
+     * @param statusBarColorProvider Provides the status bar color.
+     * @param ephemeralTabCoordinatorSupplier Supplies the {@link EphemeralTabCoordinator}.
+     * @param intentRequestTracker Tracks intent requests.
+     * @param controlContainerHeightResource The resource for the control container.
+     * @param insetObserverView The {@link InsetObserverView}.
+     * @param backButtonShouldCloseTabFn Function which supplies whether or not the back button
+     *         should close the tab.
      */
-    public TabbedRootUiCoordinator(ChromeActivity activity,
-            Callback<Boolean> onOmniboxFocusChangedListener,
-            OneshotSupplier<ToolbarIntentMetadata> intentMetadataOneshotSupplier,
-            ObservableSupplier<ShareDelegate> shareDelegateSupplier,
-            ActivityTabProvider tabProvider,
-            ObservableSupplierImpl<EphemeralTabCoordinator> ephemeralTabCoordinatorSupplier,
-            ObservableSupplier<Profile> profileSupplier,
-            ObservableSupplier<BookmarkBridge> bookmarkBridgeSupplier,
-            OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
-            Supplier<ContextualSearchManager> contextualSearchManagerSupplier,
-            ObservableSupplier<TabModelSelector> tabModelSelectorSupplier,
-            OneshotSupplier<StartSurface> startSurfaceSupplier,
-            OneshotSupplier<LayoutStateProvider> layoutStateProviderOneshotSupplier,
-            Supplier<Tab> startSurfaceParentTabSupplier,
+    public TabbedRootUiCoordinator(@NonNull AppCompatActivity activity,
+            @Nullable Callback<Boolean> onOmniboxFocusChangedListener,
+            @NonNull ObservableSupplier<ShareDelegate> shareDelegateSupplier,
+            @NonNull ActivityTabProvider tabProvider,
+            @NonNull ObservableSupplier<Profile> profileSupplier,
+            @NonNull ObservableSupplier<BookmarkBridge> bookmarkBridgeSupplier,
+            @NonNull Supplier<ContextualSearchManager> contextualSearchManagerSupplier,
+            @NonNull ObservableSupplier<TabModelSelector> tabModelSelectorSupplier,
+            @NonNull OneshotSupplier<StartSurface> startSurfaceSupplier,
+            @NonNull OneshotSupplier<ToolbarIntentMetadata> intentMetadataOneshotSupplier,
+            @NonNull OneshotSupplier<LayoutStateProvider> layoutStateProviderOneshotSupplier,
+            @NonNull Supplier<Tab> startSurfaceParentTabSupplier,
             @NonNull BrowserControlsManager browserControlsManager,
-            ActivityWindowAndroid windowAndroid, JankTracker jankTracker) {
+            @NonNull ActivityWindowAndroid windowAndroid, @NonNull JankTracker jankTracker,
+            @NonNull ActivityLifecycleDispatcher activityLifecycleDispatcher,
+            @NonNull ObservableSupplier<LayoutManagerImpl> layoutManagerSupplier,
+            @NonNull MenuOrKeyboardActionController menuOrKeyboardActionController,
+            @NonNull Supplier<Integer> activityThemeColorSupplier,
+            @NonNull ObservableSupplier<ModalDialogManager> modalDialogManagerSupplier,
+            @NonNull AppMenuBlocker appMenuBlocker,
+            @NonNull BooleanSupplier supportsAppMenuSupplier,
+            @NonNull BooleanSupplier supportsFindInPage,
+            @NonNull Supplier<TabCreatorManager> tabCreatorManagerSupplier,
+            @NonNull FullscreenManager fullscreenManager,
+            @NonNull Supplier<CompositorViewHolder> compositorViewHolderSupplier,
+            @NonNull Supplier<TabContentManager> tabContentManagerSupplier,
+            @NonNull OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            @NonNull Supplier<SnackbarManager> snackbarManagerSupplier,
+            @ActivityType int activityType, @NonNull Supplier<Boolean> isInOverviewModeSupplier,
+            @NonNull Supplier<Boolean> isWarmOnResumeSupplier,
+            @NonNull AppMenuDelegate appMenuDelegate,
+            @NonNull StatusBarColorProvider statusBarColorProvider,
+            @NonNull ObservableSupplierImpl<EphemeralTabCoordinator>
+                    ephemeralTabCoordinatorSupplier,
+            @NonNull IntentRequestTracker intentRequestTracker, int controlContainerHeightResource,
+            @NonNull InsetObserverView insetObserverView,
+            @NonNull Function<Tab, Boolean> backButtonShouldCloseTabFn) {
         super(activity, onOmniboxFocusChangedListener, shareDelegateSupplier, tabProvider,
                 profileSupplier, bookmarkBridgeSupplier, contextualSearchManagerSupplier,
                 tabModelSelectorSupplier, startSurfaceSupplier, intentMetadataOneshotSupplier,
                 layoutStateProviderOneshotSupplier, startSurfaceParentTabSupplier,
-                browserControlsManager, windowAndroid, jankTracker);
+                browserControlsManager, windowAndroid, jankTracker, activityLifecycleDispatcher,
+                layoutManagerSupplier, menuOrKeyboardActionController, activityThemeColorSupplier,
+                modalDialogManagerSupplier, appMenuBlocker, supportsAppMenuSupplier,
+                supportsFindInPage, tabCreatorManagerSupplier, fullscreenManager,
+                compositorViewHolderSupplier, tabContentManagerSupplier,
+                overviewModeBehaviorSupplier, snackbarManagerSupplier, activityType,
+                isInOverviewModeSupplier, isWarmOnResumeSupplier, appMenuDelegate,
+                statusBarColorProvider);
         mEphemeralTabCoordinatorSupplier = ephemeralTabCoordinatorSupplier;
+        mIntentRequestTracker = intentRequestTracker;
+        mControlContainerHeightResource = controlContainerHeightResource;
+        mInsetObserverView = insetObserverView;
+        mBackButtonShouldCloseTabFn = backButtonShouldCloseTabFn;
         mCanAnimateBrowserControls = () -> {
             // These null checks prevent any exceptions that may be caused by callbacks after
             // destruction.
-            if (mActivity == null || mActivity.getActivityTabProvider() == null) return false;
-            final Tab tab = mActivity.getActivityTabProvider().get();
+            if (mActivity == null || mActivityTabProvider == null) return false;
+            final Tab tab = mActivityTabProvider.get();
             return tab != null && tab.isUserInteractable() && !tab.isNativePage();
         };
 
@@ -242,7 +319,7 @@
         }
         if (mStatusIndicatorCoordinator != null) {
             mStatusIndicatorCoordinator.removeObserver(mStatusIndicatorObserver);
-            mStatusIndicatorCoordinator.removeObserver(mActivity.getStatusBarColorController());
+            mStatusIndicatorCoordinator.removeObserver(mStatusBarColorController);
             mStatusIndicatorCoordinator.destroy();
         }
 
@@ -266,8 +343,9 @@
 
         if (mHistoryNavigationCoordinator != null) {
             TouchEventObserver obs = mHistoryNavigationCoordinator.getTouchEventObserver();
-            CompositorViewHolder cvh = mActivity.getCompositorViewHolder();
-            if (cvh != null && obs != null) cvh.removeTouchEventObserver(obs);
+            if (mCompositorViewHolderSupplier.hasValue() && obs != null) {
+                mCompositorViewHolderSupplier.get().removeTouchEventObserver(obs);
+            }
             mHistoryNavigationCoordinator.destroy();
             mHistoryNavigationCoordinator = null;
         }
@@ -303,7 +381,7 @@
         super.onPostInflationStartup();
 
         mSystemUiCoordinator = new TabbedSystemUiCoordinator(mActivity.getWindow(),
-                mActivity.getTabModelSelector(), mActivity.getOverviewModeBehaviorSupplier());
+                mTabModelSelectorSupplier.get(), mOverviewModeBehaviorSupplier);
     }
 
     @Override
@@ -325,17 +403,18 @@
      * Show navigation history sheet.
      */
     public void showFullHistorySheet() {
-        Tab tab = mActivity.getActivityTabProvider().get();
+        Tab tab = mActivityTabProvider.get();
         if (tab == null || tab.getWebContents() == null || !tab.isUserInteractable()) return;
         Profile profile = Profile.fromWebContents(tab.getWebContents());
         mNavigationSheet = NavigationSheet.create(
                 mActivity.getWindow().getDecorView().findViewById(android.R.id.content), mActivity,
                 this::getBottomSheetController, profile);
         mNavigationSheet.setDelegate(new TabbedSheetDelegate(tab, aTab -> {
-            HistoryManagerUtils.showHistoryManager(
-                    mActivity, aTab, mActivity.getTabModelSelector().isIncognitoSelected());
+            HistoryManagerUtils.showHistoryManager(mActivity, aTab,
+                    mTabModelSelectorSupplier.hasValue()
+                            && mTabModelSelectorSupplier.get().isIncognitoSelected());
         }, mActivity.getResources().getString(R.string.show_full_history)));
-        if (!mNavigationSheet.startAndExpand(/* forward=*/false, /* animate=*/true)) {
+        if (!mNavigationSheet.startAndExpand(/* forward= */ false, /* animate=*/true)) {
             mNavigationSheet = null;
         } else {
             getBottomSheetController().addObserver(new EmptyBottomSheetObserver() {
@@ -352,14 +431,13 @@
     public void onFinishNativeInitialization() {
         super.onFinishNativeInitialization();
         assert mLayoutManager != null;
-        CompositorViewHolder cvh = mActivity.getCompositorViewHolder();
-        ActivityTabProvider activityTabProvider = mActivity.getActivityTabProvider();
 
-        mHistoryNavigationCoordinator = HistoryNavigationCoordinator.create(
-                mActivity.getWindowAndroid(), mActivity.getLifecycleDispatcher(), cvh,
+        // final Function<Tab, Boolean> backButtonShouldCloseTabFn = mBackButtonShouldCloseTabFn;
+        mHistoryNavigationCoordinator = HistoryNavigationCoordinator.create(mWindowAndroid,
+                mActivityLifecycleDispatcher, mCompositorViewHolderSupplier.get(),
                 mCallbackController.makeCancelable(
                         () -> mLayoutManager.getActiveLayout().requestUpdate()),
-                activityTabProvider, mActivity.getInsetObserverView(), new BackActionDelegate() {
+                mActivityTabProvider, mInsetObserverView, new BackActionDelegate() {
                     @Override
                     public @ActionType int getBackActionType(Tab tab) {
                         if (isShowingStartSurfaceHomepage()) return ActionType.EXIT_APP;
@@ -370,8 +448,8 @@
                         if (TabAssociatedApp.isOpenedFromExternalApp(tab)) {
                             return ActionType.EXIT_APP;
                         }
-                        return mActivity.backShouldCloseTab(tab) ? ActionType.CLOSE_TAB
-                                                                 : ActionType.EXIT_APP;
+                        return mBackButtonShouldCloseTabFn.apply(tab) ? ActionType.CLOSE_TAB
+                                                                      : ActionType.EXIT_APP;
                     }
 
                     @Override
@@ -384,21 +462,21 @@
                     public boolean isNavigable() {
                         return isShowingStartSurfaceHomepage();
                     }
-                }, cvh::addTouchEventObserver, mLayoutManager);
-        mRootUiTabObserver.swapToTab(activityTabProvider.get());
+                }, mCompositorViewHolderSupplier.get()::addTouchEventObserver, mLayoutManager);
+        mRootUiTabObserver.swapToTab(mActivityTabProvider.get());
 
         // TODO(twellington): Supply TabModelSelector as well and move initialization earlier.
         if (DeviceFormFactor.isNonMultiDisplayContextOnTablet(mActivity)) {
             AppMenuHandler appMenuHandler =
                     mAppMenuCoordinator == null ? null : mAppMenuCoordinator.getAppMenuHandler();
             mEmptyBackgroundViewWrapper = new EmptyBackgroundViewWrapper(
-                    mActivity.getTabModelSelector(), mActivity.getTabCreator(false), mActivity,
-                    appMenuHandler, mActivity.getSnackbarManager(),
-                    mActivity.getOverviewModeBehaviorSupplier());
+                    mTabModelSelectorSupplier.get(),
+                    mTabCreatorManagerSupplier.get().getTabCreator(false), mActivity,
+                    appMenuHandler, mSnackbarManagerSupplier.get(), mOverviewModeBehaviorSupplier);
             mEmptyBackgroundViewWrapper.initialize();
         }
 
-        if (!mActivity.isTablet()
+        if (!DeviceFormFactor.isNonMultiDisplayContextOnTablet(mActivity)
                 && (TabUiFeatureUtilities.isTabGroupsAndroidEnabled(mActivity)
                         || TabUiFeatureUtilities.isConditionalTabStripEnabled())) {
             getToolbarManager().enableBottomControls();
@@ -406,9 +484,11 @@
 
         if (EphemeralTabCoordinator.isSupported()) {
             mEphemeralTabCoordinatorSupplier.set(
-                    new EphemeralTabCoordinator(mActivity, mActivity.getWindowAndroid(),
-                            mActivity.getWindow().getDecorView(), activityTabProvider,
-                            mActivity::getCurrentTabCreator, getBottomSheetController(), true));
+                    new EphemeralTabCoordinator(mActivity, mWindowAndroid,
+                            mActivity.getWindow().getDecorView(), mActivityTabProvider, () -> {
+                                return mTabCreatorManagerSupplier.get().getTabCreator(
+                                        mTabModelSelectorSupplier.get().isIncognitoSelected());
+                            }, getBottomSheetController(), true));
         }
 
         mIntentMetadataOneshotSupplier.onAvailable(mCallbackController.makeCancelable(
@@ -419,8 +499,7 @@
         // rather than relying on unowned user data.
         mPwaBottomSheetController =
                 PwaBottomSheetControllerFactory.createPwaBottomSheetController(mActivity);
-        PwaBottomSheetControllerFactory.attach(
-                mActivity.getWindowAndroid(), mPwaBottomSheetController);
+        PwaBottomSheetControllerFactory.attach(mWindowAndroid, mPwaBottomSheetController);
         initContinuousSearchCoordinator();
 
         initMerchantTrustSignals();
@@ -438,12 +517,10 @@
             return;
         }
 
-        mMerchantTrustSignalsCoordinator =
-                new MerchantTrustSignalsCoordinator(mActivity, mActivity.getWindowAndroid(),
-                        getBottomSheetController(), mActivity.getWindow().getDecorView(),
-                        MessageDispatcherProvider.from(mActivity.getWindowAndroid()),
-                        mActivity.getActivityTabProvider(), mProfileSupplier,
-                        new MerchantTrustMetrics(), mActivity.getIntentRequestTracker());
+        mMerchantTrustSignalsCoordinator = new MerchantTrustSignalsCoordinator(mActivity,
+                mWindowAndroid, getBottomSheetController(), mActivity.getWindow().getDecorView(),
+                MessageDispatcherProvider.from(mWindowAndroid), mActivityTabProvider,
+                mProfileSupplier, new MerchantTrustMetrics(), mIntentRequestTracker);
     }
 
     // Protected class methods
@@ -468,8 +545,7 @@
                 new ScrimCoordinator.SystemUiScrimDelegate() {
                     @Override
                     public void setStatusBarScrimFraction(float scrimFraction) {
-                        mActivity.getStatusBarColorController().setStatusBarScrimFraction(
-                                scrimFraction);
+                        mStatusBarColorController.setStatusBarScrimFraction(scrimFraction);
                     }
 
                     @Override
@@ -491,11 +567,10 @@
 
     private void initializeIPH(boolean intentWithEffect) {
         if (mActivity == null) return;
-        mToolbarButtonInProductHelpController =
-                new ToolbarButtonInProductHelpController(mActivity, mActivity.getWindowAndroid(),
-                        mAppMenuCoordinator, mActivity.getLifecycleDispatcher(),
-                        mActivity.getActivityTabProvider(), mActivity::isInOverviewMode,
-                        mToolbarManager.getMenuButtonView(), mToolbarManager.getSecurityIconView());
+        mToolbarButtonInProductHelpController = new ToolbarButtonInProductHelpController(mActivity,
+                mWindowAndroid, mAppMenuCoordinator, mActivityLifecycleDispatcher,
+                mActivityTabProvider, mIsInOverviewModeSupplier,
+                mToolbarManager.getMenuButtonView(), mToolbarManager.getSecurityIconView());
         mReadLaterIPHController = new ReadLaterIPHController(mActivity,
                 getToolbarManager().getMenuButtonView(), mAppMenuCoordinator.getAppMenuHandler());
 
@@ -518,39 +593,36 @@
                             mAppMenuCoordinator.getAppMenuHandler(), mStatusIndicatorCoordinator);
         }
 
-        mAddToHomescreenIPHController = new AddToHomescreenIPHController(mActivity,
-                mActivity.getWindowAndroid(), mActivity.getModalDialogManager(),
-                mAppMenuCoordinator.getAppMenuHandler(), R.id.add_to_homescreen_id,
-                ()
-                        -> mActivity.getToolbarManager().getMenuButtonView(),
-                MessageDispatcherProvider.from(mActivity.getWindowAndroid()));
+        mAddToHomescreenIPHController = new AddToHomescreenIPHController(mActivity, mWindowAndroid,
+                mModalDialogManagerSupplier.get(), mAppMenuCoordinator.getAppMenuHandler(),
+                R.id.add_to_homescreen_id, () -> {
+                    return mToolbarManager.getMenuButtonView();
+                }, MessageDispatcherProvider.from(mWindowAndroid));
         mAddToHomescreenMostVisitedTileObserver = new AddToHomescreenMostVisitedTileClickObserver(
-                mActivity.getActivityTabProvider(), mAddToHomescreenIPHController);
+                mActivityTabProvider, mAddToHomescreenIPHController);
         mAppBannerInProductHelpController =
                 AppBannerInProductHelpControllerFactory.createAppBannerInProductHelpController(
                         mActivity, mAppMenuCoordinator.getAppMenuHandler(),
-                        ()
-                                -> mActivity.getToolbarManager().getMenuButtonView(),
-                        R.id.add_to_homescreen_id);
+                        () -> mToolbarManager.getMenuButtonView(), R.id.add_to_homescreen_id);
         AppBannerInProductHelpControllerFactory.attach(
-                mActivity.getWindowAndroid(), mAppBannerInProductHelpController);
+                mWindowAndroid, mAppBannerInProductHelpController);
 
         if (FeedFeatures.isWebFeedUIEnabled()) {
             mWebFeedFollowIntroController = new WebFeedFollowIntroController(mActivity,
-                    mAppMenuCoordinator.getAppMenuHandler(), mActivity.getActivityTabProvider(),
-                    mToolbarManager.getMenuButtonView(),
-                    ()
-                            -> mActivity.getTabCreator(/*incognito=*/false)
-                                       .launchUrl(NewTabPageUtils.encodeNtpUrl(
-                                                          NewTabPageLaunchOrigin.WEB_FEED),
-                                               TabLaunchType.FROM_CHROME_UI),
-                    mActivity.getModalDialogManager(), mActivity.getSnackbarManager());
+                    mAppMenuCoordinator.getAppMenuHandler(), mActivityTabProvider,
+                    mToolbarManager.getMenuButtonView(), () -> {
+                        mTabCreatorManagerSupplier.get()
+                                .getTabCreator(/*incognito=*/false)
+                                .launchUrl(NewTabPageUtils.encodeNtpUrl(
+                                                   NewTabPageLaunchOrigin.WEB_FEED),
+                                        TabLaunchType.FROM_CHROME_UI);
+                    }, mModalDialogManagerSupplier.get(), mSnackbarManagerSupplier.get());
         }
     }
 
     private void updateTopControlsHeight(boolean animate) {
-        final BrowserControlsSizer browserControlsSizer = mActivity.getBrowserControlsManager();
-        final int resourceId = mActivity.getControlContainerHeightResource();
+        final BrowserControlsSizer browserControlsSizer = mBrowserControlsManager;
+        final int resourceId = mControlContainerHeightResource;
         final int topControlsNewHeight = mActivity.getResources().getDimensionPixelSize(resourceId)
                 + mStatusIndicatorHeight + mContinuousSearchHeight;
 
@@ -567,7 +639,7 @@
         CommerceSubscriptionsServiceFactory factory = new CommerceSubscriptionsServiceFactory();
         mCommerceSubscriptionsService = factory.getForLastUsedProfile();
         mCommerceSubscriptionsService.initDeferredStartupForActivity(
-                mActivity.getTabModelSelector(), mActivity.getLifecycleDispatcher());
+                mTabModelSelectorSupplier.get(), mActivityLifecycleDispatcher);
     }
 
     private void initStatusIndicatorCoordinator(LayoutManagerImpl layoutManager) {
@@ -579,10 +651,10 @@
             return;
         }
 
-        final BrowserControlsSizer browserControlsSizer = mActivity.getBrowserControlsManager();
+        final BrowserControlsSizer browserControlsSizer = mBrowserControlsManager;
         mStatusIndicatorCoordinator = new StatusIndicatorCoordinator(mActivity,
-                mActivity.getCompositorViewHolder().getResourceManager(), browserControlsSizer,
-                mActivity.getStatusBarColorController()::getStatusBarColorWithoutStatusIndicator,
+                mCompositorViewHolderSupplier.get().getResourceManager(), browserControlsSizer,
+                mStatusBarColorController::getStatusBarColorWithoutStatusIndicator,
                 mCanAnimateBrowserControls, layoutManager::requestUpdate);
         layoutManager.addSceneOverlay(mStatusIndicatorCoordinator.getSceneLayer());
         mStatusIndicatorObserver = new StatusIndicatorCoordinator.StatusIndicatorObserver() {
@@ -593,7 +665,7 @@
             }
         };
         mStatusIndicatorCoordinator.addObserver(mStatusIndicatorObserver);
-        mStatusIndicatorCoordinator.addObserver(mActivity.getStatusBarColorController());
+        mStatusIndicatorCoordinator.addObserver(mStatusBarColorController);
 
         // Don't initialize the offline indicator controller if the feature is disabled.
         if (!ChromeFeatureList.isEnabled(ChromeFeatureList.OFFLINE_INDICATOR_V2)) {
@@ -632,16 +704,14 @@
         }
 
         Supplier<Integer> defaultTopContainerHeightSupplier = ()
-                -> mActivity.getResources().getDimensionPixelSize(
-                        mActivity.getControlContainerHeightResource());
+                -> mActivity.getResources().getDimensionPixelSize(mControlContainerHeightResource);
         final ViewStub viewStub = mActivity.findViewById(R.id.continuous_search_container_stub);
-        final BrowserControlsSizer browserControlsSizer = mActivity.getBrowserControlsManager();
+        final BrowserControlsSizer browserControlsSizer = mBrowserControlsManager;
         mContinuousSearchContainerCoordinator = new ContinuousSearchContainerCoordinator(viewStub,
-                mLayoutManager, mActivity.getCompositorViewHolder().getResourceManager(),
-                mActivity.getActivityTabProvider(), browserControlsSizer,
-                mCanAnimateBrowserControls, defaultTopContainerHeightSupplier,
-                getTopUiThemeColorProvider(), mActivity.getResources(),
-                mToolbarManager::setForceHideShadow);
+                mLayoutManager, mCompositorViewHolderSupplier.get().getResourceManager(),
+                mActivityTabProvider, browserControlsSizer, mCanAnimateBrowserControls,
+                defaultTopContainerHeightSupplier, getTopUiThemeColorProvider(),
+                mActivity.getResources(), mToolbarManager::setForceHideShadow);
         mContinuousSearchObserver = (newHeight, animate) -> {
             mContinuousSearchHeight = newHeight;
             updateTopControlsHeight(animate);
@@ -651,7 +721,7 @@
                 isObscured -> mContinuousSearchContainerCoordinator.updateTabObscured(isObscured);
         getTabObscuringHandler().addObserver(mContinuousSearchTabObscuringHandlerObserver);
 
-        if (!mActivity.supportsFindInPage()) return;
+        if (!mSupportsFindInPageSupplier.getAsBoolean()) return;
 
         assert mFindToolbarManager != null;
 
@@ -766,19 +836,16 @@
             return true;
         }
         if (DataReductionPromoScreen.launchDataReductionPromo(
-                    mActivity, mActivity.getTabModelSelector().getCurrentModel().isIncognito())) {
+                    mActivity, mTabModelSelectorSupplier.get().isIncognitoSelected())) {
             return true;
         }
-        if (DefaultBrowserPromoUtils.prepareLaunchPromoIfNeeded(
-                    mActivity, mActivity.getWindowAndroid())) {
+        if (DefaultBrowserPromoUtils.prepareLaunchPromoIfNeeded(mActivity, mWindowAndroid)) {
             return true;
         }
-        if (AppLanguagePromoDialog.maybeShowPrompt(mActivity,
-                    mActivity.getModalDialogManagerSupplier(),
+        if (AppLanguagePromoDialog.maybeShowPrompt(mActivity, mModalDialogManagerSupplier,
                     () -> ApplicationLifetime.terminate(true))) {
             return true;
         }
-        return LanguageAskPrompt.maybeShowLanguageAskPrompt(
-                mActivity, mActivity.getModalDialogManagerSupplier());
+        return LanguageAskPrompt.maybeShowLanguageAskPrompt(mActivity, mModalDialogManagerSupplier);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index ccaba10..4728785b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -16,6 +16,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.appcompat.app.AppCompatActivity;
 import androidx.appcompat.content.res.AppCompatResources;
 
 import org.chromium.base.ApiCompatibilityUtils;
@@ -24,6 +25,7 @@
 import org.chromium.base.TraceEvent;
 import org.chromium.base.jank_tracker.JankTracker;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.supplier.BooleanSupplier;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.OneshotSupplier;
@@ -34,11 +36,13 @@
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.ChromeActionModeHandler;
 import org.chromium.chrome.browser.ChromePowerModeVoter;
-import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.bookmarks.BookmarkBridge;
+import org.chromium.chrome.browser.compositor.CompositorViewHolder;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl;
+import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
+import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
 import org.chromium.chrome.browser.crash.PureJavaExceptionReporter;
 import org.chromium.chrome.browser.directactions.DirectActionInitializer;
@@ -49,10 +53,12 @@
 import org.chromium.chrome.browser.flags.ActivityType;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.fullscreen.BrowserControlsManager;
+import org.chromium.chrome.browser.fullscreen.FullscreenManager;
 import org.chromium.chrome.browser.identity_disc.IdentityDiscController;
 import org.chromium.chrome.browser.image_descriptions.ImageDescriptionsController;
 import org.chromium.chrome.browser.layouts.LayoutStateProvider;
 import org.chromium.chrome.browser.layouts.LayoutType;
+import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.DestroyObserver;
 import org.chromium.chrome.browser.lifecycle.InflationObserver;
 import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
@@ -77,6 +83,7 @@
 import org.chromium.chrome.browser.tab.AutofillSessionLifetimeController;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.theme.TopUiThemeColorProvider;
 import org.chromium.chrome.browser.toolbar.ButtonDataProvider;
@@ -92,7 +99,10 @@
 import org.chromium.chrome.browser.ui.appmenu.AppMenuBlocker;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinatorFactory;
+import org.chromium.chrome.browser.ui.appmenu.AppMenuDelegate;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
+import org.chromium.chrome.browser.ui.system.StatusBarColorController;
+import org.chromium.chrome.browser.ui.system.StatusBarColorController.StatusBarColorProvider;
 import org.chromium.chrome.browser.vr.VrModuleProvider;
 import org.chromium.chrome.features.start_surface.StartSurface;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
@@ -116,6 +126,7 @@
 import org.chromium.ui.base.ActivityWindowAndroid;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.base.PageTransition;
+import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogManagerObserver;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.vr.VrModeObserver;
@@ -138,14 +149,14 @@
             new TabObscuringHandlerSupplier();
     private final JankTracker mJankTracker;
 
-    protected ChromeActivity mActivity;
+    protected AppCompatActivity mActivity;
     protected @Nullable AppMenuCoordinator mAppMenuCoordinator;
     private final MenuOrKeyboardActionController mMenuOrKeyboardActionController;
     private final AccessibilityVisibilityHandler mAccessibilityVisibilityHandler;
     private final @Nullable AutofillSessionLifetimeController mAutofillSessionLifetimeController;
-    private final ActivityWindowAndroid mWindowAndroid;
+    protected final ActivityWindowAndroid mWindowAndroid;
 
-    private ActivityTabProvider mActivityTabProvider;
+    protected final ActivityTabProvider mActivityTabProvider;
     private ObservableSupplier<ShareDelegate> mShareDelegateSupplier;
 
     protected @Nullable FindToolbarManager mFindToolbarManager;
@@ -190,8 +201,8 @@
     private BottomSheetObserver mContextualSearchSuppressor;
     private final Supplier<ContextualSearchManager> mContextualSearchManagerSupplier;
     protected final CallbackController mCallbackController;
-    private final BrowserControlsManager mBrowserControlsManager;
-    private ObservableSupplier<TabModelSelector> mTabModelSelectorSupplier;
+    protected final BrowserControlsManager mBrowserControlsManager;
+    protected ObservableSupplier<TabModelSelector> mTabModelSelectorSupplier;
     protected final OneshotSupplier<StartSurface> mStartSurfaceSupplier;
     @Nullable
     private ManagedMessageDispatcher mMessageDispatcher;
@@ -207,39 +218,95 @@
     @Nullable
     private VoiceRecognitionHandler.Observer mMicStateObserver;
     private MediaCaptureOverlayController mCaptureController;
+    protected final ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
+    private final ObservableSupplier<LayoutManagerImpl> mLayoutManagerSupplier;
+    protected final ObservableSupplier<ModalDialogManager> mModalDialogManagerSupplier;
+    private final AppMenuBlocker mAppMenuBlocker;
+    private final BooleanSupplier mSupportsAppMenuSupplier;
+    protected final BooleanSupplier mSupportsFindInPageSupplier;
+    protected final Supplier<TabCreatorManager> mTabCreatorManagerSupplier;
+    private final FullscreenManager mFullscreenManager;
+    protected final Supplier<CompositorViewHolder> mCompositorViewHolderSupplier;
+    protected final StatusBarColorController mStatusBarColorController;
+    protected final OneshotSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
+    protected final Supplier<SnackbarManager> mSnackbarManagerSupplier;
+    protected final @ActivityType int mActivityType;
+    protected final Supplier<Boolean> mIsInOverviewModeSupplier;
+    private final Supplier<Boolean> mIsWarmOnResumeSupplier;
+    private final AppMenuDelegate mAppMenuDelegate;
+    private final StatusBarColorProvider mStatusBarColorProvider;
+    private final Supplier<TabContentManager> mTabContentManagerSupplier;
 
     /**
      * Create a new {@link RootUiCoordinator} for the given activity.
-     * @param activity The containing {@link ChromeActivity}. TODO(https://crbug.com/931496):
-     *         Remove this in favor of passing in direct dependencies.
-     * @param onOmniboxFocusChangedListener Callback<Boolean> callback to invoke when Omnibox focus
+     * @param activity The activity whose UI the coordinator is responsible for.
+     * @param onOmniboxFocusChangedListener callback to invoke when Omnibox focus
      *         changes.
-     * @param shareDelegateSupplier Supplies {@link ShareDelegate} object.
+     * @param shareDelegateSupplier Supplies the {@link ShareDelegate}.
      * @param tabProvider The {@link ActivityTabProvider} to get current tab of the activity.
      * @param profileSupplier Supplier of the currently applicable profile.
      * @param bookmarkBridgeSupplier Supplier of the bookmark bridge for the current profile.
      * @param contextualSearchManagerSupplier Supplier of the {@link ContextualSearchManager}.
-     * @param tabModelSelectorSupplier Supplier of the {@link TabModelSelector}.
+     * @param tabModelSelectorSupplier Supplies the {@link TabModelSelector}.
      * @param startSurfaceSupplier Supplier of the {@link StartSurface}.
      * @param intentMetadataOneshotSupplier Supplier with information about the launching intent.
      * @param layoutStateProviderOneshotSupplier Supplier of the {@link LayoutStateProvider}.
      * @param startSurfaceParentTabSupplier Supplies the parent tab for the StartSurface.
      * @param browserControlsManager Manages the browser controls.
      * @param windowAndroid The current {@link WindowAndroid}.
+     * @param jankTracker Tracks the jank in the app.
+     * @param activityLifecycleDispatcher Allows observation of the activity lifecycle.
+     * @param layoutManagerSupplier Supplies the {@link LayoutManager}.
+     * @param menuOrKeyboardActionController Controls the menu or keyboard action controller.
+     * @param activityThemeColorSupplier Supplies the activity color theme.
+     * @param modalDialogManagerSupplier Supplies the {@link ModalDialogManager}.
+     * @param appMenuBlocker Controls the app menu blocking.
+     * @param supportsAppMenuSupplier Supplies the support state for the app menu.
+     * @param supportsFindInPage Supplies the support state for find in page.
+     * @param tabCreatorManagerSupplier Supplies the {@link TabCreatorManager}.
+     * @param fullscreenManager Manages the fullscreen state.
+     * @param compositorViewHolderSupplier Supplies the {@link CompositorViewHolder}.
+     * @param tabContentManagerSupplier Supplies the {@link TabContentManager}.
+     * @param overviewModeBehaviorSupplier Supplier of the overview mode manager.
+     * @param snackbarManagerSupplier Supplies the {@link SnackbarManager}.
+     * @param activityType The {@link ActivityType} for the activity.
+     * @param isInOverviewModeSupplier Supplies whether the app is in overview mode.
+     * @param isWarmOnResumeSupplier Supplies whether the app was warm on resume.
+     * @param appMenuDelegate The app menu delegate.
+     * @param statusBarColorProvider Provides the status bar color.
      */
-    public RootUiCoordinator(ChromeActivity activity,
+    public RootUiCoordinator(@NonNull AppCompatActivity activity,
             @Nullable Callback<Boolean> onOmniboxFocusChangedListener,
-            ObservableSupplier<ShareDelegate> shareDelegateSupplier,
-            ActivityTabProvider tabProvider, ObservableSupplier<Profile> profileSupplier,
-            ObservableSupplier<BookmarkBridge> bookmarkBridgeSupplier,
-            Supplier<ContextualSearchManager> contextualSearchManagerSupplier,
-            ObservableSupplier<TabModelSelector> tabModelSelectorSupplier,
-            OneshotSupplier<StartSurface> startSurfaceSupplier,
-            OneshotSupplier<ToolbarIntentMetadata> intentMetadataOneshotSupplier,
-            OneshotSupplier<LayoutStateProvider> layoutStateProviderOneshotSupplier,
+            @NonNull ObservableSupplier<ShareDelegate> shareDelegateSupplier,
+            @NonNull ActivityTabProvider tabProvider,
+            @NonNull ObservableSupplier<Profile> profileSupplier,
+            @NonNull ObservableSupplier<BookmarkBridge> bookmarkBridgeSupplier,
+            @NonNull Supplier<ContextualSearchManager> contextualSearchManagerSupplier,
+            @NonNull ObservableSupplier<TabModelSelector> tabModelSelectorSupplier,
+            @NonNull OneshotSupplier<StartSurface> startSurfaceSupplier,
+            @NonNull OneshotSupplier<ToolbarIntentMetadata> intentMetadataOneshotSupplier,
+            @NonNull OneshotSupplier<LayoutStateProvider> layoutStateProviderOneshotSupplier,
             @NonNull Supplier<Tab> startSurfaceParentTabSupplier,
             @NonNull BrowserControlsManager browserControlsManager,
-            @NonNull ActivityWindowAndroid windowAndroid, JankTracker jankTracker) {
+            @NonNull ActivityWindowAndroid windowAndroid, @NonNull JankTracker jankTracker,
+            @NonNull ActivityLifecycleDispatcher activityLifecycleDispatcher,
+            @NonNull ObservableSupplier<LayoutManagerImpl> layoutManagerSupplier,
+            @NonNull MenuOrKeyboardActionController menuOrKeyboardActionController,
+            @NonNull Supplier<Integer> activityThemeColorSupplier,
+            @NonNull ObservableSupplier<ModalDialogManager> modalDialogManagerSupplier,
+            @NonNull AppMenuBlocker appMenuBlocker,
+            @NonNull BooleanSupplier supportsAppMenuSupplier,
+            @NonNull BooleanSupplier supportsFindInPage,
+            @NonNull Supplier<TabCreatorManager> tabCreatorManagerSupplier,
+            @NonNull FullscreenManager fullscreenManager,
+            @NonNull Supplier<CompositorViewHolder> compositorViewHolderSupplier,
+            @NonNull Supplier<TabContentManager> tabContentManagerSupplier,
+            @NonNull OneshotSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier,
+            @NonNull Supplier<SnackbarManager> snackbarManagerSupplier,
+            @ActivityType int activityType, @NonNull Supplier<Boolean> isInOverviewModeSupplier,
+            @NonNull Supplier<Boolean> isWarmOnResumeSupplier,
+            @NonNull AppMenuDelegate appMenuDelegate,
+            @NonNull StatusBarColorProvider statusBarColorProvider) {
         mJankTracker = jankTracker;
         mCallbackController = new CallbackController();
         mActivity = activity;
@@ -247,25 +314,42 @@
         setupUnownedUserDataSuppliers();
         mOnOmniboxFocusChangedListener = onOmniboxFocusChangedListener;
         mBrowserControlsManager = browserControlsManager;
-        mActivity.getLifecycleDispatcher().register(this);
+        mModalDialogManagerSupplier = modalDialogManagerSupplier;
+        mActivityLifecycleDispatcher = activityLifecycleDispatcher;
+        mActivityLifecycleDispatcher.register(this);
+        mAppMenuBlocker = appMenuBlocker;
+        mSupportsAppMenuSupplier = supportsAppMenuSupplier;
+        mSupportsFindInPageSupplier = supportsFindInPage;
+        mTabCreatorManagerSupplier = tabCreatorManagerSupplier;
+        mFullscreenManager = fullscreenManager;
+        mCompositorViewHolderSupplier = compositorViewHolderSupplier;
+        mTabContentManagerSupplier = tabContentManagerSupplier;
+        mOverviewModeBehaviorSupplier = overviewModeBehaviorSupplier;
+        mSnackbarManagerSupplier = snackbarManagerSupplier;
+        mActivityType = activityType;
+        mIsInOverviewModeSupplier = isInOverviewModeSupplier;
+        mIsWarmOnResumeSupplier = isWarmOnResumeSupplier;
+        mAppMenuDelegate = appMenuDelegate;
+        mStatusBarColorProvider = statusBarColorProvider;
 
-        mMenuOrKeyboardActionController = mActivity.getMenuOrKeyboardActionController();
+        mMenuOrKeyboardActionController = menuOrKeyboardActionController;
         mMenuOrKeyboardActionController.registerMenuOrKeyboardActionHandler(this);
         mActivityTabProvider = tabProvider;
 
         mLayoutManagerSupplierCallback = this::onLayoutManagerAvailable;
-        mActivity.getLayoutManagerSupplier().addObserver(mLayoutManagerSupplierCallback);
+        mLayoutManagerSupplier = layoutManagerSupplier;
+        mLayoutManagerSupplier.addObserver(mLayoutManagerSupplierCallback);
 
         mShareDelegateSupplier = shareDelegateSupplier;
         mTabObscuringHandlerSupplier.set(new TabObscuringHandler());
         mAccessibilityVisibilityHandler =
-                new AccessibilityVisibilityHandler(activity.getLifecycleDispatcher(),
+                new AccessibilityVisibilityHandler(mActivityLifecycleDispatcher,
                         mActivityTabProvider, mTabObscuringHandlerSupplier.get());
         // While Autofill is supported on Android O, meaningful Autofill interactions in Chrome
         // require the compatibility mode introduced in Android P.
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
             mAutofillSessionLifetimeController = new AutofillSessionLifetimeController(
-                    activity, activity.getLifecycleDispatcher(), mActivityTabProvider);
+                    activity, mActivityLifecycleDispatcher, mActivityTabProvider);
         } else {
             mAutofillSessionLifetimeController = null;
         }
@@ -289,8 +373,14 @@
         mStartSurfaceParentTabSupplier = startSurfaceParentTabSupplier;
 
         mTopUiThemeColorProvider = new TopUiThemeColorProvider(mActivity, mActivityTabProvider,
-                mActivity::getActivityThemeColor, mActivity::isTablet,
+                activityThemeColorSupplier,
+                DeviceFormFactor.isNonMultiDisplayContextOnTablet(activity),
                 shouldAllowThemingInNightMode(), shouldAllowBrightThemeColors());
+
+        mStatusBarColorController = new StatusBarColorController(mActivity.getWindow(),
+                DeviceFormFactor.isNonMultiDisplayContextOnTablet(/* Context */ mActivity),
+                mActivity.getResources(), mStatusBarColorProvider, mOverviewModeBehaviorSupplier,
+                mActivityLifecycleDispatcher, mActivityTabProvider, mTopUiThemeColorProvider);
     }
 
     // TODO(pnoland, crbug.com/865801): remove this in favor of wiring it directly.
@@ -298,6 +388,10 @@
         return mToolbarManager;
     }
 
+    public StatusBarColorController getStatusBarColorController() {
+        return mStatusBarColorController;
+    }
+
     // TODO(jinsukkim): remove this in favor of wiring it directly.
     /**
      * @return {@link ThemeColorProvider} for top UI.
@@ -309,7 +403,7 @@
     public void onAttachFragment(Fragment fragment) {
         if (fragment instanceof QrCodeDialog) {
             QrCodeDialog qrCodeDialog = (QrCodeDialog) fragment;
-            qrCodeDialog.setAndroidPermissionDelegate(mActivity.getWindowAndroid());
+            qrCodeDialog.setAndroidPermissionDelegate(mWindowAndroid);
         }
     }
 
@@ -321,7 +415,8 @@
         mMenuOrKeyboardActionController.unregisterMenuOrKeyboardActionHandler(this);
 
         destroyUnownedUserDataSuppliers();
-        mActivity.getLayoutManagerSupplier().removeObserver(mLayoutManagerSupplierCallback);
+        mActivityLifecycleDispatcher.unregister(this);
+        mLayoutManagerSupplier.removeObserver(mLayoutManagerSupplierCallback);
 
         if (mMessageDispatcher != null) {
             mMessageDispatcher.dismissAllMessages(DismissReason.ACTIVITY_DESTROYED);
@@ -363,7 +458,7 @@
 
         if (mAppMenuCoordinator != null) {
             mAppMenuCoordinator.unregisterAppMenuBlocker(this);
-            mAppMenuCoordinator.unregisterAppMenuBlocker(mActivity);
+            mAppMenuCoordinator.unregisterAppMenuBlocker(mAppMenuBlocker);
             mAppMenuCoordinator.destroy();
         }
 
@@ -376,8 +471,8 @@
 
         if (mVrModeObserver != null) VrModuleProvider.unregisterVrModeObserver(mVrModeObserver);
 
-        if (mModalDialogManagerObserver != null && mActivity.getModalDialogManager() != null) {
-            mActivity.getModalDialogManager().removeObserver(mModalDialogManagerObserver);
+        if (mModalDialogManagerObserver != null && mModalDialogManagerSupplier.hasValue()) {
+            mModalDialogManagerSupplier.get().removeObserver(mModalDialogManagerObserver);
         }
 
         if (mBottomSheetManager != null) mBottomSheetManager.onDestroy();
@@ -446,16 +541,16 @@
         initAppMenu();
         initDirectActionInitializer();
         initContextualSearchSuppressor();
-        if (mAppMenuCoordinator != null) {
+        if (mAppMenuCoordinator != null && mModalDialogManagerSupplier.hasValue()) {
             mModalDialogManagerObserver = new ModalDialogManagerObserver() {
                 @Override
                 public void onDialogAdded(PropertyModel model) {
                     mAppMenuCoordinator.getAppMenuHandler().hideAppMenu();
                 }
             };
-            mActivity.getModalDialogManager().addObserver(mModalDialogManagerObserver);
+            mModalDialogManagerSupplier.get().addObserver(mModalDialogManagerObserver);
         }
-        mChromeActionModeHandler = new ChromeActionModeHandler(mActivity.getActivityTabProvider(),
+        mChromeActionModeHandler = new ChromeActionModeHandler(mActivityTabProvider,
                 mToolbarManager::onActionBarVisibilityChanged, (searchText) -> {
                     if (mTabModelSelectorSupplier.get() == null) return;
 
@@ -463,7 +558,7 @@
                             searchText, ActionModeCallbackHelper.MAX_SEARCH_QUERY_LENGTH);
                     if (TextUtils.isEmpty(query)) return;
 
-                    Tab tab = mActivity.getActivityTabProvider().get();
+                    Tab tab = mActivityTabProvider.get();
                     TrackerFactory
                             .getTrackerForProfile(Profile.fromWebContents(tab.getWebContents()))
                             .notifyEvent(EventConstants.WEB_SEARCH_PERFORMED);
@@ -484,7 +579,7 @@
         VrModuleProvider.registerVrModeObserver(mVrModeObserver);
 
         mCaptureController = new MediaCaptureOverlayController(
-                mActivity.getWindowAndroid(), mActivity.findViewById(R.id.capture_overlay));
+                mWindowAndroid, mActivity.findViewById(R.id.capture_overlay));
 
         // Ensure the bottom sheet's container has been laid out at least once before hiding it.
         // TODO(1196804): This should be owned by the BottomSheetControllerImpl, but there are some
@@ -516,16 +611,16 @@
             mMessageDispatcher = MessagesFactory.createMessageDispatcher(container,
                     mMessageContainerCoordinator::getMessageMaxTranslation,
                     new ChromeMessageAutodismissDurationProvider(),
-                    mWindowAndroid::startAnimationOverContent, mActivity.getWindowAndroid());
+                    mWindowAndroid::startAnimationOverContent, mWindowAndroid);
             mMessageQueueMediator = new ChromeMessageQueueMediator(mBrowserControlsManager,
                     mMessageContainerCoordinator, mActivityTabProvider,
-                    mLayoutStateProviderOneShotSupplier, mActivity.getModalDialogManagerSupplier(),
+                    mLayoutStateProviderOneShotSupplier, mModalDialogManagerSupplier,
                     mMessageDispatcher);
             mMessageDispatcher.setDelegate(mMessageQueueMediator);
             MessagesFactory.attachMessageDispatcher(mWindowAndroid, mMessageDispatcher);
         }
         DownloadManagerService.getDownloadManagerService().onActivityLaunched(
-                mActivity, mMessageDispatcher, mActivity.getModalDialogManager());
+                mActivity, mMessageDispatcher, mModalDialogManagerSupplier.get());
     }
 
     /**
@@ -591,7 +686,7 @@
             DemoPaintPreview.showForTab(mActivityTabProvider.get());
         } else if (id == R.id.get_image_descriptions_id) {
             ImageDescriptionsController.getInstance().onImageDescriptionsMenuItemSelected(mActivity,
-                    mActivity.getModalDialogManager(), mActivityTabProvider.get().getWebContents());
+                    mModalDialogManagerSupplier.get(), mActivityTabProvider.get().getWebContents());
         }
 
         return false;
@@ -695,12 +790,12 @@
             };
 
             mIdentityDiscController = new IdentityDiscController(
-                    mActivity, mActivity.getLifecycleDispatcher(), mProfileSupplier);
+                    mActivity, mActivityLifecycleDispatcher, mProfileSupplier);
             ShareButtonController shareButtonController = new ShareButtonController(mActivity,
                     AppCompatResources.getDrawable(
                             mActivity, R.drawable.ic_toolbar_share_offset_24dp),
                     mActivityTabProvider, mShareDelegateSupplier, trackerSupplier, new ShareUtils(),
-                    mActivity.getLifecycleDispatcher(), mActivity.getModalDialogManager(),
+                    mActivityLifecycleDispatcher, mModalDialogManagerSupplier.get(),
                     () -> mToolbarManager.setUrlBarFocus(false, OmniboxFocusReason.UNFOCUS));
             VoiceToolbarButtonController.VoiceSearchDelegate voiceSearchDelegate =
                     new VoiceToolbarButtonController.VoiceSearchDelegate() {
@@ -724,20 +819,17 @@
             VoiceToolbarButtonController voiceToolbarButtonController =
                     new VoiceToolbarButtonController(mActivity,
                             AppCompatResources.getDrawable(mActivity, R.drawable.btn_mic),
-                            mActivityTabProvider, trackerSupplier,
-                            mActivity.getLifecycleDispatcher(), mActivity.getModalDialogManager(),
-                            voiceSearchDelegate);
+                            mActivityTabProvider, trackerSupplier, mActivityLifecycleDispatcher,
+                            mModalDialogManagerSupplier.get(), voiceSearchDelegate);
             OptionalNewTabButtonController newTabButtonController =
                     new OptionalNewTabButtonController(mActivity,
                             AppCompatResources.getDrawable(mActivity, R.drawable.new_tab_icon),
-                            mActivity.getLifecycleDispatcher(),
-                            mActivity.getTabCreatorManagerSupplier(), mTabModelSelectorSupplier,
-                            trackerSupplier);
+                            mActivityLifecycleDispatcher, mTabCreatorManagerSupplier,
+                            mTabModelSelectorSupplier, trackerSupplier);
             AdaptiveToolbarButtonController adaptiveToolbarButtonController =
                     new AdaptiveToolbarButtonController(mActivity, new SettingsLauncherImpl(),
-                            mActivity.getLifecycleDispatcher(),
-                            new AdaptiveButtonActionMenuCoordinator(), mActivity.getWindowAndroid(),
-                            SharedPreferencesManager.getInstance());
+                            mActivityLifecycleDispatcher, new AdaptiveButtonActionMenuCoordinator(),
+                            mWindowAndroid, SharedPreferencesManager.getInstance());
             adaptiveToolbarButtonController.addButtonVariant(
                     AdaptiveToolbarButtonVariant.NEW_TAB, newTabButtonController);
             adaptiveToolbarButtonController.addButtonVariant(
@@ -747,24 +839,22 @@
             mButtonDataProviders =
                     Arrays.asList(mIdentityDiscController, adaptiveToolbarButtonController);
             mToolbarManager = new ToolbarManager(mActivity, mBrowserControlsManager,
-                    mActivity.getFullscreenManager(), toolbarContainer,
-                    mActivity.getCompositorViewHolder(), urlFocusChangedCallback,
-                    mTopUiThemeColorProvider, mTabObscuringHandlerSupplier.get(),
-                    mShareDelegateSupplier, mIdentityDiscController, mButtonDataProviders,
-                    mActivityTabProvider, mScrimCoordinator, mActionModeControllerCallback,
-                    mFindToolbarManager, mProfileSupplier, mBookmarkBridgeSupplier,
-                    mCanAnimateBrowserControls, mLayoutStateProviderOneShotSupplier,
-                    mAppMenuSupplier, shouldShowMenuUpdateBadge(), mTabModelSelectorSupplier,
-                    mStartSurfaceSupplier, mOmniboxFocusStateSupplier,
-                    mIntentMetadataOneshotSupplier, mPromoShownOneshotSupplier, mWindowAndroid,
-                    mActivity::isInOverviewMode, mActivity.getModalDialogManagerSupplier(),
-                    mActivity.getStatusBarColorController(),
-                    /* appMenuDelegate= */ mActivity, mActivity.getLifecycleDispatcher(),
-                    mStartSurfaceParentTabSupplier, mBottomSheetController,
-                    mActivity::isWarmOnResume, mActivity.getTabContentManager(),
-                    /* tabCreatorManager= */ mActivity, mActivity.getOverviewModeBehaviorSupplier(),
-                    mActivity.getSnackbarManager(), mJankTracker);
-            if (!mActivity.supportsAppMenu()) {
+                    mFullscreenManager, toolbarContainer, mCompositorViewHolderSupplier.get(),
+                    urlFocusChangedCallback, mTopUiThemeColorProvider,
+                    mTabObscuringHandlerSupplier.get(), mShareDelegateSupplier,
+                    mIdentityDiscController, mButtonDataProviders, mActivityTabProvider,
+                    mScrimCoordinator, mActionModeControllerCallback, mFindToolbarManager,
+                    mProfileSupplier, mBookmarkBridgeSupplier, mCanAnimateBrowserControls,
+                    mLayoutStateProviderOneShotSupplier, mAppMenuSupplier,
+                    shouldShowMenuUpdateBadge(), mTabModelSelectorSupplier, mStartSurfaceSupplier,
+                    mOmniboxFocusStateSupplier, mIntentMetadataOneshotSupplier,
+                    mPromoShownOneshotSupplier, mWindowAndroid, mIsInOverviewModeSupplier,
+                    mModalDialogManagerSupplier, mStatusBarColorController, mAppMenuDelegate,
+                    mActivityLifecycleDispatcher, mStartSurfaceParentTabSupplier,
+                    mBottomSheetController, mIsWarmOnResumeSupplier,
+                    mTabContentManagerSupplier.get(), mTabCreatorManagerSupplier.get(),
+                    mOverviewModeBehaviorSupplier, mSnackbarManagerSupplier.get(), mJankTracker);
+            if (!mSupportsAppMenuSupplier.getAsBoolean()) {
                 mToolbarManager.getToolbar().disableMenuButton();
             }
 
@@ -787,8 +877,7 @@
                 new ScrimCoordinator.SystemUiScrimDelegate() {
                     @Override
                     public void setStatusBarScrimFraction(float scrimFraction) {
-                        mActivity.getStatusBarColorController().setStatusBarScrimFraction(
-                                scrimFraction);
+                        mStatusBarColorController.setStatusBarScrimFraction(scrimFraction);
                     }
 
                     @Override
@@ -857,14 +946,14 @@
     private void initAppMenu() {
         // TODO(https://crbug.com/931496): Revisit this as part of the broader
         // discussion around activity-specific UI customizations.
-        if (mActivity.supportsAppMenu()) {
+        if (mSupportsAppMenuSupplier.getAsBoolean()) {
             mAppMenuCoordinator = AppMenuCoordinatorFactory.createAppMenuCoordinator(mActivity,
-                    mActivity.getLifecycleDispatcher(), mToolbarManager, mActivity,
+                    mActivityLifecycleDispatcher, mToolbarManager, mAppMenuDelegate,
                     mActivity.getWindow().getDecorView(),
                     mActivity.getWindow().getDecorView().findViewById(R.id.menu_anchor_stub));
 
             mAppMenuCoordinator.registerAppMenuBlocker(this);
-            mAppMenuCoordinator.registerAppMenuBlocker(mActivity);
+            mAppMenuCoordinator.registerAppMenuBlocker(mAppMenuBlocker);
 
             mAppMenuSupplier.set(mAppMenuCoordinator);
         }
@@ -875,7 +964,7 @@
     }
 
     private void initFindToolbarManager() {
-        if (!mActivity.supportsFindInPage()) return;
+        if (!mSupportsFindInPageSupplier.getAsBoolean()) return;
 
         int stubId = R.id.find_toolbar_stub;
         if (DeviceFormFactor.isNonMultiDisplayContextOnTablet(mActivity)) {
@@ -945,7 +1034,7 @@
         };
 
         Supplier<OverlayPanelManager> panelManagerSupplier = ()
-                -> mActivity.getCompositorViewHolder().getLayoutManager().getOverlayPanelManager();
+                -> mCompositorViewHolderSupplier.get().getLayoutManager().getOverlayPanelManager();
 
         // TODO(1094000): Initialize after inflation so we don't need to pass in view suppliers.
         mBottomSheetController = BottomSheetControllerFactory.createBottomSheetController(
@@ -960,7 +1049,7 @@
         BottomSheetControllerFactory.attach(mWindowAndroid, mBottomSheetController);
 
         mBottomSheetManager = new BottomSheetManager(mBottomSheetController, mActivityTabProvider,
-                mBrowserControlsManager, mActivity::getModalDialogManager,
+                mBrowserControlsManager, mModalDialogManagerSupplier,
                 this::getBottomSheetSnackbarManager, mTabObscuringHandlerSupplier.get(),
                 mOmniboxFocusStateSupplier, panelManagerSupplier, mStartSurfaceSupplier);
     }
@@ -1003,13 +1092,11 @@
     }
 
     private void initDirectActionInitializer() {
-        @ActivityType
-        int activityType = mActivity.getActivityType();
-        mDirectActionInitializer = new DirectActionInitializer(mActivity, activityType, mActivity,
-                mActivity::onBackPressed, mTabModelSelectorSupplier.get(), mFindToolbarManager,
-                getBottomSheetController(), mBrowserControlsManager,
-                mActivity.getCompositorViewHolder(), mActivity.getActivityTabProvider());
-        mActivity.getLifecycleDispatcher().register(mDirectActionInitializer);
+        mDirectActionInitializer = new DirectActionInitializer(mActivity, mActivityType,
+                mMenuOrKeyboardActionController, mActivity::onBackPressed,
+                mTabModelSelectorSupplier.get(), mFindToolbarManager, getBottomSheetController(),
+                mBrowserControlsManager, mCompositorViewHolderSupplier.get(), mActivityTabProvider);
+        mActivityLifecycleDispatcher.register(mDirectActionInitializer);
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ShareIntentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ShareIntentTest.java
index 259433a9..05466121 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ShareIntentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ShareIntentTest.java
@@ -4,7 +4,6 @@
 
 package org.chromium.chrome.browser;
 
-import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -34,9 +33,8 @@
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.browser.tabmodel.MockTabModelSelector;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
-import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.base.ActivityWindowAndroid;
 
-import java.lang.ref.WeakReference;
 import java.util.concurrent.ExecutionException;
 
 /**
@@ -135,40 +133,42 @@
             // package and class names do not matter.
             return new MockChromeActivity(mActivityTestRule.getActivity());
         });
-        RootUiCoordinator rootUiCoordinator = TestThreadUtils.runOnUiThreadBlocking(() -> {
-            return new RootUiCoordinator(mockActivity, null,
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            ActivityWindowAndroid activityWindowAndroid =
+                    new ActivityWindowAndroid(mockActivity, false, null);
+            BrowserControlsManager browserControlsManager = new BrowserControlsManager(
+                    mockActivity, BrowserControlsManager.ControlsPosition.TOP);
+            RootUiCoordinator rootUiCoordinator = new RootUiCoordinator(mockActivity, null,
                     mockActivity.getShareDelegateSupplier(), mockActivity.getActivityTabProvider(),
                     null, null, null, null, new OneshotSupplierImpl<>(),
                     new OneshotSupplierImpl<>(), new OneshotSupplierImpl<>(),
                     ()
                             -> null,
-                    new BrowserControlsManager(
-                            mockActivity, BrowserControlsManager.ControlsPosition.TOP),
-                    mActivityTestRule.getActivity().getWindowAndroid(), new DummyJankTracker());
-        });
-        ShareHelper.setLastShareComponentName(
-                null, new ComponentName("test.package", "test.activity"));
+                    browserControlsManager, activityWindowAndroid, new DummyJankTracker(),
+                    mockActivity.getLifecycleDispatcher(), mockActivity.getLayoutManagerSupplier(),
+                    /* menuOrKeyboardActionController= */ mockActivity,
+                    mockActivity::getActivityThemeColor,
+                    mockActivity.getModalDialogManagerSupplier(),
+                    /* appMenuBlocker= */ mockActivity, mockActivity::supportsAppMenu,
+                    mockActivity::supportsFindInPage, mockActivity.getTabCreatorManagerSupplier(),
+                    browserControlsManager.getFullscreenManager(),
+                    mockActivity.getCompositorViewHolderSupplier(),
+                    mockActivity.getTabContentManagerSupplier(),
+                    mockActivity.getOverviewModeBehaviorSupplier(),
+                    mockActivity::getSnackbarManager, mockActivity.getActivityType(),
+                    mockActivity::isInOverviewMode, mockActivity::isWarmOnResume,
+                    /* appMenuDelegate= */ mockActivity,
+                    /* statusBarColorProvider= */ mockActivity);
 
-        WindowAndroid window = TestThreadUtils.runOnUiThreadBlocking(() -> {
-            return new WindowAndroid(mActivityTestRule.getActivity()) {
-                @Override
-                public WeakReference<Activity> getActivity() {
-                    return new WeakReference<>(mockActivity);
-                }
-            };
-        });
-        TestThreadUtils.runOnUiThreadBlocking(
-                () -> mockActivity.getActivityTab().updateAttachment(window, null));
-
-        TestThreadUtils.runOnUiThreadBlocking(
-                ()
-                        -> rootUiCoordinator.onShareMenuItemSelected(
-                                true /* shareDirectly */, false /* isIncognito */));
-
-        ShareHelper.setLastShareComponentName(null, new ComponentName("", ""));
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            ShareHelper.setLastShareComponentName(
+                    null, new ComponentName("test.package", "test.activity"));
+            mockActivity.getActivityTab().updateAttachment(activityWindowAndroid, null);
+            rootUiCoordinator.onShareMenuItemSelected(
+                    true /* shareDirectly */, false /* isIncognito */);
+            ShareHelper.setLastShareComponentName(null, new ComponentName("", ""));
             mockActivity.getActivityTab().updateAttachment(null, null);
-            window.destroy();
+            activityWindowAndroid.destroy();
         });
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripTest.java
index 2c13a67..fe023e0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripTest.java
@@ -541,12 +541,14 @@
 
         // Create visibility callback helper.
         final CallbackHelper helper = new CallbackHelper();
-        tab.addObserver(new StripLayoutTab.Observer() {
-            @Override
-            public void onVisibilityChanged(boolean visible) {
-                // Notify the callback when tab becomes visible.
-                if (visible) helper.notifyCalled();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.addObserver(new StripLayoutTab.Observer() {
+                @Override
+                public void onVisibilityChanged(boolean visible) {
+                    // Notify the callback when tab becomes visible.
+                    if (visible) helper.notifyCalled();
+                }
+            });
         });
 
         // Open another incognito tab to switch to the incognito model.
@@ -717,12 +719,14 @@
 
         // Create visibility callback helper.
         final CallbackHelper helper = new CallbackHelper();
-        tab.addObserver(new StripLayoutTab.Observer() {
-            @Override
-            public void onVisibilityChanged(boolean visible) {
-                // Notify the helper when tab becomes visible.
-                if (visible) helper.notifyCalled();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tab.addObserver(new StripLayoutTab.Observer() {
+                @Override
+                public void onVisibilityChanged(boolean visible) {
+                    // Notify the helper when tab becomes visible.
+                    if (visible) helper.notifyCalled();
+                }
+            });
         });
 
         // Select tab 0.
@@ -760,11 +764,13 @@
 
         // Create callback helper to be notified when first tab becomes visible.
         final CallbackHelper visibleHelper = new CallbackHelper();
-        tabs[0].addObserver(new StripLayoutTab.Observer() {
-            @Override
-            public void onVisibilityChanged(boolean visible) {
-                if (visible) visibleHelper.notifyCalled();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tabs[0].addObserver(new StripLayoutTab.Observer() {
+                @Override
+                public void onVisibilityChanged(boolean visible) {
+                    if (visible) visibleHelper.notifyCalled();
+                }
+            });
         });
 
         // Switch to the first tab and wait until it's visible.
@@ -779,11 +785,13 @@
 
         // Create callback helper to be notified when first tab is no longer visible.
         final CallbackHelper notVisibleHelper = new CallbackHelper();
-        tabs[0].addObserver(new StripLayoutTab.Observer() {
-            @Override
-            public void onVisibilityChanged(boolean visible) {
-                if (!visible) notVisibleHelper.notifyCalled();
-            }
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            tabs[0].addObserver(new StripLayoutTab.Observer() {
+                @Override
+                public void onVisibilityChanged(boolean visible) {
+                    if (!visible) notVisibleHelper.notifyCalled();
+                }
+            });
         });
 
         // Scroll tab strip to 0 and check tab positions.
@@ -863,7 +871,8 @@
                 tabModelSelectedCallback.notifyCalled();
             }
         };
-        mActivityTestRule.getActivity().getTabModelSelector().addObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> mActivityTestRule.getActivity().getTabModelSelector().addObserver(observer));
         StripLayoutHelperManager manager =
                 TabStripUtils.getStripLayoutHelperManager(mActivityTestRule.getActivity());
         TabStripUtils.clickCompositorButton(manager.getModelSelectorButton(),
@@ -873,7 +882,9 @@
         } catch (TimeoutException e) {
             Assert.fail("Tab model selected event never occurred.");
         }
-        mActivityTestRule.getActivity().getTabModelSelector().removeObserver(observer);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mActivityTestRule.getActivity().getTabModelSelector().removeObserver(observer);
+        });
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/content/TabObserverRegistrarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/content/TabObserverRegistrarTest.java
index 4060a6a..9e7e3ddd3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/content/TabObserverRegistrarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/content/TabObserverRegistrarTest.java
@@ -79,7 +79,8 @@
         TabObserverRegistrar tabObserverRegistrar =
                 customTabActivity.getComponent().resolveTabObserverRegistrar();
         LoadUrlTabObserver loadUrlTabObserver = new LoadUrlTabObserver();
-        tabObserverRegistrar.registerActivityTabObserver(loadUrlTabObserver);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> tabObserverRegistrar.registerActivityTabObserver(loadUrlTabObserver));
 
         final TabModelSelector tabSelector = customTabActivity.getTabModelSelector();
         final Tab initialActiveTab = tabSelector.getCurrentTab();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandlerTest.java
index 893badd..d43161c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandlerTest.java
@@ -612,6 +612,7 @@
         doReturn(new GURL("https://www.google.com/search?q=abc")).when(mMatch).getUrl();
         doReturn(true).when(mMatch).isSearchSuggestion();
         mActivityTestRule.startMainActivityOnBlankPage();
+        mActivityTestRule.waitForActivityNativeInitializationComplete();
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mWindowAndroid = new TestWindowAndroid(mActivityTestRule.getActivity());
@@ -1428,6 +1429,10 @@
     public void testParseResults_VoiceResponseURLConversion() {
         doReturn(false).when(mMatch).isSearchSuggestion();
         TestThreadUtils.runOnUiThreadBlocking(() -> {
+            // Needed to interact with classifier.
+            // AutocompleteCoordinator#classify() requires a valid profile.
+            mProfileSupplier.set(Profile.getLastUsedRegularProfile());
+
             String[] texts =
                     new String[] {"a", "www. b .co .uk", "engadget .com", "www.google.com"};
             float[] confidences = new float[] {1.0f, 1.0f, 1.0f, 1.0f};
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ui/system/StatusBarColorControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ui/system/StatusBarColorControllerTest.java
index 243aeda..cd07a12 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ui/system/StatusBarColorControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ui/system/StatusBarColorControllerTest.java
@@ -140,7 +140,9 @@
     public void testColorWithStatusIndicator() {
         final ChromeActivity activity = sActivityTestRule.getActivity();
         final StatusBarColorController statusBarColorController =
-                sActivityTestRule.getActivity().getStatusBarColorController();
+                sActivityTestRule.getActivity()
+                        .getRootUiCoordinatorForTesting()
+                        .getStatusBarColorController();
         final Supplier<Integer> statusBarColor = () -> activity.getWindow().getStatusBarColor();
         final int initialColor = statusBarColor.get();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkIntegrationTest.java
index e39234d9..718318c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkIntegrationTest.java
@@ -8,7 +8,6 @@
 
 import android.content.Intent;
 import android.net.Uri;
-import android.os.Build.VERSION_CODES;
 import android.support.test.InstrumentationRegistry;
 
 import androidx.test.filters.LargeTest;
@@ -22,7 +21,6 @@
 
 import org.chromium.base.CommandLine;
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.FlakyTest;
 import org.chromium.chrome.browser.flags.ActivityType;
@@ -52,6 +50,8 @@
                                           .around(mActivityTestRule)
                                           .around(mCertVerifierRule);
 
+    private static final long STARTUP_TIMEOUT = 15000L;
+
     @Before
     public void setUp() {
         mActivityTestRule.getEmbeddedTestServerRule().setServerUsesHttps(true);
@@ -68,8 +68,6 @@
     @Test
     @LargeTest
     @Feature({"Webapps"})
-    @DisableIf.Build(message = "See https://crbug.com/1199869",
-            sdk_is_greater_than = VERSION_CODES.O_MR1, sdk_is_less_than = VERSION_CODES.Q)
     public void testDeepLink() {
         String pageUrl = "https://pwa-directory.appspot.com/defaultresponse";
 
@@ -79,7 +77,8 @@
 
         InstrumentationRegistry.getTargetContext().startActivity(intent);
 
-        WebappActivity lastActivity = ChromeActivityTestRule.waitFor(WebappActivity.class);
+        WebappActivity lastActivity =
+                ChromeActivityTestRule.waitFor(WebappActivity.class, STARTUP_TIMEOUT);
         Assert.assertEquals(ActivityType.WEB_APK, lastActivity.getActivityType());
         Assert.assertEquals(pageUrl, lastActivity.getIntentDataProvider().getUrlToLoad());
     }
@@ -105,7 +104,8 @@
 
         InstrumentationRegistry.getTargetContext().startActivity(intent);
 
-        WebappActivity lastActivity = ChromeActivityTestRule.waitFor(WebappActivity.class);
+        WebappActivity lastActivity =
+                ChromeActivityTestRule.waitFor(WebappActivity.class, STARTUP_TIMEOUT);
         Assert.assertEquals(ActivityType.WEB_APK, lastActivity.getActivityType());
 
         Tab tab = lastActivity.getActivityTab();
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 013a835..86cf864 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2184,6 +2184,7 @@
     "//components/segmentation_platform/internal",
     "//components/segmentation_platform/public",
     "//components/send_tab_to_self",
+    "//components/services/app_service/public/cpp:crosapi_utils",
     "//components/services/app_service/public/cpp:intents",
     "//components/services/app_service/public/cpp:protocol_handling",
     "//components/services/app_service/public/cpp:types",
@@ -2989,6 +2990,8 @@
       "download/android/available_offline_content_provider.h",
       "download/android/chrome_duplicate_download_infobar_delegate.cc",
       "download/android/chrome_duplicate_download_infobar_delegate.h",
+      "download/android/dangerous_download_dialog_bridge.cc",
+      "download/android/dangerous_download_dialog_bridge.h",
       "download/android/dangerous_download_infobar_delegate.cc",
       "download/android/dangerous_download_infobar_delegate.h",
       "download/android/download_controller.cc",
@@ -4014,6 +4017,11 @@
       "search/background/ntp_background_service_observer.h",
       "search/background/ntp_backgrounds.cc",
       "search/background/ntp_backgrounds.h",
+      "search/background/ntp_custom_background_service.cc",
+      "search/background/ntp_custom_background_service.h",
+      "search/background/ntp_custom_background_service_factory.cc",
+      "search/background/ntp_custom_background_service_factory.h",
+      "search/background/ntp_custom_background_service_observer.h",
       "search/chrome_colors/chrome_colors_factory.cc",
       "search/chrome_colors/chrome_colors_factory.h",
       "search/chrome_colors/chrome_colors_service.cc",
@@ -6690,6 +6698,16 @@
 
   if (use_cups) {
     configs += [ "//printing:cups" ]
+    if (is_chromeos) {
+      sources += [
+        "chromeos/printing/cups_wrapper.cc",
+        "chromeos/printing/cups_wrapper.h",
+        "chromeos/printing/cups_wrapper_impl.cc",
+        "chromeos/printing/printer_error_codes.cc",
+        "chromeos/printing/printer_error_codes.h",
+      ]
+      deps += [ "//chromeos/components/print_management/mojom" ]
+    }
     if (is_chromeos_ash) {
       deps += [
         "//chrome/services/cups_proxy",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index c3124d3..c35f6bf 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3097,6 +3097,9 @@
      flag_descriptions::kButtonARCNetworkDiagnosticsName,
      flag_descriptions::kButtonARCNetworkDiagnosticsDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kButtonARCNetworkDiagnostics)},
+    {"calendar-view", flag_descriptions::kCalendarViewName,
+     flag_descriptions::kCalendarViewDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(chromeos::features::kCalendarView)},
     {"cellular-allow-per-network-roaming",
      flag_descriptions::kCellularAllowPerNetworkRoamingName,
      flag_descriptions::kCellularAllowPerNetworkRoamingDescription, kOsCrOS,
@@ -3540,9 +3543,6 @@
     {"chrome-share-screenshot", flag_descriptions::kChromeShareScreenshotName,
      flag_descriptions::kChromeShareScreenshotDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kChromeShareScreenshot)},
-    {"chrome-sharing-hub", flag_descriptions::kChromeSharingHubName,
-     flag_descriptions::kChromeSharingHubDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kChromeSharingHub)},
     {"webnotes-stylize", flag_descriptions::kWebNotesStylizeName,
      flag_descriptions::kWebNotesStylizeDescription, kOsAndroid,
      FEATURE_WITH_PARAMS_VALUE_TYPE(content_creation::kWebNotesStylizeEnabled,
@@ -3796,10 +3796,6 @@
      flag_descriptions::kMediaRouterCastAllowAllIPsName,
      flag_descriptions::kMediaRouterCastAllowAllIPsDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(media_router::kCastAllowAllIPsFeature)},
-    {"cast-media-route-provider",
-     flag_descriptions::kCastMediaRouteProviderName,
-     flag_descriptions::kCastMediaRouteProviderDescription, kOsDesktop,
-     FEATURE_VALUE_TYPE(media_router::kCastMediaRouteProvider)},
     {"global-media-controls-cast-start-stop",
      flag_descriptions::kGlobalMediaControlsCastStartStopName,
      flag_descriptions::kGlobalMediaControlsCastStartStopDescription,
diff --git a/chrome/browser/android/omnibox/autocomplete_controller_android.cc b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
index 55030a9f..52e83ea5 100644
--- a/chrome/browser/android/omnibox/autocomplete_controller_android.cc
+++ b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
@@ -540,6 +540,8 @@
   AutocompleteControllerAndroid* native_bridge =
       AutocompleteControllerAndroid::Factory::GetForProfile(
           ProfileAndroid::FromProfileAndroid(jprofile));
+  if (!native_bridge)
+    return {};
   return native_bridge->GetJavaObject();
 }
 
diff --git a/chrome/browser/android/webapk/webapk_update_data_fetcher.cc b/chrome/browser/android/webapk/webapk_update_data_fetcher.cc
index 91116b7..32433ab 100644
--- a/chrome/browser/android/webapk/webapk_update_data_fetcher.cc
+++ b/chrome/browser/android/webapk/webapk_update_data_fetcher.cc
@@ -23,6 +23,7 @@
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/common/manifest/manifest_util.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/smhasher/src/MurmurHash2.h"
 #include "ui/android/color_helpers.h"
@@ -143,7 +144,7 @@
   // observing too. It is based on our assumption that it is invalid for
   // web developers to change the Web Manifest location. When it does
   // change, we will treat the new Web Manifest as the one of another WebAPK.
-  if (!data.NoBlockingErrors() || data.manifest.IsEmpty() ||
+  if (!data.NoBlockingErrors() || blink::IsEmptyManifest(data.manifest) ||
       web_manifest_url_ != data.manifest_url ||
       !webapps::WebappsUtils::AreWebManifestUrlsWebApkCompatible(
           data.manifest)) {
diff --git a/chrome/browser/apps/app_service/app_icon_factory.cc b/chrome/browser/apps/app_service/app_icon_factory.cc
index 6b6681e..1334463 100644
--- a/chrome/browser/apps/app_service/app_icon_factory.cc
+++ b/chrome/browser/apps/app_service/app_icon_factory.cc
@@ -27,7 +27,7 @@
 #include "chrome/browser/extensions/chrome_app_icon_loader.h"
 #include "chrome/browser/favicon/favicon_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/web_applications/components/app_icon_manager.h"
+#include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/common/chrome_features.h"
@@ -303,7 +303,7 @@
 
 absl::optional<IconPurpose> GetIconPurpose(
     const std::string& web_app_id,
-    const web_app::AppIconManager& icon_manager,
+    const web_app::WebAppIconManager& icon_manager,
     int size_hint_in_dip) {
   // Get the max supported pixel size.
   int max_icon_size_in_px = 0;
@@ -449,7 +449,7 @@
 
   void LoadWebAppIcon(const std::string& web_app_id,
                       const GURL& launch_url,
-                      const web_app::AppIconManager& icon_manager,
+                      const web_app::WebAppIconManager& icon_manager,
                       Profile* profile);
 
   void LoadExtensionIcon(const extensions::Extension* extension,
@@ -635,7 +635,7 @@
 void IconLoadingPipeline::LoadWebAppIcon(
     const std::string& web_app_id,
     const GURL& launch_url,
-    const web_app::AppIconManager& icon_manager,
+    const web_app::WebAppIconManager& icon_manager,
     Profile* profile) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
diff --git a/chrome/browser/apps/app_service/app_icon_factory_unittest.cc b/chrome/browser/apps/app_service/app_icon_factory_unittest.cc
index a51ad1d..9920be0d 100644
--- a/chrome/browser/apps/app_service/app_icon_factory_unittest.cc
+++ b/chrome/browser/apps/app_service/app_icon_factory_unittest.cc
@@ -19,7 +19,6 @@
 #include "cc/test/pixel_test_utils.h"
 #include "chrome/browser/apps/app_service/app_icon_factory.h"
 #include "chrome/browser/extensions/chrome_app_icon.h"
-#include "chrome/browser/web_applications/components/app_icon_manager.h"
 #include "chrome/browser/web_applications/components/app_registry_controller.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
@@ -576,7 +575,7 @@
     }
 
     base::RunLoop run_loop;
-    icon_manager_->WriteData(app_id, std::move(icon_bitmaps), {},
+    icon_manager_->WriteData(app_id, std::move(icon_bitmaps), {}, {},
                              base::BindLambdaForTesting([&](bool success) {
                                EXPECT_TRUE(success);
                                run_loop.Quit();
diff --git a/chrome/browser/apps/app_service/app_service_proxy_lacros.cc b/chrome/browser/apps/app_service/app_service_proxy_lacros.cc
index d254f253..08b62d03 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_lacros.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_lacros.cc
@@ -535,14 +535,6 @@
 void AppServiceProxyLacros::OnApps(std::vector<apps::mojom::AppPtr> deltas,
                                    apps::mojom::AppType app_type,
                                    bool should_notify_initialized) {
-  if (app_service_.is_connected()) {
-    for (const auto& delta : deltas) {
-      if (!apps_util::IsInstalled(delta->readiness)) {
-        app_service_->RemovePreferredApp(delta->app_type, delta->app_id);
-      }
-    }
-  }
-
   app_registry_cache_.OnApps(std::move(deltas), app_type,
                              should_notify_initialized);
 }
diff --git a/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc b/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc
index b1940ae..1889fc6c 100644
--- a/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc
+++ b/chrome/browser/apps/app_service/publishers/web_apps_crosapi.cc
@@ -16,21 +16,13 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/services/app_service/public/cpp/crosapi_utils.h"
 #include "components/services/app_service/public/cpp/instance_registry.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 #include "extensions/common/constants.h"
 
 namespace {
 
-std::vector<apps::mojom::AppPtr> CloneApps(
-    const std::vector<apps::mojom::AppPtr>& clone_from) {
-  std::vector<apps::mojom::AppPtr> clone_to;
-  for (const auto& app : clone_from) {
-    clone_to.push_back(app->Clone());
-  }
-  return clone_to;
-}
-
 std::vector<apps::mojom::CapabilityAccessPtr> CloneCapabilityAccesses(
     const std::vector<apps::mojom::CapabilityAccessPtr>& clone_from) {
   std::vector<apps::mojom::CapabilityAccessPtr> clone_to;
@@ -224,7 +216,7 @@
   if (!base::FeatureList::IsEnabled(features::kWebAppsCrosapi))
     return;
   for (auto& subscriber : subscribers_) {
-    subscriber->OnApps(CloneApps(deltas), apps::mojom::AppType::kWeb,
+    subscriber->OnApps(apps_util::CloneApps(deltas), apps::mojom::AppType::kWeb,
                        false /* should_notify_initialized */);
   }
 }
diff --git a/chrome/browser/apps/app_service/subscriber_crosapi.cc b/chrome/browser/apps/app_service/subscriber_crosapi.cc
index 5f868ec..4c75205 100644
--- a/chrome/browser/apps/app_service/subscriber_crosapi.cc
+++ b/chrome/browser/apps/app_service/subscriber_crosapi.cc
@@ -10,18 +10,24 @@
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/profiles/profile.h"
+#include "components/services/app_service/public/cpp/crosapi_utils.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
 
+namespace {
+
+bool Accepts(apps::mojom::AppType app_type) {
+  return app_type == apps::mojom::AppType::kUnknown ||
+         app_type == apps::mojom::AppType::kArc ||
+         app_type == apps::mojom::AppType::kWeb ||
+         app_type == apps::mojom::AppType::kSystemWeb ||
+         app_type == apps::mojom::AppType::kStandaloneBrowserExtension;
+}
+
+}  // namespace
+
 namespace apps {
 
-SubscriberCrosapi::SubscriberCrosapi(Profile* profile) {
-  auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile);
-  mojo::Remote<apps::mojom::AppService>& app_service = proxy->AppService();
-  DCHECK(app_service.is_bound());
-  mojo::PendingRemote<apps::mojom::Subscriber> subscriber;
-  receivers_.Add(this, subscriber.InitWithNewPipeAndPassReceiver());
-  app_service->RegisterSubscriber(std::move(subscriber), nullptr);
-}
+SubscriberCrosapi::SubscriberCrosapi(Profile* profile) : profile_(profile) {}
 
 SubscriberCrosapi::~SubscriberCrosapi() = default;
 
@@ -41,7 +47,10 @@
 void SubscriberCrosapi::OnApps(std::vector<apps::mojom::AppPtr> deltas,
                                apps::mojom::AppType app_type,
                                bool should_notify_initialized) {
-  NOTIMPLEMENTED();
+  if (Accepts(app_type) && subscriber_.is_bound()) {
+    subscriber_->OnApps(apps_util::CloneApps(deltas), app_type,
+                        should_notify_initialized);
+  }
 }
 
 void SubscriberCrosapi::OnCapabilityAccesses(
@@ -51,7 +60,7 @@
 
 void SubscriberCrosapi::Clone(
     mojo::PendingReceiver<apps::mojom::Subscriber> receiver) {
-  NOTIMPLEMENTED();
+  receivers_.Add(this, std::move(receiver));
 }
 
 void SubscriberCrosapi::OnPreferredAppSet(
@@ -87,6 +96,13 @@
   subscriber_.Bind(std::move(subscriber));
   subscriber_.set_disconnect_handler(base::BindOnce(
       &SubscriberCrosapi::OnSubscriberDisconnected, base::Unretained(this)));
+
+  auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile_);
+  mojo::Remote<apps::mojom::AppService>& app_service = proxy->AppService();
+  DCHECK(app_service.is_bound());
+  mojo::PendingRemote<apps::mojom::Subscriber> app_service_subscriber;
+  receivers_.Add(this, app_service_subscriber.InitWithNewPipeAndPassReceiver());
+  app_service->RegisterSubscriber(std::move(app_service_subscriber), nullptr);
 }
 
 void SubscriberCrosapi::OnSubscriberDisconnected() {
diff --git a/chrome/browser/apps/app_service/subscriber_crosapi.h b/chrome/browser/apps/app_service/subscriber_crosapi.h
index c67570ef..7574573 100644
--- a/chrome/browser/apps/app_service/subscriber_crosapi.h
+++ b/chrome/browser/apps/app_service/subscriber_crosapi.h
@@ -66,6 +66,8 @@
   mojo::Receiver<crosapi::mojom::AppServiceProxy> crosapi_receiver_{this};
   mojo::ReceiverSet<apps::mojom::Subscriber> receivers_;
   mojo::Remote<crosapi::mojom::AppServiceSubscriber> subscriber_;
+
+  Profile* profile_;
 };
 
 }  // namespace apps
diff --git a/chrome/browser/apps/app_service/webapk/webapk_install_task.cc b/chrome/browser/apps/app_service/webapk/webapk_install_task.cc
index 02b7113..af6e4886 100644
--- a/chrome/browser/apps/app_service/webapk/webapk_install_task.cc
+++ b/chrome/browser/apps/app_service/webapk/webapk_install_task.cc
@@ -18,8 +18,8 @@
 #include "base/threading/thread_restrictions.h"
 #include "chrome/browser/apps/app_service/webapk/webapk_prefs.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/web_applications/components/app_icon_manager.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
+#include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/common/chrome_switches.h"
@@ -326,7 +326,7 @@
   webapk->set_android_abi(GetArcAbi(arc_features.value()));
 
   auto& icon_manager = web_app_provider_->icon_manager();
-  absl::optional<web_app::AppIconManager::IconSizeAndPurpose>
+  absl::optional<web_app::WebAppIconManager::IconSizeAndPurpose>
       icon_size_and_purpose = icon_manager.FindIconMatchBigger(
           app_id_, {IconPurpose::MASKABLE, IconPurpose::ANY}, kMinimumIconSize);
 
diff --git a/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc b/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc
index 5f4611b..e98247b 100644
--- a/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc
+++ b/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc
@@ -201,10 +201,8 @@
     return absl::nullopt;
   }
 
-  blink::mojom::CaptureLinks capture_links = provider->registrar()
-                                                 .AsWebAppRegistrar()
-                                                 ->GetAppById(*app_id)
-                                                 ->capture_links();
+  blink::mojom::CaptureLinks capture_links =
+      provider->registrar().GetAppById(*app_id)->capture_links();
 
   if (capture_links == blink::mojom::CaptureLinks::kUndefined &&
       app_in_tabbed_mode && tabbed_link_capturing) {
diff --git a/chrome/browser/apps/intent_helper/intent_picker_internal.cc b/chrome/browser/apps/intent_helper/intent_picker_internal.cc
index b0ca01d2..6667cac 100644
--- a/chrome/browser/apps/intent_helper/intent_picker_internal.cc
+++ b/chrome/browser/apps/intent_helper/intent_picker_internal.cc
@@ -12,8 +12,8 @@
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/intent_picker_tab_helper.h"
-#include "chrome/browser/web_applications/components/app_icon_manager.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
+#include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "components/no_state_prefetch/browser/no_state_prefetch_contents.h"
diff --git a/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.cc b/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.cc
index 063c28bf..94290ee 100644
--- a/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.cc
+++ b/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.cc
@@ -154,6 +154,7 @@
   void OnWindowDestroying(aura::Window* window) override {
     if (window_observations_.IsObservingSource(window))
       window_observations_.RemoveObservation(window);
+    owner_->OnWindowDestroying(window);
   }
 
  private:
@@ -162,27 +163,6 @@
       window_observations_{this};
 };
 
-class ArcAccessibilityTreeTracker::AppListPrefsObserver
-    : public ArcAppListPrefs::Observer {
- public:
-  AppListPrefsObserver(ArcAccessibilityTreeTracker* owner,
-                       Profile* const profile)
-      : owner_(owner) {
-    auto* app_list_prefs = ArcAppListPrefs::Get(profile);
-    if (app_list_prefs)
-      app_list_observation_.Observe(app_list_prefs);
-  }
-
-  void OnTaskDestroyed(int32_t task_id) override {
-    owner_->OnTaskDestroyed(task_id);
-  }
-
- private:
-  ArcAccessibilityTreeTracker* owner_;
-  base::ScopedObservation<ArcAppListPrefs, ArcAppListPrefs::Observer>
-      app_list_observation_{this};
-};
-
 class ArcAccessibilityTreeTracker::ArcInputMethodManagerServiceObserver
     : public ArcInputMethodManagerService::Observer {
  public:
@@ -269,8 +249,6 @@
       tree_source_delegate_(tree_source_delegate),
       accessibility_helper_instance_(accessibility_helper_instance),
       windows_observer_(std::make_unique<WindowsObserver>(this)),
-      app_list_prefs_observer_(
-          std::make_unique<AppListPrefsObserver>(this, profile)),
       input_manager_service_observer_(
           std::make_unique<ArcInputMethodManagerServiceObserver>(this,
                                                                  profile)),
@@ -299,7 +277,12 @@
     DispatchCustomSpokenFeedbackToggled(gained_arc);
 }
 
-void ArcAccessibilityTreeTracker::OnTaskDestroyed(int32_t task_id) {
+void ArcAccessibilityTreeTracker::OnWindowDestroying(aura::Window* window) {
+  const auto& task_id_opt = arc::GetWindowTaskId(window);
+  if (!task_id_opt.has_value())
+    return;
+
+  int32_t task_id = task_id_opt.value();
   task_id_to_window_.erase(task_id);
   trees_.erase(KeyForTaskId(task_id));
   base::EraseIf(window_id_to_task_id_,
@@ -308,7 +291,6 @@
 
 void ArcAccessibilityTreeTracker::Shutdown() {
   input_manager_service_observer_.reset();
-  app_list_prefs_observer_.reset();
 }
 
 void ArcAccessibilityTreeTracker::OnEnabledFeatureChanged(
diff --git a/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.h b/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.h
index d91619a..ce8e4530 100644
--- a/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.h
+++ b/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker.h
@@ -60,7 +60,7 @@
 
   void OnWindowFocused(aura::Window* gained_focus, aura::Window* lost_focus);
 
-  void OnTaskDestroyed(int32_t task_id);
+  void OnWindowDestroying(aura::Window* window);
 
   void Shutdown();
 
@@ -111,7 +111,6 @@
  private:
   class FocusChangeObserver;
   class WindowsObserver;
-  class AppListPrefsObserver;
   class ArcInputMethodManagerServiceObserver;
   class MojoConnectionObserver;
   class ArcNotificationSurfaceManagerObserver;
@@ -140,7 +139,6 @@
 
   std::unique_ptr<FocusChangeObserver> focus_change_observer_;
   std::unique_ptr<WindowsObserver> windows_observer_;
-  std::unique_ptr<AppListPrefsObserver> app_list_prefs_observer_;
   std::unique_ptr<ArcInputMethodManagerServiceObserver>
       input_manager_service_observer_;
   std::unique_ptr<MojoConnectionObserver> connection_observer_;
diff --git a/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker_unittest.cc b/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker_unittest.cc
index 5334457..e675321 100644
--- a/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker_unittest.cc
+++ b/chrome/browser/ash/arc/accessibility/arc_accessibility_tree_tracker_unittest.cc
@@ -130,10 +130,10 @@
   ASSERT_NE(nullptr, tree);
   ASSERT_EQ(2U, key_to_tree.size());
 
-  tree_tracker.OnTaskDestroyed(1);
+  tree_tracker.OnWindowDestroying(test_window.get());
   ASSERT_EQ(1U, key_to_tree.size());
 
-  tree_tracker.OnTaskDestroyed(2);
+  tree_tracker.OnWindowDestroying(test_window2.get());
   ASSERT_EQ(0U, key_to_tree.size());
 }
 
diff --git a/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.cc b/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.cc
index 1d2d503..49fbca2 100644
--- a/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.cc
+++ b/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.cc
@@ -81,7 +81,7 @@
 }
 
 void ArcNearbyShareBridge::StartNearbyShare(
-    int32_t task_id,
+    uint32_t task_id,
     mojom::ShareIntentInfoPtr share_info,
     mojo::PendingRemote<mojom::NearbyShareSessionInstance> session_instance,
     StartNearbyShareCallback callback) {
diff --git a/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.h b/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.h
index 0c5cf97..391f30d 100644
--- a/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.h
+++ b/chrome/browser/ash/arc/nearby_share/arc_nearby_share_bridge.h
@@ -46,7 +46,7 @@
 
   // mojom::NearbyShareHost overrides.
   void StartNearbyShare(
-      int32_t task_id,
+      uint32_t task_id,
       mojom::ShareIntentInfoPtr info,
       mojo::PendingRemote<mojom::NearbyShareSessionInstance> instance,
       StartNearbyShareCallback callback) override;
diff --git a/chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.cc b/chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.cc
index afc82bc..6f4967bf 100644
--- a/chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.cc
+++ b/chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.cc
@@ -47,7 +47,7 @@
 
 NearbyShareSessionImpl::NearbyShareSessionImpl(
     Profile* profile,
-    int32_t task_id,
+    uint32_t task_id,
     mojom::ShareIntentInfoPtr share_info,
     mojo::PendingRemote<mojom::NearbyShareSessionInstance> session_instance,
     mojo::PendingReceiver<mojom::NearbyShareSessionHost> session_receiver,
@@ -120,7 +120,10 @@
     bool visible) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
-  if (visible && (arc::GetWindowTaskId(window) == task_id_)) {
+  absl::optional<int> task_id = arc::GetWindowTaskId(window);
+  DCHECK(task_id.has_value());
+  DCHECK_GE(task_id.value(), 0);
+  if (visible && (task_id.value() == task_id_)) {
     VLOG(1) << "ARC Window is visible";
     if (window_initialization_timer_.IsRunning()) {
       window_initialization_timer_.Stop();
diff --git a/chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.h b/chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.h
index 2917f21e..dd5349a1 100644
--- a/chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.h
+++ b/chrome/browser/ash/arc/nearby_share/nearby_share_session_impl.h
@@ -33,7 +33,7 @@
 
   NearbyShareSessionImpl(
       Profile* profile,
-      int32_t task_id,
+      uint32_t task_id,
       mojom::ShareIntentInfoPtr share_info,
       mojo::PendingRemote<mojom::NearbyShareSessionInstance> session_instance,
       mojo::PendingReceiver<mojom::NearbyShareSessionHost> session_receiver,
@@ -88,7 +88,7 @@
   void OnCleanupSession();
 
   // Android activity's task ID
-  int32_t task_id_;
+  uint32_t task_id_;
 
   // Used to send messages to ARC.
   mojo::Remote<mojom::NearbyShareSessionInstance> session_instance_;
diff --git a/chrome/browser/ash/borealis/borealis_context_manager_unittest.cc b/chrome/browser/ash/borealis/borealis_context_manager_unittest.cc
index 4857d81..5a339cd 100644
--- a/chrome/browser/ash/borealis/borealis_context_manager_unittest.cc
+++ b/chrome/browser/ash/borealis/borealis_context_manager_unittest.cc
@@ -106,6 +106,16 @@
     histogram_tester_.reset();
   }
 
+  void SendVmStartedSignal() {
+    auto* concierge_client = chromeos::FakeConciergeClient::Get();
+
+    vm_tools::concierge::VmStartedSignal signal;
+    signal.set_name("test_vm_name");
+    signal.set_owner_id(
+        ash::ProfileHelper::GetUserIdHashFromProfile(profile_.get()));
+    concierge_client->NotifyVmStarted(signal);
+  }
+
   void SendVmStoppedSignal() {
     auto* concierge_client = chromeos::FakeConciergeClient::Get();
 
@@ -414,6 +424,10 @@
 TEST_F(BorealisContextManagerTest, VmShutsDownAfterChromeCrashes) {
   chromeos::FakeConciergeClient* fake_concierge_client =
       chromeos::FakeConciergeClient::Get();
+
+  // Ensure that GetVmInfo returns success - a VM "still running".
+  SendVmStartedSignal();
+
   profile_->set_last_session_exited_cleanly(false);
   BorealisContextManagerImpl context_manager(profile_.get());
   task_environment_.RunUntilIdle();
diff --git a/chrome/browser/ash/crosapi/local_printer_ash.cc b/chrome/browser/ash/crosapi/local_printer_ash.cc
index 60bf2e9..14128d34 100644
--- a/chrome/browser/ash/crosapi/local_printer_ash.cc
+++ b/chrome/browser/ash/crosapi/local_printer_ash.cc
@@ -92,9 +92,16 @@
 
 }  // namespace
 
-LocalPrinterAsh::LocalPrinterAsh() = default;
+LocalPrinterAsh::LocalPrinterAsh()
+    : profile_manager_(g_browser_process->profile_manager()) {
+  if (profile_manager_)
+    profile_manager_->AddObserver(this);
+}
 
-LocalPrinterAsh::~LocalPrinterAsh() = default;
+LocalPrinterAsh::~LocalPrinterAsh() {
+  if (profile_manager_)
+    profile_manager_->RemoveObserver(this);
+}
 
 // static
 mojom::PrintServersConfigPtr LocalPrinterAsh::ConfigToMojom(
@@ -137,6 +144,43 @@
   receivers_.Add(this, std::move(pending_receiver));
 }
 
+void LocalPrinterAsh::OnProfileAdded(Profile*) {
+  if (observers_registered_)
+    return;
+  Profile* profile = GetProfile();
+  if (!profile)
+    return;
+  auto* printers_manager_factory =
+      chromeos::CupsPrintersManagerFactory::GetForBrowserContext(profile);
+  // In unit tests, `printers_manager_factory` can be null.
+  if (!printers_manager_factory) {
+    LOG(ERROR) << "CupsPrintersManagerFactory object not found";
+    return;
+  }
+  observers_registered_ = true;
+  // RemoveObserver() is not called since this object outlasts the
+  // BrowserContextKeyedServices it's observing -
+  // BrowserContextKeyedServices are destroyed in
+  // ChromeBrowserMainParts::PostMainMessageLoopRun() while this object is
+  // destroyed in ~ChromeBrowserMainParts().
+  auto* print_servers_manager =
+      printers_manager_factory->GetPrintServersManager();
+  if (print_servers_manager) {
+    print_servers_manager->AddObserver(this);
+  } else {
+    // This can occur during browser tests.
+    LOG(ERROR) << "PrintServersManager object not found";
+  }
+  auto* print_job_manager =
+      chromeos::CupsPrintJobManagerFactory::GetForBrowserContext(profile);
+  print_job_manager->AddObserver(this);
+}
+
+void LocalPrinterAsh::OnProfileManagerDestroying() {
+  profile_manager_->RemoveObserver(this);
+  profile_manager_ = nullptr;
+}
+
 void LocalPrinterAsh::OnPrintJobCreated(
     base::WeakPtr<chromeos::CupsPrintJob> job) {
   NotifyPrintJobUpdate(job, mojom::PrintJobStatus::kCreated);
@@ -204,32 +248,11 @@
     remote->OnServerPrintersChanged();
 }
 
-void LocalPrinterAsh::RegisterObservers() {
-  if (observers_registered_)
-    return;
-  Profile* profile = GetProfile();
-  if (!profile)
-    return;
-  observers_registered_ = true;
-  auto* print_servers_manager =
-      chromeos::CupsPrintersManagerFactory::GetForBrowserContext(profile)
-          ->GetPrintServersManager();
-  if (print_servers_manager) {
-    print_servers_manager->AddObserver(this);
-  } else {
-    // This can occur during browser tests.
-    LOG(ERROR) << "PrintServersManager object not found";
-  }
-  auto* print_job_manager =
-      chromeos::CupsPrintJobManagerFactory::GetForBrowserContext(profile);
-  print_job_manager->AddObserver(this);
-}
-
 void LocalPrinterAsh::GetPrinters(GetPrintersCallback callback) {
   Profile* profile = GetProfile();
   DCHECK(profile);
   // Printing is not allowed during OOBE.
-  DCHECK(!chromeos::ProfileHelper::IsSigninProfile(profile));
+  DCHECK(!ash::ProfileHelper::IsSigninProfile(profile));
   chromeos::CupsPrintersManager* printers_manager =
       chromeos::CupsPrintersManagerFactory::GetForBrowserContext(profile);
   std::vector<mojom::LocalDestinationInfoPtr> printers;
@@ -371,7 +394,6 @@
 void LocalPrinterAsh::AddPrintServerObserver(
     mojo::PendingRemote<mojom::PrintServerObserver> remote,
     AddPrintServerObserverCallback callback) {
-  RegisterObservers();
   print_server_remotes_.Add(std::move(remote));
   std::move(callback).Run();
 }
@@ -439,9 +461,8 @@
 void LocalPrinterAsh::GetUsernamePerPolicy(
     GetUsernamePerPolicyCallback callback) {
   Profile* profile = GetProfile();
-  const std::string username = chromeos::ProfileHelper::Get()
-                                   ->GetUserByProfile(profile)
-                                   ->display_email();
+  const std::string username =
+      ash::ProfileHelper::Get()->GetUserByProfile(profile)->display_email();
   std::move(callback).Run(profile->GetPrefs()->GetBoolean(
                               prefs::kPrintingSendUsernameAndFilenameEnabled)
                               ? absl::make_optional(username)
@@ -489,8 +510,10 @@
 }
 
 Profile* LocalPrinterAsh::GetProfile() {
-  DCHECK(user_manager::UserManager::IsInitialized());
-  DCHECK(user_manager::UserManager::Get()->IsUserLoggedIn());
+  if (!user_manager::UserManager::IsInitialized() ||
+      !user_manager::UserManager::Get()->IsUserLoggedIn()) {
+    return nullptr;
+  }
   return ProfileManager::GetPrimaryUserProfile();
 }
 
@@ -498,7 +521,6 @@
     mojo::PendingRemote<mojom::PrintJobObserver> remote,
     mojom::PrintJobSource source,
     AddPrintJobObserverCallback callback) {
-  RegisterObservers();
   if (source == mojom::PrintJobSource::kExtension)
     extension_print_job_remotes_.Add(std::move(remote));
   if (source == mojom::PrintJobSource::kAny)
diff --git a/chrome/browser/ash/crosapi/local_printer_ash.h b/chrome/browser/ash/crosapi/local_printer_ash.h
index c7a461a..ce1588a 100644
--- a/chrome/browser/ash/crosapi/local_printer_ash.h
+++ b/chrome/browser/ash/crosapi/local_printer_ash.h
@@ -13,12 +13,14 @@
 #include "chrome/browser/chromeos/printing/cups_print_job.h"
 #include "chrome/browser/chromeos/printing/cups_print_job_manager.h"
 #include "chrome/browser/chromeos/printing/print_servers_manager.h"
+#include "chrome/browser/profiles/profile_manager_observer.h"
 #include "chromeos/crosapi/mojom/local_printer.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/bindings/remote_set.h"
 
 class Profile;
+class ProfileManager;
 
 namespace chromeos {
 class CupsPrinterStatus;
@@ -33,6 +35,7 @@
 // Implements the crosapi interface for LocalPrinter. Lives in Ash-Chrome on the
 // UI thread.
 class LocalPrinterAsh : public mojom::LocalPrinter,
+                        public ProfileManagerObserver,
                         public chromeos::CupsPrintJobManager::Observer,
                         public chromeos::PrintServersManager::Observer {
  public:
@@ -64,6 +67,10 @@
 
   void BindReceiver(mojo::PendingReceiver<mojom::LocalPrinter> receiver);
 
+  // ProfileManagerObserver:
+  void OnProfileAdded(Profile* profile) override;
+  void OnProfileManagerDestroying() override;
+
   // chromeos::CupsPrintJobManager::Observer:
   void OnPrintJobCreated(base::WeakPtr<chromeos::CupsPrintJob> job) override;
   void OnPrintJobStarted(base::WeakPtr<chromeos::CupsPrintJob> job) override;
@@ -119,13 +126,7 @@
   virtual std::unique_ptr<chromeos::PrinterConfigurer> CreatePrinterConfigurer(
       Profile* profile);
 
-  // Registers observers via AddObserver(). RemoveObserver() is not called
-  // since this object outlasts the BrowserContextKeyedServices it's
-  // observing - BrowserContextKeyedServices are destroyed in
-  // ChromeBrowserMainParts::PostMainMessageLoopRun() while this object is
-  // destroyed in ~ChromeBrowserMainParts().
-  // Does nothing if observers have already been registered.
-  void RegisterObservers();
+  ProfileManager* profile_manager_ = nullptr;
 
   bool observers_registered_ = false;
 
diff --git a/chrome/browser/ash/crostini/crostini_manager.cc b/chrome/browser/ash/crostini/crostini_manager.cc
index b789e469..e9738c1 100644
--- a/chrome/browser/ash/crostini/crostini_manager.cc
+++ b/chrome/browser/ash/crostini/crostini_manager.cc
@@ -1027,31 +1027,26 @@
       base::BindOnce(&CrostiniManager::CheckPaths),
       base::BindOnce(&CrostiniManager::MaybeUpdateCrostiniAfterChecks,
                      weak_ptr_factory_.GetWeakPtr()));
+
   // Probe Concierge - if it's still running after an unclean shutdown, a
   // success response will be received.
-  if (profile_->GetLastSessionExitType() == Profile::EXIT_CRASHED) {
-    vm_tools::concierge::GetVmInfoRequest concierge_request;
-    concierge_request.set_owner_id(owner_id_);
-    concierge_request.set_name(kCrostiniDefaultVmName);
-    GetConciergeClient()->GetVmInfo(
-        std::move(concierge_request),
-        base::BindOnce(
-            [](base::WeakPtr<CrostiniManager> weak_this,
-               absl::optional<vm_tools::concierge::GetVmInfoResponse> reply) {
-              if (weak_this) {
-                VLOG(1) << "Exit type: "
-                        << static_cast<int>(Profile::EXIT_CRASHED);
-                VLOG(1) << "GetVmInfo result: "
-                        << (reply.has_value() && reply->success());
-                weak_this->is_unclean_startup_ =
-                    reply.has_value() && reply->success();
-                if (weak_this->is_unclean_startup_) {
-                  weak_this->RemoveUncleanSshfsMounts();
-                }
+  vm_tools::concierge::GetVmInfoRequest concierge_request;
+  concierge_request.set_owner_id(owner_id_);
+  concierge_request.set_name(kCrostiniDefaultVmName);
+  GetConciergeClient()->GetVmInfo(
+      std::move(concierge_request),
+      base::BindOnce(
+          [](base::WeakPtr<CrostiniManager> weak_this,
+             absl::optional<vm_tools::concierge::GetVmInfoResponse> reply) {
+            if (weak_this) {
+              weak_this->is_unclean_startup_ =
+                  reply.has_value() && reply->success();
+              if (weak_this->is_unclean_startup_) {
+                weak_this->RemoveUncleanSshfsMounts();
               }
-            },
-            weak_ptr_factory_.GetWeakPtr()));
-  }
+            }
+          },
+          weak_ptr_factory_.GetWeakPtr()));
 }
 
 // static
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest.cc b/chrome/browser/ash/file_manager/file_manager_browsertest.cc
index 57b7310..93e2691 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest.cc
@@ -416,10 +416,13 @@
         TestCase("keyboardCopyDownloads"),
         TestCase("keyboardCopyDownloads").EnableTrash(),
         TestCase("keyboardCopyDrive"),
+// TODO(crbug.com/1236842): Remove flakiness and enable this test.
+#if !defined(ADDRESS_SANITIZER) && defined(NDEBUG)
         TestCase("keyboardFocusOutlineVisible"),
         TestCase("keyboardFocusOutlineVisible").EnableTrash(),
         TestCase("keyboardFocusOutlineVisibleMouse"),
         TestCase("keyboardFocusOutlineVisibleMouse").EnableTrash(),
+#endif
         TestCase("keyboardSelectDriveDirectoryTree"),
         TestCase("keyboardDisableCopyWhenDialogDisplayed"),
         TestCase("keyboardOpenNewWindow"),
@@ -767,10 +770,15 @@
         TestCase("transferDragDropActiveDrop").FilesSwa(),
         TestCase("transferDragDropTreeItemAccepts"),
         TestCase("transferDragDropTreeItemAccepts").FilesSwa(),
+// TODO(crbug.com/1236842): Remove flakiness and enable this test.
+#if !defined(ADDRESS_SANITIZER) && defined(NDEBUG)
         TestCase("transferDragDropTreeItemDenies"),
         TestCase("transferDragDropTreeItemDenies").FilesSwa(),
+#endif
         TestCase("transferDragAndHoverTreeItemEntryList"),
         TestCase("transferDragAndHoverTreeItemEntryList").FilesSwa(),
+// TODO(crbug.com/1236842): Remove flakiness and enable this test.
+#if !defined(ADDRESS_SANITIZER) && defined(NDEBUG)
         TestCase("transferDragAndHoverTreeItemFakeEntry"),
         TestCase("transferDragAndHoverTreeItemFakeEntry").FilesSwa(),
         TestCase("transferDragAndHoverTreeItemFakeEntry")
@@ -778,6 +786,7 @@
         TestCase("transferDragAndHoverTreeItemFakeEntry")
             .EnableSinglePartitionFormat()
             .FilesSwa(),
+#endif
         TestCase("transferDragFileListItemSelects"),
         TestCase("transferDragFileListItemSelects").FilesSwa(),
         TestCase("transferDragAndDrop"),
@@ -884,12 +893,15 @@
         TestCase("tabindexFocusDirectorySelectedSharesheetEnabled")
             .EnableSharesheet(),
         TestCase("tabindexOpenDialogDownloads").WithBrowser(),
-        TestCase("tabindexOpenDialogDownloads").WithBrowser().InGuestMode(),
+        TestCase("tabindexOpenDialogDownloads").WithBrowser().InGuestMode()
+// TODO(crbug.com/1236842): Remove flakiness and enable this test.
+#if !defined(ADDRESS_SANITIZER) && defined(NDEBUG)
+            ,
         TestCase("tabindexSaveFileDialogDrive").WithBrowser(),
         TestCase("tabindexSaveFileDialogDownloads").WithBrowser(),
-        TestCase("tabindexSaveFileDialogDownloads")
-            .WithBrowser()
-            .InGuestMode()));
+        TestCase("tabindexSaveFileDialogDownloads").WithBrowser().InGuestMode()
+#endif
+            ));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     FileDialog, /* file_dialog.js */
@@ -905,7 +917,10 @@
         TestCase("saveFileDialogDownloads").WithBrowser(),
         TestCase("saveFileDialogDownloads").WithBrowser().InGuestMode(),
         TestCase("saveFileDialogDownloads").WithBrowser().InIncognito(),
+// TODO(crbug.com/1236842): Remove flakiness and enable this test.
+#if !defined(ADDRESS_SANITIZER) && defined(NDEBUG)
         TestCase("saveFileDialogDownloadsNewFolderButton").WithBrowser(),
+#endif
         TestCase("saveFileDialogPanelsDisabled").WithBrowser(),
         TestCase("openFileDialogCancelDownloads").WithBrowser(),
         TestCase("openFileDialogEscapeDownloads").WithBrowser(),
diff --git a/chrome/browser/ash/plugin_vm/plugin_vm_manager_impl_unittest.cc b/chrome/browser/ash/plugin_vm/plugin_vm_manager_impl_unittest.cc
index 8a33080..755c52b 100644
--- a/chrome/browser/ash/plugin_vm/plugin_vm_manager_impl_unittest.cc
+++ b/chrome/browser/ash/plugin_vm/plugin_vm_manager_impl_unittest.cc
@@ -126,6 +126,14 @@
     VmPluginDispatcherClient().NotifyVmStateChanged(state_changed_signal);
   }
 
+  void NotifyVmStarted() {
+    vm_tools::concierge::VmStartedSignal signal;
+    signal.set_name(kPluginVmName);
+    signal.set_owner_id(
+        ash::ProfileHelper::GetUserIdHashFromProfile(testing_profile_.get()));
+    ConciergeClient().NotifyVmStarted(signal);
+  }
+
   content::BrowserTaskEnvironment task_environment_;
   std::unique_ptr<TestingProfile> testing_profile_;
   std::unique_ptr<PluginVmTestHelper> test_helper_;
@@ -245,6 +253,7 @@
   EXPECT_TRUE(
       chrome_shelf_controller_->IsOpen(ash::ShelfID(kPluginVmShelfAppId)));
 
+  NotifyVmStarted();
   NotifyVmStateChanged(vm_tools::plugin_dispatcher::VmState::VM_STATE_RUNNING);
   task_environment_.RunUntilIdle();
   EXPECT_GE(ConciergeClient().get_vm_info_call_count(), 1);
diff --git a/chrome/browser/ash/policy/handlers/restore_on_startup_browsertest_chromeos.cc b/chrome/browser/ash/policy/handlers/restore_on_startup_browsertest.cc
similarity index 84%
rename from chrome/browser/ash/policy/handlers/restore_on_startup_browsertest_chromeos.cc
rename to chrome/browser/ash/policy/handlers/restore_on_startup_browsertest.cc
index c39bf0f..6ecbb9bf 100644
--- a/chrome/browser/ash/policy/handlers/restore_on_startup_browsertest_chromeos.cc
+++ b/chrome/browser/ash/policy/handlers/restore_on_startup_browsertest.cc
@@ -32,9 +32,9 @@
 
 // Verifies that the |kRestoreOnStartup| and |kRestoreOnStartupURLs| policies
 // are honored on Chrome OS.
-class RestoreOnStartupTestChromeOS : public LoginPolicyTestBase {
+class RestoreOnStartupTest : public LoginPolicyTestBase {
  public:
-  RestoreOnStartupTestChromeOS();
+  RestoreOnStartupTest() = default;
 
   // LoginPolicyTestBase:
   void GetMandatoryPoliciesValue(base::DictionaryValue* policy) const override;
@@ -42,12 +42,10 @@
   void VerifyStartUpURLs();
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(RestoreOnStartupTestChromeOS);
+  DISALLOW_COPY_AND_ASSIGN(RestoreOnStartupTest);
 };
 
-RestoreOnStartupTestChromeOS::RestoreOnStartupTestChromeOS() {}
-
-void RestoreOnStartupTestChromeOS::GetMandatoryPoliciesValue(
+void RestoreOnStartupTest::GetMandatoryPoliciesValue(
     base::DictionaryValue* policy) const {
   policy->SetInteger(key::kRestoreOnStartup,
                      SessionStartupPref::kPrefValueURLs);
@@ -57,7 +55,7 @@
   policy->SetKey(key::kRestoreOnStartupURLs, std::move(urls));
 }
 
-void RestoreOnStartupTestChromeOS::VerifyStartUpURLs() {
+void RestoreOnStartupTest::VerifyStartUpURLs() {
   const BrowserList* const browser_list = BrowserList::GetInstance();
   ASSERT_EQ(1U, browser_list->size());
   const Browser* const browser = browser_list->get(0);
@@ -70,14 +68,14 @@
 }
 
 // Verify that the policies are honored on a new user's login.
-IN_PROC_BROWSER_TEST_F(RestoreOnStartupTestChromeOS, PRE_LogInAndVerify) {
+IN_PROC_BROWSER_TEST_F(RestoreOnStartupTest, PRE_LogInAndVerify) {
   SkipToLoginScreen();
   LogIn(kAccountId, kAccountPassword, kEmptyServices);
   VerifyStartUpURLs();
 }
 
 // Verify that the policies are honored on an existing user's login.
-IN_PROC_BROWSER_TEST_F(RestoreOnStartupTestChromeOS, LogInAndVerify) {
+IN_PROC_BROWSER_TEST_F(RestoreOnStartupTest, LogInAndVerify) {
   ash::LoginScreenTestApi::SubmitPassword(AccountId::FromUserEmail(kAccountId),
                                           kAccountPassword,
                                           true /* check_if_submittable */);
diff --git a/chrome/browser/ash/policy/login/force_maximize_on_first_run_chromeos_browsertest.cc b/chrome/browser/ash/policy/login/force_maximize_on_first_run_browsertest.cc
similarity index 100%
rename from chrome/browser/ash/policy/login/force_maximize_on_first_run_chromeos_browsertest.cc
rename to chrome/browser/ash/policy/login/force_maximize_on_first_run_browsertest.cc
diff --git a/chrome/browser/ash/system_logs/network_health_source.cc b/chrome/browser/ash/system_logs/network_health_source.cc
index 1b5f284..e7c5b47c 100644
--- a/chrome/browser/ash/system_logs/network_health_source.cc
+++ b/chrome/browser/ash/system_logs/network_health_source.cc
@@ -119,6 +119,9 @@
     case RoutineProblems::Tag::VIDEO_CONFERENCING_PROBLEMS:
       problemsStr = ProblemsToStr(problems->get_video_conferencing_problems());
       break;
+    case RoutineProblems::Tag::ARC_HTTP_PROBLEMS:
+      problemsStr = ProblemsToStr(problems->get_arc_http_problems());
+      break;
   }
   return problemsStr;
 }
diff --git a/chrome/browser/autofill/autofill_browsertest.cc b/chrome/browser/autofill/autofill_browsertest.cc
index 8cfabf0..ff5114a3 100644
--- a/chrome/browser/autofill/autofill_browsertest.cc
+++ b/chrome/browser/autofill/autofill_browsertest.cc
@@ -854,8 +854,12 @@
             base::BindRepeating(&PrerenderAutofillTest::web_contents,
                                 base::Unretained(this))) {}
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    InProcessBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     ASSERT_TRUE(embedded_test_server()->Start());
   }
 
diff --git a/chrome/browser/banners/android/chrome_app_banner_manager_android.cc b/chrome/browser/banners/android/chrome_app_banner_manager_android.cc
index bbd645a..d2b8aad 100644
--- a/chrome/browser/banners/android/chrome_app_banner_manager_android.cc
+++ b/chrome/browser/banners/android/chrome_app_banner_manager_android.cc
@@ -24,6 +24,7 @@
 #include "components/webapps/browser/installable/installable_data.h"
 #include "content/public/browser/manifest_icon_downloader.h"
 #include "content/public/browser/web_contents.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 
 using base::android::ConvertJavaStringToUTF16;
 using base::android::ConvertJavaStringToUTF8;
@@ -121,9 +122,8 @@
   auto a2hs_params = CreateAddToHomescreenParams(install_source);
   return PwaBottomSheetController::MaybeShow(
       web_contents(), GetAppName(), primary_icon_, has_maskable_primary_icon_,
-      manifest_.start_url, screenshots_,
-      manifest_.description.value_or(std::u16string()), expand_sheet,
-      std::move(a2hs_params),
+      manifest().start_url, screenshots_, manifest().description.value_or(u""),
+      expand_sheet, std::move(a2hs_params),
       base::BindRepeating(&ChromeAppBannerManagerAndroid::OnInstallEvent,
                           ChromeAppBannerManagerAndroid::GetAndroidWeakPtr()));
 }
diff --git a/chrome/browser/banners/app_banner_manager_browsertest.cc b/chrome/browser/banners/app_banner_manager_browsertest.cc
index 16dedae..b20a582 100644
--- a/chrome/browser/banners/app_banner_manager_browsertest.cc
+++ b/chrome/browser/banners/app_banner_manager_browsertest.cc
@@ -15,6 +15,7 @@
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
 #include "chrome/browser/banners/app_banner_manager_browsertest_base.h"
 #include "chrome/browser/banners/app_banner_manager_desktop.h"
 #include "chrome/browser/profiles/profile.h"
@@ -711,7 +712,7 @@
   GURL test_url = GetBannerURLWithAction("stash_event");
   service->ResetBaseScoreForURL(test_url, 10);
 
-  blink::Manifest manifest;
+  blink::mojom::Manifest manifest;
   std::vector<SkBitmap> screenshots;
   installable_manager_->FailNext(base::WrapUnique(
       new InstallableData({MANIFEST_URL_CHANGED}, GURL::EmptyGURL(), manifest,
diff --git a/chrome/browser/banners/app_banner_manager_desktop.cc b/chrome/browser/banners/app_banner_manager_desktop.cc
index 743191a..b975b21 100644
--- a/chrome/browser/banners/app_banner_manager_desktop.cc
+++ b/chrome/browser/banners/app_banner_manager_desktop.cc
@@ -26,6 +26,7 @@
 #include "extensions/common/constants.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ash/arc/arc_util.h"
@@ -145,7 +146,7 @@
 bool AppBannerManagerDesktop::IsWebAppConsideredInstalled() const {
   return web_app::FindInstalledAppWithUrlInScope(
              Profile::FromBrowserContext(web_contents()->GetBrowserContext()),
-             manifest_.start_url)
+             manifest().start_url)
       .has_value();
 }
 
@@ -158,7 +159,7 @@
 
 bool AppBannerManagerDesktop::ShouldAllowWebAppReplacementInstall() {
   // Only allow replacement install if this specific app is already installed.
-  web_app::AppId app_id = web_app::GenerateAppIdFromManifest(manifest_);
+  web_app::AppId app_id = web_app::GenerateAppIdFromManifest(manifest());
   if (!registrar().IsLocallyInstalled(app_id))
     return false;
 
diff --git a/chrome/browser/banners/app_banner_manager_desktop_browsertest.cc b/chrome/browser/banners/app_banner_manager_desktop_browsertest.cc
index 271a614..62e3589 100644
--- a/chrome/browser/banners/app_banner_manager_desktop_browsertest.cc
+++ b/chrome/browser/banners/app_banner_manager_desktop_browsertest.cc
@@ -27,9 +27,9 @@
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
 #include "chrome/browser/ui/web_applications/web_app_dialog_utils.h"
 #include "chrome/browser/web_applications/components/external_install_options.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.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/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/test/base/ui_test_utils.h"
diff --git a/chrome/browser/bluetooth/web_bluetooth_browsertest.cc b/chrome/browser/bluetooth/web_bluetooth_browsertest.cc
index 186b500..6532a336 100644
--- a/chrome/browser/bluetooth/web_bluetooth_browsertest.cc
+++ b/chrome/browser/bluetooth/web_bluetooth_browsertest.cc
@@ -1117,8 +1117,12 @@
   ~WebBluetoothTestWithNewPermissionsBackendEnabledInPrerendering() override =
       default;
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    WebBluetoothTestWithNewPermissionsBackendEnabled::SetUp();
+  }
+
   void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     WebBluetoothTestWithNewPermissionsBackendEnabled::SetUpOnMainThread();
     ASSERT_TRUE(test_server_handle_ =
                     embedded_test_server()->StartAndReturnHandle());
diff --git a/chrome/browser/captive_portal/captive_portal_browsertest.cc b/chrome/browser/captive_portal/captive_portal_browsertest.cc
index c8d3ac4..96cad052 100644
--- a/chrome/browser/captive_portal/captive_portal_browsertest.cc
+++ b/chrome/browser/captive_portal/captive_portal_browsertest.cc
@@ -3039,9 +3039,13 @@
             base::Unretained(this))) {}
   ~CaptivePortalForPrerenderingTest() override = default;
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    CaptivePortalBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     CaptivePortalBrowserTest::SetUpOnMainThread();
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     host_resolver()->AddRule("*", "127.0.0.1");
     ASSERT_TRUE(embedded_test_server()->Start());
   }
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 9c1ef9f..e2b0a63 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3400,6 +3400,9 @@
     case ui::NativeTheme::PreferredContrast::kLess:
       web_prefs->preferred_contrast = blink::mojom::PreferredContrast::kLess;
       break;
+    case ui::NativeTheme::PreferredContrast::kCustom:
+      web_prefs->preferred_contrast = blink::mojom::PreferredContrast::kCustom;
+      break;
   }
 
   UpdatePreferredColorScheme(
diff --git a/chrome/browser/chrome_content_browser_client_browsertest.cc b/chrome/browser/chrome_content_browser_client_browsertest.cc
index a37a8da..10b75a82 100644
--- a/chrome/browser/chrome_content_browser_client_browsertest.cc
+++ b/chrome/browser/chrome_content_browser_client_browsertest.cc
@@ -378,6 +378,8 @@
         return "more";
       case ui::NativeTheme::PreferredContrast::kLess:
         return "less";
+      case ui::NativeTheme::PreferredContrast::kCustom:
+        return "custom";
     }
   }
 
@@ -437,7 +439,8 @@
     PrefersContrastTest,
     testing::Values(ui::NativeTheme::PreferredContrast::kNoPreference,
                     ui::NativeTheme::PreferredContrast::kMore,
-                    ui::NativeTheme::PreferredContrast::kLess));
+                    ui::NativeTheme::PreferredContrast::kLess,
+                    ui::NativeTheme::PreferredContrast::kCustom));
 
 class ProtocolHandlerTest : public InProcessBrowserTest {
  public:
diff --git a/chrome/browser/chrome_service_worker_browsertest.cc b/chrome/browser/chrome_service_worker_browsertest.cc
index 6ec7df6..00904e3c 100644
--- a/chrome/browser/chrome_service_worker_browsertest.cc
+++ b/chrome/browser/chrome_service_worker_browsertest.cc
@@ -55,6 +55,7 @@
 #include "ppapi/shared_impl/ppapi_switches.h"
 #include "third_party/blink/public/common/messaging/string_message_codec.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_registration_options.mojom.h"
 #include "url/origin.h"
@@ -537,7 +538,7 @@
 
   static void ManifestCallbackAndRun(base::OnceClosure continuation,
                                      const GURL&,
-                                     const blink::Manifest&) {
+                                     blink::mojom::ManifestPtr) {
     std::move(continuation).Run();
   }
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 4e3e7cb4..fb1b92b 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -2925,6 +2925,8 @@
     "net/delay_network_call.h",
     "net/dhcp_wpad_url_client.cc",
     "net/dhcp_wpad_url_client.h",
+    "net/network_diagnostics/arc_http_routine.cc",
+    "net/network_diagnostics/arc_http_routine.h",
     "net/network_diagnostics/captive_portal_routine.cc",
     "net/network_diagnostics/captive_portal_routine.h",
     "net/network_diagnostics/dns_latency_routine.cc",
@@ -3283,11 +3285,6 @@
       "printing/cups_proxy_service_manager.h",
       "printing/cups_proxy_service_manager_factory.cc",
       "printing/cups_proxy_service_manager_factory.h",
-      "printing/cups_wrapper.cc",
-      "printing/cups_wrapper.h",
-      "printing/cups_wrapper_impl.cc",
-      "printing/printer_error_codes.cc",
-      "printing/printer_error_codes.h",
       "printing/printer_info_cups.cc",
     ]
   } else {
@@ -4187,9 +4184,6 @@
     "../download/notification/download_item_notification_unittest.cc",
     "../extensions/api/enterprise_platform_keys/enterprise_platform_keys_api_unittest.cc",
     "../extensions/api/enterprise_platform_keys_private/enterprise_platform_keys_private_api_unittest.cc",
-    "../extensions/api/printing/fake_print_job_controller.cc",
-    "../extensions/api/printing/fake_print_job_controller.h",
-    "../extensions/api/printing/printer_capabilities_provider_unittest.cc",
     "../extensions/updater/local_extension_cache_unittest.cc",
     "../memory/memory_kills_monitor_unittest.cc",
     "../metrics/chromeos_metrics_provider_unittest.cc",
@@ -4247,6 +4241,7 @@
     "nearby/bluetooth_adapter_manager_unittest.cc",
     "nearby/nearby_process_manager_impl_unittest.cc",
     "net/client_cert_store_chromeos_unittest.cc",
+    "net/network_diagnostics/arc_http_routine_unittest.cc",
     "net/network_diagnostics/captive_portal_routine_unittest.cc",
     "net/network_diagnostics/dns_latency_routine_unittest.cc",
     "net/network_diagnostics/dns_resolution_routine_unittest.cc",
@@ -4369,13 +4364,9 @@
   ]
   if (use_cups) {
     sources += [
-      "../extensions/api/printing/printing_api_handler_unittest.cc",
-      "../extensions/api/printing/printing_api_utils_unittest.cc",
       "extensions/printing_metrics/print_job_finished_event_dispatcher_unittest.cc",
       "extensions/printing_metrics/print_job_info_idl_conversions_unittest.cc",
       "extensions/printing_metrics/printing_metrics_api_unittest.cc",
-      "printing/test_cups_wrapper.cc",
-      "printing/test_cups_wrapper.h",
     ]
   }
 
diff --git a/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller_impl.h b/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller_impl.h
index 01e27262..6838ff8 100644
--- a/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller_impl.h
+++ b/chrome/browser/chromeos/android_sms/android_sms_app_setup_controller_impl.h
@@ -8,8 +8,8 @@
 #include "base/memory/weak_ptr.h"
 #include "base/unguessable_token.h"
 #include "chrome/browser/chromeos/android_sms/android_sms_app_setup_controller.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/components/web_app_id.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "net/cookies/canonical_cookie.h"
 #include "net/cookies/cookie_access_result.h"
 #include "url/gurl.h"
@@ -23,11 +23,6 @@
 }  // namespace mojom
 }  // namespace network
 
-namespace web_app {
-enum class InstallResultCode;
-class ExternallyManagedAppManager;
-}  // namespace web_app
-
 namespace chromeos {
 
 namespace android_sms {
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
index 9d357e1..016b8e1a 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -3941,15 +3941,24 @@
     }
   }
 
-  window_state_observer_ = std::make_unique<WindowStateChangeObserver>(
-      window, expected_state,
-      base::BindOnce(
-          &AutotestPrivateSetAppWindowStateFunction::WindowStateChanged, this,
-          expected_state));
+  const bool wait = params->wait && *params->wait;
+
+  if (wait) {
+    window_state_observer_ = std::make_unique<WindowStateChangeObserver>(
+        window, expected_state,
+        base::BindOnce(
+            &AutotestPrivateSetAppWindowStateFunction::WindowStateChanged, this,
+            expected_state));
+  }
 
   const ash::WMEvent event(ToWMEventType(params->change.event_type));
   ash::WindowState::Get(window)->OnWMEvent(&event);
 
+  if (!wait) {
+    return RespondNow(OneArgument(base::Value(
+        api::autotest_private::ToString(ToWindowStateType(expected_state)))));
+  }
+
   return RespondLater();
 }
 
diff --git a/chrome/browser/chromeos/net/network_diagnostics/arc_http_routine.cc b/chrome/browser/chromeos/net/network_diagnostics/arc_http_routine.cc
new file mode 100644
index 0000000..8d33fb7
--- /dev/null
+++ b/chrome/browser/chromeos/net/network_diagnostics/arc_http_routine.cc
@@ -0,0 +1,135 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "chrome/browser/chromeos/net/network_diagnostics/arc_http_routine.h"
+#include "chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.h"
+#include "components/arc/arc_service_manager.h"
+#include "net/http/http_status_code.h"
+#include "url/gurl.h"
+
+namespace chromeos {
+namespace network_diagnostics {
+
+namespace {
+
+constexpr int kTotalHostsToQuery = 3;
+// The length of a random eight letter prefix.
+constexpr int kHostPrefixLength = 8;
+constexpr int kHttpPort = 443;
+constexpr char kHttpScheme[] = "http://";
+
+// Requests taking longer than 1000 ms are problematic.
+constexpr int kProblemLatencyMs = 1000;
+// Requests lasting between 500 ms and 1000 ms are potentially problematic.
+constexpr int kPotentialProblemLatencyMs = 500;
+
+}  // namespace
+
+ArcHttpRoutine::ArcHttpRoutine()
+    : hostnames_to_request_http_(
+          util::GetRandomHostsWithSchemeAndPortAndGenerate204Path(
+              kTotalHostsToQuery,
+              kHostPrefixLength,
+              kHttpScheme,
+              kHttpPort)) {}
+
+ArcHttpRoutine::~ArcHttpRoutine() = default;
+
+mojom::RoutineType ArcHttpRoutine::Type() {
+  return mojom::RoutineType::kArcHttp;
+}
+
+void ArcHttpRoutine::Run() {
+  AttemptNextRequest();
+}
+
+void ArcHttpRoutine::AttemptNextRequest() {
+  // If no more hostnames to request, report success and analyze results.
+  if (hostnames_to_request_http_.empty()) {
+    successfully_requested_targets_ = true;
+    AnalyzeResultsAndExecuteCallback();
+    return;
+  }
+
+  GURL url = hostnames_to_request_http_.back();
+  hostnames_to_request_http_.pop_back();
+
+  // Call the HttpTest API from the instance of NetInstance.
+  arc::mojom::NetInstance* net_instance = GetNetInstance();
+  net_instance->HttpTest("" /* default network */, url,
+                         base::BindOnce(&ArcHttpRoutine::OnRequestComplete,
+                                        weak_ptr_factory_.GetWeakPtr()));
+}
+
+arc::mojom::NetInstance* ArcHttpRoutine::GetNetInstance() {
+  // If |net_instance_| is not already set for testing purposes, get instance
+  // of NetInstance service.
+  if (net_instance_) {
+    return net_instance_;
+  }
+
+  // Call the singleton for ArcServiceManager and check if it is null.
+  auto* arc_service_manager = arc::ArcServiceManager::Get();
+  if (!arc_service_manager) {
+    failed_to_get_arc_service_manager_ = true;
+    AnalyzeResultsAndExecuteCallback();
+    return nullptr;
+  }
+
+  // Get an instance of the NetInstance service and check if it is null.
+  auto* arc_bridge_service = arc_service_manager->arc_bridge_service();
+  arc::mojom::NetInstance* net_instance =
+      ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service->net(), HttpTest);
+  if (!net_instance) {
+    failed_to_get_net_instance_service_for_http_test_ = true;
+    AnalyzeResultsAndExecuteCallback();
+    return nullptr;
+  }
+  return net_instance;
+}
+
+void ArcHttpRoutine::OnRequestComplete(
+    arc::mojom::ArcHttpTestResultPtr result) {
+  if (!result->is_successful ||
+      result->status_code != net::HttpStatusCode::HTTP_OK) {
+    successfully_requested_targets_ = false;
+    AnalyzeResultsAndExecuteCallback();
+    return;
+  }
+
+  max_latency_ = std::max(max_latency_, result->duration_ms);
+  AttemptNextRequest();
+}
+
+void ArcHttpRoutine::AnalyzeResultsAndExecuteCallback() {
+  if (!successfully_requested_targets_) {
+    set_verdict(mojom::RoutineVerdict::kProblem);
+    problems_.push_back(mojom::ArcHttpProblem::kFailedHttpRequests);
+  } else if (failed_to_get_arc_service_manager_) {
+    set_verdict(mojom::RoutineVerdict::kNotRun);
+    problems_.push_back(mojom::ArcHttpProblem::kFailedToGetArcServiceManager);
+  } else if (failed_to_get_net_instance_service_for_http_test_) {
+    set_verdict(mojom::RoutineVerdict::kNotRun);
+    problems_.push_back(
+        mojom::ArcHttpProblem::kFailedToGetNetInstanceForHttpTest);
+  } else if (max_latency_ <= kProblemLatencyMs &&
+             max_latency_ > kPotentialProblemLatencyMs) {
+    set_verdict(mojom::RoutineVerdict::kProblem);
+    problems_.push_back(mojom::ArcHttpProblem::kHighLatency);
+  } else if (max_latency_ > kProblemLatencyMs) {
+    set_verdict(mojom::RoutineVerdict::kProblem);
+    problems_.push_back(mojom::ArcHttpProblem::kVeryHighLatency);
+  } else {
+    set_verdict(mojom::RoutineVerdict::kNoProblem);
+  }
+  set_problems(mojom::RoutineProblems::NewArcHttpProblems(problems_));
+  ExecuteCallback();
+}
+
+}  // namespace network_diagnostics
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/net/network_diagnostics/arc_http_routine.h b/chrome/browser/chromeos/net/network_diagnostics/arc_http_routine.h
new file mode 100644
index 0000000..d2df9ad
--- /dev/null
+++ b/chrome/browser/chromeos/net/network_diagnostics/arc_http_routine.h
@@ -0,0 +1,63 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_ARC_HTTP_ROUTINE_H_
+#define CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_ARC_HTTP_ROUTINE_H_
+
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_routine.h"
+#include "components/arc/mojom/net.mojom.h"
+#include "components/arc/session/arc_bridge_service.h"
+
+namespace chromeos {
+namespace network_diagnostics {
+
+// Performs HTTP GET requests from within ARC to a random set of URLs
+// and returns the result.
+class ArcHttpRoutine : public NetworkDiagnosticsRoutine {
+ public:
+  using RunArcHttpCallback =
+      mojom::NetworkDiagnosticsRoutines::RunArcHttpCallback;
+
+  ArcHttpRoutine();
+  ArcHttpRoutine(const ArcHttpRoutine&) = delete;
+  ArcHttpRoutine& operator=(const ArcHttpRoutine&) = delete;
+  ~ArcHttpRoutine() override;
+
+  // NetworkDiagnosticsRoutine:
+  mojom::RoutineType Type() override;
+  void Run() override;
+  void AnalyzeResultsAndExecuteCallback() override;
+
+  void set_net_instance_for_testing(arc::mojom::NetInstance* net_instance) {
+    net_instance_ = net_instance;
+  }
+
+ private:
+  // Attempts the next HTTP request.
+  void AttemptNextRequest();
+
+  // Gets NetInstance service if it is not already set for testing
+  // purposes as |net_instance_|.
+  arc::mojom::NetInstance* GetNetInstance();
+
+  // Processes the |result| of an HttpTest API request.
+  void OnRequestComplete(arc::mojom::ArcHttpTestResultPtr result);
+
+  std::vector<GURL> hostnames_to_request_http_;
+  std::vector<mojom::ArcHttpProblem> problems_;
+  bool successfully_requested_targets_ = true;
+  bool failed_to_get_arc_service_manager_ = false;
+  bool failed_to_get_net_instance_service_for_http_test_ = false;
+  arc::mojom::NetInstance* net_instance_ = nullptr;
+  int64_t max_latency_ = 0;
+  base::WeakPtrFactory<ArcHttpRoutine> weak_ptr_factory_{this};
+};
+
+}  // namespace network_diagnostics
+}  // namespace chromeos
+
+#endif  //  CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_ARC_HTTP_ROUTINE_H_
diff --git a/chrome/browser/chromeos/net/network_diagnostics/arc_http_routine_unittest.cc b/chrome/browser/chromeos/net/network_diagnostics/arc_http_routine_unittest.cc
new file mode 100644
index 0000000..f27fe13
--- /dev/null
+++ b/chrome/browser/chromeos/net/network_diagnostics/arc_http_routine_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "base/test/task_environment.h"
+#include "chrome/browser/chromeos/net/network_diagnostics/arc_http_routine.h"
+#include "components/arc/test/fake_net_instance.h"
+#include "content/public/test/browser_task_environment.h"
+#include "net/http/http_status_code.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace network_diagnostics {
+
+namespace {
+
+const int kNoProblemDelayMs = 100;
+const int kHighLatencyDelayMs = 550;
+const int kVeryHighLatencyDelayMs = 1050;
+
+}  // namespace
+
+class ArcHttpRoutineTest : public ::testing::Test {
+ public:
+  ArcHttpRoutineTest() {}
+
+  ArcHttpRoutineTest(const ArcHttpRoutineTest&) = delete;
+  ArcHttpRoutineTest& operator=(const ArcHttpRoutineTest&) = delete;
+  ~ArcHttpRoutineTest() override = default;
+
+  void CompareResult(
+      mojom::RoutineVerdict expected_verdict,
+      const std::vector<mojom::ArcHttpProblem>& expected_problems,
+      mojom::RoutineResultPtr result) {
+    EXPECT_EQ(expected_verdict, result->verdict);
+    EXPECT_EQ(expected_problems, result->problems->get_arc_http_problems());
+    run_loop_.Quit();
+  }
+
+ protected:
+  void RunRoutine(mojom::RoutineVerdict expected_routine_verdict,
+                  const std::vector<mojom::ArcHttpProblem>& expected_problems) {
+    arc_http_routine_->RunRoutine(
+        base::BindOnce(&ArcHttpRoutineTest::CompareResult, weak_ptr(),
+                       expected_routine_verdict, expected_problems));
+    run_loop_.Run();
+  }
+
+  void SetUpRoutine(arc::mojom::ArcHttpTestResult result) {
+    // Set up the fake NetworkInstance service.
+    fake_net_instance_ = std::make_unique<arc::FakeNetInstance>();
+    fake_net_instance_->set_http_test_result(result);
+
+    // Set up routine with fake NetworkInstance service.
+    arc_http_routine_ = std::make_unique<ArcHttpRoutine>();
+    arc_http_routine_->set_net_instance_for_testing(fake_net_instance_.get());
+  }
+
+  base::WeakPtr<ArcHttpRoutineTest> weak_ptr() {
+    return weak_factory_.GetWeakPtr();
+  }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  base::RunLoop run_loop_;
+  std::unique_ptr<ArcHttpRoutine> arc_http_routine_;
+  std::unique_ptr<arc::FakeNetInstance> fake_net_instance_;
+  base::WeakPtrFactory<ArcHttpRoutineTest> weak_factory_{this};
+};
+
+TEST_F(ArcHttpRoutineTest, TestNoProblem) {
+  arc::mojom::ArcHttpTestResult result;
+  result.is_successful = true;
+  result.status_code = net::HttpStatusCode::HTTP_OK;
+  result.duration_ms = kNoProblemDelayMs;
+
+  SetUpRoutine(result);
+  RunRoutine(mojom::RoutineVerdict::kNoProblem, {});
+}
+
+TEST_F(ArcHttpRoutineTest, TestHttpRequestFailed) {
+  arc::mojom::ArcHttpTestResult result;
+  result.is_successful = false;
+
+  SetUpRoutine(result);
+  RunRoutine(mojom::RoutineVerdict::kProblem,
+             {mojom::ArcHttpProblem::kFailedHttpRequests});
+}
+
+TEST_F(ArcHttpRoutineTest, TestBadStatusCode) {
+  arc::mojom::ArcHttpTestResult result;
+  result.is_successful = true;
+  result.status_code = net::HttpStatusCode::HTTP_BAD_REQUEST;
+  result.duration_ms = kNoProblemDelayMs;
+
+  SetUpRoutine(result);
+  RunRoutine(mojom::RoutineVerdict::kProblem,
+             {mojom::ArcHttpProblem::kFailedHttpRequests});
+}
+
+TEST_F(ArcHttpRoutineTest, TestHighLatency) {
+  arc::mojom::ArcHttpTestResult result;
+  result.is_successful = true;
+  result.status_code = net::HttpStatusCode::HTTP_OK;
+  result.duration_ms = kHighLatencyDelayMs;
+
+  SetUpRoutine(result);
+  RunRoutine(mojom::RoutineVerdict::kProblem,
+             {mojom::ArcHttpProblem::kHighLatency});
+}
+
+TEST_F(ArcHttpRoutineTest, TestVeryHighLatency) {
+  arc::mojom::ArcHttpTestResult result;
+  result.is_successful = true;
+  result.status_code = net::HttpStatusCode::HTTP_OK;
+  result.duration_ms = kVeryHighLatencyDelayMs;
+
+  SetUpRoutine(result);
+  RunRoutine(mojom::RoutineVerdict::kProblem,
+             {mojom::ArcHttpProblem::kVeryHighLatency});
+}
+
+}  // namespace network_diagnostics
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics.cc b/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics.cc
index 8a07932..24b490d 100644
--- a/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics.cc
+++ b/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "chrome/browser/chromeos/net/network_diagnostics/arc_http_routine.h"
 #include "chrome/browser/chromeos/net/network_diagnostics/captive_portal_routine.h"
 #include "chrome/browser/chromeos/net/network_diagnostics/dns_latency_routine.h"
 #include "chrome/browser/chromeos/net/network_diagnostics/dns_resolution_routine.h"
@@ -254,6 +255,11 @@
   RunRoutine(std::move(routine), std::move(callback));
 }
 
+void NetworkDiagnostics::RunArcHttp(RunArcHttpCallback callback) {
+  auto routine = std::make_unique<ArcHttpRoutine>();
+  RunRoutine(std::move(routine), std::move(callback));
+}
+
 void NetworkDiagnostics::RunRoutine(
     std::unique_ptr<NetworkDiagnosticsRoutine> routine,
     RoutineResultCallback callback) {
diff --git a/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics.h b/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics.h
index c13829e..49e76d3 100644
--- a/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics.h
+++ b/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics.h
@@ -60,6 +60,7 @@
   void RunHttpsLatency(RunHttpsLatencyCallback callback) override;
   void RunVideoConferencing(const absl::optional<std::string>& stun_server_name,
                             RunVideoConferencingCallback callback) override;
+  void RunArcHttp(RunArcHttpCallback callback) override;
   void GetResult(const mojom::RoutineType type,
                  GetResultCallback callback) override;
   void GetAllResults(GetAllResultsCallback callback) override;
diff --git a/chrome/browser/chromeos/printing/cups_printers_manager.cc b/chrome/browser/chromeos/printing/cups_printers_manager.cc
index 5116f53..4201afe 100644
--- a/chrome/browser/chromeos/printing/cups_printers_manager.cc
+++ b/chrome/browser/chromeos/printing/cups_printers_manager.cc
@@ -683,6 +683,7 @@
     if (code == PpdProvider::SUCCESS) {
       ppd_resolution_tracker_.MarkResolutionSuccessful(printer_id, ref);
     } else {
+      LOG(WARNING) << "Failed to resolve PPD reference for " << printer_id;
       ppd_resolution_tracker_.MarkResolutionFailed(printer_id);
       if (!usb_manufacturer.empty()) {
         ppd_resolution_tracker_.SetManufacturer(printer_id, usb_manufacturer);
diff --git a/chrome/browser/content_settings/content_settings_browsertest.cc b/chrome/browser/content_settings/content_settings_browsertest.cc
index d392fe29a..57bbff6 100644
--- a/chrome/browser/content_settings/content_settings_browsertest.cc
+++ b/chrome/browser/content_settings/content_settings_browsertest.cc
@@ -1329,8 +1329,12 @@
             &ContentSettingsWithPrerenderingBrowserTest::GetWebContents,
             base::Unretained(this))) {}
 
+  void SetUp() override {
+    prerender_test_helper().SetUp(embedded_test_server());
+    ContentSettingsTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
-    prerender_test_helper().SetUpOnMainThread(embedded_test_server());
     ContentSettingsTest::SetUpOnMainThread();
     ASSERT_TRUE(embedded_test_server()->Start());
   }
diff --git a/chrome/browser/content_settings/mixed_content_settings_tab_helper_browsertest.cc b/chrome/browser/content_settings/mixed_content_settings_tab_helper_browsertest.cc
index bf9263d..6a8bd44d 100644
--- a/chrome/browser/content_settings/mixed_content_settings_tab_helper_browsertest.cc
+++ b/chrome/browser/content_settings/mixed_content_settings_tab_helper_browsertest.cc
@@ -27,12 +27,15 @@
             base::Unretained(this))) {}
   ~MixedContentSettingsTabHelperBrowserTest() override = default;
 
+  void SetUp() override {
+    prerender_helper_.SetUp(&ssl_server_);
+    InProcessBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     host_resolver()->AddRule("*", "127.0.0.1");
     ssl_server_.AddDefaultHandlers(GetChromeTestDataDir());
     ssl_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
-
-    prerender_helper_.SetUpOnMainThread(&ssl_server_);
     ASSERT_TRUE(ssl_server_.Start());
   }
 
diff --git a/chrome/browser/content_settings/sound_content_setting_observer.cc b/chrome/browser/content_settings/sound_content_setting_observer.cc
index 0c5a817e..03389fdd 100644
--- a/chrome/browser/content_settings/sound_content_setting_observer.cc
+++ b/chrome/browser/content_settings/sound_content_setting_observer.cc
@@ -155,10 +155,12 @@
 
 void SoundContentSettingObserver::CheckSoundBlocked(bool is_audible) {
   if (is_audible && GetCurrentContentSetting() == CONTENT_SETTING_BLOCK) {
-    // The tab has tried to play sound, but was muted.
-    // This is a page level event so it is OK to get the main frame here.
-    // TODO(https://crbug.com/1103176): We should figure a way of not having to
-    // use GetMainFrame here. (pass the source frame somehow)
+    // Since this is a page-level event and only primary pages can play audio
+    // in prerendering, we get `settings` from the main frame of the primary
+    // page.
+    // TODO(https://crbug.com/1103176): For other types of FrameTrees(fenced
+    // frames, portals) than prerendering, we should figure a way of not having
+    // to use GetMainFrame here. (pass the source frame somehow)
     content_settings::PageSpecificContentSettings* settings =
         content_settings::PageSpecificContentSettings::GetForFrame(
             web_contents()->GetMainFrame());
diff --git a/chrome/browser/content_settings/sound_content_setting_observer_browsertest.cc b/chrome/browser/content_settings/sound_content_setting_observer_browsertest.cc
index 9592185c..ad0a798 100644
--- a/chrome/browser/content_settings/sound_content_setting_observer_browsertest.cc
+++ b/chrome/browser/content_settings/sound_content_setting_observer_browsertest.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
@@ -50,6 +51,7 @@
     audio_state_changed_ = true;
   }
 
+  void set_audio_state_changed(bool value) { audio_state_changed_ = value; }
   bool audio_state_changed() { return audio_state_changed_; }
 
  private:
@@ -253,9 +255,8 @@
 // SoundContentSettingObserver::ReadyToCommitNavigation() with NavigationHandle
 // URL, it uses a page that has a sub frame to make sure that it's called with
 // the correct URL.
-// Flaky crashes. https://crbug.com/1228609
 IN_PROC_BROWSER_TEST_F(SoundContentSettingObserverBrowserTest,
-                       DISABLED_AddAutoplayFlagsInPrerendering) {
+                       AddAutoplayFlagsInPrerendering) {
   // Sets up the embedded test server to serve the test javascript file.
   net::test_server::EmbeddedTestServerHandle test_server_handle;
   ASSERT_TRUE(test_server_handle =
@@ -329,7 +330,7 @@
   ASSERT_TRUE(test_server_handle =
                   embedded_test_server()->StartAndReturnHandle());
 
-  // Allows to play a sound by default.
+  // Blocks to play a sound by default.
   HostContentSettingsMap* content_settings =
       HostContentSettingsMapFactory::GetForProfile(browser()->profile());
   content_settings->SetDefaultContentSetting(ContentSettingsType::SOUND,
@@ -387,3 +388,100 @@
   // CONTENT_SETTING_BLOCK.
   EXPECT_TRUE(observer->HasLoggedSiteMutedUkmForTesting());
 }
+
+// Tests that the page-specific settings for `ContentSettingsType::SOUND` is not
+// updated in the prerendered page to make sure that it's fine that
+// SoundContentSettingObserver::CheckSoundBlocked() access to the main frame
+// from WebContents.
+IN_PROC_BROWSER_TEST_F(SoundContentSettingObserverBrowserTest,
+                       NotUpdateCheckSoundBlockedInPrerendering) {
+  net::test_server::EmbeddedTestServerHandle test_server_handle;
+  ASSERT_TRUE(test_server_handle =
+                  embedded_test_server()->StartAndReturnHandle());
+
+  // Load a simple page.
+  GURL url = embedded_test_server()->GetURL("/simple.html");
+  ui_test_utils::NavigateToURL(browser(), url);
+
+  const std::string kPlayingSoundScript(
+      "var context = new window.AudioContext();"
+      "var oscillator = context.createOscillator();"
+      "oscillator.connect(context.destination);"
+      "oscillator.start();");
+
+  base::RunLoop run_loop;
+  TestAudioStartObserver audio_start_observer(web_contents(),
+                                              run_loop.QuitClosure());
+  ASSERT_TRUE(
+      content::ExecJs(web_contents()->GetMainFrame(), kPlayingSoundScript));
+  run_loop.Run();
+  // The page should try to start the audio.
+  EXPECT_TRUE(audio_start_observer.audio_state_changed());
+
+  // Since the main frame is playing a sound, WebContents knows it is audible.
+  EXPECT_TRUE(web_contents()->IsCurrentlyAudible());
+
+  // Change the profile-wide settings to block sound by default.
+  HostContentSettingsMap* content_settings =
+      HostContentSettingsMapFactory::GetForProfile(browser()->profile());
+  content_settings->SetDefaultContentSetting(ContentSettingsType::SOUND,
+                                             CONTENT_SETTING_BLOCK);
+  // Check that the primary page's content settings know that sound is blocked.
+  auto* primary_page_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
+          web_contents()->GetMainFrame());
+  EXPECT_TRUE(
+      primary_page_settings->IsContentBlocked(ContentSettingsType::SOUND));
+
+  // Start a prerendered page.
+  content::test::PrerenderHostRegistryObserver registry_observer(
+      *web_contents());
+  auto prerender_url = embedded_test_server()->GetURL("/empty.html");
+  int host_id = prerender_helper()->AddPrerender(prerender_url);
+  content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
+  content::RenderFrameHost* render_frame_host =
+      prerender_helper()->GetPrerenderedMainFrameHost(host_id);
+
+  // Reset the flag to test if audio state is changed by running the script to
+  // play a sound on the prerendered page.
+  audio_start_observer.set_audio_state_changed(false);
+  // Try to play a sound in the prerendering page.
+  content::ExecuteScriptAsync(render_frame_host, kPlayingSoundScript);
+  // The prerendering page should not start the audio.
+  EXPECT_FALSE(audio_start_observer.audio_state_changed());
+  // The main page is still audible as the prerendering doesn't affect playing a
+  // sound.
+  EXPECT_TRUE(web_contents()->IsCurrentlyAudible());
+
+  // Set the profile-wide contents settings to block sound again, to see if this
+  // updates the page-specific settings for the prerendered page or primary
+  // page.
+  content_settings->SetDefaultContentSetting(ContentSettingsType::SOUND,
+                                             CONTENT_SETTING_BLOCK);
+  auto* prerendered_frame_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
+          render_frame_host);
+  // Audio is not blocked on the prerendered page since it doesn't play a sound.
+  EXPECT_FALSE(
+      prerendered_frame_settings->IsContentBlocked(ContentSettingsType::SOUND));
+  // Audio is blocked on the current page since it is playing a sound.
+  EXPECT_TRUE(
+      primary_page_settings->IsContentBlocked(ContentSettingsType::SOUND));
+
+  // Activate the prerendering page.
+  prerender_helper()->NavigatePrimaryPage(prerender_url);
+  EXPECT_TRUE(host_observer.was_activated());
+
+  // Since audio stream status is posted to UI thread, wait until it's idle.
+  base::RunLoop().RunUntilIdle();
+
+  auto* activated_page_settings =
+      content_settings::PageSpecificContentSettings::GetForFrame(
+          web_contents()->GetMainFrame());
+  // It should be false since the page is activated from the prerendering.
+  // TODO(crbug.com/1228567): If AudioContext activation is handled, the audio
+  // would be played and the status also would be updated.
+  EXPECT_FALSE(
+      activated_page_settings->IsContentBlocked(ContentSettingsType::SOUND));
+  EXPECT_FALSE(web_contents()->IsCurrentlyAudible());
+}
diff --git a/chrome/browser/download/android/BUILD.gn b/chrome/browser/download/android/BUILD.gn
index 10808d1..12757f1 100644
--- a/chrome/browser/download/android/BUILD.gn
+++ b/chrome/browser/download/android/BUILD.gn
@@ -24,6 +24,7 @@
 
 android_library("java") {
   sources = [
+    "java/src/org/chromium/chrome/browser/download/DangerousDownloadDialogBridge.java",
     "java/src/org/chromium/chrome/browser/download/DownloadConstants.java",
     "java/src/org/chromium/chrome/browser/download/DownloadDelegateImpl.java",
     "java/src/org/chromium/chrome/browser/download/DownloadDialogBridge.java",
@@ -37,6 +38,7 @@
     "java/src/org/chromium/chrome/browser/download/MediaStoreHelper.java",
     "java/src/org/chromium/chrome/browser/download/MimeUtils.java",
     "java/src/org/chromium/chrome/browser/download/StringUtils.java",
+    "java/src/org/chromium/chrome/browser/download/dialogs/DangerousDownloadDialog.java",
     "java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialog.java",
     "java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialogImpl.java",
     "java/src/org/chromium/chrome/browser/download/dialogs/DownloadDateTimePickerDialogProperties.java",
@@ -124,6 +126,7 @@
 
 generate_jni("jni_headers") {
   sources = [
+    "java/src/org/chromium/chrome/browser/download/DangerousDownloadDialogBridge.java",
     "java/src/org/chromium/chrome/browser/download/DownloadDialogBridge.java",
     "java/src/org/chromium/chrome/browser/download/DownloadInfo.java",
     "java/src/org/chromium/chrome/browser/download/DownloadManagerBridge.java",
diff --git a/chrome/browser/download/android/dangerous_download_dialog_bridge.cc b/chrome/browser/download/android/dangerous_download_dialog_bridge.cc
new file mode 100644
index 0000000..469dfeb0
--- /dev/null
+++ b/chrome/browser/download/android/dangerous_download_dialog_bridge.cc
@@ -0,0 +1,104 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/download/android/dangerous_download_dialog_bridge.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/files/file_path.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/android/android_theme_resources.h"
+#include "chrome/browser/android/resource_mapper.h"
+#include "chrome/browser/download/android/jni_headers/DangerousDownloadDialogBridge_jni.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/android/window_android.h"
+#include "ui/base/l10n/l10n_util.h"
+
+using base::android::JavaParamRef;
+
+namespace {
+// Helper method to find a download from a list of downloads based on its GUID.
+download::DownloadItem* FindAndRemoveDownload(
+    JNIEnv* env,
+    std::vector<download::DownloadItem*>* downloads,
+    const JavaParamRef<jstring>& jdownload_guid) {
+  std::string guid = ConvertJavaStringToUTF8(env, jdownload_guid);
+
+  auto iter = std::find_if(downloads->begin(), downloads->end(),
+                           [&guid](download::DownloadItem* download) {
+                             return download->GetGuid() == guid;
+                           });
+
+  if (iter == downloads->end())
+    return nullptr;
+
+  download::DownloadItem* result = *iter;
+  downloads->erase(iter);
+  return result;
+}
+
+}  // namespace
+
+DangerousDownloadDialogBridge::DangerousDownloadDialogBridge() {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  java_object_.Reset(Java_DangerousDownloadDialogBridge_create(
+      env, reinterpret_cast<intptr_t>(this)));
+}
+
+DangerousDownloadDialogBridge::~DangerousDownloadDialogBridge() {
+  for (auto* download_item : download_items_)
+    download_item->RemoveObserver(this);
+  Java_DangerousDownloadDialogBridge_destroy(
+      base::android::AttachCurrentThread(), java_object_);
+}
+
+void DangerousDownloadDialogBridge::Show(download::DownloadItem* download_item,
+                                         ui::WindowAndroid* window_android) {
+  // Don't shown dangerous download again if it is already showing.
+  if (std::find(download_items_.begin(), download_items_.end(),
+                download_item) != download_items_.end()) {
+    return;
+  }
+  download_item->AddObserver(this);
+  download_items_.push_back(download_item);
+
+  JNIEnv* env = base::android::AttachCurrentThread();
+  Java_DangerousDownloadDialogBridge_showDialog(
+      env, java_object_, window_android->GetJavaObject(),
+      base::android::ConvertUTF8ToJavaString(env, download_item->GetGuid()),
+      base::android::ConvertUTF16ToJavaString(
+          env,
+          base::UTF8ToUTF16(download_item->GetFileNameToReportUser().value())),
+      download_item->GetTotalBytes(),
+      ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_INFOBAR_WARNING));
+}
+
+void DangerousDownloadDialogBridge::OnDownloadDestroyed(
+    download::DownloadItem* download_item) {
+  auto iter =
+      std::find(download_items_.begin(), download_items_.end(), download_item);
+  if (iter != download_items_.end())
+    download_items_.erase(iter);
+}
+
+void DangerousDownloadDialogBridge::Accepted(
+    JNIEnv* env,
+    const JavaParamRef<jstring>& jdownload_guid) {
+  download::DownloadItem* download =
+      FindAndRemoveDownload(env, &download_items_, jdownload_guid);
+  if (download)
+    download->ValidateDangerousDownload();
+}
+
+void DangerousDownloadDialogBridge::Cancelled(
+    JNIEnv* env,
+    const JavaParamRef<jstring>& jdownload_guid) {
+  download::DownloadItem* download =
+      FindAndRemoveDownload(env, &download_items_, jdownload_guid);
+  if (download)
+    download->Remove();
+}
diff --git a/chrome/browser/download/android/dangerous_download_dialog_bridge.h b/chrome/browser/download/android/dangerous_download_dialog_bridge.h
new file mode 100644
index 0000000..9c5fb51
--- /dev/null
+++ b/chrome/browser/download/android/dangerous_download_dialog_bridge.h
@@ -0,0 +1,50 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_DOWNLOAD_ANDROID_DANGEROUS_DOWNLOAD_DIALOG_BRIDGE_H_
+#define CHROME_BROWSER_DOWNLOAD_ANDROID_DANGEROUS_DOWNLOAD_DIALOG_BRIDGE_H_
+
+#include <vector>
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "components/download/public/common/download_item.h"
+#include "ui/gfx/native_widget_types.h"
+
+// Class for showing dialogs to asks whether user wants to download a dangerous
+// file.
+class DangerousDownloadDialogBridge : public download::DownloadItem::Observer {
+ public:
+  DangerousDownloadDialogBridge();
+  DangerousDownloadDialogBridge(const DangerousDownloadDialogBridge&) = delete;
+  DangerousDownloadDialogBridge& operator=(
+      const DangerousDownloadDialogBridge&) = delete;
+
+  ~DangerousDownloadDialogBridge() override;
+
+  // Called to create and show a dialog for a dangerous download.
+  void Show(download::DownloadItem* download_item,
+            ui::WindowAndroid* window_android);
+
+  // Called from Java via JNI.
+  void Accepted(JNIEnv* env,
+                const base::android::JavaParamRef<jstring>& jdownload_guid);
+
+  // Called from Java via JNI.
+  void Cancelled(JNIEnv* env,
+                 const base::android::JavaParamRef<jstring>& jdownload_guid);
+
+  // download::DownloadItem::Observer:
+  void OnDownloadDestroyed(download::DownloadItem* download_item) override;
+
+ private:
+  // Download items that are requesting the dialog. Could get deleted while
+  // the dialog is showing.
+  std::vector<download::DownloadItem*> download_items_;
+
+  // The corresponding java object.
+  base::android::ScopedJavaGlobalRef<jobject> java_object_;
+};
+
+#endif  // CHROME_BROWSER_DOWNLOAD_ANDROID_DANGEROUS_DOWNLOAD_DIALOG_BRIDGE_H_
diff --git a/chrome/browser/download/android/download_controller.cc b/chrome/browser/download/android/download_controller.cc
index 87335789..a264f5c 100644
--- a/chrome/browser/download/android/download_controller.cc
+++ b/chrome/browser/download/android/download_controller.cc
@@ -440,6 +440,20 @@
     return;
   }
 
+  if (base::FeatureList::IsEnabled(
+          chrome::android::kEnableDangerousDownloadDialog)) {
+    ui::ViewAndroid* view_android =
+        web_contents ? web_contents->GetNativeView() : nullptr;
+    ui::WindowAndroid* window_android =
+        view_android ? view_android->GetWindowAndroid() : nullptr;
+    if (!dangerous_download_bridge_) {
+      dangerous_download_bridge_ =
+          std::make_unique<DangerousDownloadDialogBridge>();
+    }
+    dangerous_download_bridge_->Show(item, window_android);
+    return;
+  }
+
   DangerousDownloadInfoBarDelegate::Create(
       infobars::ContentInfoBarManager::FromWebContents(web_contents), item);
 }
diff --git a/chrome/browser/download/android/download_controller.h b/chrome/browser/download/android/download_controller.h
index 5fcbba21..6b03235e 100644
--- a/chrome/browser/download/android/download_controller.h
+++ b/chrome/browser/download/android/download_controller.h
@@ -25,6 +25,7 @@
 
 #include "base/android/scoped_java_ref.h"
 #include "base/memory/singleton.h"
+#include "chrome/browser/download/android/dangerous_download_dialog_bridge.h"
 #include "chrome/browser/download/android/download_controller_base.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_key.h"
@@ -105,6 +106,8 @@
   // from the beginning and all downloaded data will be lost.
   StrongValidatorsMap strong_validators_map_;
 
+  std::unique_ptr<DangerousDownloadDialogBridge> dangerous_download_bridge_;
+
   DISALLOW_COPY_AND_ASSIGN(DownloadController);
 };
 
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DangerousDownloadDialogBridge.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DangerousDownloadDialogBridge.java
new file mode 100644
index 0000000..6f3c9942
--- /dev/null
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DangerousDownloadDialogBridge.java
@@ -0,0 +1,79 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.download;
+
+import android.app.Activity;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.NativeMethods;
+import org.chromium.chrome.browser.download.dialogs.DangerousDownloadDialog;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.modaldialog.ModalDialogManagerHolder;
+
+/**
+ * Glues dangerous download dialogs UI code and handles the communication to download native
+ * backend.
+ */
+public class DangerousDownloadDialogBridge {
+    private long mNativeDangerousDownloadDialogBridge;
+
+    /**
+     * Constructor, taking a pointer to the native instance.
+     * @nativeDangerousDownloadDialogBridge Pointer to the native object.
+     */
+    public DangerousDownloadDialogBridge(long nativeDangerousDownloadDialogBridge) {
+        mNativeDangerousDownloadDialogBridge = nativeDangerousDownloadDialogBridge;
+    }
+
+    @CalledByNative
+    public static DangerousDownloadDialogBridge create(long nativeDialog) {
+        return new DangerousDownloadDialogBridge(nativeDialog);
+    }
+
+    /**
+     * Called to show a warning dialog for dangerous download.
+     * @param windowAndroid Window to show the dialog.
+     * @param guid GUID of the download.
+     * @param fileName Name of the download file.
+     * @param totalBytes Total bytes of the file.
+     * @param iconId The icon resource for the warning dialog.
+     */
+    @CalledByNative
+    public void showDialog(WindowAndroid windowAndroid, String guid, String fileName,
+            long totalBytes, int iconId) {
+        Activity activity = windowAndroid.getActivity().get();
+        if (activity == null) onCancel(guid);
+
+        new DangerousDownloadDialog().show(activity,
+                ((ModalDialogManagerHolder) activity).getModalDialogManager(), fileName, totalBytes,
+                iconId, (accepted) -> {
+                    if (accepted) {
+                        onAccepted(guid);
+                    } else {
+                        onCancel(guid);
+                    }
+                });
+    }
+
+    @CalledByNative
+    private void destroy() {
+        mNativeDangerousDownloadDialogBridge = 0;
+    }
+
+    private void onAccepted(String guid) {
+        DangerousDownloadDialogBridgeJni.get().accepted(mNativeDangerousDownloadDialogBridge, guid);
+    }
+
+    private void onCancel(String guid) {
+        DangerousDownloadDialogBridgeJni.get().cancelled(
+                mNativeDangerousDownloadDialogBridge, guid);
+    }
+
+    @NativeMethods
+    interface Natives {
+        void accepted(long nativeDangerousDownloadDialogBridge, String guid);
+        void cancelled(long nativeDangerousDownloadDialogBridge, String guid);
+    }
+}
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DangerousDownloadDialog.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DangerousDownloadDialog.java
new file mode 100644
index 0000000..4c700a3
--- /dev/null
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/dialogs/DangerousDownloadDialog.java
@@ -0,0 +1,93 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.download.dialogs;
+
+import android.content.Context;
+
+import androidx.core.content.res.ResourcesCompat;
+
+import org.chromium.base.Callback;
+import org.chromium.chrome.browser.download.R;
+import org.chromium.components.browser_ui.util.DownloadUtils;
+import org.chromium.ui.modaldialog.DialogDismissalCause;
+import org.chromium.ui.modaldialog.ModalDialogManager;
+import org.chromium.ui.modaldialog.ModalDialogProperties;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * Dialog for confirming that user want to download a dangerous file, using the default model dialog
+ * from ModalDialogManager.
+ */
+public class DangerousDownloadDialog {
+    public DangerousDownloadDialog() {}
+
+    /**
+     * Called to show a warning dialog for dangerous download.
+     * @param context Context for showing the dialog.
+     * @param modalDialogManager Manager for managing the modal dialog.
+     * @param fileName Name of the download file.
+     * @param totalBytes Total bytes of the file.
+     * @param iconId Icon ID of the warning dialog.
+     * @param callback Callback to run when confirming the download, true for accept the download,
+     *         false otherwise.
+     */
+    public void show(Context context, ModalDialogManager modalDialogManager, String fileName,
+            long totalBytes, int iconId, Callback<Boolean> callback) {
+        String message =
+                context.getResources().getString(R.string.dangerous_download_dialog_text, fileName);
+        if (totalBytes > 0) {
+            message += "\n\n(" + DownloadUtils.getStringForBytes(context, totalBytes) + ")";
+        }
+
+        PropertyModel propertyModel =
+                new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
+                        .with(ModalDialogProperties.CONTROLLER,
+                                new ModalDialogProperties.Controller() {
+                                    @Override
+                                    public void onClick(PropertyModel model, int buttonType) {
+                                        if (callback != null) {
+                                            callback.onResult(buttonType
+                                                    == ModalDialogProperties.ButtonType.POSITIVE);
+                                        }
+                                        modalDialogManager.dismissDialog(model,
+                                                buttonType
+                                                                == DialogDismissalCause
+                                                                           .POSITIVE_BUTTON_CLICKED
+                                                        ? DialogDismissalCause
+                                                                  .POSITIVE_BUTTON_CLICKED
+                                                        : DialogDismissalCause
+                                                                  .NEGATIVE_BUTTON_CLICKED);
+                                    }
+
+                                    @Override
+                                    public void onDismiss(PropertyModel model, int dismissalCause) {
+                                        if (callback != null
+                                                && dismissalCause
+                                                        != DialogDismissalCause
+                                                                   .POSITIVE_BUTTON_CLICKED
+                                                && dismissalCause
+                                                        != DialogDismissalCause
+                                                                   .NEGATIVE_BUTTON_CLICKED) {
+                                            callback.onResult(false);
+                                        }
+                                    }
+                                })
+                        .with(ModalDialogProperties.TITLE,
+                                context.getResources().getString(
+                                        R.string.dangerous_download_dialog_title))
+                        .with(ModalDialogProperties.MESSAGE, message)
+                        .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT,
+                                context.getResources().getString(
+                                        R.string.dangerous_download_dialog_confirm_text))
+                        .with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT,
+                                context.getResources().getString(R.string.cancel))
+                        .with(ModalDialogProperties.TITLE_ICON,
+                                ResourcesCompat.getDrawable(
+                                        context.getResources(), iconId, context.getTheme()))
+                        .build();
+
+        modalDialogManager.showDialog(propertyModel, ModalDialogManager.ModalDialogType.APP);
+    }
+}
diff --git a/chrome/browser/download/download_browsertest.cc b/chrome/browser/download/download_browsertest.cc
index 03162a2..cf59ed5 100644
--- a/chrome/browser/download/download_browsertest.cc
+++ b/chrome/browser/download/download_browsertest.cc
@@ -1287,9 +1287,13 @@
                                 base::Unretained(this))) {}
   ~PrerenderDownloadTest() override = default;
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    DownloadTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     DownloadTest::SetUpOnMainThread();
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     ASSERT_TRUE(test_server_handle_ =
                     embedded_test_server()->StartAndReturnHandle());
   }
diff --git a/chrome/browser/download/download_history.cc b/chrome/browser/download/download_history.cc
index 582d9e7..7d872df 100644
--- a/chrome/browser/download/download_history.cc
+++ b/chrome/browser/download/download_history.cc
@@ -178,6 +178,10 @@
   download.by_ext_id = by_ext_id;
   download.by_ext_name = by_ext_name;
   download.download_slice_info = history::GetHistoryDownloadSliceInfos(*item);
+  auto& reroute_info = item->GetRerouteInfo();
+  if (reroute_info.IsInitialized()) {
+    download.reroute_info_serialized = reroute_info.SerializeAsString();
+  }
   TruncatedDataUrlAtTheEndIfNeeded(&download.url_chain);
   return download;
 }
@@ -199,7 +203,8 @@
   // rename it. If Chrome is killed before committing the history here,
   // that temporary file will still get permanently left.
   // See http://crbug.com/664677.
-  if (previous == nullptr || previous->current_path != current.current_path)
+  if (previous == nullptr || previous->current_path != current.current_path ||
+      previous->reroute_info_serialized != current.reroute_info_serialized)
     return ShouldUpdateHistoryResult::UPDATE_IMMEDIATELY;
 
   // Ignore url_chain, referrer, site_url, http_method, mime_type,
@@ -379,6 +384,7 @@
         history::ToContentDownloadInterruptReason(row.interrupt_reason);
     std::vector<GURL> url_chain = row.url_chain;
     TruncatedDataUrlAtTheEndIfNeeded(&url_chain);
+    // TODO(https://crbug.com/1203753) Load reroute_info.
     download::DownloadItem* item = notifier_.GetManager()->CreateDownloadItem(
         row.guid, loading_id_, row.current_path, row.target_path, url_chain,
         row.referrer_url, row.site_url, row.tab_url, row.tab_referrer_url,
diff --git a/chrome/browser/download/download_history_unittest.cc b/chrome/browser/download/download_history_unittest.cc
index b28ac045..138d135 100644
--- a/chrome/browser/download/download_history_unittest.cc
+++ b/chrome/browser/download/download_history_unittest.cc
@@ -465,6 +465,16 @@
                                           row->by_ext_name);
 #endif
 
+    download::DownloadItemRerouteInfo reroute_info;
+    if (!row->reroute_info_serialized.empty()) {
+      ASSERT_TRUE(reroute_info.ParseFromString(row->reroute_info_serialized));
+      EXPECT_CALL(item(index), GetRerouteInfo())
+          .WillRepeatedly(ReturnRefOfCopy(reroute_info));
+    } else {
+      EXPECT_CALL(item(index), GetRerouteInfo())
+          .WillRepeatedly(ReturnRefOfCopy(download::DownloadItemRerouteInfo()));
+    }
+
     std::vector<download::DownloadItem*> items;
     for (size_t i = 0; i < items_.size(); ++i) {
       items.push_back(&item(i));
@@ -916,6 +926,37 @@
   ExpectDownloadCreated(row);
 }
 
+// Test that reroute info will be stored into history.
+TEST_F(DownloadHistoryTest, RerouteInfo) {
+  // Create a fresh item not from download DB
+  CreateDownloadHistory({});
+
+  history::DownloadRow row;
+  download::DownloadItemRerouteInfo reroute_info;
+  reroute_info.set_service_provider(enterprise_connectors::BOX);
+  reroute_info.mutable_box()->set_folder_id("12345");
+  row.reroute_info_serialized = reroute_info.SerializeAsString();
+  InitBasicItem(FILE_PATH_LITERAL("/foo/bar.pdf"), "http://example.com/bar.pdf",
+                "http://example.com/referrer.html",
+                download::DownloadItem::IN_PROGRESS, &row);
+
+  // Incomplete download will not be inserted into history.
+  CallOnDownloadCreated(0);
+  ExpectNoDownloadCreated();
+
+  // Completed download should be inserted.
+  reroute_info.mutable_box()->set_file_id("67890");
+  EXPECT_CALL(item(0), IsDone()).WillRepeatedly(Return(true));
+  EXPECT_CALL(item(0), GetState())
+      .WillRepeatedly(Return(download::DownloadItem::COMPLETE));
+  EXPECT_CALL(item(0), GetRerouteInfo())
+      .WillRepeatedly(ReturnRefOfCopy(reroute_info));
+  row.state = history::DownloadState::COMPLETE;
+  row.reroute_info_serialized = reroute_info.SerializeAsString();
+  item(0).NotifyObserversDownloadUpdated();
+  ExpectDownloadCreated(row);
+}
+
 // Tests that overwritten download is removed from history DB after the
 // expiration time.
 TEST_F(DownloadHistoryTest,
diff --git a/chrome/browser/enterprise/connectors/file_system/account_info_utils.cc b/chrome/browser/enterprise/connectors/file_system/account_info_utils.cc
index e6d2ca6..bfafda3 100644
--- a/chrome/browser/enterprise/connectors/file_system/account_info_utils.cc
+++ b/chrome/browser/enterprise/connectors/file_system/account_info_utils.cc
@@ -180,6 +180,11 @@
       folder_name);
 }
 
+void ClearDefaultFolder(PrefService* prefs,
+                        const std::string& service_provider) {
+  SetDefaultFolder(prefs, service_provider, std::string(), std::string());
+}
+
 std::string GetDefaultFolderId(PrefService* prefs,
                                const std::string& service_provider) {
   auto folder_id = prefs->GetString(
@@ -191,13 +196,16 @@
                                  const std::string& service_provider) {
   auto folder_id = GetDefaultFolderId(prefs, service_provider);
   DCHECK_EQ(service_provider, kFileSystemServiceProviderPrefNameBox);
-  return BoxApiCallFlow::MakeUrlToShowFolder(folder_id).spec();
+  std::string url = BoxApiCallFlow::MakeUrlToShowFolder(folder_id).spec();
+  DCHECK_EQ(url.empty(), folder_id.empty());
+  return url;
 }
 
 std::string GetDefaultFolderName(PrefService* prefs,
                                  const std::string& service_provider) {
-  return prefs->GetString(
+  std::string name = prefs->GetString(
       GetPrefPath(kUploadFolderNamePrefPathTemplate, service_provider));
+  return name.empty() ? kUploadFolderDefaultName : name;
 }
 
 void SetFileSystemAccountInfo(PrefService* prefs,
diff --git a/chrome/browser/enterprise/connectors/file_system/account_info_utils.h b/chrome/browser/enterprise/connectors/file_system/account_info_utils.h
index 0b289f50..763d530 100644
--- a/chrome/browser/enterprise/connectors/file_system/account_info_utils.h
+++ b/chrome/browser/enterprise/connectors/file_system/account_info_utils.h
@@ -43,12 +43,14 @@
                                std::string* access_token,
                                std::string* refresh_token);
 
-// Stores/retrieves the default folder id and name stored for the given service
-// provider.
+// Stores/retrieves/clears the default folder id and name stored for the given
+// service provider.
 void SetDefaultFolder(PrefService* prefs,
                       const std::string& service_provider,
                       std::string folder_id,
                       std::string folder_name);
+void ClearDefaultFolder(PrefService* prefs,
+                        const std::string& service_provider);
 std::string GetDefaultFolderId(PrefService* prefs,
                                const std::string& service_provider);
 std::string GetDefaultFolderLink(PrefService* prefs,
diff --git a/chrome/browser/enterprise/connectors/file_system/signin_experience.cc b/chrome/browser/enterprise/connectors/file_system/signin_experience.cc
index 760535e..88520f3 100644
--- a/chrome/browser/enterprise/connectors/file_system/signin_experience.cc
+++ b/chrome/browser/enterprise/connectors/file_system/signin_experience.cc
@@ -220,6 +220,7 @@
 // Clear authentication tokens and stored account info.
 bool ClearFileSystemConnectorLinkedAccount(const FileSystemSettings& settings,
                                            PrefService* prefs) {
+  ClearDefaultFolder(prefs, settings.service_provider);
   return ClearFileSystemOAuth2Tokens(prefs, settings.service_provider) &&
          ClearFileSystemAccountInfo(prefs, settings.service_provider);
 }
@@ -247,12 +248,11 @@
   AccountInfo account_info;
   account_info.account_name = *account_name;
   account_info.account_login = *account_login;
-  account_info.folder_link = GetDefaultFolderLink(prefs, provider);
   account_info.folder_name = GetDefaultFolderName(prefs, provider);
+  account_info.folder_link = GetDefaultFolderLink(prefs, provider);
   DCHECK(!account_info.account_name.empty());
   DCHECK(!account_info.account_login.empty());
   DCHECK(!account_info.folder_name.empty());
-  DCHECK(!account_info.folder_link.empty());
   return absl::make_optional<AccountInfo>(std::move(account_info));
 }
 
diff --git a/chrome/browser/extensions/api/declarative_webrequest/webrequest_action_unittest.cc b/chrome/browser/extensions/api/declarative_webrequest/webrequest_action_unittest.cc
index 3856f1ed..dbe4a9cd 100644
--- a/chrome/browser/extensions/api/declarative_webrequest/webrequest_action_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_webrequest/webrequest_action_unittest.cc
@@ -51,14 +51,13 @@
 std::unique_ptr<WebRequestActionSet> CreateSetOfActions(const char* json) {
   std::unique_ptr<base::Value> parsed_value(
       base::test::ParseJsonDeprecated(json));
-  const base::ListValue* parsed_list;
-  CHECK(parsed_value->GetAsList(&parsed_list));
+  CHECK(parsed_value->is_list());
 
   WebRequestActionSet::Values actions;
-  for (const auto& entry : parsed_list->GetList()) {
-    const base::DictionaryValue* dict;
-    CHECK(entry.GetAsDictionary(&dict));
-    actions.push_back(dict->CreateDeepCopy());
+  for (const base::Value& entry : parsed_value->GetList()) {
+    CHECK(entry.is_dict());
+    actions.push_back(base::DictionaryValue::From(
+        base::Value::ToUniquePtrValue(entry.Clone())));
   }
 
   std::string error;
@@ -185,14 +184,14 @@
   EXPECT_FALSE(result.get());
 
   // Test wrong instanceType element.
-  input.SetString(keys::kInstanceTypeKey, kUnknownActionType);
+  input.SetStringKey(keys::kInstanceTypeKey, kUnknownActionType);
   error.clear();
   result = WebRequestAction::Create(NULL, NULL, input, &error, &bad_message);
   EXPECT_NE("", error);
   EXPECT_FALSE(result.get());
 
   // Test success
-  input.SetString(keys::kInstanceTypeKey, keys::kCancelRequestType);
+  input.SetStringKey(keys::kInstanceTypeKey, keys::kCancelRequestType);
   error.clear();
   result = WebRequestAction::Create(NULL, NULL, input, &error, &bad_message);
   EXPECT_EQ("", error);
@@ -218,13 +217,14 @@
   EXPECT_EQ(std::numeric_limits<int>::min(), result->GetMinimumPriority());
 
   base::DictionaryValue correct_action;
-  correct_action.SetString(keys::kInstanceTypeKey, keys::kIgnoreRulesType);
-  correct_action.SetInteger(keys::kLowerPriorityThanKey, 10);
+  correct_action.SetStringKey(keys::kInstanceTypeKey, keys::kIgnoreRulesType);
+  correct_action.SetIntKey(keys::kLowerPriorityThanKey, 10);
   base::DictionaryValue incorrect_action;
-  incorrect_action.SetString(keys::kInstanceTypeKey, kUnknownActionType);
+  incorrect_action.SetStringKey(keys::kInstanceTypeKey, kUnknownActionType);
 
   // Test success.
-  input.push_back(correct_action.CreateDeepCopy());
+  input.push_back(base::DictionaryValue::From(
+      base::Value::ToUniquePtrValue(correct_action.Clone())));
   error.clear();
   result = WebRequestActionSet::Create(NULL, NULL, input, &error, &bad_message);
   EXPECT_TRUE(error.empty()) << error;
@@ -236,7 +236,8 @@
   EXPECT_EQ(10, result->GetMinimumPriority());
 
   // Test failure.
-  input.push_back(incorrect_action.CreateDeepCopy());
+  input.push_back(base::DictionaryValue::From(
+      base::Value::ToUniquePtrValue(incorrect_action.Clone())));
   error.clear();
   result = WebRequestActionSet::Create(NULL, NULL, input, &error, &bad_message);
   EXPECT_NE("", error);
diff --git a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
index 1e52b4d..fc798ab 100644
--- a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
+++ b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
@@ -813,7 +813,7 @@
       ::onc::global_network_config::kAllowOnlyPolicyNetworksToAutoconnect,
       base::Value(true));
   global_config.SetKey(
-      ::onc::global_network_config::kAllowOnlyPolicyNetworksToConnect,
+      ::onc::global_network_config::kAllowOnlyPolicyWiFiToConnect,
       base::Value(false));
   global_config.SetKey("SomeNewGlobalPolicy", base::Value(false));
   chromeos::NetworkHandler::Get()
diff --git a/chrome/browser/extensions/api/printing/fake_print_job_controller_ash.cc b/chrome/browser/extensions/api/printing/fake_print_job_controller_ash.cc
new file mode 100644
index 0000000..6b0fcd64
--- /dev/null
+++ b/chrome/browser/extensions/api/printing/fake_print_job_controller_ash.cc
@@ -0,0 +1,93 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/api/printing/fake_print_job_controller_ash.h"
+
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/check.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/chromeos/printing/cups_print_job.h"
+#include "chrome/browser/chromeos/printing/cups_print_job_manager.h"
+#include "chrome/browser/chromeos/printing/cups_printers_manager.h"
+#include "chrome/browser/chromeos/printing/history/print_job_info_proto_conversions.h"
+#include "chrome/browser/chromeos/printing/test_cups_print_job_manager.h"
+#include "chrome/browser/extensions/api/printing/printing_api_handler.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chromeos/printing/printer_configuration.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
+#include "printing/metafile_skia.h"
+#include "printing/print_settings.h"
+#include "printing/printed_document.h"
+
+namespace extensions {
+
+FakePrintJobControllerAsh::FakePrintJobControllerAsh(
+    chromeos::TestCupsPrintJobManager* print_job_manager,
+    chromeos::CupsPrintersManager* printers_manager)
+    : print_job_manager_(print_job_manager),
+      printers_manager_(printers_manager) {}
+
+FakePrintJobControllerAsh::~FakePrintJobControllerAsh() = default;
+
+void FakePrintJobControllerAsh::StartPrintJob(
+    const std::string& extension_id,
+    std::unique_ptr<printing::MetafileSkia> metafile,
+    std::unique_ptr<printing::PrintSettings> settings,
+    StartPrintJobCallback callback) {
+  absl::optional<chromeos::Printer> printer =
+      printers_manager_->GetPrinter(base::UTF16ToUTF8(settings->device_name()));
+  if (!printer) {
+    std::move(callback).Run(nullptr);
+    return;
+  }
+
+  // Create a new CupsPrintJob.
+  auto print_job = std::make_unique<chromeos::CupsPrintJob>(
+      *printer, ++job_id_, base::UTF16ToUTF8(settings->title()),
+      /*total_page_number=*/1, printing::PrintJob::Source::EXTENSION,
+      extension_id, chromeos::PrintSettingsToProto(*settings));
+  print_job_manager_->CreatePrintJob(print_job.get());
+  std::move(callback).Run(
+      std::make_unique<std::string>(print_job->GetUniqueId()));
+  jobs_[print_job->GetUniqueId()] = std::move(print_job);
+
+  // Notify DOC_DONE.
+  auto job = base::MakeRefCounted<printing::PrintJob>();
+  job->SetSource(crosapi::mojom::PrintJob::Source::EXTENSION, extension_id);
+  auto document = base::MakeRefCounted<printing::PrintedDocument>(
+      std::move(settings), std::u16string(), 0);
+  auto details = base::MakeRefCounted<printing::JobEventDetails>(
+      printing::JobEventDetails::DOC_DONE, job_id_, document.get());
+  PrintingAPIHandler::Get(ProfileManager::GetPrimaryUserProfile())
+      ->Observe(chrome::NOTIFICATION_PRINT_JOB_EVENT,
+                content::Source<printing::PrintJob>(job.get()),
+                content::Details<printing::JobEventDetails>(details.get()));
+}
+
+void FakePrintJobControllerAsh::OnPrintJobCreated(
+    const std::string& extension_id,
+    const std::string& job_id) {
+  DCHECK(jobs_.contains(job_id));
+  DCHECK(jobs_[job_id]);
+  print_job_manager_->StartPrintJob(jobs_[job_id].get());
+}
+
+void FakePrintJobControllerAsh::OnPrintJobFinished(const std::string& job_id) {
+  jobs_.erase(job_id);
+}
+
+chromeos::CupsPrintJob* FakePrintJobControllerAsh::GetCupsPrintJob(
+    const std::string& job_id) {
+  auto it = jobs_.find(job_id);
+  if (it == jobs_.end())
+    return nullptr;
+  return it->second.get();
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/printing/fake_print_job_controller_ash.h b/chrome/browser/extensions/api/printing/fake_print_job_controller_ash.h
new file mode 100644
index 0000000..890c6e323
--- /dev/null
+++ b/chrome/browser/extensions/api/printing/fake_print_job_controller_ash.h
@@ -0,0 +1,57 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_PRINTING_FAKE_PRINT_JOB_CONTROLLER_ASH_H_
+#define CHROME_BROWSER_EXTENSIONS_API_PRINTING_FAKE_PRINT_JOB_CONTROLLER_ASH_H_
+
+#include <memory>
+
+#include "base/containers/flat_map.h"
+#include "chrome/browser/extensions/api/printing/print_job_controller.h"
+
+namespace chromeos {
+class CupsPrintersManager;
+class CupsPrintJob;
+class TestCupsPrintJobManager;
+}  // namespace chromeos
+
+namespace extensions {
+
+// Fake print job controller which doesn't send print jobs to actual printing
+// pipeline.
+// It's used in unit and API integration tests.
+class FakePrintJobControllerAsh : public PrintJobController {
+ public:
+  FakePrintJobControllerAsh(
+      chromeos::TestCupsPrintJobManager* print_job_manager,
+      chromeos::CupsPrintersManager* printers_manager);
+  ~FakePrintJobControllerAsh() override;
+
+  // PrintJobController:
+  void StartPrintJob(const std::string& extension_id,
+                     std::unique_ptr<printing::MetafileSkia> metafile,
+                     std::unique_ptr<printing::PrintSettings> settings,
+                     StartPrintJobCallback callback) override;
+  void OnPrintJobCreated(const std::string& extension_id,
+                         const std::string& job_id) override;
+  void OnPrintJobFinished(const std::string& job_id) override;
+
+  // Helper method to be used in tests to access ongoing print jobs.
+  chromeos::CupsPrintJob* GetCupsPrintJob(const std::string& job_id);
+
+ private:
+  // Not owned by FakePrintJobControllerAsh.
+  chromeos::TestCupsPrintJobManager* print_job_manager_;
+  chromeos::CupsPrintersManager* printers_manager_;
+
+  // Stores ongoing print jobs as a mapping from job id to CupsPrintJob.
+  base::flat_map<std::string, std::unique_ptr<chromeos::CupsPrintJob>> jobs_;
+
+  // Current job id.
+  int job_id_ = 0;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_PRINTING_FAKE_PRINT_JOB_CONTROLLER_ASH_H_
diff --git a/chrome/browser/extensions/api/printing/printer_capabilities_provider_unittest.cc b/chrome/browser/extensions/api/printing/printer_capabilities_provider_unittest.cc
deleted file mode 100644
index 1b6643c..0000000
--- a/chrome/browser/extensions/api/printing/printer_capabilities_provider_unittest.cc
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/extensions/api/printing/printer_capabilities_provider.h"
-
-#include "base/bind.h"
-#include "base/memory/scoped_refptr.h"
-#include "base/run_loop.h"
-#include "chrome/browser/chromeos/printing/test_cups_printers_manager.h"
-#include "chrome/browser/chromeos/printing/test_printer_configurer.h"
-#include "content/public/test/browser_task_environment.h"
-#include "printing/backend/test_print_backend.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace extensions {
-
-namespace {
-
-class PrinterCapabilitiesProviderPrintBackend
-    : public printing::TestPrintBackend {
- public:
-  PrinterCapabilitiesProviderPrintBackend() = default;
-
-  // PrintBackend:
-  printing::mojom::ResultCode GetPrinterSemanticCapsAndDefaults(
-      const std::string& printer_name,
-      printing::PrinterSemanticCapsAndDefaults* printer_info) override {
-    capabilities_requests_counter_++;
-    return printing::TestPrintBackend::GetPrinterSemanticCapsAndDefaults(
-        printer_name, printer_info);
-  }
-
-  int capabilities_requests_counter() { return capabilities_requests_counter_; }
-
- private:
-  ~PrinterCapabilitiesProviderPrintBackend() override = default;
-
-  int capabilities_requests_counter_ = 0;
-};
-
-constexpr char kPrinterId[] = "printer";
-
-}  // namespace
-
-class PrinterCapabilitiesProviderTest : public testing::Test {
- public:
-  PrinterCapabilitiesProviderTest() = default;
-  ~PrinterCapabilitiesProviderTest() override = default;
-
-  PrinterCapabilitiesProviderTest(const PrinterCapabilitiesProviderTest&) =
-      delete;
-  PrinterCapabilitiesProviderTest& operator=(
-      const PrinterCapabilitiesProviderTest&) = delete;
-
-  void SetUp() override {
-    printers_manager_ = std::make_unique<chromeos::TestCupsPrintersManager>();
-    auto printer_configurer =
-        std::make_unique<chromeos::TestPrinterConfigurer>();
-    printer_configurer_ = printer_configurer.get();
-    test_backend_ =
-        base::MakeRefCounted<PrinterCapabilitiesProviderPrintBackend>();
-    printing::PrintBackend::SetPrintBackendForTesting(test_backend_.get());
-    printer_capabilities_provider_ =
-        std::make_unique<PrinterCapabilitiesProvider>(
-            printers_manager_.get(), std::move(printer_configurer));
-  }
-
-  void TearDown() override {
-    printer_capabilities_provider_.reset();
-    test_backend_.reset();
-    printers_manager_.reset();
-  }
-
-  void OnPrinterCapabilitiesRetrieved(
-      base::RepeatingClosure run_loop_closure,
-      absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities) {
-    capabilities_ = std::move(capabilities);
-    run_loop_closure.Run();
-  }
-
- protected:
-  content::BrowserTaskEnvironment task_environment_;
-  scoped_refptr<PrinterCapabilitiesProviderPrintBackend> test_backend_;
-  std::unique_ptr<chromeos::TestCupsPrintersManager> printers_manager_;
-  chromeos::TestPrinterConfigurer* printer_configurer_;
-  std::unique_ptr<PrinterCapabilitiesProvider> printer_capabilities_provider_;
-  absl::optional<printing::PrinterSemanticCapsAndDefaults> capabilities_;
-};
-
-// Tests that no capabilities are returned if the printer is not added to
-// printers manager.
-TEST_F(PrinterCapabilitiesProviderTest, GetPrinterInfo_NotAddedPrinter) {
-  base::RunLoop run_loop;
-  printer_capabilities_provider_->GetPrinterCapabilities(
-      kPrinterId,
-      base::BindOnce(
-          &PrinterCapabilitiesProviderTest::OnPrinterCapabilitiesRetrieved,
-          base::Unretained(this), run_loop.QuitClosure()));
-  run_loop.Run();
-
-  EXPECT_FALSE(capabilities_);
-}
-
-// Tests that no capabilities are returned if printer is unreachable from CUPS.
-TEST_F(PrinterCapabilitiesProviderTest, GetPrinterInfo_NoCapabilities) {
-  chromeos::Printer printer = chromeos::Printer(kPrinterId);
-  printers_manager_->AddPrinter(printer, chromeos::PrinterClass::kEnterprise);
-
-  base::RunLoop run_loop;
-  printer_capabilities_provider_->GetPrinterCapabilities(
-      kPrinterId,
-      base::BindOnce(
-          &PrinterCapabilitiesProviderTest::OnPrinterCapabilitiesRetrieved,
-          base::Unretained(this), run_loop.QuitClosure()));
-  run_loop.Run();
-
-  // No capabilities should be returned since PrintBackend doesn't know anything
-  // about the printer with given id.
-  EXPECT_FALSE(capabilities_);
-}
-
-// Tests that correct capabilities are returned if the printer is added to
-// printers manager but not installed yet.
-TEST_F(PrinterCapabilitiesProviderTest, GetPrinterInfo_NotInstalledPrinter) {
-  chromeos::Printer printer = chromeos::Printer(kPrinterId);
-  printers_manager_->AddPrinter(printer, chromeos::PrinterClass::kEnterprise);
-
-  auto capabilities =
-      std::make_unique<printing::PrinterSemanticCapsAndDefaults>();
-  capabilities->copies_max = 2;
-  capabilities->color_changeable = true;
-  // Add printer capabilities to |test_backend_|.
-  test_backend_->AddValidPrinter(
-      kPrinterId, std::move(capabilities),
-      std::make_unique<printing::PrinterBasicInfo>());
-
-  base::RunLoop run_loop;
-  printer_capabilities_provider_->GetPrinterCapabilities(
-      kPrinterId,
-      base::BindOnce(
-          &PrinterCapabilitiesProviderTest::OnPrinterCapabilitiesRetrieved,
-          base::Unretained(this), run_loop.QuitClosure()));
-  run_loop.Run();
-
-  EXPECT_TRUE(printer_configurer_->IsConfigured(kPrinterId));
-  ASSERT_TRUE(capabilities_);
-  EXPECT_EQ(2, capabilities_->copies_max);
-  EXPECT_TRUE(capabilities_->color_changeable);
-  EXPECT_FALSE(capabilities_->color_default);
-}
-
-// Tests that correct capabilities are returned if the printer is added to
-// printers manager and installed.
-TEST_F(PrinterCapabilitiesProviderTest, GetPrinterInfo_InstalledPrinter) {
-  chromeos::Printer printer = chromeos::Printer(kPrinterId);
-  printers_manager_->AddPrinter(printer, chromeos::PrinterClass::kEnterprise);
-  printers_manager_->InstallPrinter(kPrinterId);
-
-  // Add printer capabilities to |test_backend_|.
-  test_backend_->AddValidPrinter(
-      kPrinterId, std::make_unique<printing::PrinterSemanticCapsAndDefaults>(),
-      std::make_unique<printing::PrinterBasicInfo>());
-
-  base::RunLoop run_loop;
-  printer_capabilities_provider_->GetPrinterCapabilities(
-      kPrinterId,
-      base::BindOnce(
-          &PrinterCapabilitiesProviderTest::OnPrinterCapabilitiesRetrieved,
-          base::Unretained(this), run_loop.QuitClosure()));
-  run_loop.Run();
-
-  EXPECT_FALSE(printer_configurer_->IsConfigured(kPrinterId));
-  ASSERT_TRUE(capabilities_);
-  EXPECT_EQ(1, capabilities_->copies_max);
-}
-
-// Tests that capabilities are cached but not fetched after every
-// PrinterCapabilitiesProvider request.
-TEST_F(PrinterCapabilitiesProviderTest, GetPrinterInfo_CachedCapabilities) {
-  chromeos::Printer printer = chromeos::Printer(kPrinterId);
-  printers_manager_->AddPrinter(printer, chromeos::PrinterClass::kEnterprise);
-  printers_manager_->InstallPrinter(kPrinterId);
-
-  // Add printer capabilities to |test_backend_|.
-  test_backend_->AddValidPrinter(
-      kPrinterId, std::make_unique<printing::PrinterSemanticCapsAndDefaults>(),
-      std::make_unique<printing::PrinterBasicInfo>());
-
-  base::RunLoop run_loop;
-  printer_capabilities_provider_->GetPrinterCapabilities(
-      kPrinterId,
-      base::BindOnce(
-          &PrinterCapabilitiesProviderTest::OnPrinterCapabilitiesRetrieved,
-          base::Unretained(this), run_loop.QuitClosure()));
-  run_loop.Run();
-
-  // GetPrinterSemanticCapsAndDefaults() should be called when we fetch printer
-  // capabilities.
-  EXPECT_EQ(1, test_backend_->capabilities_requests_counter());
-  ASSERT_TRUE(capabilities_);
-  EXPECT_EQ(1, capabilities_->copies_max);
-
-  base::RunLoop cached_capabilities_run_loop;
-  printer_capabilities_provider_->GetPrinterCapabilities(
-      kPrinterId,
-      base::BindOnce(
-          &PrinterCapabilitiesProviderTest::OnPrinterCapabilitiesRetrieved,
-          base::Unretained(this), cached_capabilities_run_loop.QuitClosure()));
-  cached_capabilities_run_loop.Run();
-
-  // GetPrinterSemanticCapsAndDefaults() shouldn't be called again.
-  EXPECT_EQ(1, test_backend_->capabilities_requests_counter());
-  ASSERT_TRUE(capabilities_);
-  EXPECT_EQ(1, capabilities_->copies_max);
-}
-
-}  // namespace extensions
diff --git a/chrome/browser/extensions/api/printing/printing_api_handler_unittest.cc b/chrome/browser/extensions/api/printing/printing_api_handler_unittest.cc
index 510ab742..9c3169e 100644
--- a/chrome/browser/extensions/api/printing/printing_api_handler_unittest.cc
+++ b/chrome/browser/extensions/api/printing/printing_api_handler_unittest.cc
@@ -12,11 +12,7 @@
 #include "base/json/json_reader.h"
 #include "base/run_loop.h"
 #include "base/values.h"
-#include "chrome/browser/chromeos/printing/cups_print_job.h"
-#include "chrome/browser/chromeos/printing/test_cups_print_job_manager.h"
-#include "chrome/browser/chromeos/printing/test_cups_printers_manager.h"
 #include "chrome/browser/chromeos/printing/test_cups_wrapper.h"
-#include "chrome/browser/chromeos/printing/test_printer_configurer.h"
 #include "chrome/browser/extensions/api/printing/fake_print_job_controller.h"
 #include "chrome/browser/printing/print_preview_sticky_settings.h"
 #include "chrome/browser/profiles/profile.h"
@@ -26,6 +22,8 @@
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
+#include "chrome/test/chromeos/printing/fake_local_printer_chromeos.h"
+#include "chromeos/crosapi/mojom/local_printer.mojom.h"
 #include "chromeos/printing/printer_configuration.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/browser/blob_handle.h"
@@ -39,6 +37,7 @@
 #include "printing/mojom/print.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace extensions {
 
@@ -92,8 +91,6 @@
 constexpr int kJobId = 10;
 
 constexpr char kId1[] = "id1";
-constexpr char kId2[] = "id2";
-constexpr char kId3[] = "id3";
 constexpr char kName[] = "name";
 constexpr char kDescription[] = "description";
 constexpr char kUri[] = "ipp://1.2.3.4";
@@ -180,32 +177,18 @@
   return api::printing::SubmitJob::Params::Create(args);
 }
 
-chromeos::Printer ConstructPrinter(const std::string& id,
-                                   const std::string& name,
-                                   const std::string& description,
-                                   const std::string& uri,
-                                   chromeos::Printer::Source source) {
-  chromeos::Printer printer(id);
-  printer.set_display_name(name);
-  printer.set_description(description);
-  EXPECT_TRUE(printer.SetUri(uri));
-  printer.set_source(source);
-  return printer;
-}
-
-std::unique_ptr<printing::PrinterSemanticCapsAndDefaults>
+absl::optional<printing::PrinterSemanticCapsAndDefaults>
 ConstructPrinterCapabilities() {
-  auto capabilities =
-      std::make_unique<printing::PrinterSemanticCapsAndDefaults>();
-  capabilities->color_model = printing::mojom::ColorModel::kColor;
-  capabilities->duplex_modes.push_back(printing::mojom::DuplexMode::kSimplex);
-  capabilities->copies_max = 5;
-  capabilities->dpis.push_back(gfx::Size(kHorizontalDpi, kVerticalDpi));
+  printing::PrinterSemanticCapsAndDefaults capabilities;
+  capabilities.color_model = printing::mojom::ColorModel::kColor;
+  capabilities.duplex_modes.push_back(printing::mojom::DuplexMode::kSimplex);
+  capabilities.copies_max = 5;
+  capabilities.dpis.push_back(gfx::Size(kHorizontalDpi, kVerticalDpi));
   printing::PrinterSemanticCapsAndDefaults::Paper paper;
   paper.vendor_id = kMediaSizeVendorId;
   paper.size_um = gfx::Size(kMediaSizeWidth, kMediaSizeHeight);
-  capabilities->papers.push_back(paper);
-  capabilities->collate_capable = true;
+  capabilities.papers.push_back(paper);
+  capabilities.collate_capable = true;
   return capabilities;
 }
 
@@ -232,13 +215,89 @@
 
 }  // namespace
 
+class TestLocalPrinter : public FakeLocalPrinter {
+ public:
+  struct JobInfo {
+    std::string printer_id;
+    unsigned int job_id;
+  };
+
+  TestLocalPrinter() = default;
+  TestLocalPrinter(TestLocalPrinter&) = delete;
+  TestLocalPrinter& operator=(const TestLocalPrinter&) = delete;
+  ~TestLocalPrinter() override = default;
+
+  std::vector<JobInfo> jobs_cancelled() { return jobs_cancelled_; }
+
+  void AddPrinter(crosapi::mojom::LocalDestinationInfoPtr printer) {
+    printers_.push_back(std::move(printer));
+  }
+
+  void SetCaps(const std::string& id,
+               crosapi::mojom::CapabilitiesResponsePtr caps) {
+    DCHECK(caps);
+    caps_map_[id] = std::move(caps);
+  }
+
+  void GetPrinters(GetPrintersCallback cb) override {
+    std::move(cb).Run(std::move(printers_));
+    printers_.clear();
+  }
+
+  void GetCapability(const std::string& id, GetCapabilityCallback cb) override {
+    auto it = caps_map_.find(id);
+    if (it == caps_map_.end()) {
+      std::move(cb).Run(nullptr);
+      return;
+    }
+    std::move(cb).Run(std::move(it->second));
+    caps_map_.erase(it);
+  }
+
+  void CancelPrintJob(const std::string& printer_id,
+                      unsigned int job_id,
+                      CancelPrintJobCallback cb) override {
+    jobs_cancelled_.push_back(JobInfo{printer_id, job_id});
+    std::move(cb).Run(true);
+  }
+
+ private:
+  std::vector<JobInfo> jobs_cancelled_;
+  std::map<std::string, crosapi::mojom::CapabilitiesResponsePtr> caps_map_;
+  std::vector<crosapi::mojom::LocalDestinationInfoPtr> printers_;
+};
+
 class PrintingAPIHandlerUnittest : public testing::Test {
  public:
   PrintingAPIHandlerUnittest()
       : disable_pdf_flattening_reset_(
             PrintJobSubmitter::DisablePdfFlatteningForTesting()) {}
+  PrintingAPIHandlerUnittest(const PrintingAPIHandlerUnittest&) = delete;
+  PrintingAPIHandlerUnittest& operator=(const PrintingAPIHandlerUnittest&) =
+      delete;
   ~PrintingAPIHandlerUnittest() override = default;
 
+  std::vector<TestLocalPrinter::JobInfo> GetJobsCancelled() {
+    return local_printer_.jobs_cancelled();
+  }
+
+  void AddPrinter(const crosapi::mojom::LocalDestinationInfo& printer) {
+    local_printer_.AddPrinter(printer.Clone());
+  }
+
+  void SetCaps(std::string id, crosapi::mojom::CapabilitiesResponsePtr caps) {
+    local_printer_.SetCaps(id, std::move(caps));
+  }
+
+  void CreatePrintJob(const std::string& printer_id,
+                      int job_id,
+                      const std::string& extension_id,
+                      crosapi::mojom::PrintJob::Source source =
+                          crosapi::mojom::PrintJob::Source::EXTENSION) {
+    print_job_controller_->CreatePrintJob(printer_id, job_id, extension_id,
+                                          source);
+  }
+
   void SetUp() override {
     profile_manager_ = std::make_unique<TestingProfileManager>(
         TestingBrowserProcess::GetGlobal());
@@ -259,53 +318,26 @@
                      .Build();
     ExtensionRegistry::Get(testing_profile_)->AddEnabled(extension_);
 
-    print_job_manager_ =
-        std::make_unique<chromeos::TestCupsPrintJobManager>(testing_profile_);
-    printers_manager_ = std::make_unique<chromeos::TestCupsPrintersManager>();
-    auto print_job_controller = std::make_unique<FakePrintJobController>(
-        print_job_manager_.get(), printers_manager_.get());
+    auto print_job_controller = std::make_unique<FakePrintJobController>();
     print_job_controller_ = print_job_controller.get();
     auto cups_wrapper = std::make_unique<chromeos::TestCupsWrapper>();
     cups_wrapper_ = cups_wrapper.get();
-    test_backend_ = base::MakeRefCounted<printing::TestPrintBackend>();
-    printing::PrintBackend::SetPrintBackendForTesting(test_backend_.get());
     event_router_ = CreateAndUseTestEventRouter(testing_profile_);
 
     printing_api_handler_ = PrintingAPIHandler::CreateForTesting(
         testing_profile_, event_router_,
-        ExtensionRegistry::Get(testing_profile_), print_job_manager_.get(),
-        printers_manager_.get(), std::move(print_job_controller),
-        std::make_unique<chromeos::TestPrinterConfigurer>(),
-        std::move(cups_wrapper));
+        ExtensionRegistry::Get(testing_profile_),
+        std::move(print_job_controller), std::move(cups_wrapper),
+        &local_printer_);
+    print_job_controller_->set_handler(printing_api_handler_.get());
   }
 
   void TearDown() override {
     printing_api_handler_.reset();
-
-    test_backend_.reset();
-    printers_manager_.reset();
-    print_job_manager_.reset();
-
     testing_profile_ = nullptr;
     profile_manager_->DeleteTestingProfile(chrome::kInitialProfile);
   }
 
-  void AddUnavailablePrinter(const std::string& printer_id) {
-    chromeos::Printer printer = chromeos::Printer(printer_id);
-    printers_manager_->AddPrinter(printer, chromeos::PrinterClass::kEnterprise);
-  }
-
-  void AddAvailablePrinter(
-      const std::string& printer_id,
-      std::unique_ptr<printing::PrinterSemanticCapsAndDefaults> capabilities) {
-    AddUnavailablePrinter(printer_id);
-
-    // Add printer capabilities to |test_backend_|.
-    test_backend_->AddValidPrinter(
-        printer_id, std::move(capabilities),
-        std::make_unique<printing::PrinterBasicInfo>());
-  }
-
   void OnJobSubmitted(base::RepeatingClosure run_loop_closure,
                       absl::optional<api::printing::SubmitJobStatus> status,
                       std::unique_ptr<std::string> job_id,
@@ -316,6 +348,12 @@
     run_loop_closure.Run();
   }
 
+  void OnPrintersRetrieved(base::RepeatingClosure run_loop_closure,
+                           std::vector<api::printing::Printer> printers) {
+    printers_ = std::move(printers);
+    run_loop_closure.Run();
+  }
+
   void OnPrinterInfoRetrieved(
       base::RepeatingClosure run_loop_closure,
       absl::optional<base::Value> capabilities,
@@ -333,10 +371,7 @@
  protected:
   content::BrowserTaskEnvironment task_environment_;
   TestingProfile* testing_profile_;
-  scoped_refptr<printing::TestPrintBackend> test_backend_;
   TestEventRouter* event_router_ = nullptr;
-  std::unique_ptr<chromeos::TestCupsPrintJobManager> print_job_manager_;
-  std::unique_ptr<chromeos::TestCupsPrintersManager> printers_manager_;
   FakePrintJobController* print_job_controller_;
   chromeos::TestCupsWrapper* cups_wrapper_;
   std::unique_ptr<PrintingAPIHandler> printing_api_handler_;
@@ -346,14 +381,14 @@
   absl::optional<base::Value> capabilities_;
   absl::optional<api::printing::PrinterStatus> printer_status_;
   absl::optional<std::string> error_;
+  std::vector<api::printing::Printer> printers_;
 
  private:
+  TestLocalPrinter local_printer_;
   // Resets |disable_pdf_flattening_for_testing| back to false automatically
   // after the test is over.
   base::AutoReset<bool> disable_pdf_flattening_reset_;
   std::unique_ptr<TestingProfileManager> profile_manager_;
-
-  DISALLOW_COPY_AND_ASSIGN(PrintingAPIHandlerUnittest);
 };
 
 // Test that |OnJobStatusChanged| is dispatched when the print job status is
@@ -362,12 +397,8 @@
   PrintingEventObserver event_observer(
       event_router_, api::printing::OnJobStatusChanged::kEventName);
 
-  std::unique_ptr<chromeos::CupsPrintJob> print_job =
-      std::make_unique<chromeos::CupsPrintJob>(
-          chromeos::Printer(kPrinterId), kJobId, "title",
-          /*total_page_number=*/3, ::printing::PrintJob::Source::EXTENSION,
-          kExtensionId, chromeos::printing::proto::PrintSettings());
-  print_job_manager_->CreatePrintJob(print_job.get());
+  CreatePrintJob(kPrinterId, kJobId, kExtensionId);
+  EXPECT_EQ(1u, print_job_controller_->print_jobs().size());
 
   EXPECT_EQ(kExtensionId, event_observer.extension_id());
   const base::Value& event_args = event_observer.event_args();
@@ -375,8 +406,7 @@
   ASSERT_EQ(2u, event_args.GetList().size());
   base::Value job_id = event_args.GetList()[0].Clone();
   ASSERT_TRUE(job_id.is_string());
-  EXPECT_EQ(chromeos::CupsPrintJob::CreateUniqueId(kPrinterId, kJobId),
-            job_id.GetString());
+  EXPECT_FALSE(job_id.GetString().empty());
   base::Value job_status = event_args.GetList()[1].Clone();
   ASSERT_TRUE(job_status.is_string());
   EXPECT_EQ(api::printing::JOB_STATUS_PENDING,
@@ -389,14 +419,10 @@
   PrintingEventObserver event_observer(
       event_router_, api::printing::OnJobStatusChanged::kEventName);
 
-  std::unique_ptr<chromeos::CupsPrintJob> print_job =
-      std::make_unique<chromeos::CupsPrintJob>(
-          chromeos::Printer(kPrinterId), kJobId, "title",
-          /*total_page_number=*/3, ::printing::PrintJob::Source::PRINT_PREVIEW,
-          /*source_id=*/"", chromeos::printing::proto::PrintSettings());
-  print_job_manager_->CreatePrintJob(print_job.get());
+  CreatePrintJob(kPrinterId, kJobId, "",
+                 crosapi::mojom::PrintJob::Source::PRINT_PREVIEW);
+  EXPECT_EQ(0u, print_job_controller_->print_jobs().size());
 
-  // Check that the print job created on Print Preview doesn't show up.
   EXPECT_EQ("", event_observer.extension_id());
   EXPECT_TRUE(event_observer.event_args().is_none());
 }
@@ -404,22 +430,27 @@
 // Test that calling GetPrinters() returns no printers before any are added to
 // the profile.
 TEST_F(PrintingAPIHandlerUnittest, GetPrinters_NoPrinters) {
-  std::vector<api::printing::Printer> printers =
-      printing_api_handler_->GetPrinters();
-  EXPECT_TRUE(printers.empty());
+  base::RunLoop run_loop;
+  printing_api_handler_->GetPrinters(
+      base::BindOnce(&PrintingAPIHandlerUnittest::OnPrintersRetrieved,
+                     base::Unretained(this), run_loop.QuitClosure()));
+  run_loop.Run();
+  EXPECT_TRUE(printers_.empty());
 }
 
 // Test that calling GetPrinters() returns the mock printer.
 TEST_F(PrintingAPIHandlerUnittest, GetPrinters_OnePrinter) {
-  chromeos::Printer printer = ConstructPrinter(kId1, kName, kDescription, kUri,
-                                               chromeos::Printer::SRC_POLICY);
-  printers_manager_->AddPrinter(printer, chromeos::PrinterClass::kEnterprise);
+  AddPrinter(crosapi::mojom::LocalDestinationInfo(
+      kId1, kName, kDescription, true, absl::make_optional(kUri)));
 
-  std::vector<api::printing::Printer> printers =
-      printing_api_handler_->GetPrinters();
+  base::RunLoop run_loop;
+  printing_api_handler_->GetPrinters(
+      base::BindOnce(&PrintingAPIHandlerUnittest::OnPrintersRetrieved,
+                     base::Unretained(this), run_loop.QuitClosure()));
+  run_loop.Run();
 
-  ASSERT_EQ(1u, printers.size());
-  const api::printing::Printer& idl_printer = printers[0];
+  ASSERT_EQ(1u, printers_.size());
+  const api::printing::Printer& idl_printer = printers_.front();
 
   EXPECT_EQ(kId1, idl_printer.id);
   EXPECT_EQ(kName, idl_printer.name);
@@ -430,40 +461,23 @@
   EXPECT_EQ(nullptr, idl_printer.recently_used_rank);
 }
 
-// Test that calling GetPrinters() returns printers of all classes.
-TEST_F(PrintingAPIHandlerUnittest, GetPrinters_ThreePrinters) {
-  chromeos::Printer printer1 = chromeos::Printer(kId1);
-  chromeos::Printer printer2 = chromeos::Printer(kId2);
-  chromeos::Printer printer3 = chromeos::Printer(kId3);
-  printers_manager_->AddPrinter(printer1, chromeos::PrinterClass::kEnterprise);
-  printers_manager_->AddPrinter(printer2, chromeos::PrinterClass::kSaved);
-  printers_manager_->AddPrinter(printer3, chromeos::PrinterClass::kAutomatic);
-
-  std::vector<api::printing::Printer> printers =
-      printing_api_handler_->GetPrinters();
-
-  ASSERT_EQ(3u, printers.size());
-  std::vector<std::string> printer_ids;
-  for (const auto& printer : printers)
-    printer_ids.push_back(printer.id);
-  EXPECT_THAT(printer_ids, testing::UnorderedElementsAre(kId1, kId2, kId3));
-}
-
 // Test that calling GetPrinters() returns printers with correct |is_default|
 // flag.
 TEST_F(PrintingAPIHandlerUnittest, GetPrinters_IsDefault) {
   testing_profile_->GetPrefs()->SetString(
       prefs::kPrintPreviewDefaultDestinationSelectionRules,
       R"({"kind": "local", "idPattern": "id.*"})");
-  chromeos::Printer printer = ConstructPrinter(kId1, kName, kDescription, kUri,
-                                               chromeos::Printer::SRC_POLICY);
-  printers_manager_->AddPrinter(printer, chromeos::PrinterClass::kEnterprise);
+  AddPrinter(crosapi::mojom::LocalDestinationInfo(
+      kId1, kName, kDescription, true, absl::make_optional(kUri)));
 
-  std::vector<api::printing::Printer> printers =
-      printing_api_handler_->GetPrinters();
+  base::RunLoop run_loop;
+  printing_api_handler_->GetPrinters(
+      base::BindOnce(&PrintingAPIHandlerUnittest::OnPrintersRetrieved,
+                     base::Unretained(this), run_loop.QuitClosure()));
+  run_loop.Run();
 
-  ASSERT_EQ(1u, printers.size());
-  api::printing::Printer idl_printer = std::move(printers[0]);
+  ASSERT_EQ(1u, printers_.size());
+  api::printing::Printer idl_printer = std::move(printers_.front());
 
   EXPECT_EQ(kId1, idl_printer.id);
   EXPECT_TRUE(idl_printer.is_default);
@@ -486,16 +500,17 @@
     ]
   })");
   sticky_settings->SaveInPrefs(testing_profile_->GetPrefs());
+  AddPrinter(crosapi::mojom::LocalDestinationInfo(
+      kId1, kName, kDescription, true, absl::make_optional(kUri)));
 
-  chromeos::Printer printer = ConstructPrinter(kId1, kName, kDescription, kUri,
-                                               chromeos::Printer::SRC_POLICY);
-  printers_manager_->AddPrinter(printer, chromeos::PrinterClass::kEnterprise);
+  base::RunLoop run_loop;
+  printing_api_handler_->GetPrinters(
+      base::BindOnce(&PrintingAPIHandlerUnittest::OnPrintersRetrieved,
+                     base::Unretained(this), run_loop.QuitClosure()));
+  run_loop.Run();
 
-  std::vector<api::printing::Printer> printers =
-      printing_api_handler_->GetPrinters();
-
-  ASSERT_EQ(1u, printers.size());
-  api::printing::Printer idl_printer = std::move(printers[0]);
+  ASSERT_EQ(1u, printers_.size());
+  api::printing::Printer idl_printer = std::move(printers_.front());
 
   EXPECT_EQ(kId1, idl_printer.id);
   ASSERT_TRUE(idl_printer.recently_used_rank);
@@ -521,8 +536,9 @@
 }
 
 TEST_F(PrintingAPIHandlerUnittest, GetPrinterInfo_NoCapabilities) {
-  AddUnavailablePrinter(kPrinterId);
-  printers_manager_->InstallPrinter(kPrinterId);
+  auto caps = crosapi::mojom::CapabilitiesResponse::New();
+  caps->basic_info = crosapi::mojom::LocalDestinationInfo::New();
+  SetCaps(kPrinterId, std::move(caps));
 
   base::RunLoop run_loop;
   printing_api_handler_->GetPrinterInfo(
@@ -537,15 +553,19 @@
   EXPECT_FALSE(error_.has_value());
 }
 
-TEST_F(PrintingAPIHandlerUnittest, GetPrinterInfo) {
-  AddAvailablePrinter(
-      kPrinterId, std::make_unique<printing::PrinterSemanticCapsAndDefaults>());
+TEST_F(PrintingAPIHandlerUnittest, GetPrinterInfo_OutOfPaper) {
+  auto caps = crosapi::mojom::CapabilitiesResponse::New();
+  caps->basic_info = crosapi::mojom::LocalDestinationInfo::New();
+  caps->capabilities = printing::PrinterSemanticCapsAndDefaults();
+  SetCaps(kPrinterId, std::move(caps));
 
   // Mock CUPS wrapper to return predefined status for given printer.
-  printing::PrinterStatus::PrinterReason reason;
-  reason.reason = printing::PrinterStatus::PrinterReason::Reason::kMediaEmpty;
+  printing::PrinterStatus::PrinterReason reason{
+      printing::PrinterStatus::PrinterReason::Reason::kMediaEmpty,
+      printing::PrinterStatus::PrinterReason::Severity::kWarning};
   cups_wrapper_->SetPrinterStatus(kPrinterId, reason);
 
+  std::map<std::string, crosapi::mojom::PrinterStatusPtr> status_map_;
   base::RunLoop run_loop;
   printing_api_handler_->GetPrinterInfo(
       kPrinterId,
@@ -592,7 +612,10 @@
 }
 
 TEST_F(PrintingAPIHandlerUnittest, SubmitJob_UnsupportedContentType) {
-  AddAvailablePrinter(kPrinterId, ConstructPrinterCapabilities());
+  auto caps = crosapi::mojom::CapabilitiesResponse::New();
+  caps->basic_info = crosapi::mojom::LocalDestinationInfo::New();
+  caps->capabilities = printing::PrinterSemanticCapsAndDefaults();
+  SetCaps(kPrinterId, std::move(caps));
 
   auto params =
       ConstructSubmitJobParams(kPrinterId, /*title=*/"", kCjt, "image/jpeg",
@@ -611,10 +634,14 @@
   ASSERT_TRUE(error_.has_value());
   EXPECT_EQ("Unsupported content type", error_.value());
   EXPECT_FALSE(submit_job_status_.has_value());
+  EXPECT_EQ(0u, print_job_controller_->print_jobs().size());
 }
 
 TEST_F(PrintingAPIHandlerUnittest, SubmitJob_InvalidPrintTicket) {
-  AddAvailablePrinter(kPrinterId, ConstructPrinterCapabilities());
+  auto caps = crosapi::mojom::CapabilitiesResponse::New();
+  caps->basic_info = crosapi::mojom::LocalDestinationInfo::New();
+  caps->capabilities = ConstructPrinterCapabilities();
+  SetCaps(kPrinterId, std::move(caps));
 
   auto params = ConstructSubmitJobParams(kPrinterId, /*title=*/"",
                                          kIncompleteCjt, "application/pdf",
@@ -633,6 +660,7 @@
   ASSERT_TRUE(error_.has_value());
   EXPECT_EQ("Invalid ticket", error_.value());
   EXPECT_FALSE(submit_job_status_.has_value());
+  EXPECT_EQ(0u, print_job_controller_->print_jobs().size());
 }
 
 TEST_F(PrintingAPIHandlerUnittest, SubmitJob_InvalidPrinterId) {
@@ -652,10 +680,13 @@
   ASSERT_TRUE(error_.has_value());
   EXPECT_EQ("Invalid printer ID", error_.value());
   EXPECT_FALSE(submit_job_status_.has_value());
+  EXPECT_EQ(0u, print_job_controller_->print_jobs().size());
 }
 
 TEST_F(PrintingAPIHandlerUnittest, SubmitJob_PrinterUnavailable) {
-  AddUnavailablePrinter(kPrinterId);
+  auto caps = crosapi::mojom::CapabilitiesResponse::New();
+  caps->basic_info = crosapi::mojom::LocalDestinationInfo::New();
+  SetCaps(kPrinterId, std::move(caps));
 
   auto params = ConstructSubmitJobParams(kPrinterId, /*title=*/"", kCjt,
                                          "application/pdf",
@@ -674,11 +705,14 @@
   ASSERT_TRUE(error_.has_value());
   EXPECT_EQ("Printer is unavailable at the moment", error_.value());
   EXPECT_FALSE(submit_job_status_.has_value());
+  EXPECT_EQ(0u, print_job_controller_->print_jobs().size());
 }
 
 TEST_F(PrintingAPIHandlerUnittest, SubmitJob_UnsupportedTicket) {
-  AddAvailablePrinter(
-      kPrinterId, std::make_unique<printing::PrinterSemanticCapsAndDefaults>());
+  auto caps = crosapi::mojom::CapabilitiesResponse::New();
+  caps->basic_info = crosapi::mojom::LocalDestinationInfo::New();
+  caps->capabilities = printing::PrinterSemanticCapsAndDefaults();
+  SetCaps(kPrinterId, std::move(caps));
 
   auto params = ConstructSubmitJobParams(kPrinterId, /*title=*/"", kCjt,
                                          "application/pdf",
@@ -698,10 +732,14 @@
   ASSERT_TRUE(error_.has_value());
   EXPECT_EQ("Ticket is unsupported on the given printer", error_.value());
   EXPECT_FALSE(submit_job_status_.has_value());
+  EXPECT_EQ(0u, print_job_controller_->print_jobs().size());
 }
 
 TEST_F(PrintingAPIHandlerUnittest, SubmitJob_InvalidData) {
-  AddAvailablePrinter(kPrinterId, ConstructPrinterCapabilities());
+  auto caps = crosapi::mojom::CapabilitiesResponse::New();
+  caps->basic_info = crosapi::mojom::LocalDestinationInfo::New();
+  caps->capabilities = ConstructPrinterCapabilities();
+  SetCaps(kPrinterId, std::move(caps));
 
   auto params = ConstructSubmitJobParams(
       kPrinterId, /*title=*/"", kCjt, "application/pdf",
@@ -720,10 +758,14 @@
   ASSERT_TRUE(error_.has_value());
   EXPECT_EQ("Invalid document", error_.value());
   EXPECT_FALSE(submit_job_status_.has_value());
+  EXPECT_EQ(0u, print_job_controller_->print_jobs().size());
 }
 
 TEST_F(PrintingAPIHandlerUnittest, SubmitJob) {
-  AddAvailablePrinter(kPrinterId, ConstructPrinterCapabilities());
+  auto caps = crosapi::mojom::CapabilitiesResponse::New();
+  caps->basic_info = crosapi::mojom::LocalDestinationInfo::New();
+  caps->capabilities = ConstructPrinterCapabilities();
+  SetCaps(kPrinterId, std::move(caps));
 
   // Create Blob with given data.
   std::unique_ptr<content::BlobHandle> blob = CreateMemoryBackedBlob(
@@ -743,6 +785,7 @@
   EXPECT_FALSE(error_.has_value());
   ASSERT_TRUE(submit_job_status_.has_value());
   EXPECT_EQ(api::printing::SUBMIT_JOB_STATUS_OK, submit_job_status_.value());
+  EXPECT_EQ(1u, print_job_controller_->print_jobs().size());
 }
 
 TEST_F(PrintingAPIHandlerUnittest, CancelJob_InvalidId) {
@@ -751,27 +794,28 @@
 
   ASSERT_TRUE(error.has_value());
   EXPECT_EQ("No active print job with given ID", error.value());
+  EXPECT_TRUE(GetJobsCancelled().empty());
+  EXPECT_EQ(0u, print_job_controller_->print_jobs().size());
 }
 
 TEST_F(PrintingAPIHandlerUnittest, CancelJob_InvalidId_OtherExtension) {
-  std::unique_ptr<chromeos::CupsPrintJob> print_job =
-      std::make_unique<chromeos::CupsPrintJob>(
-          chromeos::Printer(kPrinterId), kJobId, "title",
-          /*total_page_number=*/3, ::printing::PrintJob::Source::EXTENSION,
-          kExtensionId, chromeos::printing::proto::PrintSettings());
-  print_job_manager_->CreatePrintJob(print_job.get());
+  CreatePrintJob(kPrinterId, kJobId, kExtensionId);
 
   // Try to cancel print job from other extension.
   absl::optional<std::string> error = printing_api_handler_->CancelJob(
-      kExtensionId2,
-      chromeos::CupsPrintJob::CreateUniqueId(kPrinterId, kJobId));
+      kExtensionId2, PrintingAPIHandler::CreateUniqueId(kPrinterId, kJobId));
 
   ASSERT_TRUE(error.has_value());
   EXPECT_EQ("No active print job with given ID", error.value());
+  EXPECT_TRUE(GetJobsCancelled().empty());
+  EXPECT_EQ(1u, print_job_controller_->print_jobs().size());
 }
 
 TEST_F(PrintingAPIHandlerUnittest, CancelJob_InvalidState) {
-  AddAvailablePrinter(kPrinterId, ConstructPrinterCapabilities());
+  auto caps = crosapi::mojom::CapabilitiesResponse::New();
+  caps->basic_info = crosapi::mojom::LocalDestinationInfo::New();
+  caps->capabilities = ConstructPrinterCapabilities();
+  SetCaps(kPrinterId, std::move(caps));
 
   // Create Blob with given data.
   std::unique_ptr<content::BlobHandle> blob = CreateMemoryBackedBlob(
@@ -789,8 +833,13 @@
   run_loop.Run();
 
   // Explicitly complete started print job.
-  print_job_manager_->CompletePrintJob(
-      print_job_controller_->GetCupsPrintJob(*job_id_));
+  ASSERT_TRUE(job_id_);
+  ASSERT_TRUE(job_id_->size() > 1);
+  int index = job_id_->size() - 1;
+  printing_api_handler_->OnPrintJobUpdate(
+      job_id_->substr(0, index), (*job_id_)[index] - '0',
+      crosapi::mojom::PrintJobStatus::kDone);
+  EXPECT_EQ(0u, print_job_controller_->print_jobs().size());
 
   // Try to cancel already completed print job.
   absl::optional<std::string> error =
@@ -798,10 +847,15 @@
 
   ASSERT_TRUE(error.has_value());
   EXPECT_EQ("No active print job with given ID", error.value());
+  EXPECT_TRUE(GetJobsCancelled().empty());
+  EXPECT_EQ(0u, print_job_controller_->print_jobs().size());
 }
 
 TEST_F(PrintingAPIHandlerUnittest, CancelJob) {
-  AddAvailablePrinter(kPrinterId, ConstructPrinterCapabilities());
+  auto caps = crosapi::mojom::CapabilitiesResponse::New();
+  caps->basic_info = crosapi::mojom::LocalDestinationInfo::New();
+  caps->capabilities = ConstructPrinterCapabilities();
+  SetCaps(kPrinterId, std::move(caps));
 
   // Create Blob with given data.
   std::unique_ptr<content::BlobHandle> blob = CreateMemoryBackedBlob(
@@ -819,10 +873,20 @@
   run_loop.Run();
 
   // Cancel started print job.
+  ASSERT_TRUE(job_id_);
   absl::optional<std::string> error =
       printing_api_handler_->CancelJob(kExtensionId, *job_id_);
 
   EXPECT_FALSE(error.has_value());
+  ASSERT_EQ(1u, GetJobsCancelled().size());
+  EXPECT_EQ(*job_id_,
+            PrintingAPIHandler::CreateUniqueId(GetJobsCancelled()[0].printer_id,
+                                               GetJobsCancelled()[0].job_id));
+  EXPECT_EQ(1u, print_job_controller_->print_jobs().size());
+  printing_api_handler_->OnPrintJobUpdate(
+      GetJobsCancelled()[0].printer_id, GetJobsCancelled()[0].job_id,
+      crosapi::mojom::PrintJobStatus::kCancelled);
+  EXPECT_EQ(0u, print_job_controller_->print_jobs().size());
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/printing/printing_api_utils_unittest.cc b/chrome/browser/extensions/api/printing/printing_api_utils_unittest.cc
index 037d14d5..4e6219f 100644
--- a/chrome/browser/extensions/api/printing/printing_api_utils_unittest.cc
+++ b/chrome/browser/extensions/api/printing/printing_api_utils_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/extensions/api/printing/printing_api_utils.h"
 
 #include "base/json/json_reader.h"
+#include "chromeos/crosapi/mojom/local_printer.mojom.h"
 #include "chromeos/printing/printer_configuration.h"
 #include "printing/backend/print_backend.h"
 #include "printing/mojom/print.mojom.h"
@@ -131,11 +132,8 @@
 }
 
 TEST(PrintingApiUtilsTest, PrinterToIdl) {
-  chromeos::Printer printer(kId);
-  printer.set_display_name(kName);
-  printer.set_description(kDescription);
-  EXPECT_TRUE(printer.SetUri(kUri));
-  printer.set_source(chromeos::Printer::SRC_POLICY);
+  crosapi::mojom::LocalDestinationInfo printer(kId, kName, kDescription, true,
+                                               kUri);
 
   absl::optional<DefaultPrinterRules> default_printer_rules =
       DefaultPrinterRules();
diff --git a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
index c6cc9e0..9458bff 100644
--- a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
+++ b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
@@ -530,10 +530,6 @@
   features.Append(GenerateFeatureFlag(
       "multiword", chromeos::features::IsAssistiveMultiWordEnabled()));
   features.Append(GenerateFeatureFlag(
-      "floatingkeyboarddefault",
-      base::FeatureList::IsEnabled(
-          chromeos::features::kVirtualKeyboardFloatingDefault)));
-  features.Append(GenerateFeatureFlag(
       "stylushandwriting",
       base::FeatureList::IsEnabled(chromeos::features::kImeStylusHandwriting)));
 
diff --git a/chrome/browser/extensions/extension_service_sync_unittest.cc b/chrome/browser/extensions/extension_service_sync_unittest.cc
index 6da87344..ab4fbc8 100644
--- a/chrome/browser/extensions/extension_service_sync_unittest.cc
+++ b/chrome/browser/extensions/extension_service_sync_unittest.cc
@@ -234,6 +234,25 @@
   }
 };
 
+TEST_F(ExtensionServiceSyncTest, DeleteAllInstalledBookMarkAppsDuringSync) {
+  InitializeEmptyExtensionService();
+
+  // Install the bookmark app.
+  InstallCRX(data_dir().AppendASCII("good.crx"),
+             ManifestLocation::kExternalPref, INSTALL_NEW,
+             Extension::FROM_BOOKMARK);
+  const Extension* extension = registry()->GetInstalledExtension(good_crx);
+  ASSERT_TRUE(extension);
+  ASSERT_TRUE(extension->from_bookmark());
+  ASSERT_FALSE(extensions::util::ShouldSync(extension, profile()));
+
+  StartSyncing(syncer::EXTENSIONS);
+
+  // Should uninstall the bookmark app.
+  EXPECT_FALSE(
+      registry()->GetExtensionById(good_crx, ExtensionRegistry::EVERYTHING));
+}
+
 TEST_F(ExtensionServiceSyncTest, DeferredSyncStartupPreInstalledComponent) {
   InitializeEmptyExtensionService();
 
diff --git a/chrome/browser/extensions/extension_sync_service.cc b/chrome/browser/extensions/extension_sync_service.cc
index 87afc40..6a9ea82 100644
--- a/chrome/browser/extensions/extension_sync_service.cc
+++ b/chrome/browser/extensions/extension_sync_service.cc
@@ -179,6 +179,23 @@
     }
   }
 
+  ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
+  std::unique_ptr<ExtensionSet> all_extensions =
+      registry->GenerateInstalledExtensionsSet();
+  for (const auto& extension : *all_extensions) {
+    if (extension->from_bookmark()) {
+      // Deleting deprecated bookmark apps.
+      const std::string& id = extension->id();
+      std::u16string error;
+      bool uninstalled = extension_service()->UninstallExtension(
+          id, extensions::UNINSTALL_REASON_SYNC, &error);
+      if (!uninstalled) {
+        LOG(WARNING) << "Failed to uninstall bookmark apps with id '" << id
+                     << "' : " << error;
+      }
+    }
+  }
+
   // Now push the local state to sync.
   // Note: We'd like to only send out changes for extensions which have
   // NeedsSync set. However, we can't tell if our changes ever made it to the
diff --git a/chrome/browser/favicon/content_favicon_driver_browsertest.cc b/chrome/browser/favicon/content_favicon_driver_browsertest.cc
index b91206b..b8ee615 100644
--- a/chrome/browser/favicon/content_favicon_driver_browsertest.cc
+++ b/chrome/browser/favicon/content_favicon_driver_browsertest.cc
@@ -236,9 +236,13 @@
                                 base::Unretained(this))) {}
   ~ContentFaviconDriverTest() override = default;
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    InProcessBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     host_resolver()->AddRule("*", "127.0.0.1");
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
diff --git a/chrome/browser/file_system_access/file_system_access_tab_helper_browsertest.cc b/chrome/browser/file_system_access/file_system_access_tab_helper_browsertest.cc
index 30896a210..0e10270e 100644
--- a/chrome/browser/file_system_access/file_system_access_tab_helper_browsertest.cc
+++ b/chrome/browser/file_system_access/file_system_access_tab_helper_browsertest.cc
@@ -54,6 +54,11 @@
   FileSystemAccessTabHelperPrerenderingBrowserTest& operator=(
       const FileSystemAccessTabHelperPrerenderingBrowserTest&) = delete;
 
+  void SetUp() override {
+    prerender_test_helper_.SetUp(embedded_test_server());
+    InProcessBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     FileSystemAccessPermissionContextFactory::GetInstance()
         ->SetTestingFactoryAndUse(
@@ -63,7 +68,6 @@
                     BuildMockFileSystemAccessPermissionContext,
                 base::Unretained(this)));
 
-    prerender_test_helper_.SetUpOnMainThread(embedded_test_server());
     host_resolver()->AddRule("*", "127.0.0.1");
     ASSERT_TRUE(embedded_test_server()->Start());
   }
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 12eb1a7..a7297fdf 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -542,6 +542,11 @@
     "expiry_milestone": 96
   },
   {
+    "name": "calendar-view",
+    "owners": ["jiamingc"],
+    "expiry_milestone" : 103
+  },
+  {
     "name": "camera-system-web-app",
     "owners": [ "chromeos-camera-eng@google.com" ],
     "expiry_milestone": 92
@@ -552,11 +557,6 @@
     "expiry_milestone": 100
   },
   {
-    "name": "cast-media-route-provider",
-    "owners": [ "takumif", "openscreen-eng@google.com" ],
-    "expiry_milestone": 93
-  },
-  {
     "name": "categorical-search",
     "owners": [ "tby", "thanhdng", "wrong" ],
     "expiry_milestone": 96
@@ -3699,15 +3699,6 @@
     "expiry_milestone": -1
   },
   {
-    "name": "load-media-router-component-extension",
-    "owners": [ "mfoltz", "media-dev" ],
-    // This flag has two purposes: in-team development/Q&A, and allowing
-    // Chromium users to load this extension, which isn't normally distributed
-    // with Chromium. It can be removed once the extension is removed, which has
-    // external dependencies.
-    "expiry_milestone": -1
-  },
-  {
     "name": "location-permissions-prompt",
     "owners": [ "thegreenfrog", "bling-flags@google.com" ],
     "expiry_milestone": 91
diff --git a/chrome/browser/flag-never-expire-list.json b/chrome/browser/flag-never-expire-list.json
index 0fdbdab..0b2519af 100644
--- a/chrome/browser/flag-never-expire-list.json
+++ b/chrome/browser/flag-never-expire-list.json
@@ -76,7 +76,6 @@
   "ios-breadcrumbs",
   "list-all-display-modes",
   "lite-video-force-override-decision",
-  "load-media-router-component-extension",
   "media-router-cast-allow-all-ips",
   "memlog",
   "memlog-sampling-rate",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 8652744..61af972 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3115,10 +3115,6 @@
 const char kChromeShareScreenshotDescription[] =
     "Enables UI to edit and share screenshots";
 
-const char kChromeSharingHubName[] = "Chrome Sharing Hub";
-const char kChromeSharingHubDescription[] =
-    "Enables the Chrome Sharing Hub/custom share sheet for Android.";
-
 const char kClipboardSuggestionContentHiddenName[] =
     "Clipboard suggestion content hidden";
 const char kClipboardSuggestionContentHiddenDescription[] =
@@ -3767,11 +3763,6 @@
     "enabled, whether or not to use SODA for live captions instead of the web "
     "api. Turn on the feature in chrome://settings/accessibility.";
 
-const char kCastMediaRouteProviderName[] = "Cast Media Route Provider";
-const char kCastMediaRouteProviderDescription[] =
-    "Enables the native Cast Media Route Provider implementation to be used "
-    "instead of the implementation in the Media Router component extension.";
-
 const char kCopyLinkToTextName[] = "Copy Link To Text";
 const char kCopyLinkToTextDescription[] =
     "Adds an item to the context menu to allow a user to copy a link to the "
@@ -4248,6 +4239,12 @@
     "Enables the display of a button on the ARC Provisioning failure dialog "
     "that opens the connectivity section of the diagnostics app.";
 
+const char kCalendarViewName[] =
+    "Productivity experiment: Monthly Calendar View";
+const char kCalendarViewDescription[] =
+    "Show Monthly Calendar View with Google Calendar events to increase "
+    "productivity by helping users view their schedules more quickly.";
+
 const char kPreferConstantFrameRateName[] = "Prefer Constant Frame Rate";
 const char kPreferConstantFrameRateDescription[] =
     "Enables this flag to prefer using constant frame rate for camera when "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index a433131..231731c3 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1788,9 +1788,6 @@
 extern const char kChromeShareScreenshotName[];
 extern const char kChromeShareScreenshotDescription[];
 
-extern const char kChromeSharingHubName[];
-extern const char kChromeSharingHubDescription[];
-
 extern const char kClipboardSuggestionContentHiddenName[];
 extern const char kClipboardSuggestionContentHiddenDescription[];
 
@@ -2147,9 +2144,6 @@
 extern const char kEnableAccessibilityLiveCaptionSodaName[];
 extern const char kEnableAccessibilityLiveCaptionSodaDescription[];
 
-extern const char kCastMediaRouteProviderName[];
-extern const char kCastMediaRouteProviderDescription[];
-
 extern const char kCopyLinkToTextName[];
 extern const char kCopyLinkToTextDescription[];
 
@@ -2432,6 +2426,9 @@
 extern const char kButtonARCNetworkDiagnosticsName[];
 extern const char kButtonARCNetworkDiagnosticsDescription[];
 
+extern const char kCalendarViewName[];
+extern const char kCalendarViewDescription[];
+
 extern const char kPreferConstantFrameRateName[];
 extern const char kPreferConstantFrameRateDescription[];
 
diff --git a/chrome/browser/flags/OWNERS b/chrome/browser/flags/OWNERS
index e6e37a3..09418179 100644
--- a/chrome/browser/flags/OWNERS
+++ b/chrome/browser/flags/OWNERS
@@ -4,6 +4,6 @@
 
 # This is for simple changes of adding/removing features.  For more structural
 # changes, use the normal OWNERS rules.
-per-file *ChromeFeatureList.java=*
-per-file *ChromeSwitches.java.tmpl=*
-per-file *chrome_feature_list.*=*
+per-file android/chrome_feature_list.*=*
+per-file android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java=*
+per-file android/java_templates/ChromeSwitches.java.tmpl=*
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index e9d26a3..a4f8bbd 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -177,7 +177,6 @@
     &kChromeShareHighlightsAndroid,
     &kChromeShareLongScreenshot,
     &kChromeShareScreenshot,
-    &kChromeSharingHub,
     &kChromeSurveyNextAndroid,
     &kCommandLineOnNonRooted,
     &kConditionalTabStripAndroid,
@@ -468,9 +467,6 @@
 const base::Feature kChromeShareScreenshot{"ChromeShareScreenshot",
                                            base::FEATURE_ENABLED_BY_DEFAULT};
 
-const base::Feature kChromeSharingHub{"ChromeSharingHub",
-                                      base::FEATURE_ENABLED_BY_DEFAULT};
-
 const base::Feature kChromeSurveyNextAndroid{"ChromeSurveyNextAndroid",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
 
@@ -571,6 +567,9 @@
 const base::Feature kDynamicColorAndroid{"DynamicColorAndroid",
                                          base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kEnableDangerousDownloadDialog{
+    "EnableDangerousDownloadDialog", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kEnhancedProtectionPromoCard{
     "EnhancedProtectionPromoCard", base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 457c965..0b421bf 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -49,7 +49,6 @@
 extern const base::Feature kChromeShareHighlightsAndroid;
 extern const base::Feature kChromeShareLongScreenshot;
 extern const base::Feature kChromeShareScreenshot;
-extern const base::Feature kChromeSharingHub;
 extern const base::Feature kChromeSurveyNextAndroid;
 extern const base::Feature kCommandLineOnNonRooted;
 extern const base::Feature kConditionalTabStripAndroid;
@@ -83,6 +82,7 @@
 extern const base::Feature kDownloadRename;
 extern const base::Feature kDuetTabStripIntegrationAndroid;
 extern const base::Feature kDynamicColorAndroid;
+extern const base::Feature kEnableDangerousDownloadDialog;
 extern const base::Feature kEnhancedProtectionPromoCard;
 extern const base::Feature kExperimentsForAgsa;
 extern const base::Feature kExploreSites;
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 80f6f4e..5928a0c 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -92,7 +92,7 @@
      * Returns whether the specified feature is enabled or not.
      *
      * Note: Features queried through this API must be added to the array
-     * |kFeaturesExposedToJava| in chrome/browser/android/chrome_feature_list.cc
+     * |kFeaturesExposedToJava| in chrome/browser/flags/android/chrome_feature_list.cc
      *
      * Calling this has the side effect of bucketing this client, which may cause an experiment to
      * be marked as active.
@@ -115,7 +115,7 @@
      * Returns a field trial param for the specified feature.
      *
      * Note: Features queried through this API must be added to the array
-     * |kFeaturesExposedToJava| in chrome/browser/android/chrome_feature_list.cc
+     * |kFeaturesExposedToJava| in chrome/browser/flags/android/chrome_feature_list.cc
      *
      * @param featureName The name of the feature to retrieve a param for.
      * @param paramName The name of the param for which to get as an integer.
@@ -134,7 +134,7 @@
      * Returns a field trial param as an int for the specified feature.
      *
      * Note: Features queried through this API must be added to the array
-     * |kFeaturesExposedToJava| in chrome/browser/android/chrome_feature_list.cc
+     * |kFeaturesExposedToJava| in chrome/browser/flags/android/chrome_feature_list.cc
      *
      * @param featureName The name of the feature to retrieve a param for.
      * @param paramName The name of the param for which to get as an integer.
@@ -156,7 +156,7 @@
      * Returns a field trial param as a double for the specified feature.
      *
      * Note: Features queried through this API must be added to the array
-     * |kFeaturesExposedToJava| in chrome/browser/android/chrome_feature_list.cc
+     * |kFeaturesExposedToJava| in chrome/browser/flags/android/chrome_feature_list.cc
      *
      * @param featureName The name of the feature to retrieve a param for.
      * @param paramName The name of the param for which to get as an integer.
@@ -192,7 +192,7 @@
      * Returns a field trial param as a boolean for the specified feature.
      *
      * Note: Features queried through this API must be added to the array
-     * |kFeaturesExposedToJava| in chrome/browser/android/chrome_feature_list.cc
+     * |kFeaturesExposedToJava| in chrome/browser/flags/android/chrome_feature_list.cc
      *
      * @param featureName The name of the feature to retrieve a param for.
      * @param paramName The name of the param for which to get as an integer.
@@ -289,7 +289,6 @@
     public static final String CHROME_SHARE_HIGHLIGHTS_ANDROID = "ChromeShareHighlightsAndroid";
     public static final String CHROME_SHARE_LONG_SCREENSHOT = "ChromeShareLongScreenshot";
     public static final String CHROME_SHARE_SCREENSHOT = "ChromeShareScreenshot";
-    public static final String CHROME_SHARING_HUB = "ChromeSharingHub";
     public static final String COMMAND_LINE_ON_NON_ROOTED = "CommandLineOnNonRooted";
     public static final String COMMERCE_MERCHANT_VIEWER = "CommerceMerchantViewer";
     public static final String COMMERCE_PRICE_TRACKING = "CommercePriceTracking";
diff --git a/chrome/browser/geolocation/geolocation_browsertest.cc b/chrome/browser/geolocation/geolocation_browsertest.cc
index 39ac4bc..33ccb5a 100644
--- a/chrome/browser/geolocation/geolocation_browsertest.cc
+++ b/chrome/browser/geolocation/geolocation_browsertest.cc
@@ -615,12 +615,14 @@
                                 base::Unretained(this))) {}
   ~GeolocationPrerenderBrowserTest() override = default;
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    GeolocationBrowserTest::SetUp();
+  }
+
   // GeolocationBrowserTest:
   void SetUpOnMainThread() override {
     current_browser_ = browser();
-
-    // embedded_test_server is started on MainThread for the prerender helper.
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     host_resolver()->AddRule("*", "127.0.0.1");
     GeolocationBrowserTest::SetUpOnMainThread();
   }
diff --git a/chrome/browser/history/history_browsertest.cc b/chrome/browser/history/history_browsertest.cc
index 7acbdf21..9fc70b8 100644
--- a/chrome/browser/history/history_browsertest.cc
+++ b/chrome/browser/history/history_browsertest.cc
@@ -825,9 +825,12 @@
             base::BindRepeating(&HistoryPrerenderBrowserTest::web_contents,
                                 base::Unretained(this))) {}
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    HistoryBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
-    HistoryBrowserTest::SetUpOnMainThread();
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     ASSERT_TRUE(embedded_test_server()->Start());
   }
 
diff --git a/chrome/browser/installable/installable_manager_browsertest.cc b/chrome/browser/installable/installable_manager_browsertest.cc
index 10b2a917..d05b6d2 100644
--- a/chrome/browser/installable/installable_manager_browsertest.cc
+++ b/chrome/browser/installable/installable_manager_browsertest.cc
@@ -32,6 +32,8 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/manifest/manifest_util.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 
 namespace webapps {
 
@@ -145,7 +147,7 @@
   void OnDidFinishInstallableCheck(const InstallableData& data) {
     errors_ = data.errors;
     manifest_url_ = data.manifest_url;
-    manifest_ = data.manifest;
+    manifest_ = data.manifest.Clone();
     primary_icon_url_ = data.primary_icon_url;
     if (data.primary_icon)
       primary_icon_ = std::make_unique<SkBitmap>(*data.primary_icon);
@@ -161,7 +163,10 @@
 
   const std::vector<InstallableStatusCode>& errors() const { return errors_; }
   const GURL& manifest_url() const { return manifest_url_; }
-  const blink::Manifest& manifest() const { return manifest_; }
+  const blink::mojom::Manifest& manifest() const {
+    DCHECK(manifest_);
+    return *manifest_;
+  }
   const GURL& primary_icon_url() const { return primary_icon_url_; }
   const SkBitmap* primary_icon() const { return primary_icon_.get(); }
   bool has_maskable_primary_icon() const { return has_maskable_primary_icon_; }
@@ -175,7 +180,7 @@
   base::RepeatingClosure quit_closure_;
   std::vector<InstallableStatusCode> errors_;
   GURL manifest_url_;
-  blink::Manifest manifest_;
+  blink::mojom::ManifestPtr manifest_ = blink::mojom::Manifest::New();
   GURL primary_icon_url_;
   std::unique_ptr<SkBitmap> primary_icon_;
   bool has_maskable_primary_icon_;
@@ -204,7 +209,7 @@
   void OnDidFinishFirstCheck(const InstallableData& data) {
     errors_ = data.errors;
     manifest_url_ = data.manifest_url;
-    manifest_ = data.manifest;
+    manifest_ = data.manifest.Clone();
     primary_icon_url_ = data.primary_icon_url;
     if (data.primary_icon)
       primary_icon_ = std::make_unique<SkBitmap>(*data.primary_icon);
@@ -223,12 +228,13 @@
     EXPECT_EQ(primary_icon_.get(), data.primary_icon);
     EXPECT_EQ(valid_manifest_, data.valid_manifest);
     EXPECT_EQ(has_worker_, data.has_worker);
-    EXPECT_EQ(manifest_.IsEmpty(), data.manifest.IsEmpty());
-    EXPECT_EQ(manifest_.start_url, data.manifest.start_url);
-    EXPECT_EQ(manifest_.display, data.manifest.display);
-    EXPECT_EQ(manifest_.name, data.manifest.name);
-    EXPECT_EQ(manifest_.short_name, data.manifest.short_name);
-    EXPECT_EQ(manifest_.display_override, data.manifest.display_override);
+    EXPECT_EQ(blink::IsEmptyManifest(*manifest_),
+              blink::IsEmptyManifest(data.manifest));
+    EXPECT_EQ(manifest_->start_url, data.manifest.start_url);
+    EXPECT_EQ(manifest_->display, data.manifest.display);
+    EXPECT_EQ(manifest_->name, data.manifest.name);
+    EXPECT_EQ(manifest_->short_name, data.manifest.short_name);
+    EXPECT_EQ(manifest_->display_override, data.manifest.display_override);
 
     std::move(quit_closure_).Run();
   }
@@ -239,7 +245,7 @@
   base::OnceClosure quit_closure_;
   std::vector<InstallableStatusCode> errors_;
   GURL manifest_url_;
-  blink::Manifest manifest_;
+  blink::mojom::ManifestPtr manifest_;
   GURL primary_icon_url_;
   std::unique_ptr<SkBitmap> primary_icon_;
   bool valid_manifest_;
@@ -422,7 +428,7 @@
   // Ensure that the InstallableManager starts off with everything null.
   InstallableManager* manager = GetManager(browser());
 
-  EXPECT_TRUE(manager->manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
   EXPECT_TRUE(manager->manifest_url().is_empty());
   EXPECT_TRUE(manager->icons_.empty());
   EXPECT_FALSE(manager->valid_manifest());
@@ -451,7 +457,7 @@
   RunInstallableManager(incognito_browser, tester.get(), GetManifestParams());
   run_loop.Run();
 
-  EXPECT_TRUE(manager->manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
   EXPECT_TRUE(manager->manifest_url().is_empty());
   EXPECT_TRUE(manager->icons_.empty());
   EXPECT_FALSE(manager->valid_manifest());
@@ -479,7 +485,7 @@
   run_loop.Run();
 
   // If there is no manifest, everything should be empty.
-  EXPECT_TRUE(tester->manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(tester->manifest()));
   EXPECT_TRUE(tester->manifest_url().is_empty());
   EXPECT_TRUE(tester->primary_icon_url().is_empty());
   EXPECT_EQ(nullptr, tester->primary_icon());
@@ -504,7 +510,7 @@
 
   // The installable manager should return a manifest URL even if it 404s.
   // However, the check should fail with a ManifestEmpty error.
-  EXPECT_TRUE(tester->manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(tester->manifest()));
 
   EXPECT_FALSE(tester->manifest_url().is_empty());
   EXPECT_TRUE(tester->primary_icon_url().is_empty());
@@ -529,7 +535,7 @@
                                    "/banners/manifest_test_page.html");
   run_loop.Run();
 
-  EXPECT_FALSE(tester->manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
   EXPECT_FALSE(tester->manifest_url().is_empty());
 
   EXPECT_TRUE(tester->primary_icon_url().is_empty());
@@ -556,7 +562,7 @@
                                    "/banners/manifest_test_page.html");
   run_loop.Run();
 
-  EXPECT_FALSE(tester->manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
   EXPECT_FALSE(tester->manifest_url().is_empty());
 
   EXPECT_TRUE(tester->primary_icon_url().is_empty());
@@ -585,7 +591,7 @@
             "/banners/manifest_too_small_icon.json"));
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
 
     EXPECT_TRUE(tester->primary_icon_url().is_empty());
@@ -609,7 +615,7 @@
     RunInstallableManager(browser(), tester.get(), GetWebAppParams());
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
 
     EXPECT_TRUE(tester->primary_icon_url().is_empty());
@@ -634,7 +640,7 @@
     RunInstallableManager(browser(), tester.get(), params);
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
 
     EXPECT_TRUE(tester->primary_icon_url().is_empty());
@@ -662,7 +668,7 @@
                                          "/banners/play_app_manifest.json"));
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
     EXPECT_TRUE(tester->manifest().prefer_related_applications);
 
@@ -685,7 +691,7 @@
     RunInstallableManager(browser(), tester.get(), GetPrimaryIconParams());
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
     EXPECT_TRUE(tester->manifest().prefer_related_applications);
 
@@ -711,7 +717,7 @@
     RunInstallableManager(browser(), tester.get(), params);
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
     EXPECT_TRUE(tester->manifest().prefer_related_applications);
 
@@ -737,7 +743,7 @@
     RunInstallableManager(browser(), tester.get(), params);
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
     EXPECT_TRUE(tester->manifest().prefer_related_applications);
 
@@ -767,7 +773,7 @@
                                      "/banners/manifest_test_page.html");
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
 
     EXPECT_FALSE(tester->primary_icon_url().is_empty());
@@ -789,7 +795,7 @@
                           GetPrimaryIconAndSplashIconParams());
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
 
     EXPECT_FALSE(tester->primary_icon_url().is_empty());
@@ -814,7 +820,7 @@
         GetURLOfPageWithServiceWorkerAndManifest(
             "/banners/manifest_bad_non_maskable_icon.json"));
     run_loop.Run();
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
     EXPECT_FALSE(tester->primary_icon_url().is_empty());
     EXPECT_NE(nullptr, tester->primary_icon());
@@ -847,7 +853,7 @@
     RunInstallableManager(browser(), tester.get(), GetWebAppParams());
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
     EXPECT_FALSE(tester->primary_icon_url().is_empty());
     EXPECT_NE(nullptr, tester->primary_icon());
@@ -859,7 +865,7 @@
     // Verify that the returned state matches manager internal state.
     InstallableManager* manager = GetManager(browser());
 
-    EXPECT_FALSE(manager->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(manager->manifest()));
     EXPECT_FALSE(manager->manifest_url().is_empty());
     EXPECT_EQ(1u, manager->icons_.size());
     EXPECT_FALSE(manager->valid_manifest());
@@ -886,7 +892,7 @@
     RunInstallableManager(browser(), tester.get(), params);
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
     EXPECT_FALSE(tester->primary_icon_url().is_empty());
     EXPECT_NE(nullptr, tester->primary_icon());
@@ -898,7 +904,7 @@
     // Verify that the returned state matches manager internal state.
     InstallableManager* manager = GetManager(browser());
 
-    EXPECT_FALSE(manager->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(manager->manifest()));
     EXPECT_FALSE(manager->manifest_url().is_empty());
     EXPECT_EQ(1u, manager->icons_.size());
     EXPECT_FALSE(manager->valid_manifest());
@@ -918,7 +924,7 @@
     ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
     InstallableManager* manager = GetManager(browser());
 
-    EXPECT_TRUE(manager->manifest().IsEmpty());
+    EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
     EXPECT_TRUE(manager->manifest_url().is_empty());
     EXPECT_FALSE(manager->valid_manifest());
     EXPECT_FALSE(manager->has_worker());
@@ -946,7 +952,7 @@
 
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
 
     EXPECT_FALSE(tester->primary_icon_url().is_empty());
@@ -976,7 +982,7 @@
 
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
 
     EXPECT_FALSE(tester->primary_icon_url().is_empty());
@@ -1005,7 +1011,7 @@
 
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
 
     EXPECT_FALSE(tester->primary_icon_url().is_empty());
@@ -1035,7 +1041,7 @@
 
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
 
     EXPECT_FALSE(tester->primary_icon_url().is_empty());
@@ -1116,7 +1122,7 @@
 
   // The installable manager should only retrieve items in the main frame;
   // everything should be empty here.
-  EXPECT_TRUE(tester->manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(tester->manifest()));
   EXPECT_TRUE(tester->manifest_url().is_empty());
   EXPECT_TRUE(tester->primary_icon_url().is_empty());
   EXPECT_EQ(nullptr, tester->primary_icon());
@@ -1140,7 +1146,7 @@
         "/banners/manifest_no_service_worker.html");
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
 
     EXPECT_TRUE(tester->primary_icon_url().is_empty());
@@ -1163,7 +1169,7 @@
     RunInstallableManager(browser(), tester.get(), params);
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
 
     EXPECT_FALSE(tester->primary_icon_url().is_empty());
@@ -1203,8 +1209,8 @@
   }
 
   // We should now be waiting for the service worker.
-  EXPECT_TRUE(tester->manifest().IsEmpty());
-  EXPECT_FALSE(manager->manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(tester->manifest()));
+  EXPECT_FALSE(blink::IsEmptyManifest(manager->manifest()));
   EXPECT_FALSE(manager->manifest_url().is_empty());
   EXPECT_FALSE(manager->has_worker());
   EXPECT_EQ(1u, manager->icons_.size());
@@ -1231,7 +1237,7 @@
                        base::Unretained(nested_tester.get())));
     run_loop.Run();
 
-    EXPECT_FALSE(nested_tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(nested_tester->manifest()));
     EXPECT_FALSE(nested_tester->manifest_url().is_empty());
     EXPECT_FALSE(nested_tester->primary_icon_url().is_empty());
     EXPECT_NE(nullptr, nested_tester->primary_icon());
@@ -1257,7 +1263,7 @@
   tester_run_loop.Run();
 
   // We should have passed now.
-  EXPECT_FALSE(tester->manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
   EXPECT_FALSE(tester->manifest_url().is_empty());
   EXPECT_FALSE(tester->primary_icon_url().is_empty());
   EXPECT_NE(nullptr, tester->primary_icon());
@@ -1267,7 +1273,7 @@
   CheckServiceWorkerForTester(tester.get());
 
   // Verify internal state.
-  EXPECT_FALSE(manager->manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(manager->manifest()));
   EXPECT_FALSE(manager->manifest_url().is_empty());
   EXPECT_FALSE(manager->valid_manifest());
   EXPECT_EQ(1u, manager->icons_.size());
@@ -1315,7 +1321,7 @@
   tester_run_loop.Run();
 
   // We should fail the check.
-  EXPECT_FALSE(tester->manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
   EXPECT_FALSE(tester->manifest_url().is_empty());
   EXPECT_FALSE(tester->primary_icon_url().is_empty());
   EXPECT_NE(nullptr, tester->primary_icon());
@@ -1353,7 +1359,7 @@
     tester_run_loop.Run();
 
     // We should have returned with an error.
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_TRUE(tester->valid_manifest());
     EXPECT_FALSE(tester->has_worker());
     EXPECT_EQ(std::vector<InstallableStatusCode>{NO_MATCHING_SERVICE_WORKER},
@@ -1384,7 +1390,7 @@
     tester_run_loop.Run();
 
     // The callback result will depend on the state of offline support.
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_TRUE(tester->valid_manifest());
     CheckServiceWorkerForTester(tester.get());
   }
@@ -1403,7 +1409,7 @@
   RunInstallableManager(browser(), tester.get(), GetWebAppParams());
   run_loop.Run();
 
-  EXPECT_FALSE(tester->manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
   EXPECT_FALSE(tester->manifest_url().is_empty());
 
   EXPECT_FALSE(tester->primary_icon_url().is_empty());
@@ -1431,7 +1437,7 @@
   RunInstallableManager(browser(), tester.get(), params);
   run_loop.Run();
 
-  EXPECT_FALSE(tester->manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
   EXPECT_FALSE(tester->manifest_url().is_empty());
 
   EXPECT_FALSE(tester->primary_icon_url().is_empty());
@@ -1453,7 +1459,7 @@
                                        "/banners/manifest_data_url_icon.json"));
   run_loop.Run();
 
-  EXPECT_FALSE(tester->manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
   EXPECT_FALSE(tester->manifest_url().is_empty());
 
   EXPECT_FALSE(tester->primary_icon_url().is_empty());
@@ -1480,7 +1486,7 @@
                                        "/banners/manifest_bad_icon.json"));
   run_loop.Run();
 
-  EXPECT_FALSE(tester->manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
   EXPECT_FALSE(tester->manifest_url().is_empty());
   EXPECT_TRUE(tester->primary_icon_url().is_empty());
   EXPECT_EQ(nullptr, tester->primary_icon());
@@ -1506,7 +1512,7 @@
     run_loop.Run();
 
     EXPECT_FALSE(tester->manifest_url().is_empty());
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->primary_icon_url().is_empty());
     EXPECT_NE(nullptr, tester->primary_icon());
     EXPECT_TRUE(tester->valid_manifest());
@@ -1528,7 +1534,7 @@
 
     // The smaller primary icon requirements should allow this to pass.
     EXPECT_FALSE(tester->manifest_url().is_empty());
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->primary_icon_url().is_empty());
     EXPECT_NE(nullptr, tester->primary_icon());
     EXPECT_TRUE(tester->valid_manifest());
@@ -1562,7 +1568,7 @@
 
   run_loop.Run();
 
-  EXPECT_FALSE(tester->manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
   EXPECT_FALSE(tester->manifest_url().is_empty());
   EXPECT_FALSE(tester->primary_icon_url().is_empty());
   EXPECT_NE(nullptr, tester->primary_icon());
@@ -1573,7 +1579,7 @@
 
   InstallableManager* manager = GetManager(browser());
 
-  EXPECT_FALSE(manager->manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(manager->manifest()));
   EXPECT_FALSE(manager->manifest_url().is_empty());
   EXPECT_FALSE(manager->valid_manifest());
   EXPECT_EQ(1u, manager->icons_.size());
@@ -1622,7 +1628,7 @@
                        base::Unretained(tester.get())));
     run_loop.Run();
 
-    EXPECT_TRUE(tester->manifest().IsEmpty());
+    EXPECT_TRUE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_EQ(NO_MANIFEST, manager->manifest_error());
     EXPECT_EQ(std::vector<InstallableStatusCode>{NO_MANIFEST},
               tester->errors());
@@ -1635,7 +1641,7 @@
     EXPECT_TRUE(content::ExecuteScript(web_contents, "addManifestLinkTag()"));
     run_loop.Run();
 
-    EXPECT_TRUE(manager->manifest().IsEmpty());
+    EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
     EXPECT_EQ(NO_ERROR_DETECTED, manager->manifest_error());
   }
 
@@ -1651,7 +1657,7 @@
                        base::Unretained(tester.get())));
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
     EXPECT_EQ(u"Manifest test app", tester->manifest().name);
     EXPECT_EQ(std::u16string(),
@@ -1669,7 +1675,7 @@
         web_contents, "changeManifestUrl('" + manifest_url.spec() + "');"));
     run_loop.Run();
 
-    EXPECT_TRUE(manager->manifest().IsEmpty());
+    EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
   }
 
   {
@@ -1684,7 +1690,7 @@
                        base::Unretained(tester.get())));
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_EQ(std::u16string(),
               tester->manifest().name.value_or(std::u16string()));
     EXPECT_EQ(u"Manifest", tester->manifest().short_name);
@@ -1755,7 +1761,7 @@
 
   run_loop.Run();
 
-  EXPECT_FALSE(tester->manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
   EXPECT_FALSE(tester->manifest_url().is_empty());
 
   EXPECT_TRUE(tester->primary_icon_url().is_empty());
@@ -1865,7 +1871,7 @@
 
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
 
     EXPECT_FALSE(tester->primary_icon_url().is_empty());
@@ -1893,7 +1899,7 @@
 
     run_loop.Run();
 
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_FALSE(tester->manifest_url().is_empty());
 
     EXPECT_FALSE(tester->primary_icon_url().is_empty());
@@ -1941,7 +1947,7 @@
           "/banners/manifest_display_override.json"));
   run_loop.Run();
 
-  EXPECT_FALSE(tester->manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
   EXPECT_FALSE(tester->manifest_url().is_empty());
   ASSERT_EQ(2u, tester->manifest().display_override.size());
   EXPECT_EQ(blink::mojom::DisplayMode::kMinimalUi,
@@ -1972,7 +1978,7 @@
           "/banners/manifest_display_override_contains_browser.json"));
   run_loop.Run();
 
-  EXPECT_FALSE(tester->manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
   EXPECT_FALSE(tester->manifest_url().is_empty());
   ASSERT_EQ(3u, tester->manifest().display_override.size());
   EXPECT_EQ(blink::mojom::DisplayMode::kBrowser,
@@ -1999,7 +2005,7 @@
           "/banners/manifest_display_override_display_is_browser.json"));
   run_loop.Run();
 
-  EXPECT_FALSE(tester->manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
   EXPECT_FALSE(tester->manifest_url().is_empty());
   ASSERT_EQ(1u, tester->manifest().display_override.size());
   EXPECT_EQ(blink::mojom::DisplayMode::kStandalone,
@@ -2067,7 +2073,7 @@
   }
   // It should have no data since manifest_test_page.html is loaded in the
   // prerendering.
-  EXPECT_TRUE(manager->manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
   EXPECT_EQ(NO_MANIFEST, manager->manifest_error());
 
   {
@@ -2079,7 +2085,7 @@
     run_loop.Run();
   }
 
-  EXPECT_TRUE(manager->manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
   EXPECT_EQ(NO_ERROR_DETECTED, manager->manifest_error());
 
   {
@@ -2093,7 +2099,7 @@
         base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                        base::Unretained(tester.get())));
     run_loop.Run();
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
     EXPECT_EQ(u"Manifest test app", tester->manifest().name);
     EXPECT_EQ(std::u16string(),
@@ -2157,7 +2163,7 @@
   }
   // It should have no data since manifest_test_page.html is loaded in the
   // prerendering.
-  EXPECT_TRUE(manager->manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
   EXPECT_EQ(NO_MANIFEST, manager->manifest_error());
 
   {
@@ -2171,7 +2177,7 @@
   }
 
   EXPECT_TRUE(host_observer.was_activated());
-  EXPECT_TRUE(manager->manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
   EXPECT_EQ(NO_ERROR_DETECTED, manager->manifest_error());
 
   {
@@ -2185,7 +2191,7 @@
         base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                        base::Unretained(tester.get())));
     run_loop.Run();
-    EXPECT_FALSE(tester->manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_EQ(std::vector<InstallableStatusCode>{}, tester->errors());
     EXPECT_EQ(u"Manifest test app", tester->manifest().name);
     EXPECT_EQ(std::u16string(),
@@ -2223,7 +2229,7 @@
                        base::Unretained(tester.get())));
     run_loop.Run();
   }
-  EXPECT_TRUE(manager->manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
   EXPECT_EQ(NO_MANIFEST, manager->manifest_error());
 
   // OnResetData() is called when a navigation is finished.
@@ -2234,7 +2240,7 @@
   prerender_helper()->NavigatePrimaryPage(prerender_url);
 
   EXPECT_TRUE(host_observer.was_activated());
-  EXPECT_TRUE(manager->manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(manager->manifest()));
   EXPECT_EQ(NO_ERROR_DETECTED, manager->manifest_error());
 
   {
@@ -2249,7 +2255,7 @@
         base::BindOnce(&CallbackTester::OnDidFinishInstallableCheck,
                        base::Unretained(tester.get())));
     run_loop.Run();
-    EXPECT_TRUE(tester->manifest().IsEmpty());
+    EXPECT_TRUE(blink::IsEmptyManifest(tester->manifest()));
     EXPECT_EQ(NO_MANIFEST, manager->manifest_error());
     EXPECT_EQ(std::vector<InstallableStatusCode>{NO_MANIFEST},
               tester->errors());
diff --git a/chrome/browser/lacros/download_controller_client_lacros.cc b/chrome/browser/lacros/download_controller_client_lacros.cc
index 74b892b..b7ea0b36 100644
--- a/chrome/browser/lacros/download_controller_client_lacros.cc
+++ b/chrome/browser/lacros/download_controller_client_lacros.cc
@@ -80,13 +80,14 @@
   // Returns all downloads, no matter the type or state.
   std::vector<download::DownloadItem*> GetAllDownloads() {
     download::SimpleDownloadManager::DownloadVector downloads;
-    manager_->GetAllDownloads(&downloads);
+    if (manager_->IsManagerInitialized())
+      manager_->GetAllDownloads(&downloads);
     return downloads;
   }
 
   // Pauses the download associated with the specified `download_guid`.
   void Pause(const std::string& download_guid) {
-    download::DownloadItem* download = GetDownloadByGuid(download_guid);
+    auto* download = manager_->GetDownloadByGuid(download_guid);
     if (download)
       download->Pause();
   }
@@ -95,7 +96,7 @@
   // `user_resume` is `true`, it signifies that this invocation was triggered by
   // an explicit user action.
   void Resume(const std::string& download_guid, bool user_resume) {
-    download::DownloadItem* download = GetDownloadByGuid(download_guid);
+    auto* download = manager_->GetDownloadByGuid(download_guid);
     if (download)
       download->Resume(user_resume);
   }
@@ -104,7 +105,7 @@
   // `user_cancel` is `true`, it signifies that this invocation was triggered by
   // an explicit user action.
   void Cancel(const std::string& download_guid, bool user_cancel) {
-    download::DownloadItem* download = GetDownloadByGuid(download_guid);
+    auto* download = manager_->GetDownloadByGuid(download_guid);
     if (download)
       download->Cancel(user_cancel);
   }
@@ -113,7 +114,7 @@
   // `open_when_complete`.
   void SetOpenWhenComplete(const std::string& download_guid,
                            bool open_when_complete) {
-    download::DownloadItem* download = GetDownloadByGuid(download_guid);
+    auto* download = manager_->GetDownloadByGuid(download_guid);
     if (download)
       download->SetOpenWhenComplete(open_when_complete);
   }
@@ -162,16 +163,6 @@
     controller_client_->OnDownloadDestroyed(item);
   }
 
-  download::DownloadItem* GetDownloadByGuid(const std::string& guid) {
-    download::SimpleDownloadManager::DownloadVector downloads;
-    manager_->GetAllDownloads(&downloads);
-    for (auto* download : downloads) {
-      if (download->GetGuid() == guid)
-        return download;
-    }
-    return nullptr;
-  }
-
   DownloadControllerClientLacros* const controller_client_;
 
   content::DownloadManager* const manager_;
diff --git a/chrome/browser/lacros/download_controller_client_lacros_browsertest.cc b/chrome/browser/lacros/download_controller_client_lacros_browsertest.cc
index 87de4df..0dbd5d72 100644
--- a/chrome/browser/lacros/download_controller_client_lacros_browsertest.cc
+++ b/chrome/browser/lacros/download_controller_client_lacros_browsertest.cc
@@ -66,9 +66,6 @@
     profile()->GetDownloadManager()->Shutdown();
     profile()->SetDownloadManagerForTesting(std::move(download_manager));
 
-    ON_CALL(*download_manager_, IsManagerInitialized())
-        .WillByDefault(testing::Return(true));
-
     // Download controller client.
     download_controller_client_ =
         std::make_unique<DownloadControllerClientLacros>();
@@ -97,19 +94,40 @@
 
   // Mock `download_manager()` response for `GetAllDownloads()`.
   EXPECT_CALL(*download_manager(), GetAllDownloads)
-      .WillOnce(testing::Invoke(
+      .WillRepeatedly(testing::Invoke(
           [&](std::vector<download::DownloadItem*>* download_ptrs) {
             for (auto& download : downloads)
               download_ptrs->push_back(download.get());
           }));
 
-  // Invoke `GetAllDownloads()`.
-  base::RunLoop run_loop;
-  download_controller_client()->GetAllDownloads(base::BindLambdaForTesting(
-      [&](std::vector<crosapi::mojom::DownloadItemPtr> downloads) {
-        EXPECT_EQ(downloads.size(), 2u);
-        EXPECT_TRUE(IsSortedChronologicallyByStartTime(downloads));
-        run_loop.Quit();
-      }));
-  run_loop.Run();
+  // Initially indicate that `download_manager()` is uninitialized.
+  ON_CALL(*download_manager(), IsManagerInitialized())
+      .WillByDefault(testing::Return(false));
+
+  {
+    // Invoke `GetAllDownloads()`.
+    base::RunLoop run_loop;
+    download_controller_client()->GetAllDownloads(base::BindLambdaForTesting(
+        [&](std::vector<crosapi::mojom::DownloadItemPtr> downloads) {
+          EXPECT_EQ(downloads.size(), 0u);
+          run_loop.Quit();
+        }));
+    run_loop.Run();
+  }
+
+  // Now indicate that `download_manager()` is initialized.
+  ON_CALL(*download_manager(), IsManagerInitialized())
+      .WillByDefault(testing::Return(true));
+
+  {
+    // Invoke `GetAllDownloads()`.
+    base::RunLoop run_loop;
+    download_controller_client()->GetAllDownloads(base::BindLambdaForTesting(
+        [&](std::vector<crosapi::mojom::DownloadItemPtr> downloads) {
+          EXPECT_EQ(downloads.size(), 2u);
+          EXPECT_TRUE(IsSortedChronologicallyByStartTime(downloads));
+          run_loop.Quit();
+        }));
+    run_loop.Run();
+  }
 }
diff --git a/chrome/browser/lite_video/lite_video_keyed_service_browsertest.cc b/chrome/browser/lite_video/lite_video_keyed_service_browsertest.cc
index b185cae5..97e6c2f 100644
--- a/chrome/browser/lite_video/lite_video_keyed_service_browsertest.cc
+++ b/chrome/browser/lite_video/lite_video_keyed_service_browsertest.cc
@@ -878,6 +878,10 @@
       const LiteVideoKeyedServicePrerenderBrowserTest&) = delete;
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
+    // TODO(crbug.com/846380): move ScopedFeatureList init to the constructor.
+    // Due to LiteVideoKeyedServiceBrowserTest's use of ScopedFeatureList, we
+    // construct the prerender helper here to ensure the correct relative order
+    // of construction and destriction of the lists.
     prerender_helper_ = std::make_unique<content::test::PrerenderTestHelper>(
         base::BindRepeating(
             &LiteVideoKeyedServicePrerenderBrowserTest::GetWebContents,
@@ -886,7 +890,9 @@
   }
 
   void SetUpOnMainThread() override {
-    prerender_helper_->SetUpOnMainThread(embedded_test_server());
+    // We set up here rather than in the earlier SetUp due to the creation
+    // timing of prerender_helper_ (SetUp happens prior to SetUpCommandLine).
+    prerender_helper_->SetUp(embedded_test_server());
     host_resolver()->AddRule("*", "127.0.0.1");
     ASSERT_TRUE(embedded_test_server()->Start());
   }
diff --git a/chrome/browser/login_detection/login_detection_browsertest.cc b/chrome/browser/login_detection/login_detection_browsertest.cc
index b1d172d..26e4a92d 100644
--- a/chrome/browser/login_detection/login_detection_browsertest.cc
+++ b/chrome/browser/login_detection/login_detection_browsertest.cc
@@ -155,7 +155,11 @@
 
 class LoginDetectionPrerenderBrowserTest : public LoginDetectionBrowserTest {
  public:
-  LoginDetectionPrerenderBrowserTest() = default;
+  LoginDetectionPrerenderBrowserTest() {
+    prerender_helper_ = std::make_unique<content::test::PrerenderTestHelper>(
+        base::BindRepeating(&LoginDetectionPrerenderBrowserTest::GetWebContents,
+                            base::Unretained(this)));
+  }
   ~LoginDetectionPrerenderBrowserTest() override = default;
   LoginDetectionPrerenderBrowserTest(
       const LoginDetectionPrerenderBrowserTest&) = delete;
@@ -163,14 +167,12 @@
   LoginDetectionPrerenderBrowserTest& operator=(
       const LoginDetectionPrerenderBrowserTest&) = delete;
 
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    prerender_helper_ = std::make_unique<content::test::PrerenderTestHelper>(
-        base::BindRepeating(&LoginDetectionPrerenderBrowserTest::GetWebContents,
-                            base::Unretained(this)));
+  void SetUp() override {
+    prerender_helper_->SetUp(embedded_test_server());
+    LoginDetectionBrowserTest::SetUp();
   }
 
   void SetUpOnMainThread() override {
-    prerender_helper_->SetUpOnMainThread(embedded_test_server());
     host_resolver()->AddRule("*", "127.0.0.1");
     ASSERT_TRUE(embedded_test_server()->Start());
   }
diff --git a/chrome/browser/media/DEPS b/chrome/browser/media/DEPS
index 0e330e9..e4f4f6a 100644
--- a/chrome/browser/media/DEPS
+++ b/chrome/browser/media/DEPS
@@ -4,6 +4,7 @@
   "+media/audio",
   "+media/base",
   "+media/cast",
+  "+media/cdm/cdm_preference_data.h",
   "+media/cdm/supported_cdm_versions.h",
   "+media/media_buildflags.h",
   "+media/mojo/mojom",
diff --git a/chrome/browser/media/cdm_document_service_impl.cc b/chrome/browser/media/cdm_document_service_impl.cc
index d4d8a726..8e1f63c 100644
--- a/chrome/browser/media/cdm_document_service_impl.cc
+++ b/chrome/browser/media/cdm_document_service_impl.cc
@@ -242,7 +242,8 @@
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if defined(OS_WIN)
-void CdmDocumentServiceImpl::GetCdmOriginId(GetCdmOriginIdCallback callback) {
+void CdmDocumentServiceImpl::GetCdmPreferenceData(
+    GetCdmPreferenceDataCallback callback) {
   const url::Origin cdm_origin = origin();
   if (cdm_origin.opaque()) {
     mojo::ReportBadMessage("EME use is not allowed on opaque origin");
@@ -252,7 +253,22 @@
   PrefService* user_prefs = user_prefs::UserPrefs::Get(
       content::WebContents::FromRenderFrameHost(render_frame_host())
           ->GetBrowserContext());
+
   std::move(callback).Run(
-      CdmPrefServiceHelper::GetCdmOriginId(user_prefs, cdm_origin));
+      CdmPrefServiceHelper::GetCdmPreferenceData(user_prefs, cdm_origin));
+}
+
+void CdmDocumentServiceImpl::SetCdmClientToken(
+    const std::vector<uint8_t>& client_token) {
+  const url::Origin cdm_origin = origin();
+  if (cdm_origin.opaque()) {
+    mojo::ReportBadMessage("EME use is not allowed on opaque origin");
+    return;
+  }
+
+  PrefService* user_prefs = user_prefs::UserPrefs::Get(
+      content::WebContents::FromRenderFrameHost(render_frame_host())
+          ->GetBrowserContext());
+  CdmPrefServiceHelper::SetCdmClientToken(user_prefs, cdm_origin, client_token);
 }
 #endif  // defined(OS_WIN)
diff --git a/chrome/browser/media/cdm_document_service_impl.h b/chrome/browser/media/cdm_document_service_impl.h
index c5aa475..57c4e51 100644
--- a/chrome/browser/media/cdm_document_service_impl.h
+++ b/chrome/browser/media/cdm_document_service_impl.h
@@ -46,7 +46,8 @@
   void IsVerifiedAccessEnabled(IsVerifiedAccessEnabledCallback callback) final;
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 #if defined(OS_WIN)
-  void GetCdmOriginId(GetCdmOriginIdCallback callback) final;
+  void GetCdmPreferenceData(GetCdmPreferenceDataCallback callback) final;
+  void SetCdmClientToken(const std::vector<uint8_t>& client_token) final;
 #endif  // defined(OS_WIN)
 
  private:
diff --git a/chrome/browser/media/cdm_document_service_impl_test.cc b/chrome/browser/media/cdm_document_service_impl_test.cc
index 5da14169..6c376abb 100644
--- a/chrome/browser/media/cdm_document_service_impl_test.cc
+++ b/chrome/browser/media/cdm_document_service_impl_test.cc
@@ -6,12 +6,17 @@
 
 #include <memory>
 
+#include "base/json/values_util.h"
 #include "base/test/gmock_callback_support.h"
 #include "base/test/mock_callback.h"
 #include "base/unguessable_token.h"
+#include "base/values.h"
+#include "chrome/common/pref_names.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_browser_process.h"
+#include "components/prefs/scoped_user_pref_update.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "components/user_prefs/user_prefs.h"
 #include "media/mojo/mojom/cdm_document_service.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -22,13 +27,18 @@
 using testing::DoAll;
 using testing::SaveArg;
 
+namespace {
+// copied from cdm_pref_service_helper.cc for testing
+const char kOriginId[] = "origin_id";
+}  // namespace
+
 namespace content {
 
 const char kTestOrigin[] = "https://foo.bar";
 const char kTestOrigin2[] = "https://bar.foo";
 
-using GetCdmOriginIdMockCB =
-    base::MockOnceCallback<void(const base::UnguessableToken&)>;
+using GetCdmPreferenceDataMockCB =
+    base::MockOnceCallback<void(std::unique_ptr<media::CdmPreferenceData>)>;
 
 class CdmDocumentServiceImplTest : public ChromeRenderViewHostTestHarness {
  public:
@@ -44,16 +54,41 @@
         cdm_document_service_.BindNewPipeAndPassReceiver());
   }
 
-  const base::UnguessableToken GetCdmOriginId() {
-    base::UnguessableToken origin_id;
-    GetCdmOriginIdMockCB mock_cb;
-    EXPECT_CALL(mock_cb, Run(_)).WillOnce(SaveArg<0>(&origin_id));
+  std::unique_ptr<media::CdmPreferenceData> GetCdmPreferenceData() {
+    std::unique_ptr<media::CdmPreferenceData> cdm_preference_data;
+    GetCdmPreferenceDataMockCB mock_cb;
+    EXPECT_CALL(mock_cb, Run(_))
+        .WillOnce([&cdm_preference_data](
+                      std::unique_ptr<media::CdmPreferenceData> ptr) {
+          cdm_preference_data = std::move(ptr);
+        });
 
-    cdm_document_service_->GetCdmOriginId(mock_cb.Get());
+    cdm_document_service_->GetCdmPreferenceData(mock_cb.Get());
     base::RunLoop().RunUntilIdle();
 
-    EXPECT_FALSE(origin_id.is_empty());
-    return origin_id;
+    return cdm_preference_data;
+  }
+
+  void SetCdmClientToken(const std::vector<uint8_t>& client_token) {
+    cdm_document_service_->SetCdmClientToken(client_token);
+    base::RunLoop().RunUntilIdle();
+  }
+
+  void CorruptCdmPreference() {
+    PrefService* user_prefs = user_prefs::UserPrefs::Get(
+        web_contents()->GetMainFrame()->GetBrowserContext());
+
+    // Create (or overwrite) an entry with only an origin id to simulate some
+    // kind of corruption or simply an update to the preference format.
+    base::Value entry(base::Value::Type::DICTIONARY);
+    entry.SetKey(kOriginId, base::UnguessableTokenToValue(
+                                base::UnguessableToken::Create()));
+
+    DictionaryPrefUpdate update(user_prefs, prefs::kMediaCdmOriginData);
+    base::DictionaryValue* dict = update.Get();
+    const std::string serialized_origin =
+        web_contents()->GetMainFrame()->GetLastCommittedOrigin().Serialize();
+    dict->SetKey(serialized_origin, std::move(entry));
   }
 
  protected:
@@ -63,27 +98,97 @@
 // Verify that we get a non null origin id.
 TEST_F(CdmDocumentServiceImplTest, GetOriginId) {
   NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
-  ignore_result(GetCdmOriginId());
+  auto pref_data = GetCdmPreferenceData();
+  ASSERT_FALSE(pref_data->origin_id.is_empty());
+}
+
+// Verify that we get a non null and different origin id if the preference gets
+// corrupted.
+TEST_F(CdmDocumentServiceImplTest, GetOriginIdAfterCorruption) {
+  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
+  auto pref_data_before = GetCdmPreferenceData();
+
+  CorruptCdmPreference();
+  auto pref_data_after = GetCdmPreferenceData();
+  ASSERT_FALSE(pref_data_after->origin_id.is_empty());
+  ASSERT_NE(pref_data_before->origin_id, pref_data_after->origin_id);
 }
 
 // Verify that we can correctly get an existing origin id.
 TEST_F(CdmDocumentServiceImplTest, GetSameOriginId) {
+  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
+  base::UnguessableToken origin_id1 = GetCdmPreferenceData()->origin_id;
+
+  // Create an unrelated origin id
+  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin2));
+  base::UnguessableToken origin_id2 = GetCdmPreferenceData()->origin_id;
+
+  // Get the origin id for the first origin
+  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
+  base::UnguessableToken origin_id3 = GetCdmPreferenceData()->origin_id;
+
+  ASSERT_NE(origin_id2, origin_id1);
+  ASSERT_EQ(origin_id1, origin_id3);
+}
+
+TEST_F(CdmDocumentServiceImplTest, GetNullClientToken) {
+  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
+  auto cdm_preference_data = GetCdmPreferenceData();
+
+  ASSERT_FALSE(cdm_preference_data->client_token);
+}
+
+TEST_F(CdmDocumentServiceImplTest, SetClientToken) {
+  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
+  // Call GetCdmPreferenceData to create the origin id first, otherwise
+  // `SetCdmClientToken()` will assume the preference data associated with the
+  // origin was recently cleared and will not save the client token.
+  ignore_result(GetCdmPreferenceData());
+
+  std::vector<uint8_t> expected_client_token = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+  SetCdmClientToken(expected_client_token);
+
+  auto cdm_preference_data = GetCdmPreferenceData();
+
+  ASSERT_EQ(cdm_preference_data->client_token, expected_client_token);
+}
+
+// Sets a client token for one origin and check that we get the same
+// client token after navigating back to that origin.
+TEST_F(CdmDocumentServiceImplTest, GetSameClientToken) {
   const auto kOrigin = url::Origin::Create(GURL(kTestOrigin));
   const auto kOtherOrigin = url::Origin::Create(GURL(kTestOrigin2));
 
   NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
-  base::UnguessableToken origin_id1 = GetCdmOriginId();
+  // Call GetCdmPreferenceData to create the origin id first, otherwise
+  // `SetCdmClientToken()` will assume the preference data associated with the
+  // origin was recently cleared and will not save the client token.
+  ignore_result(GetCdmPreferenceData());
+  std::vector<uint8_t> expected_client_token = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+  SetCdmClientToken(expected_client_token);
 
-  // Create an unrelated origin id
   NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin2));
-  base::UnguessableToken origin_id2 = GetCdmOriginId();
+  ignore_result(GetCdmPreferenceData());
+  SetCdmClientToken({1, 2, 3, 4, 5});
 
-  // Get the origin id for the first origin
   NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
-  base::UnguessableToken origin_id3 = GetCdmOriginId();
+  auto cdm_preference_data = GetCdmPreferenceData();
 
-  ASSERT_NE(origin_id2, origin_id1);
-  ASSERT_EQ(origin_id1, origin_id3);
+  ASSERT_EQ(cdm_preference_data->client_token, expected_client_token);
+}
+
+// If an entry cannot be parsed correctly, `SetCdmClientToken` should simply
+// remove that entry and return without saving the client token.
+TEST_F(CdmDocumentServiceImplTest, SetClientTokenAfterCorruption) {
+  NavigateToUrlAndCreateCdmDocumentService(GURL(kTestOrigin));
+  ignore_result(GetCdmPreferenceData());
+  CorruptCdmPreference();
+
+  std::vector<uint8_t> expected_client_token = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+  SetCdmClientToken(expected_client_token);
+
+  auto cdm_preference_data = GetCdmPreferenceData();
+  ASSERT_FALSE(cdm_preference_data->client_token.has_value());
 }
 
 }  // namespace content
diff --git a/chrome/browser/media/cdm_pref_service_helper.cc b/chrome/browser/media/cdm_pref_service_helper.cc
index e31214b..ee1a361c 100644
--- a/chrome/browser/media/cdm_pref_service_helper.cc
+++ b/chrome/browser/media/cdm_pref_service_helper.cc
@@ -6,8 +6,9 @@
 
 #include "base/logging.h"
 
+#include "base/base64.h"
 #include "base/json/values_util.h"
-#include "base/memory/ptr_util.h"
+#include "base/values.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_registry_simple.h"
@@ -20,45 +21,79 @@
 namespace {
 
 const char kOriginId[] = "origin_id";
-const char kCreationTime[] = "creation_time";
+const char kOriginIdCreationTime[] = "origin_id_creation_time";
 
-// Data stored in the kMediaCdmOrigin Pref dictionary.
+const char kClientToken[] = "client_token";
+const char kClientTokenCreationTime[] = "client_token_creation_time";
+
+// Data stored in the kMediaCdmOriginData Pref dictionary.
 // {
 //     $origin_string: {
 //         # A unique random string for the real "origin_string".
 //         "origin_id": $origin_id
-//         "creation_time": $creation_time
+//         "origin_id_creation_time": $origin_id_creation_time
+//         "client_token": $client_token (optional)
+//         "client_token_creation_time": $client_token_creation_time (optional)
 //     },
 //     more origin_string map...
 // }
-class OriginData {
+class CdmData {
  public:
-  OriginData(const base::UnguessableToken& origin_id, base::Time time)
-      : origin_id_(origin_id), creation_time_(time) {
+  CdmData(const base::UnguessableToken& origin_id, base::Time origin_id_time)
+      : origin_id_(origin_id), origin_id_creation_time_(origin_id_time) {
     DCHECK(origin_id_);
   }
 
   const base::UnguessableToken& origin_id() const { return origin_id_; }
 
-  base::Time creation_time() const { return creation_time_; }
+  base::Time origin_id_creation_time() const {
+    return origin_id_creation_time_;
+  }
+
+  const absl::optional<std::vector<uint8_t>> client_token() const {
+    return client_token_;
+  }
+
+  base::Time client_token_creation_time() const {
+    return client_token_creation_time_;
+  }
+
+  void SetClientToken(const std::vector<uint8_t>& client_token,
+                      const base::Time creation_time) {
+    VLOG(1) << __func__;
+    client_token_ = client_token;
+    client_token_creation_time_ = creation_time;
+  }
 
   base::Value ToDictValue() const {
     base::Value dict(base::Value::Type::DICTIONARY);
 
+    // Origin ID
     dict.SetKey(kOriginId, base::UnguessableTokenToValue(origin_id_));
-    dict.SetKey(kCreationTime, base::TimeToValue(creation_time_));
+    dict.SetKey(kOriginIdCreationTime,
+                base::TimeToValue(origin_id_creation_time_));
+
+    // Optional Client Token
+    if (client_token_.has_value() && !client_token_->empty()) {
+      std::string encoded_client_token =
+          base::Base64Encode(client_token_.value());
+      dict.SetStringKey(kClientToken, encoded_client_token);
+      dict.SetKey(kClientTokenCreationTime,
+                  base::TimeToValue(client_token_creation_time_));
+    }
 
     return dict;
   }
 
-  // Convert `origin_dict` to OriginData. `origin_dict` contains the origin id
-  // and the time it was first created. Return nullptr if `origin_dict` has any
-  // corruption, e.g. format error, missing fields, invalid value.
-  static std::unique_ptr<OriginData> FromDictValue(
-      const base::Value& origin_dict) {
-    DCHECK(origin_dict.is_dict());
-
-    const base::Value* origin_id_value = origin_dict.FindKey(kOriginId);
+  // Convert `cdm_data_dict` to CdmData. `cdm_data_dict` contains the origin id
+  // and the time it was first created as well as the client token and the time
+  // it was set/updated. Return nullptr if `cdm_data_dict` has any corruption,
+  // e.g. format error, missing fields, invalid value.
+  static std::unique_ptr<CdmData> FromDictValue(
+      const base::Value& cdm_data_dict) {
+    DCHECK(cdm_data_dict.is_dict());
+    // Origin ID
+    const base::Value* origin_id_value = cdm_data_dict.FindKey(kOriginId);
     if (!origin_id_value)
       return nullptr;
 
@@ -67,20 +102,52 @@
     if (!origin_id)
       return nullptr;
 
-    const base::Value* time_value = origin_dict.FindKey(kCreationTime);
+    const base::Value* time_value =
+        cdm_data_dict.FindKey(kOriginIdCreationTime);
     if (!time_value)
       return nullptr;
 
-    absl::optional<base::Time> time = base::ValueToTime(time_value);
-    if (!time || time.value().is_null())
+    absl::optional<base::Time> origin_id_time = base::ValueToTime(time_value);
+    if (!origin_id_time || origin_id_time.value().is_null())
       return nullptr;
 
-    return std::make_unique<OriginData>(origin_id.value(), time.value());
+    auto cdm_data =
+        std::make_unique<CdmData>(origin_id.value(), origin_id_time.value());
+
+    // Client Token
+    const std::string* encoded_client_token =
+        cdm_data_dict.FindStringKey(kClientToken);
+    if (encoded_client_token) {
+      std::string decoded_client_token;
+      if (!base::Base64Decode(*encoded_client_token, &decoded_client_token))
+        return nullptr;
+
+      std::vector<uint8_t> client_token(decoded_client_token.begin(),
+                                        decoded_client_token.end());
+
+      time_value = cdm_data_dict.FindKey(kClientTokenCreationTime);
+
+      // If we have a client token but no creation time, this is an error.
+      if (!time_value)
+        return nullptr;
+
+      absl::optional<base::Time> client_token_time =
+          base::ValueToTime(time_value);
+      if (!client_token_time)
+        return nullptr;
+
+      cdm_data->SetClientToken(client_token, client_token_time.value());
+    }
+
+    return cdm_data;
   }
 
  private:
   base::UnguessableToken origin_id_;
-  base::Time creation_time_;
+  base::Time origin_id_creation_time_;
+
+  absl::optional<std::vector<uint8_t>> client_token_;
+  base::Time client_token_creation_time_;
 };
 
 }  // namespace
@@ -89,45 +156,85 @@
 CdmPrefServiceHelper::~CdmPrefServiceHelper() = default;
 
 void CdmPrefServiceHelper::RegisterProfilePrefs(PrefRegistrySimple* registry) {
-  registry->RegisterDictionaryPref(prefs::kMediaCdmOrigin);
+  registry->RegisterDictionaryPref(prefs::kMediaCdmOriginData);
 }
 
-base::UnguessableToken CdmPrefServiceHelper::GetCdmOriginId(
-    PrefService* user_prefs,
-    const url::Origin& cdm_origin) {
+std::unique_ptr<media::CdmPreferenceData>
+CdmPrefServiceHelper::GetCdmPreferenceData(PrefService* user_prefs,
+                                           const url::Origin& cdm_origin) {
+  VLOG(1) << __func__;
   // Access to the PrefService must be made from the UI thread.
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   const base::DictionaryValue* dict =
-      user_prefs->GetDictionary(prefs::kMediaCdmOrigin);
+      user_prefs->GetDictionary(prefs::kMediaCdmOriginData);
 
   DCHECK(!cdm_origin.opaque());
   if (cdm_origin.opaque()) {
     mojo::ReportBadMessage("EME use is not allowed on opaque origin");
-    return base::UnguessableToken::Null();
+    return nullptr;
   }
 
   const std::string serialized_cdm_origin = cdm_origin.Serialize();
   DCHECK(!serialized_cdm_origin.empty());
 
-  const base::Value* origin_dict =
+  const base::Value* cdm_data_dict =
       dict->FindKeyOfType(serialized_cdm_origin, base::Value::Type::DICTIONARY);
 
-  std::unique_ptr<OriginData> origin_data;
-  if (origin_dict) {
-    origin_data = OriginData::FromDictValue(*origin_dict);
-  }
+  std::unique_ptr<CdmData> cdm_data;
+  if (cdm_data_dict)
+    cdm_data = CdmData::FromDictValue(*cdm_data_dict);
 
   // Create an new entry or overwrite the existing one in case we weren't able
   // to get a valid origin ID from `FromDictValue()`.
-  if (!origin_data) {
-    DictionaryPrefUpdate update(user_prefs, prefs::kMediaCdmOrigin);
+  if (!cdm_data) {
+    DictionaryPrefUpdate update(user_prefs, prefs::kMediaCdmOriginData);
     base::DictionaryValue* dict = update.Get();
 
-    origin_data = std::make_unique<OriginData>(base::UnguessableToken::Create(),
-                                               base::Time::Now());
-    dict->SetKey(serialized_cdm_origin, origin_data->ToDictValue());
+    cdm_data = std::make_unique<CdmData>(base::UnguessableToken::Create(),
+                                         base::Time::Now());
+    dict->SetKey(serialized_cdm_origin, cdm_data->ToDictValue());
   }
 
-  return origin_data->origin_id();
+  return std::make_unique<media::CdmPreferenceData>(cdm_data->origin_id(),
+                                                    cdm_data->client_token());
+}
+
+void CdmPrefServiceHelper::SetCdmClientToken(
+    PrefService* user_prefs,
+    const url::Origin& cdm_origin,
+    const std::vector<uint8_t>& client_token) {
+  VLOG(1) << __func__;
+  // Access to the PrefService must be made from the UI thread.
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  DCHECK(!cdm_origin.opaque());
+
+  const std::string serialized_cdm_origin = cdm_origin.Serialize();
+  DCHECK(!serialized_cdm_origin.empty());
+
+  DictionaryPrefUpdate update(user_prefs, prefs::kMediaCdmOriginData);
+  base::DictionaryValue* dict = update.Get();
+
+  base::Value* dict_value =
+      dict->FindKeyOfType(serialized_cdm_origin, base::Value::Type::DICTIONARY);
+  std::unique_ptr<CdmData> cdm_data;
+  if (!dict_value) {
+    // If there is no preference associated with the origin at this point, this
+    // means that the preference data was deleted by the user recently. No need
+    // to save the client token in that case.
+    return;
+  }
+
+  cdm_data = CdmData::FromDictValue(*dict_value);
+  if (!cdm_data) {
+    DVLOG(ERROR) << "The CDM preference data for origin \""
+                 << serialized_cdm_origin
+                 << "\" could not be parsed. Removing entry from preferences.";
+    dict->RemoveKey(serialized_cdm_origin);
+    return;
+  }
+
+  cdm_data->SetClientToken(client_token, base::Time::Now());
+  dict->SetKey(serialized_cdm_origin, cdm_data->ToDictValue());
 }
diff --git a/chrome/browser/media/cdm_pref_service_helper.h b/chrome/browser/media/cdm_pref_service_helper.h
index 3ff46c1..e65c7db 100644
--- a/chrome/browser/media/cdm_pref_service_helper.h
+++ b/chrome/browser/media/cdm_pref_service_helper.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_MEDIA_CDM_PREF_SERVICE_HELPER_H_
 #define CHROME_BROWSER_MEDIA_CDM_PREF_SERVICE_HELPER_H_
 
+#include "media/cdm/cdm_preference_data.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/origin.h"
 
 class PrefService;
@@ -24,8 +26,20 @@
 
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
-  static base::UnguessableToken GetCdmOriginId(PrefService* user_prefs,
-                                               const url::Origin& origin);
+  // Gets the CDM preference data associated with the current origin. If no
+  // preference data exist for the current origin, an entry is created with a
+  // new origin id and an empty client token. Returns nullptr if the preference
+  // could not be retrieved.
+  static std::unique_ptr<media::CdmPreferenceData> GetCdmPreferenceData(
+      PrefService* user_prefs,
+      const url::Origin& cdm_origin);
+
+  // Sets the client token for the origin associated with the CDM. The token is
+  // set by the CDM. If no entry exist for the current origin, the client token
+  // will not be saved.
+  static void SetCdmClientToken(PrefService* user_prefs,
+                                const url::Origin& cdm_origin,
+                                const std::vector<uint8_t>& client_token);
 };
 
 #endif  // CHROME_BROWSER_MEDIA_CDM_PREF_SERVICE_HELPER_H_
diff --git a/chrome/browser/media/encrypted_media_browsertest.cc b/chrome/browser/media/encrypted_media_browsertest.cc
index 7cb2f85..bca95b6 100644
--- a/chrome/browser/media/encrypted_media_browsertest.cc
+++ b/chrome/browser/media/encrypted_media_browsertest.cc
@@ -695,7 +695,13 @@
 }
 
 // Only use MSE since this is independent to the demuxer.
-IN_PROC_BROWSER_TEST_P(MseEncryptedMediaTest, RemoveTemporarySession) {
+// Disable the test on Linux and CrOS due to flaky. crbug.com/1232895
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+#define MAYBE_RemoveTemporarySession DISABLED_RemoveTemporarySession
+#else
+#define MAYBE_RemoveTemporarySession RemoveTemporarySession
+#endif
+IN_PROC_BROWSER_TEST_P(MseEncryptedMediaTest, MAYBE_RemoveTemporarySession) {
   if (!IsPlayBackPossible(CurrentKeySystem()))
     GTEST_SKIP() << "RemoveTemporarySession test requires license server.";
 
diff --git a/chrome/browser/media/history/media_history_browsertest.cc b/chrome/browser/media/history/media_history_browsertest.cc
index cf72b37..0028cf48f 100644
--- a/chrome/browser/media/history/media_history_browsertest.cc
+++ b/chrome/browser/media/history/media_history_browsertest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chrome/browser/media/history/media_history_playback_table.h"
 #include "chrome/browser/media/history/media_history_store.h"
 
 #include "base/callback_helpers.h"
@@ -1176,9 +1177,14 @@
             base::Unretained(this))) {
     feature_list_.InitAndEnableFeature(blink::features::kPrerender2);
   }
+
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    MediaHistoryBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     web_contents_ = browser()->tab_strip_model()->GetActiveWebContents();
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     MediaHistoryBrowserTest::SetUpOnMainThread();
   }
 
diff --git a/chrome/browser/media/router/media_router_feature.cc b/chrome/browser/media/router/media_router_feature.cc
index aa6a3df..68c5869 100644
--- a/chrome/browser/media/router/media_router_feature.cc
+++ b/chrome/browser/media/router/media_router_feature.cc
@@ -29,21 +29,12 @@
 #if !defined(OS_ANDROID)
 const base::Feature kMediaRouter{"MediaRouter",
                                  base::FEATURE_ENABLED_BY_DEFAULT};
-// Controls if browser side DialMediaRouteProvider is enabled.
-const base::Feature kDialMediaRouteProvider{"DialMediaRouteProvider",
-                                            base::FEATURE_ENABLED_BY_DEFAULT};
-const base::Feature kCastMediaRouteProvider{"CastMediaRouteProvider",
-                                            base::FEATURE_ENABLED_BY_DEFAULT};
 const base::Feature kCastAllowAllIPsFeature{"CastAllowAllIPs",
                                             base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kGlobalMediaControlsCastStartStop{
     "GlobalMediaControlsCastStartStop", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kAllowAllSitesToInitiateMirroring{
     "AllowAllSitesToInitiateMirroring", base::FEATURE_DISABLED_BY_DEFAULT};
-const base::Feature kCastToMeetingFromCastDialog{
-    "CastToMeetingFromCastDialog", base::FEATURE_DISABLED_BY_DEFAULT};
-const base::Feature kCastFeedbackDialog{"CastFeedbackDialog",
-                                        base::FEATURE_ENABLED_BY_DEFAULT};
 #endif  // !defined(OS_ANDROID)
 
 namespace {
@@ -131,14 +122,6 @@
   return token;
 }
 
-bool DialMediaRouteProviderEnabled() {
-  return base::FeatureList::IsEnabled(kDialMediaRouteProvider);
-}
-
-bool CastMediaRouteProviderEnabled() {
-  return base::FeatureList::IsEnabled(kCastMediaRouteProvider);
-}
-
 bool GlobalMediaControlsCastStartStopEnabled() {
   return base::FeatureList::IsEnabled(kGlobalMediaControlsCastStartStop);
 }
diff --git a/chrome/browser/media/router/media_router_feature.h b/chrome/browser/media/router/media_router_feature.h
index c700420c..a526009 100644
--- a/chrome/browser/media/router/media_router_feature.h
+++ b/chrome/browser/media/router/media_router_feature.h
@@ -32,12 +32,6 @@
 // and triggers a permission prompt.
 extern const base::Feature kMediaRouter;
 
-// TODO(crbug.com/1028753): Remove default-enabled kDialMediaRouteProvider after
-// tests stop disabling it.
-extern const base::Feature kDialMediaRouteProvider;
-
-extern const base::Feature kCastMediaRouteProvider;
-
 // If enabled, allows Media Router to connect to Cast devices on all IP
 // addresses, not just RFC1918/RFC4193 private addresses. Workaround for
 // https://crbug.com/813974.
@@ -50,15 +44,6 @@
 // Presentation API. If disabled, only the allowlisted sites can do so.
 extern const base::Feature kAllowAllSitesToInitiateMirroring;
 
-// If enabled, meetings appear as receivers in the Cast menu.
-extern const base::Feature kCastToMeetingFromCastDialog;
-
-// If enabled, users can submit Cast feedback via the chrome://cast-feedback
-// WebUI.
-// TODO(crbug.com/1173633): Remove this flag now that the feature is enabled by
-// default.
-extern const base::Feature kCastFeedbackDialog;
-
 namespace prefs {
 // Pref name for the enterprise policy for allowing Cast devices on all IPs.
 constexpr char kMediaRouterCastAllowAllIPs[] =
@@ -87,10 +72,6 @@
 // Returns true if browser side DIAL Media Route Provider is enabled.
 bool DialMediaRouteProviderEnabled();
 
-// Returns true if browser side Cast Media Route Provider and sink query are
-// enabled.
-bool CastMediaRouteProviderEnabled();
-
 // Returns true if global media controls are used to start and stop casting.
 bool GlobalMediaControlsCastStartStopEnabled();
 #endif  // !defined(OS_ANDROID)
diff --git a/chrome/browser/media/router/mojo/media_router_desktop.cc b/chrome/browser/media/router/mojo/media_router_desktop.cc
index 39dc133..20c941a4 100644
--- a/chrome/browser/media/router/mojo/media_router_desktop.cc
+++ b/chrome/browser/media/router/mojo/media_router_desktop.cc
@@ -66,8 +66,7 @@
 void MediaRouterDesktop::GetProviderState(
     mojom::MediaRouteProviderId provider_id,
     mojom::MediaRouteProvider::GetStateCallback callback) const {
-  if (provider_id == mojom::MediaRouteProviderId::CAST &&
-      CastMediaRouteProviderEnabled()) {
+  if (provider_id == mojom::MediaRouteProviderId::CAST) {
     media_route_providers_.at(provider_id)->GetState(std::move(callback));
   } else {
     std::move(callback).Run(mojom::ProviderStatePtr());
@@ -145,10 +144,8 @@
   }
 
   InitializeWiredDisplayMediaRouteProvider();
-  if (CastMediaRouteProviderEnabled())
-    InitializeCastMediaRouteProvider();
-  if (DialMediaRouteProviderEnabled())
-    InitializeDialMediaRouteProvider();
+  InitializeCastMediaRouteProvider();
+  InitializeDialMediaRouteProvider();
 }
 
 void MediaRouterDesktop::InitializeWiredDisplayMediaRouteProvider() {
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_impl.cc b/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
index 3065aef2..93a3886 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
+++ b/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
@@ -936,7 +936,7 @@
     int32_t initiator_tab_id,
     const std::string& desktop_stream_id,
     mojo::PendingReceiver<mirroring::mojom::MirroringServiceHost> receiver) {
-  if (!CastMediaRouteProviderEnabled() || !pending_stream_request_ ||
+  if (!pending_stream_request_ ||
       pending_stream_request_->stream_id != desktop_stream_id) {
     return;
   }
diff --git a/chrome/browser/media/router/providers/cast/dual_media_sink_service.cc b/chrome/browser/media/router/providers/cast/dual_media_sink_service.cc
index 1220f2a..90df539 100644
--- a/chrome/browser/media/router/providers/cast/dual_media_sink_service.cc
+++ b/chrome/browser/media/router/providers/cast/dual_media_sink_service.cc
@@ -83,14 +83,11 @@
                           base::Unretained(this), "cast"),
       dial_media_sink_service_->impl());
 
-  if (CastMediaRouteProviderEnabled()) {
-    cast_channel::CastSocketService* cast_socket_service =
-        cast_channel::CastSocketService::GetInstance();
-    cast_app_discovery_service_ = std::make_unique<CastAppDiscoveryServiceImpl>(
-        GetCastMessageHandler(), cast_socket_service,
-        cast_media_sink_service_->impl(),
-        base::DefaultTickClock::GetInstance());
-  }
+  cast_channel::CastSocketService* cast_socket_service =
+      cast_channel::CastSocketService::GetInstance();
+  cast_app_discovery_service_ = std::make_unique<CastAppDiscoveryServiceImpl>(
+      GetCastMessageHandler(), cast_socket_service,
+      cast_media_sink_service_->impl(), base::DefaultTickClock::GetInstance());
 }
 
 DualMediaSinkService::DualMediaSinkService(
@@ -122,8 +119,6 @@
   mojo::PendingRemote<mojom::Logger> dial_pending_remote;
   logger_impl->Bind(dial_pending_remote.InitWithNewPipeAndPassReceiver());
   dial_media_sink_service_->BindLogger(std::move(dial_pending_remote));
-  if (!CastMediaRouteProviderEnabled())
-    return;
   mojo::PendingRemote<mojom::Logger> cast_discovery_pending_remote;
   logger_impl->Bind(
       cast_discovery_pending_remote.InitWithNewPipeAndPassReceiver());
diff --git a/chrome/browser/metrics/oom/out_of_memory_reporter_browsertest.cc b/chrome/browser/metrics/oom/out_of_memory_reporter_browsertest.cc
index 2ba124e..24b86a7 100644
--- a/chrome/browser/metrics/oom/out_of_memory_reporter_browsertest.cc
+++ b/chrome/browser/metrics/oom/out_of_memory_reporter_browsertest.cc
@@ -224,9 +224,12 @@
             &MAYBE_OutOfMemoryReporterPrerenderBrowserTest::web_contents,
             base::Unretained(this))) {}
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    MAYBE_OutOfMemoryReporterBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
-    // embedded_test_server is started on MainThread for the prerender helper.
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     host_resolver()->AddRule("*", "127.0.0.1");
     MAYBE_OutOfMemoryReporterBrowserTest::SetUpOnMainThread();
   }
diff --git a/chrome/browser/metrics/power/power_metrics_reporter.cc b/chrome/browser/metrics/power/power_metrics_reporter.cc
index 84e1352..ee687df9 100644
--- a/chrome/browser/metrics/power/power_metrics_reporter.cc
+++ b/chrome/browser/metrics/power/power_metrics_reporter.cc
@@ -153,7 +153,7 @@
   ReportBatteryHistograms(interval_duration, discharge_mode,
                           discharge_rate_during_interval, suffixes);
 #if defined(OS_MAC)
-  RecordCoalitionData(metrics);
+  RecordCoalitionData(metrics, suffixes);
 #endif
 }
 
diff --git a/chrome/browser/navigation_predictor/navigation_predictor_browsertest.cc b/chrome/browser/navigation_predictor/navigation_predictor_browsertest.cc
index 30448ee..93350828 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor_browsertest.cc
+++ b/chrome/browser/navigation_predictor/navigation_predictor_browsertest.cc
@@ -619,13 +619,17 @@
   NavigationPredictorPrerenderBrowserTest& operator=(
       const NavigationPredictorPrerenderBrowserTest&) = delete;
 
+  void SetUp() override {
+    prerender_test_helper_.SetUp(&test_server_);
+    NavigationPredictorBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     host_resolver()->AddRule("*", "127.0.0.1");
     test_server_.AddDefaultHandlers(GetChromeTestDataDir());
     test_server_.ServeFilesFromSourceDirectory(
         "chrome/test/data/navigation_predictor");
     test_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
-    prerender_test_helper_.SetUpOnMainThread(&test_server_);
     ASSERT_TRUE(test_server_.Start());
   }
 
diff --git a/chrome/browser/navigation_predictor/search_engine_preconnector.cc b/chrome/browser/navigation_predictor/search_engine_preconnector.cc
index c6539213..eed1a7f 100644
--- a/chrome/browser/navigation_predictor/search_engine_preconnector.cc
+++ b/chrome/browser/navigation_predictor/search_engine_preconnector.cc
@@ -98,14 +98,15 @@
   if (!base::GetFieldTrialParamByFeatureAsBool(features::kPreconnectToSearch,
                                                "skip_in_background", false) ||
       is_browser_app_likely_in_foreground) {
+    net::SchemefulSite schemeful_site(preconnect_url);
+    net::NetworkIsolationKey network_isolation_key(schemeful_site,
+                                                   schemeful_site);
     loading_predictor->PreconnectURLIfAllowed(
-        preconnect_url, /*allow_credentials=*/true,
-        net::NetworkIsolationKey(url::Origin::Create(preconnect_url),
-                                 url::Origin::Create(preconnect_url)));
+        preconnect_url, /*allow_credentials=*/true, network_isolation_key);
 
     loading_predictor->PreconnectURLIfAllowed(preconnect_url,
                                               /*allow_credentials=*/false,
-                                              net::NetworkIsolationKey());
+                                              network_isolation_key);
   }
 
   // The delay beyond the idle socket timeout that net uses when
diff --git a/chrome/browser/nearby_sharing/sharing_mojo_service.cc b/chrome/browser/nearby_sharing/sharing_mojo_service.cc
index 214a9d1..3b7c1ae8 100644
--- a/chrome/browser/nearby_sharing/sharing_mojo_service.cc
+++ b/chrome/browser/nearby_sharing/sharing_mojo_service.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/nearby_sharing/sharing_mojo_service.h"
 
-#include "chrome/browser/service_sandbox_type.h"
+#include "chromeos/services/nearby/public/mojom/sharing.mojom.h"
 #include "content/public/browser/service_process_host.h"
 
 namespace sharing {
diff --git a/chrome/browser/net/net_error_tab_helper_browsertest.cc b/chrome/browser/net/net_error_tab_helper_browsertest.cc
index 3438398..199259d 100644
--- a/chrome/browser/net/net_error_tab_helper_browsertest.cc
+++ b/chrome/browser/net/net_error_tab_helper_browsertest.cc
@@ -25,6 +25,11 @@
   NetErrorTabHelperWithPrerenderingTest& operator=(
       const NetErrorTabHelperWithPrerenderingTest&) = delete;
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    InProcessBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     chrome_browser_net::NetErrorTabHelper::set_state_for_testing(
         chrome_browser_net::NetErrorTabHelper::TESTING_DEFAULT);
@@ -35,7 +40,6 @@
               return false;
             }));
 
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     host_resolver()->AddRule("mock.http", "127.0.0.1");
     ASSERT_TRUE(embedded_test_server()->Start());
 
diff --git a/chrome/browser/net/stub_resolver_config_reader.cc b/chrome/browser/net/stub_resolver_config_reader.cc
index dfe6529..fa75cd46 100644
--- a/chrome/browser/net/stub_resolver_config_reader.cc
+++ b/chrome/browser/net/stub_resolver_config_reader.cc
@@ -353,8 +353,15 @@
     parental_controls_checked_ = true;
   }
 
+  bool additional_dns_query_types_enabled =
+      local_state_->GetBoolean(prefs::kAdditionalDnsQueryTypesEnabled);
+
   if (record_metrics) {
     UMA_HISTOGRAM_ENUMERATION("Net.DNS.DnsConfig.SecureDnsMode", mode_details);
+    if (!additional_dns_query_types_enabled || ShouldDisableDohForManaged()) {
+      UMA_HISTOGRAM_BOOLEAN("Net.DNS.DnsConfig.AdditionalDnsQueryTypesEnabled",
+                            additional_dns_query_types_enabled);
+    }
   }
 
   std::string doh_templates =
@@ -390,8 +397,7 @@
   if (update_network_service) {
     content::GetNetworkService()->ConfigureStubHostResolver(
         GetInsecureStubResolverEnabled(), secure_dns_mode,
-        std::move(servers_mojo),
-        local_state_->GetBoolean(prefs::kAdditionalDnsQueryTypesEnabled));
+        std::move(servers_mojo), additional_dns_query_types_enabled);
   }
 
   return SecureDnsConfig(secure_dns_mode, std::move(dns_over_https_servers),
diff --git a/chrome/browser/optimization_guide/blink/blink_optimization_guide_browsertest.cc b/chrome/browser/optimization_guide/blink/blink_optimization_guide_browsertest.cc
index c92deff..ff4286b 100644
--- a/chrome/browser/optimization_guide/blink/blink_optimization_guide_browsertest.cc
+++ b/chrome/browser/optimization_guide/blink/blink_optimization_guide_browsertest.cc
@@ -375,7 +375,12 @@
   }
 
   void SetUpOnMainThread() override {
-    prerender_helper_->SetUpOnMainThread(embedded_test_server());
+    // TODO(crbug.com/846380): move BlinkOptimizationGuideBrowserTest
+    // ScopedFeatureList init to the constructor.  We set up here rather than in
+    // SetUp since the prerender_helper_ is created in SetUpCommandLine to
+    // ensure correct construction/destruction order of the ScopedFeatureList in
+    // the PrerenderTestHelper and the BlinkOptimizationGuideBrowserTest.
+    prerender_helper_->SetUp(embedded_test_server());
     BlinkOptimizationGuideBrowserTest::SetUpOnMainThread();
   }
 
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager.cc b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
index e6bd1cd..2e11f0e3 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
@@ -740,7 +740,8 @@
             BackgroundDownloadServiceFactory::GetForKey(
                 profile_->GetProfileKey()),
             base::ThreadPool::CreateSequencedTaskRunner(
-                {base::MayBlock(), base::TaskPriority::BEST_EFFORT}));
+                {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+                 base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}));
     prediction_model_download_manager_->AddObserver(this);
   }
 
diff --git a/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc
index 4e2e259..065c4b5 100644
--- a/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/prerender_page_load_metrics_observer_browsertest.cc
@@ -27,34 +27,27 @@
   }
   ~PrerenderPageLoadMetricsObserverBrowserTest() override = default;
 
-  void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
-    MetricIntegrationTest::SetUpOnMainThread();
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    MetricIntegrationTest::SetUp();
   }
 
-  int GetUkmMetricEntryCount(const std::string& entry_name,
-                             const std::string& metric_name) {
-    const std::vector<ukm::TestUkmRecorder::HumanReadableUkmMetrics>
-        metric_entries = ukm_recorder().GetMetrics(entry_name, {metric_name});
-    int count = 0;
-    for (const auto& entry : metric_entries) {
-      if (base::Contains(entry, metric_name))
-        count++;
+  // Similar to UkmRecorder::GetMergedEntriesByName(), but returned map is keyed
+  // by source URL.
+  std::map<GURL, ukm::mojom::UkmEntryPtr> GetMergedUkmEntries(
+      const std::string& entry_name) {
+    auto entries =
+        ukm_recorder().GetMergedEntriesByName(PrerenderPageLoad::kEntryName);
+    std::map<GURL, ukm::mojom::UkmEntryPtr> result;
+    for (auto& kv : entries) {
+      const ukm::mojom::UkmEntry* entry = kv.second.get();
+      const ukm::UkmSource* source =
+          ukm_recorder().GetSourceForSourceId(entry->source_id);
+      EXPECT_TRUE(source);
+      EXPECT_TRUE(source->url().is_valid());
+      result.emplace(source->url(), std::move(kv.second));
     }
-    return count;
-  }
-
-  std::vector<int64_t> GetUkmMetricEntryValues(const std::string& entry_name,
-                                               const std::string& metric_name) {
-    const std::vector<ukm::TestUkmRecorder::HumanReadableUkmMetrics>
-        metric_entries = ukm_recorder().GetMetrics(entry_name, {metric_name});
-    std::vector<int64_t> metrics;
-    for (const auto& entry : metric_entries) {
-      auto it = entry.find(metric_name);
-      if (it != entry.end())
-        metrics.push_back(it->second);
-    }
-    return metrics;
+    return result;
   }
 
  protected:
@@ -110,23 +103,41 @@
   histogram_tester().ExpectTotalCount(
       internal::kHistogramPrerenderCumulativeShiftScoreMainFrame, 1);
 
-  ASSERT_THAT(GetUkmMetricEntryValues(PrerenderPageLoad::kEntryName,
-                                      PrerenderPageLoad::kWasPrerenderedName),
-              testing::ElementsAre(1));
-  EXPECT_EQ(GetUkmMetricEntryCount(
-                PrerenderPageLoad::kEntryName,
-                PrerenderPageLoad::kTiming_NavigationToActivationName),
-            1);
-  EXPECT_EQ(
-      GetUkmMetricEntryCount(
-          PrerenderPageLoad::kEntryName,
-          PrerenderPageLoad::kTiming_ActivationToFirstContentfulPaintName),
-      1);
-  EXPECT_EQ(
-      GetUkmMetricEntryCount(
-          PrerenderPageLoad::kEntryName,
-          PrerenderPageLoad::kTiming_ActivationToLargestContentfulPaintName),
-      1);
+  auto entries = GetMergedUkmEntries(PrerenderPageLoad::kEntryName);
+  EXPECT_EQ(2u, entries.size());
+
+  const ukm::mojom::UkmEntry* prerendered_page_entry =
+      entries[prerender_url].get();
+  ASSERT_TRUE(prerendered_page_entry);
+  EXPECT_FALSE(ukm_recorder().EntryHasMetric(
+      prerendered_page_entry, PrerenderPageLoad::kTriggeredPrerenderName));
+  ukm_recorder().ExpectEntryMetric(prerendered_page_entry,
+                                   PrerenderPageLoad::kWasPrerenderedName, 1);
+  EXPECT_TRUE(ukm_recorder().EntryHasMetric(
+      prerendered_page_entry,
+      PrerenderPageLoad::kTiming_NavigationToActivationName));
+  EXPECT_TRUE(ukm_recorder().EntryHasMetric(
+      prerendered_page_entry,
+      PrerenderPageLoad::kTiming_ActivationToFirstContentfulPaintName));
+  EXPECT_TRUE(ukm_recorder().EntryHasMetric(
+      prerendered_page_entry,
+      PrerenderPageLoad::kTiming_ActivationToLargestContentfulPaintName));
+
+  const ukm::mojom::UkmEntry* initiator_page_entry = entries[initial_url].get();
+  ASSERT_TRUE(initiator_page_entry);
+  ukm_recorder().ExpectEntryMetric(
+      initiator_page_entry, PrerenderPageLoad::kTriggeredPrerenderName, 1);
+  EXPECT_FALSE(ukm_recorder().EntryHasMetric(
+      initiator_page_entry, PrerenderPageLoad::kWasPrerenderedName));
+  EXPECT_FALSE(ukm_recorder().EntryHasMetric(
+      initiator_page_entry,
+      PrerenderPageLoad::kTiming_NavigationToActivationName));
+  EXPECT_FALSE(ukm_recorder().EntryHasMetric(
+      initiator_page_entry,
+      PrerenderPageLoad::kTiming_ActivationToFirstContentfulPaintName));
+  EXPECT_FALSE(ukm_recorder().EntryHasMetric(
+      initiator_page_entry,
+      PrerenderPageLoad::kTiming_ActivationToLargestContentfulPaintName));
 }
 
 IN_PROC_BROWSER_TEST_F(PrerenderPageLoadMetricsObserverBrowserTest,
@@ -183,23 +194,16 @@
   histogram_tester().ExpectTotalCount(
       internal::kHistogramPrerenderCumulativeShiftScoreMainFrame, 0);
 
-  ASSERT_THAT(GetUkmMetricEntryValues(PrerenderPageLoad::kEntryName,
-                                      PrerenderPageLoad::kWasPrerenderedName),
-              testing::ElementsAre(1));
-  EXPECT_EQ(GetUkmMetricEntryCount(
-                PrerenderPageLoad::kEntryName,
-                PrerenderPageLoad::kTiming_NavigationToActivationName),
-            1);
-  EXPECT_EQ(
-      GetUkmMetricEntryCount(
-          PrerenderPageLoad::kEntryName,
-          PrerenderPageLoad::kTiming_ActivationToFirstContentfulPaintName),
-      0);
-  EXPECT_EQ(
-      GetUkmMetricEntryCount(
-          PrerenderPageLoad::kEntryName,
-          PrerenderPageLoad::kTiming_ActivationToLargestContentfulPaintName),
-      0);
+  auto entries = GetMergedUkmEntries(PrerenderPageLoad::kEntryName);
+  const ukm::mojom::UkmEntry* entry = entries[prerender_url].get();
+  ASSERT_TRUE(entry);
+  EXPECT_TRUE(ukm_recorder().EntryHasMetric(
+      entry, PrerenderPageLoad::kTiming_NavigationToActivationName));
+  EXPECT_FALSE(ukm_recorder().EntryHasMetric(
+      entry, PrerenderPageLoad::kTiming_ActivationToFirstContentfulPaintName));
+  EXPECT_FALSE(ukm_recorder().EntryHasMetric(
+      entry,
+      PrerenderPageLoad::kTiming_ActivationToLargestContentfulPaintName));
 }
 
 IN_PROC_BROWSER_TEST_F(PrerenderPageLoadMetricsObserverBrowserTest,
@@ -232,21 +236,54 @@
                     .GetTotalCountsForPrefix("PageLoad.Clients.Prerender.")
                     .size());
 
-  EXPECT_EQ(GetUkmMetricEntryCount(PrerenderPageLoad::kEntryName,
-                                   PrerenderPageLoad::kWasPrerenderedName),
-            0);
-  EXPECT_EQ(GetUkmMetricEntryCount(
-                PrerenderPageLoad::kEntryName,
-                PrerenderPageLoad::kTiming_NavigationToActivationName),
-            0);
-  EXPECT_EQ(
-      GetUkmMetricEntryCount(
-          PrerenderPageLoad::kEntryName,
-          PrerenderPageLoad::kTiming_ActivationToFirstContentfulPaintName),
-      0);
-  EXPECT_EQ(
-      GetUkmMetricEntryCount(
-          PrerenderPageLoad::kEntryName,
-          PrerenderPageLoad::kTiming_ActivationToLargestContentfulPaintName),
-      0);
+  auto entries = GetMergedUkmEntries(PrerenderPageLoad::kEntryName);
+  EXPECT_FALSE(base::Contains(entries, prerender_url));
+}
+
+IN_PROC_BROWSER_TEST_F(PrerenderPageLoadMetricsObserverBrowserTest,
+                       Redirection) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  // Navigate to an initial page.
+  auto initial_url = embedded_test_server()->GetURL("/empty.html");
+  ui_test_utils::NavigateToURL(browser(), initial_url);
+
+  // Start a prerender.
+  GURL redirected_url = embedded_test_server()->GetURL("/title2.html");
+  GURL prerender_url = embedded_test_server()->GetURL("/server-redirect?" +
+                                                      redirected_url.spec());
+  prerender_helper_.AddPrerender(prerender_url);
+
+  // Activate and wait for FCP.
+  auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
+      web_contents());
+  waiter->AddPageExpectation(page_load_metrics::PageLoadMetricsTestWaiter::
+                                 TimingField::kFirstContentfulPaint);
+  prerender_helper_.NavigatePrimaryPage(prerender_url);
+  waiter->Wait();
+
+  // Force navigation to another page, which should force logging of histograms
+  // persisted at the end of the page load lifetime.
+  ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
+
+  // Verify that UKM records the URL after the redirection.
+  auto entries = GetMergedUkmEntries(PrerenderPageLoad::kEntryName);
+  ASSERT_FALSE(base::Contains(entries, prerender_url));
+  const ukm::mojom::UkmEntry* prerendered_page_entry =
+      entries[redirected_url].get();
+  ASSERT_TRUE(prerendered_page_entry);
+
+  EXPECT_FALSE(ukm_recorder().EntryHasMetric(
+      prerendered_page_entry, PrerenderPageLoad::kTriggeredPrerenderName));
+  ukm_recorder().ExpectEntryMetric(prerendered_page_entry,
+                                   PrerenderPageLoad::kWasPrerenderedName, 1);
+  EXPECT_TRUE(ukm_recorder().EntryHasMetric(
+      prerendered_page_entry,
+      PrerenderPageLoad::kTiming_NavigationToActivationName));
+  EXPECT_TRUE(ukm_recorder().EntryHasMetric(
+      prerendered_page_entry,
+      PrerenderPageLoad::kTiming_ActivationToFirstContentfulPaintName));
+  EXPECT_TRUE(ukm_recorder().EntryHasMetric(
+      prerendered_page_entry,
+      PrerenderPageLoad::kTiming_ActivationToLargestContentfulPaintName));
 }
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 c390e1c..cae635f 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
@@ -3682,9 +3682,9 @@
     feature_list_.InitAndEnableFeature(blink::features::kPrerender2);
   }
 
-  void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
-    PageLoadMetricsBrowserTest::SetUpOnMainThread();
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    PageLoadMetricsBrowserTest::SetUp();
   }
 
  protected:
diff --git a/chrome/browser/password_manager/password_generation_interactive_uitest.cc b/chrome/browser/password_manager/password_generation_interactive_uitest.cc
index a31bbf36..5279831 100644
--- a/chrome/browser/password_manager/password_generation_interactive_uitest.cc
+++ b/chrome/browser/password_manager/password_generation_interactive_uitest.cc
@@ -397,9 +397,9 @@
             base::Unretained(this))) {}
   ~PasswordGenerationPopupViewPrerenderingTest() override = default;
 
-  void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
-    PasswordGenerationInteractiveTest::SetUpOnMainThread();
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    PasswordGenerationInteractiveTest::SetUp();
   }
 
   content::test::PrerenderTestHelper* prerender_helper() {
diff --git a/chrome/browser/password_manager/password_manager_browsertest.cc b/chrome/browser/password_manager/password_manager_browsertest.cc
index 5972a81a..32000e7 100644
--- a/chrome/browser/password_manager/password_manager_browsertest.cc
+++ b/chrome/browser/password_manager/password_manager_browsertest.cc
@@ -4431,9 +4431,12 @@
             base::Unretained(this))) {}
   ~PasswordManagerPrerenderBrowserTest() override = default;
 
-  void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    PasswordManagerBrowserTest::SetUp();
+  }
 
+  void SetUpOnMainThread() override {
     // Register requests handler before the server is started.
     embedded_test_server()->RegisterRequestHandler(
         base::BindRepeating(&HandleTestAuthRequest));
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index f1339ce..06c47d5 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -1239,8 +1239,7 @@
 }
 
 // TODO(crbug.com/1004425): Should be allowed?
-IN_PROC_BROWSER_TEST_F(PDFExtensionTestWithoutUnseasonedOverride,
-                       EnsureOpaqueOriginRepliesBlocked) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionTest, EnsureOpaqueOriginRepliesBlocked) {
   TestGetSelectedTextReply(
       embedded_test_server()->GetURL("/pdf/data_url_rectangles.html"), false);
 }
@@ -2242,14 +2241,17 @@
   void DoActionAndCheckClipboard(base::OnceClosure action,
                                  ui::ClipboardBuffer clipboard_buffer,
                                  const std::string& expected) {
+    ASSERT_FALSE(clipboard_quit_closure_);
+
     ui::ClipboardMonitor::GetInstance()->AddObserver(this);
-    DCHECK(!clipboard_changed_);
-    DCHECK(!clipboard_quit_closure_);
+    EXPECT_FALSE(clipboard_changed_);
+    clipboard_changed_ = false;
 
     base::RunLoop run_loop;
     clipboard_quit_closure_ = run_loop.QuitClosure();
     std::move(action).Run();
     run_loop.Run();
+    EXPECT_FALSE(clipboard_quit_closure_);
 
     EXPECT_TRUE(clipboard_changed_);
     clipboard_changed_ = false;
@@ -3407,7 +3409,7 @@
   }
 
   void SetUpOnMainThread() override {
-    prerender_helper_->SetUpOnMainThread(embedded_test_server());
+    prerender_helper_->SetUp(embedded_test_server());
     PDFExtensionTest::SetUpOnMainThread();
   }
 
diff --git a/chrome/browser/performance_monitor/process_metrics_recorder_util.cc b/chrome/browser/performance_monitor/process_metrics_recorder_util.cc
index b276c4e..feeda6a 100644
--- a/chrome/browser/performance_monitor/process_metrics_recorder_util.cc
+++ b/chrome/browser/performance_monitor/process_metrics_recorder_util.cc
@@ -57,101 +57,117 @@
 }
 
 #if defined(OS_MAC)
-void RecordCoalitionData(const ProcessMonitor::Metrics& metrics) {
+void RecordCoalitionData(const ProcessMonitor::Metrics& metrics,
+                         const std::vector<const char*>& suffixes) {
   if (!metrics.coalition_data.has_value())
     return;
 
+  // Calling this function with an empty suffix list is probably a mistake.
+  DCHECK(!suffixes.empty());
+
   // TODO(crbug.com/1229884): Review the units and buckets once we have
   // sufficient data from the field.
 
-  // TODO(sebmarchand): Record with some scenario suffixes once the data have
-  // been proven to be reliable.
+  for (const char* scenario_suffix : suffixes) {
+    // Suffixes are expected to be empty or starting by a period.
+    DCHECK(::strlen(scenario_suffix) == 0U || scenario_suffix[0] == '.');
+    // Report the CPU and GPU time histogram with the same approach as the one
+    // used for the |AverageCPU2| histograms.
 
-  // Report the CPU and GPU time histogram with the same approach as the one
-  // used for the |AverageCPU2| histograms.
+    // Used to change the percentage scale from [0,1] to [0,100], e.g. 0.111
+    // 11.1% will be multiplied by 100 to be 11.1. This is necessary to reuse
+    // the same constants as for the |AverageCPU2| histograms.
+    constexpr int kPercentScaleFactor = 100;
 
-  // Used to change the percentage scale from [0,1] to [0,100], e.g. 0.111
-  // 11.1% will be multiplied by 100 to be 11.1. This is necessary to reuse the
-  // same constants as for the |AverageCPU2| histograms.
-  constexpr int kPercentScaleFactor = 100;
-
-  base::UmaHistogramCustomCounts(
-      "PerformanceMonitor.ResourceCoalition.CPUTime2",
-      metrics.coalition_data->cpu_time_per_second * kPercentScaleFactor *
-          kCPUUsageFactor,
-      kCPUUsageHistogramMin, kCPUUsageHistogramMax,
-      kCPUUsageHistogramBucketCount);
-  // The GPU usage should always be <= 100% so use a lower value for the
-  // histogram max.
-  // TODO(sebmarchand): Confirm this from the data.
-  base::UmaHistogramCustomCounts(
-      "PerformanceMonitor.ResourceCoalition.GPUTime2",
-      metrics.coalition_data->gpu_time_per_second * kPercentScaleFactor *
-          kCPUUsageFactor,
-      kCPUUsageHistogramMin, 100 * kCPUUsageFactor,
-      kCPUUsageHistogramBucketCount);
-
-  // Report the metrics based on a count (e.g. wakeups) with a millievent/sec
-  // granularity. In theory it doesn't make much sense to talk about a
-  // milliwakeups but the wakeup rate should ideally be lower than one per
-  // second in some scenarios and this will provide more granularity.
-  constexpr int kMilliFactor = 1000;
-  auto scale_sample = [](double sample) -> int {
-    // Round the sample to the nearest integer value.
-    return sample * kMilliFactor + 0.5;
-  };
-  base::UmaHistogramCounts1M(
-      "PerformanceMonitor.ResourceCoalition.InterruptWakeupsPerSecond",
-      scale_sample(metrics.coalition_data->interrupt_wakeups_per_second));
-  base::UmaHistogramCounts1M(
-      "PerformanceMonitor.ResourceCoalition.PlatformIdleWakeupsPerSecond",
-      scale_sample(metrics.coalition_data->platform_idle_wakeups_per_second));
-  base::UmaHistogramCounts10M(
-      "PerformanceMonitor.ResourceCoalition.BytesReadPerSecond",
-      scale_sample(metrics.coalition_data->bytesread_per_second));
-  base::UmaHistogramCounts10M(
-      "PerformanceMonitor.ResourceCoalition.BytesWrittenPerSecond",
-      scale_sample(metrics.coalition_data->byteswritten_per_second));
-
-  constexpr int kNanoWattToMilliWatt = 1000 * 1000;
-  // Use a maximum of 100 watts, or 100 * 1000 milliwatts.
-  base::UmaHistogramCounts100000(
-      "PerformanceMonitor.ResourceCoalition.Power",
-      metrics.coalition_data->power_nw / kNanoWattToMilliWatt + 0.5);
-
-  for (int i = 0;
-       i < static_cast<int>(ResourceCoalition::QoSLevels::kMaxValue) + 1; ++i) {
-    const char* suffix = nullptr;
-    switch (static_cast<ResourceCoalition::QoSLevels>(i)) {
-      case ResourceCoalition::QoSLevels::kDefault:
-        suffix = "Default";
-        break;
-      case ResourceCoalition::QoSLevels::kMaintenance:
-        suffix = "Maintenance";
-        break;
-      case ResourceCoalition::QoSLevels::kBackground:
-        suffix = "Background";
-        break;
-      case ResourceCoalition::QoSLevels::kUtility:
-        suffix = "Utility";
-        break;
-      case ResourceCoalition::QoSLevels::kLegacy:
-        suffix = "Legacy";
-        break;
-      case ResourceCoalition::QoSLevels::kUserInitiated:
-        suffix = "UserInitiated";
-        break;
-      case ResourceCoalition::QoSLevels::kUserInteractive:
-        suffix = "UserInteractive";
-        break;
-    }
     base::UmaHistogramCustomCounts(
         base::StrCat(
-            {"PerformanceMonitor.ResourceCoalition.QoSLevel.", suffix}),
-        metrics.coalition_data->qos_time_per_second[i] * kPercentScaleFactor *
+            {"PerformanceMonitor.ResourceCoalition.CPUTime2", scenario_suffix}),
+        metrics.coalition_data->cpu_time_per_second * kPercentScaleFactor *
             kCPUUsageFactor,
-        kCPUUsageHistogramMin, 100 * kCPUUsageFactor * 1000,
+        kCPUUsageHistogramMin, kCPUUsageHistogramMax,
         kCPUUsageHistogramBucketCount);
+    // The GPU usage should always be <= 100% so use a lower value for the
+    // histogram max.
+    // TODO(sebmarchand): Confirm this from the data.
+    base::UmaHistogramCustomCounts(
+        base::StrCat(
+            {"PerformanceMonitor.ResourceCoalition.GPUTime2", scenario_suffix}),
+        metrics.coalition_data->gpu_time_per_second * kPercentScaleFactor *
+            kCPUUsageFactor,
+        kCPUUsageHistogramMin, 100 * kCPUUsageFactor,
+        kCPUUsageHistogramBucketCount);
+
+    // Report the metrics based on a count (e.g. wakeups) with a millievent/sec
+    // granularity. In theory it doesn't make much sense to talk about a
+    // milliwakeups but the wakeup rate should ideally be lower than one per
+    // second in some scenarios and this will provide more granularity.
+    constexpr int kMilliFactor = 1000;
+    auto scale_sample = [](double sample) -> int {
+      // Round the sample to the nearest integer value.
+      return sample * kMilliFactor + 0.5;
+    };
+    base::UmaHistogramCounts1M(
+        base::StrCat(
+            {"PerformanceMonitor.ResourceCoalition.InterruptWakeupsPerSecond",
+             scenario_suffix}),
+        scale_sample(metrics.coalition_data->interrupt_wakeups_per_second));
+    base::UmaHistogramCounts1M(
+        base::StrCat({"PerformanceMonitor.ResourceCoalition."
+                      "PlatformIdleWakeupsPerSecond",
+                      scenario_suffix}),
+        scale_sample(metrics.coalition_data->platform_idle_wakeups_per_second));
+    base::UmaHistogramCounts10M(
+        base::StrCat({"PerformanceMonitor.ResourceCoalition.BytesReadPerSecond",
+                      scenario_suffix}),
+        scale_sample(metrics.coalition_data->bytesread_per_second));
+    base::UmaHistogramCounts10M(
+        base::StrCat(
+            {"PerformanceMonitor.ResourceCoalition.BytesWrittenPerSecond",
+             scenario_suffix}),
+        scale_sample(metrics.coalition_data->byteswritten_per_second));
+
+    constexpr int kNanoWattToMilliWatt = 1000 * 1000;
+    // Use a maximum of 100 watts, or 100 * 1000 milliwatts.
+    base::UmaHistogramCounts100000(
+        base::StrCat(
+            {"PerformanceMonitor.ResourceCoalition.Power", scenario_suffix}),
+        metrics.coalition_data->power_nw / kNanoWattToMilliWatt + 0.5);
+
+    for (int i = 0;
+         i < static_cast<int>(ResourceCoalition::QoSLevels::kMaxValue) + 1;
+         ++i) {
+      const char* qos_suffix = nullptr;
+      switch (static_cast<ResourceCoalition::QoSLevels>(i)) {
+        case ResourceCoalition::QoSLevels::kDefault:
+          qos_suffix = "Default";
+          break;
+        case ResourceCoalition::QoSLevels::kMaintenance:
+          qos_suffix = "Maintenance";
+          break;
+        case ResourceCoalition::QoSLevels::kBackground:
+          qos_suffix = "Background";
+          break;
+        case ResourceCoalition::QoSLevels::kUtility:
+          qos_suffix = "Utility";
+          break;
+        case ResourceCoalition::QoSLevels::kLegacy:
+          qos_suffix = "Legacy";
+          break;
+        case ResourceCoalition::QoSLevels::kUserInitiated:
+          qos_suffix = "UserInitiated";
+          break;
+        case ResourceCoalition::QoSLevels::kUserInteractive:
+          qos_suffix = "UserInteractive";
+          break;
+      }
+      base::UmaHistogramCustomCounts(
+          base::StrCat({"PerformanceMonitor.ResourceCoalition.QoSLevel.",
+                        qos_suffix, scenario_suffix}),
+          metrics.coalition_data->qos_time_per_second[i] * kPercentScaleFactor *
+              kCPUUsageFactor,
+          kCPUUsageHistogramMin, 100 * kCPUUsageFactor * 1000,
+          kCPUUsageHistogramBucketCount);
+    }
   }
 }
 #endif
diff --git a/chrome/browser/performance_monitor/process_metrics_recorder_util.h b/chrome/browser/performance_monitor/process_metrics_recorder_util.h
index d17941b..938c17d 100644
--- a/chrome/browser/performance_monitor/process_metrics_recorder_util.h
+++ b/chrome/browser/performance_monitor/process_metrics_recorder_util.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_PERFORMANCE_MONITOR_PROCESS_METRICS_RECORDER_UTIL_H_
 #define CHROME_BROWSER_PERFORMANCE_MONITOR_PROCESS_METRICS_RECORDER_UTIL_H_
 
+#include <vector>
+
 #include "build/build_config.h"
 #include "chrome/browser/performance_monitor/process_monitor.h"
 
@@ -16,7 +18,8 @@
                              const ProcessMonitor::Metrics& metrics);
 
 #if defined(OS_MAC)
-void RecordCoalitionData(const ProcessMonitor::Metrics& metrics);
+void RecordCoalitionData(const ProcessMonitor::Metrics& metrics,
+                         const std::vector<const char*>& suffixes);
 #endif
 
 }  // namespace performance_monitor
diff --git a/chrome/browser/performance_monitor/process_metrics_recorder_util_unittest.cc b/chrome/browser/performance_monitor/process_metrics_recorder_util_unittest.cc
index a554485..eef7fff 100644
--- a/chrome/browser/performance_monitor/process_metrics_recorder_util_unittest.cc
+++ b/chrome/browser/performance_monitor/process_metrics_recorder_util_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/performance_monitor/process_metrics_recorder_util.h"
 
+#include "base/strings/strcat.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "build/build_config.h"
 #include "chrome/browser/performance_monitor/resource_coalition_mac.h"
@@ -31,59 +32,84 @@
   }
 
   metrics.coalition_data = coalition_data;
-  RecordCoalitionData(metrics);
 
-  // These histograms reports the CPU/GPU times as a percentage of time with a
-  // permyriad granularity, 10% (0.1) will be represented as 1000.
-  histogram_tester.ExpectUniqueSample(
-      "PerformanceMonitor.ResourceCoalition.CPUTime2",
-      coalition_data.cpu_time_per_second * 10000, 1);
-  histogram_tester.ExpectUniqueSample(
-      "PerformanceMonitor.ResourceCoalition.GPUTime2",
-      coalition_data.gpu_time_per_second * 10000, 1);
+  std::vector<const char*> suffixes = {"", ".Foo", ".Bar"};
+  RecordCoalitionData(metrics, suffixes);
 
-  // These histograms report counts with a millievent/second granularity.
-  histogram_tester.ExpectUniqueSample(
-      "PerformanceMonitor.ResourceCoalition.InterruptWakeupsPerSecond",
-      coalition_data.interrupt_wakeups_per_second * 1000, 1);
-  histogram_tester.ExpectUniqueSample(
-      "PerformanceMonitor.ResourceCoalition.PlatformIdleWakeupsPerSecond",
-      coalition_data.platform_idle_wakeups_per_second * 1000, 1);
-  histogram_tester.ExpectUniqueSample(
-      "PerformanceMonitor.ResourceCoalition.BytesReadPerSecond",
-      coalition_data.bytesread_per_second * 1000, 1);
-  histogram_tester.ExpectUniqueSample(
-      "PerformanceMonitor.ResourceCoalition.BytesWrittenPerSecond",
-      coalition_data.byteswritten_per_second * 1000, 1);
-  // Power is reported in milliwatts (mj/s), the data is in nj/s so it has to
-  // be divided by 1000000.
-  histogram_tester.ExpectUniqueSample(
-      "PerformanceMonitor.ResourceCoalition.Power",
-      coalition_data.power_nw / 1000000, 1);
+  for (const char* scenario_suffix : suffixes) {
+    // These histograms reports the CPU/GPU times as a percentage of time with a
+    // permyriad granularity, 10% (0.1) will be represented as 1000.
+    histogram_tester.ExpectUniqueSample(
+        base::StrCat(
+            {"PerformanceMonitor.ResourceCoalition.CPUTime2", scenario_suffix}),
+        coalition_data.cpu_time_per_second * 10000, 1);
+    histogram_tester.ExpectUniqueSample(
+        base::StrCat(
+            {"PerformanceMonitor.ResourceCoalition.GPUTime2", scenario_suffix}),
+        coalition_data.gpu_time_per_second * 10000, 1);
 
-  // The QoS histograms also reports the CPU times as a percentage of time with
-  // a permyriad granularity.
-  histogram_tester.ExpectUniqueSample(
-      "PerformanceMonitor.ResourceCoalition.QoSLevel.Default",
-      coalition_data.qos_time_per_second[0] * 10000, 1);
-  histogram_tester.ExpectUniqueSample(
-      "PerformanceMonitor.ResourceCoalition.QoSLevel.Maintenance",
-      coalition_data.qos_time_per_second[1] * 10000, 1);
-  histogram_tester.ExpectUniqueSample(
-      "PerformanceMonitor.ResourceCoalition.QoSLevel.Background",
-      coalition_data.qos_time_per_second[2] * 10000, 1);
-  histogram_tester.ExpectUniqueSample(
-      "PerformanceMonitor.ResourceCoalition.QoSLevel.Utility",
-      coalition_data.qos_time_per_second[3] * 10000, 1);
-  histogram_tester.ExpectUniqueSample(
-      "PerformanceMonitor.ResourceCoalition.QoSLevel.Legacy",
-      coalition_data.qos_time_per_second[4] * 10000, 1);
-  histogram_tester.ExpectUniqueSample(
-      "PerformanceMonitor.ResourceCoalition.QoSLevel.UserInitiated",
-      coalition_data.qos_time_per_second[5] * 10000, 1);
-  histogram_tester.ExpectUniqueSample(
-      "PerformanceMonitor.ResourceCoalition.QoSLevel.UserInteractive",
-      coalition_data.qos_time_per_second[6] * 10000, 1);
+    // These histograms report counts with a millievent/second granularity.
+    histogram_tester.ExpectUniqueSample(
+        base::StrCat(
+            {"PerformanceMonitor.ResourceCoalition.InterruptWakeupsPerSecond",
+             scenario_suffix}),
+        coalition_data.interrupt_wakeups_per_second * 1000, 1);
+    histogram_tester.ExpectUniqueSample(
+        base::StrCat({"PerformanceMonitor.ResourceCoalition."
+                      "PlatformIdleWakeupsPerSecond",
+                      scenario_suffix}),
+        coalition_data.platform_idle_wakeups_per_second * 1000, 1);
+    histogram_tester.ExpectUniqueSample(
+        base::StrCat({"PerformanceMonitor.ResourceCoalition.BytesReadPerSecond",
+                      scenario_suffix}),
+        coalition_data.bytesread_per_second * 1000, 1);
+    histogram_tester.ExpectUniqueSample(
+        base::StrCat(
+            {"PerformanceMonitor.ResourceCoalition.BytesWrittenPerSecond",
+             scenario_suffix}),
+        coalition_data.byteswritten_per_second * 1000, 1);
+    // Power is reported in milliwatts (mj/s), the data is in nj/s so it has to
+    // be divided by 1000000.
+    histogram_tester.ExpectUniqueSample(
+        base::StrCat(
+            {"PerformanceMonitor.ResourceCoalition.Power", scenario_suffix}),
+        coalition_data.power_nw / 1000000, 1);
+
+    // The QoS histograms also reports the CPU times as a percentage of time
+    // with a permyriad granularity.
+    histogram_tester.ExpectUniqueSample(
+        base::StrCat({"PerformanceMonitor.ResourceCoalition.QoSLevel.Default",
+                      scenario_suffix}),
+        coalition_data.qos_time_per_second[0] * 10000, 1);
+    histogram_tester.ExpectUniqueSample(
+        base::StrCat(
+            {"PerformanceMonitor.ResourceCoalition.QoSLevel.Maintenance",
+             scenario_suffix}),
+        coalition_data.qos_time_per_second[1] * 10000, 1);
+    histogram_tester.ExpectUniqueSample(
+        base::StrCat(
+            {"PerformanceMonitor.ResourceCoalition.QoSLevel.Background",
+             scenario_suffix}),
+        coalition_data.qos_time_per_second[2] * 10000, 1);
+    histogram_tester.ExpectUniqueSample(
+        base::StrCat({"PerformanceMonitor.ResourceCoalition.QoSLevel.Utility",
+                      scenario_suffix}),
+        coalition_data.qos_time_per_second[3] * 10000, 1);
+    histogram_tester.ExpectUniqueSample(
+        base::StrCat({"PerformanceMonitor.ResourceCoalition.QoSLevel.Legacy",
+                      scenario_suffix}),
+        coalition_data.qos_time_per_second[4] * 10000, 1);
+    histogram_tester.ExpectUniqueSample(
+        base::StrCat(
+            {"PerformanceMonitor.ResourceCoalition.QoSLevel.UserInitiated",
+             scenario_suffix}),
+        coalition_data.qos_time_per_second[5] * 10000, 1);
+    histogram_tester.ExpectUniqueSample(
+        base::StrCat(
+            {"PerformanceMonitor.ResourceCoalition.QoSLevel.UserInteractive",
+             scenario_suffix}),
+        coalition_data.qos_time_per_second[6] * 10000, 1);
+  }
 }
 #endif
 
diff --git a/chrome/browser/permissions/permission_request_manager_browsertest.cc b/chrome/browser/permissions/permission_request_manager_browsertest.cc
index cc57fe5..c8ca4e8c 100644
--- a/chrome/browser/permissions/permission_request_manager_browsertest.cc
+++ b/chrome/browser/permissions/permission_request_manager_browsertest.cc
@@ -237,9 +237,13 @@
             &PermissionRequestManagerWithPrerenderingTest::GetWebContents,
             base::Unretained(this))) {}
 
+  void SetUp() override {
+    prerender_test_helper_.SetUp(embedded_test_server());
+    PermissionRequestManagerBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     PermissionRequestManagerBrowserTest::SetUpOnMainThread();
-    prerender_test_helper_.SetUpOnMainThread(embedded_test_server());
     ASSERT_TRUE(embedded_test_server()->Start());
   }
 
diff --git a/chrome/browser/plugins/pdf_iframe_navigation_throttle_browsertest.cc b/chrome/browser/plugins/pdf_iframe_navigation_throttle_browsertest.cc
index c72aba6..cb2ffdfa 100644
--- a/chrome/browser/plugins/pdf_iframe_navigation_throttle_browsertest.cc
+++ b/chrome/browser/plugins/pdf_iframe_navigation_throttle_browsertest.cc
@@ -56,10 +56,14 @@
     return browser()->tab_strip_model()->GetActiveWebContents();
   }
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    InProcessBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     InProcessBrowserTest::SetUpOnMainThread();
     host_resolver()->AddRule("*", "127.0.0.1");
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     ASSERT_TRUE(embedded_test_server()->Start());
 
     content::PluginService* plugin_service =
diff --git a/chrome/browser/policy/messaging_layer/public/report_client.cc b/chrome/browser/policy/messaging_layer/public/report_client.cc
index 5859220..dbf12a6 100644
--- a/chrome/browser/policy/messaging_layer/public/report_client.cc
+++ b/chrome/browser/policy/messaging_layer/public/report_client.cc
@@ -385,6 +385,11 @@
   return ReportQueueImpl::Create(std::move(config), storage_);
 }
 
+StatusOr<std::unique_ptr<ReportQueue, base::OnTaskRunnerDeleter>>
+ReportingClient::CreateNewSpeculativeQueue() {
+  return SpeculativeReportQueueImpl::Create();
+}
+
 // static
 void ReportingClient::AsyncStartUploader(
     bool need_encryption_key,
diff --git a/chrome/browser/policy/messaging_layer/public/report_client.h b/chrome/browser/policy/messaging_layer/public/report_client.h
index 44f447a..2849a4c1 100644
--- a/chrome/browser/policy/messaging_layer/public/report_client.h
+++ b/chrome/browser/policy/messaging_layer/public/report_client.h
@@ -87,6 +87,9 @@
   StatusOr<std::unique_ptr<ReportQueue>> CreateNewQueue(
       std::unique_ptr<ReportQueueConfiguration> config) override;
 
+  StatusOr<std::unique_ptr<ReportQueue, base::OnTaskRunnerDeleter>>
+  CreateNewSpeculativeQueue() override;
+
   // RAII class for testing ReportingClient - substitutes reporting files
   // location, signature verification public key and a cloud policy client
   // builder to return given client. Resets client when destructed.
diff --git a/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc b/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc
index c199489..7e67cea 100644
--- a/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/public/report_client_unittest.cc
@@ -157,7 +157,7 @@
   std::unique_ptr<ReportQueue> CreateQueue() {
     auto config_result = ReportQueueConfiguration::Create(
         dm_token_, destination_, policy_checker_callback_);
-    EXPECT_TRUE(config_result.ok());
+    EXPECT_TRUE(config_result.ok()) << config_result.status();
 
     test::TestEvent<StatusOr<std::unique_ptr<ReportQueue>>> create_queue_event;
     ReportQueueProvider::CreateQueue(std::move(config_result.ValueOrDie()),
@@ -172,8 +172,80 @@
     return report_queue;
   }
 
+  std::unique_ptr<ReportQueue, base::OnTaskRunnerDeleter>
+  CreateSpeculativeQueue() {
+    auto config_result = ReportQueueConfiguration::Create(
+        dm_token_, destination_, policy_checker_callback_);
+    EXPECT_TRUE(config_result.ok()) << config_result.status();
+
+    auto speculative_queue_result = ReportQueueProvider::CreateSpeculativeQueue(
+        std::move(config_result.ValueOrDie()));
+    EXPECT_TRUE(speculative_queue_result.ok())
+        << speculative_queue_result.status();
+    return std::move(speculative_queue_result.ValueOrDie());
+  }
+
   bool is_encryption_enabled() const { return GetParam(); }
 
+  auto GetEncryptionKeyInvocation() {
+    return [this](base::Value payload,
+                  policy::CloudPolicyClient::ResponseCallback done_cb) {
+      absl::optional<bool> const attach_encryption_settings =
+          payload.FindBoolKey("attachEncryptionSettings");
+      ASSERT_TRUE(attach_encryption_settings.has_value());
+      ASSERT_TRUE(attach_encryption_settings.value());  // If set, must be true.
+      ASSERT_TRUE(is_encryption_enabled());
+
+      base::Value encryption_settings{base::Value::Type::DICTIONARY};
+      std::string public_key;
+      base::Base64Encode(signed_encryption_key_.public_asymmetric_key(),
+                         &public_key);
+      encryption_settings.SetStringKey("publicKey", public_key);
+      encryption_settings.SetIntKey("publicKeyId",
+                                    signed_encryption_key_.public_key_id());
+      std::string public_key_signature;
+      base::Base64Encode(signed_encryption_key_.signature(),
+                         &public_key_signature);
+      encryption_settings.SetStringKey("publicKeySignature",
+                                       public_key_signature);
+      base::Value response{base::Value::Type::DICTIONARY};
+      response.SetPath("encryptionSettings", std::move(encryption_settings));
+      std::move(done_cb).Run(std::move(response));
+    };
+  }
+
+  auto GetVerifyDataInvocation() {
+    return [this](base::Value payload,
+                  policy::CloudPolicyClient::ResponseCallback done_cb) {
+      base::Value* const records = payload.FindListKey("encryptedRecord");
+      ASSERT_THAT(records, Ne(nullptr));
+      base::Value::ListView records_list = records->GetList();
+      ASSERT_THAT(records_list, SizeIs(1));
+      base::Value& record = records_list[0];
+      if (is_encryption_enabled()) {
+        const base::Value* const enctyption_info =
+            record.FindDictKey("encryptionInfo");
+        ASSERT_THAT(enctyption_info, Ne(nullptr));
+        const std::string* const encryption_key =
+            enctyption_info->FindStringKey("encryptionKey");
+        ASSERT_THAT(encryption_key, Ne(nullptr));
+        const std::string* const public_key_id =
+            enctyption_info->FindStringKey("publicKeyId");
+        ASSERT_THAT(public_key_id, Ne(nullptr));
+        int64_t key_id;
+        ASSERT_TRUE(base::StringToInt64(*public_key_id, &key_id));
+        EXPECT_THAT(key_id, Eq(signed_encryption_key_.public_key_id()));
+      } else {
+        ASSERT_THAT(record.FindKey("encryptionInfo"), Eq(nullptr));
+      }
+      base::Value* const seq_info = record.FindDictKey("sequenceInformation");
+      ASSERT_THAT(seq_info, Ne(nullptr));
+      base::Value response{base::Value::Type::DICTIONARY};
+      response.SetPath("lastSucceedUploadedRecord", std::move(*seq_info));
+      std::move(done_cb).Run(std::move(response));
+    };
+  }
+
   base::test::ScopedFeatureList scoped_feature_list_;
   // BrowserTaskEnvironment must be instantiated before other classes that posts
   // tasks.
@@ -235,33 +307,7 @@
 
     // Uploader is available, let it set the key.
     EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
-        .WillOnce(WithArgs<0, 2>(
-            Invoke([this](base::Value payload,
-                          policy::CloudPolicyClient::ResponseCallback done_cb) {
-              absl::optional<bool> const attach_encryption_settings =
-                  payload.FindBoolKey("attachEncryptionSettings");
-              ASSERT_TRUE(attach_encryption_settings.has_value());
-              ASSERT_TRUE(
-                  attach_encryption_settings.value());  // If set, must be true.
-              ASSERT_TRUE(is_encryption_enabled());
-
-              base::Value encryption_settings{base::Value::Type::DICTIONARY};
-              std::string public_key;
-              base::Base64Encode(signed_encryption_key_.public_asymmetric_key(),
-                                 &public_key);
-              encryption_settings.SetStringKey("publicKey", public_key);
-              encryption_settings.SetIntKey(
-                  "publicKeyId", signed_encryption_key_.public_key_id());
-              std::string public_key_signature;
-              base::Base64Encode(signed_encryption_key_.signature(),
-                                 &public_key_signature);
-              encryption_settings.SetStringKey("publicKeySignature",
-                                               public_key_signature);
-              base::Value response{base::Value::Type::DICTIONARY};
-              response.SetPath("encryptionSettings",
-                               std::move(encryption_settings));
-              std::move(done_cb).Run(std::move(response));
-            })))
+        .WillOnce(WithArgs<0, 2>(Invoke(GetEncryptionKeyInvocation())))
         .RetiresOnSaturation();
   }
 
@@ -273,39 +319,47 @@
   if (StorageSelector::is_uploader_required() &&
       !StorageSelector::is_use_missive()) {
     EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
-        .WillOnce(WithArgs<0, 2>(
-            Invoke([this](base::Value payload,
-                          policy::CloudPolicyClient::ResponseCallback done_cb) {
-              base::Value* const records =
-                  payload.FindListKey("encryptedRecord");
-              ASSERT_THAT(records, Ne(nullptr));
-              base::Value::ListView records_list = records->GetList();
-              ASSERT_THAT(records_list, SizeIs(1));
-              base::Value& record = records_list[0];
-              if (is_encryption_enabled()) {
-                const base::Value* const enctyption_info =
-                    record.FindDictKey("encryptionInfo");
-                ASSERT_THAT(enctyption_info, Ne(nullptr));
-                const std::string* const encryption_key =
-                    enctyption_info->FindStringKey("encryptionKey");
-                ASSERT_THAT(encryption_key, Ne(nullptr));
-                const std::string* const public_key_id =
-                    enctyption_info->FindStringKey("publicKeyId");
-                ASSERT_THAT(public_key_id, Ne(nullptr));
-                int64_t key_id;
-                ASSERT_TRUE(base::StringToInt64(*public_key_id, &key_id));
-                EXPECT_THAT(key_id, Eq(signed_encryption_key_.public_key_id()));
-              } else {
-                ASSERT_THAT(record.FindKey("encryptionInfo"), Eq(nullptr));
-              }
-              base::Value* const seq_info =
-                  record.FindDictKey("sequenceInformation");
-              ASSERT_THAT(seq_info, Ne(nullptr));
-              base::Value response{base::Value::Type::DICTIONARY};
-              response.SetPath("lastSucceedUploadedRecord",
-                               std::move(*seq_info));
-              std::move(done_cb).Run(std::move(response));
-            })));
+        .WillOnce(WithArgs<0, 2>(Invoke(GetVerifyDataInvocation())));
+  }
+
+  // Trigger upload.
+  task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+}
+
+// Creates speculative queue, enqueues message and verifies it is uploaded
+// eventually.
+TEST_P(ReportClientTest, SpeculativelyEnqueueMessageAndUpload) {
+  // Create queue.
+  auto report_queue = CreateSpeculativeQueue();
+
+  // Enqueue event.
+  if (is_encryption_enabled()) {
+    if (!StorageSelector::is_uploader_required() ||
+        StorageSelector::is_use_missive()) {
+      // Uploader is not available, cannot bring in the key.
+      // Abort the test with no action.
+      return;
+    }
+  }
+
+  // Enqueue event right away, before attaching an actual queue.
+  test::TestEvent<Status> enqueue_record_event;
+  report_queue->Enqueue("Record", FAST_BATCH, enqueue_record_event.cb());
+  const auto enqueue_record_result = enqueue_record_event.result();
+  EXPECT_OK(enqueue_record_result) << enqueue_record_result;
+
+  if (StorageSelector::is_uploader_required() &&
+      !StorageSelector::is_use_missive()) {
+    // Note: there does not seem to be another way to define the expectations
+    // A+B for encrypted case and just B for non-encrypted.g
+    if (is_encryption_enabled()) {
+      EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
+          .WillOnce(WithArgs<0, 2>(Invoke(GetEncryptionKeyInvocation())))
+          .WillOnce(WithArgs<0, 2>(Invoke(GetVerifyDataInvocation())));
+    } else {
+      EXPECT_CALL(*client_, UploadEncryptedReport(_, _, _))
+          .WillOnce(WithArgs<0, 2>(Invoke(GetVerifyDataInvocation())));
+    }
   }
 
   // Trigger upload.
diff --git a/chrome/browser/policy/messaging_layer/public/report_queue_impl.cc b/chrome/browser/policy/messaging_layer/public/report_queue_impl.cc
index 68b1105..7e9303b 100644
--- a/chrome/browser/policy/messaging_layer/public/report_queue_impl.cc
+++ b/chrome/browser/policy/messaging_layer/public/report_queue_impl.cc
@@ -9,11 +9,13 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/bind_post_task.h"
 #include "base/callback.h"
 #include "base/json/json_writer.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/notreached.h"
 #include "base/sequence_checker.h"
 #include "base/strings/strcat.h"
 #include "base/task/task_traits.h"
@@ -95,4 +97,177 @@
   storage_->Flush(priority, std::move(callback));
 }
 
+base::OnceCallback<void(StatusOr<std::unique_ptr<ReportQueue>>)>
+ReportQueueImpl::PrepareToAttachActualQueue() const {
+  NOTREACHED();
+  return base::BindOnce(
+      [](StatusOr<std::unique_ptr<ReportQueue>>) { NOTREACHED(); });
+}
+
+// static
+std::unique_ptr<SpeculativeReportQueueImpl, base::OnTaskRunnerDeleter>
+SpeculativeReportQueueImpl::Create() {
+  scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner =
+      base::ThreadPool::CreateSequencedTaskRunner(
+          {base::TaskPriority::BEST_EFFORT, base::MayBlock()});
+  return std::unique_ptr<SpeculativeReportQueueImpl, base::OnTaskRunnerDeleter>(
+      new SpeculativeReportQueueImpl(sequenced_task_runner),
+      base::OnTaskRunnerDeleter(sequenced_task_runner));
+}
+
+SpeculativeReportQueueImpl::SpeculativeReportQueueImpl(
+    scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner)
+    : sequenced_task_runner_(sequenced_task_runner) {
+  DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+SpeculativeReportQueueImpl::~SpeculativeReportQueueImpl() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+void SpeculativeReportQueueImpl::Flush(Priority priority,
+                                       FlushCallback callback) {
+  sequenced_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](Priority priority, FlushCallback callback,
+             base::WeakPtr<SpeculativeReportQueueImpl> self) {
+            if (!self) {
+              std::move(callback).Run(
+                  Status(error::UNAVAILABLE, "Queue has been destructed"));
+              return;
+            }
+            DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
+            if (!self->report_queue_) {
+              std::move(callback).Run(Status(error::FAILED_PRECONDITION,
+                                             "ReportQueue is not ready yet."));
+              return;
+            }
+            self->report_queue_->Flush(priority, std::move(callback));
+          },
+          priority, std::move(callback), weak_ptr_factory_.GetWeakPtr()));
+}
+
+void SpeculativeReportQueueImpl::AddRecord(base::StringPiece record,
+                                           Priority priority,
+                                           EnqueueCallback callback) const {
+  sequenced_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&SpeculativeReportQueueImpl::MaybeEnqueueRecord,
+                     weak_ptr_factory_.GetWeakPtr(), std::string(record),
+                     priority, std::move(callback)));
+}
+
+void SpeculativeReportQueueImpl::MaybeEnqueueRecord(
+    base::StringPiece record,
+    Priority priority,
+    EnqueueCallback callback) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!report_queue_) {
+    // Queue is not ready yet, store the record in the memory
+    // queue.
+    pending_records_.emplace(record, priority);
+    std::move(callback).Run(Status::StatusOK());
+    return;
+  }
+  // Queue is ready. If memory queue is empty, just forward the
+  // record.
+  if (pending_records_.empty()) {
+    report_queue_->Enqueue(record, priority, std::move(callback));
+    return;
+  }
+  // If memory queue is not empty, attach the new record at the
+  // end and initiate enqueuing of everything from there.
+  pending_records_.emplace(record, priority);
+  EnqueuePendingRecords(std::move(callback));
+}
+
+void SpeculativeReportQueueImpl::EnqueuePendingRecords(
+    EnqueueCallback callback) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!pending_records_.empty());
+  DCHECK(report_queue_);
+  std::string record(pending_records_.front().first);
+  Priority priority = pending_records_.front().second;
+  pending_records_.pop();
+  if (pending_records_.empty()) {
+    // Last of the pending records.
+    report_queue_->Enqueue(record, priority, std::move(callback));
+    return;
+  }
+  report_queue_->Enqueue(
+      record, priority,
+      base::BindPostTask(
+          sequenced_task_runner_,
+          base::BindOnce(
+              [](base::WeakPtr<const SpeculativeReportQueueImpl> self,
+                 EnqueueCallback callback, Status status) {
+                if (!status.ok()) {
+                  std::move(callback).Run(status);
+                  return;
+                }
+                if (!self) {
+                  std::move(callback).Run(
+                      Status(error::UNAVAILABLE, "Queue has been destructed"));
+                  return;
+                }
+                self->sequenced_task_runner_->PostTask(
+                    FROM_HERE,
+                    base::BindOnce(
+                        &SpeculativeReportQueueImpl::EnqueuePendingRecords,
+                        self, std::move(callback)));
+              },
+              weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
+}
+
+base::OnceCallback<void(StatusOr<std::unique_ptr<ReportQueue>>)>
+SpeculativeReportQueueImpl::PrepareToAttachActualQueue() const {
+  return base::BindPostTask(
+      sequenced_task_runner_,
+      base::BindOnce(
+          [](base::WeakPtr<SpeculativeReportQueueImpl> speculative_queue,
+             StatusOr<std::unique_ptr<ReportQueue>> actual_queue_result) {
+            if (!speculative_queue) {
+              return;  // Speculative queue was destructed in a meantime.
+            }
+            if (!actual_queue_result.ok()) {
+              return;  // Actual queue creation failed.
+            }
+            // Set actual queue for the speculative queue to use
+            // (asynchronously).
+            speculative_queue->AttachActualQueue(
+                std::move(actual_queue_result.ValueOrDie()));
+          },
+          weak_ptr_factory_.GetWeakPtr()));
+}
+
+void SpeculativeReportQueueImpl::AttachActualQueue(
+    std::unique_ptr<ReportQueue> actual_queue) {
+  sequenced_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](base::WeakPtr<SpeculativeReportQueueImpl> self,
+             std::unique_ptr<ReportQueue> actual_queue) {
+            if (!self) {
+              return;
+            }
+            DCHECK_CALLED_ON_VALID_SEQUENCE(self->sequence_checker_);
+            if (self->report_queue_) {
+              // Already attached, do nothing.
+              return;
+            }
+            self->report_queue_ = std::move(actual_queue);
+            if (!self->pending_records_.empty()) {
+              self->EnqueuePendingRecords(
+                  base::BindOnce([](Status enqueue_status) {
+                    if (!enqueue_status.ok()) {
+                      LOG(ERROR) << "Pending records failed to enqueue, status="
+                                 << enqueue_status;
+                    }
+                  }));
+            }
+          },
+          weak_ptr_factory_.GetWeakPtr(), std::move(actual_queue)));
+}
+
 }  // namespace reporting
diff --git a/chrome/browser/policy/messaging_layer/public/report_queue_impl.h b/chrome/browser/policy/messaging_layer/public/report_queue_impl.h
index b2c50dc..fbd7f60 100644
--- a/chrome/browser/policy/messaging_layer/public/report_queue_impl.h
+++ b/chrome/browser/policy/messaging_layer/public/report_queue_impl.h
@@ -12,6 +12,7 @@
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/sequenced_task_runner.h"
 #include "base/values.h"
@@ -50,6 +51,10 @@
 
   void Flush(Priority priority, FlushCallback callback) override;
 
+  // Dummy implementation for a regular queue.
+  base::OnceCallback<void(StatusOr<std::unique_ptr<ReportQueue>>)>
+  PrepareToAttachActualQueue() const override;
+
  protected:
   ReportQueueImpl(std::unique_ptr<ReportQueueConfiguration> config,
                   scoped_refptr<StorageModuleInterface> storage);
@@ -72,6 +77,65 @@
   scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_;
 };
 
+class SpeculativeReportQueueImpl : public ReportQueue {
+ public:
+  ~SpeculativeReportQueueImpl() override;
+
+  // Factory method returns a smart pointer with on-thread deleter.
+  static std::unique_ptr<SpeculativeReportQueueImpl, base::OnTaskRunnerDeleter>
+  Create();
+
+  // Forwards |Flush| to |ReportQueue|, if already created.
+  // Returns with failure otherwise.
+  void Flush(Priority priority, FlushCallback callback) override;
+
+  // Provides a callback to attach initialized actual queue to the speculative
+  // queue.
+  base::OnceCallback<void(StatusOr<std::unique_ptr<ReportQueue>>)>
+  PrepareToAttachActualQueue() const override;
+
+  // Substitutes actual queue to the speculative, when ready.
+  // Initiates processesing of all pending records.
+  void AttachActualQueue(std::unique_ptr<ReportQueue> actual_queue);
+
+ protected:
+  // Forwards |AddRecord| to |ReportQueue|, if already created.
+  // Records the record internally otherwise.
+  void AddRecord(base::StringPiece record,
+                 Priority priority,
+                 EnqueueCallback callback) const override;
+
+ private:
+  // Private constructor, used by the factory method  only.
+  explicit SpeculativeReportQueueImpl(
+      scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner);
+
+  // Enqueues head of the |pending_records_| and reapplies for the rest of it.
+  void EnqueuePendingRecords(EnqueueCallback callback) const;
+
+  // Optionally enqueues |record| to actual queue, if ready.
+  // Otherwise adds it to the end of |pending_records_|.
+  void MaybeEnqueueRecord(base::StringPiece record,
+                          Priority priority,
+                          EnqueueCallback callback) const;
+
+  // Task runner that protects |report_queue_| and |pending_records_|
+  // and allows to synchronize the initialization.
+  const scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_;
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  // Actual |ReportQueue|, once created.
+  std::unique_ptr<ReportQueue> report_queue_;
+
+  // Queue of the pending records, collected before actual queue has been
+  // created. Declared 'mutable', because it is accessed by 'const' methods.
+  mutable std::queue<std::pair<std::string /*record*/, Priority /*priority*/>>
+      pending_records_;
+
+  // Weak pointer factory.
+  base::WeakPtrFactory<SpeculativeReportQueueImpl> weak_ptr_factory_{this};
+};
+
 }  // namespace reporting
 
 #endif  // CHROME_BROWSER_POLICY_MESSAGING_LAYER_PUBLIC_REPORT_QUEUE_IMPL_H_
diff --git a/chrome/browser/policy/messaging_layer/public/report_queue_impl_unittest.cc b/chrome/browser/policy/messaging_layer/public/report_queue_impl_unittest.cc
index 1c4cfc25..2e407f2 100644
--- a/chrome/browser/policy/messaging_layer/public/report_queue_impl_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/public/report_queue_impl_unittest.cc
@@ -82,7 +82,7 @@
 
   NiceMock<MockFunction<Status()>> mocked_policy_check_;
 
-  content::BrowserTaskEnvironment task_envrionment_;
+  content::BrowserTaskEnvironment task_environment_;
 
   const Priority priority_;
 
@@ -229,5 +229,23 @@
   EXPECT_EQ(result.error_code(), error::UNKNOWN);
 }
 
+// Enqueues a random string into speculative queue, then enqueues a sting,
+// attaches actual one and ensures that the string arrives unaltered in the
+// |StorageModuleInterface|.
+TEST_F(ReportQueueImplTest, SuccessfulSpeculativeStringRecord) {
+  constexpr char kTestString[] = "El-Chupacabra";
+  test::TestEvent<Status> a;
+  auto speculative_report_queue = SpeculativeReportQueueImpl::Create();
+  speculative_report_queue->Enqueue(kTestString, priority_, a.cb());
+  EXPECT_OK(a.result());
+
+  speculative_report_queue->AttachActualQueue(std::move(report_queue_));
+  // Let everything ongoing to finish.
+  task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(test_storage_module()->priority(), priority_);
+  EXPECT_EQ(test_storage_module()->record().data(), kTestString);
+}
+
 }  // namespace
 }  // namespace reporting
diff --git a/chrome/browser/predictors/loading_predictor_browsertest.cc b/chrome/browser/predictors/loading_predictor_browsertest.cc
index 1665ddc..6949db75 100644
--- a/chrome/browser/predictors/loading_predictor_browsertest.cc
+++ b/chrome/browser/predictors/loading_predictor_browsertest.cc
@@ -2342,8 +2342,11 @@
                                 base::Unretained(this))) {}
 
  protected:
+  void SetUp() override {
+    prerender_test_helper_.SetUp(embedded_test_server());
+    InProcessBrowserTest::SetUp();
+  }
   void SetUpOnMainThread() override {
-    prerender_test_helper_.SetUpOnMainThread(embedded_test_server());
     test_server_handle_ = embedded_test_server()->StartAndReturnHandle();
     web_contents_ = browser()->tab_strip_model()->GetActiveWebContents();
   }
diff --git a/chrome/browser/predictors/loading_stats_collector_unittest.cc b/chrome/browser/predictors/loading_stats_collector_unittest.cc
index cd90bda7..b2877e4 100644
--- a/chrome/browser/predictors/loading_stats_collector_unittest.cc
+++ b/chrome/browser/predictors/loading_stats_collector_unittest.cc
@@ -52,6 +52,9 @@
   std::unique_ptr<LoadingStatsCollector> stats_collector_;
   std::unique_ptr<base::HistogramTester> histogram_tester_;
   std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
+
+  const net::NetworkIsolationKey network_isolation_key_ =
+      net::NetworkIsolationKey::CreateTransient();
 };
 
 LoadingStatsCollectorTest::LoadingStatsCollectorTest() = default;
@@ -82,7 +85,7 @@
   const std::string& script_url = "https://cdn.google.com/script.js";
   PreconnectPrediction prediction = CreatePreconnectPrediction(
       GURL(prediction_url).host(), initial_url != prediction_url,
-      {{url::Origin::Create(GURL(script_url)), 1, net::NetworkIsolationKey()}});
+      {{url::Origin::Create(GURL(script_url)), 1, network_isolation_key_}});
   EXPECT_CALL(*mock_predictor_, PredictPreconnectOrigins(GURL(initial_url), _))
       .WillOnce(DoAll(SetArgPointee<1>(prediction), Return(true)));
 
@@ -112,11 +115,10 @@
   // Predicts 4 origins: 2 useful, 2 useless.
   PreconnectPrediction prediction = CreatePreconnectPrediction(
       GURL(main_frame_url).host(), false,
-      {{url::Origin::Create(GURL(main_frame_url)), 1,
-        net::NetworkIsolationKey()},
-       {url::Origin::Create(GURL(gen(1))), 1, net::NetworkIsolationKey()},
-       {url::Origin::Create(GURL(gen(2))), 1, net::NetworkIsolationKey()},
-       {url::Origin::Create(GURL(gen(3))), 0, net::NetworkIsolationKey()}});
+      {{url::Origin::Create(GURL(main_frame_url)), 1, network_isolation_key_},
+       {url::Origin::Create(GURL(gen(1))), 1, network_isolation_key_},
+       {url::Origin::Create(GURL(gen(2))), 1, network_isolation_key_},
+       {url::Origin::Create(GURL(gen(3))), 0, network_isolation_key_}});
   EXPECT_CALL(*mock_predictor_,
               PredictPreconnectOrigins(GURL(main_frame_url), _))
       .WillOnce(DoAll(SetArgPointee<1>(prediction), Return(true)));
@@ -233,10 +235,10 @@
       CreatePreconnectPrediction(
           GURL(main_frame_url).host(), false,
           {{url::Origin::Create(GURL(main_frame_url)), 1,
-            net::NetworkIsolationKey()},
-           {url::Origin::Create(GURL(gen(1))), 1, net::NetworkIsolationKey()},
-           {url::Origin::Create(GURL(gen(2))), 1, net::NetworkIsolationKey()},
-           {url::Origin::Create(GURL(gen(3))), 0, net::NetworkIsolationKey()}});
+            network_isolation_key_},
+           {url::Origin::Create(GURL(gen(1))), 1, network_isolation_key_},
+           {url::Origin::Create(GURL(gen(2))), 1, network_isolation_key_},
+           {url::Origin::Create(GURL(gen(3))), 0, network_isolation_key_}});
   optimization_guide_prediction->predicted_subresources = {
       GURL(gen(1)), GURL(gen(2)), GURL(gen(3)), GURL(gen(4))};
 
diff --git a/chrome/browser/predictors/preconnect_manager.cc b/chrome/browser/predictors/preconnect_manager.cc
index 18a98d6..ff661c13 100644
--- a/chrome/browser/predictors/preconnect_manager.cc
+++ b/chrome/browser/predictors/preconnect_manager.cc
@@ -52,6 +52,7 @@
       network_isolation_key(std::move(network_isolation_key)),
       info(info) {
   DCHECK_GE(num_sockets, 0);
+  DCHECK(!network_isolation_key.IsEmpty());
 }
 
 PreresolveJob::PreresolveJob(PreconnectRequest preconnect_request,
@@ -63,6 +64,7 @@
           std::move(preconnect_request.network_isolation_key)),
       info(info) {
   DCHECK_GE(num_sockets, 0);
+  DCHECK(!network_isolation_key.IsEmpty());
 }
 
 PreresolveJob::PreresolveJob(PreresolveJob&& other) = default;
diff --git a/chrome/browser/predictors/preconnect_manager_unittest.cc b/chrome/browser/predictors/preconnect_manager_unittest.cc
index 3bf3b178..7dd603c8 100644
--- a/chrome/browser/predictors/preconnect_manager_unittest.cc
+++ b/chrome/browser/predictors/preconnect_manager_unittest.cc
@@ -787,15 +787,17 @@
 TEST_F(PreconnectManagerTest, TestStartPreresolveHosts) {
   GURL cdn("http://cdn.google.com");
   GURL fonts("http://fonts.google.com");
+  net::NetworkIsolationKey network_isolation_key =
+      CreateNetworkIsolationKey(cdn);
 
   EXPECT_CALL(*mock_network_context_, ResolveHostProxy(cdn.host()));
   EXPECT_CALL(*mock_network_context_, ResolveHostProxy(fonts.host()));
   preconnect_manager_->StartPreresolveHosts({cdn.host(), fonts.host()},
-                                            net::NetworkIsolationKey());
-  mock_network_context_->CompleteHostLookup(
-      cdn.host(), net::NetworkIsolationKey(), net::OK);
-  mock_network_context_->CompleteHostLookup(
-      fonts.host(), net::NetworkIsolationKey(), net::OK);
+                                            network_isolation_key);
+  mock_network_context_->CompleteHostLookup(cdn.host(), network_isolation_key,
+                                            net::OK);
+  mock_network_context_->CompleteHostLookup(fonts.host(), network_isolation_key,
+                                            net::OK);
 }
 
 TEST_F(PreconnectManagerTest, TestStartPreconnectUrl) {
diff --git a/chrome/browser/predictors/resource_prefetch_predictor.cc b/chrome/browser/predictors/resource_prefetch_predictor.cc
index 3d195d38..4f063803 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor.cc
+++ b/chrome/browser/predictors/resource_prefetch_predictor.cc
@@ -75,6 +75,7 @@
       num_sockets(num_sockets),
       network_isolation_key(network_isolation_key) {
   DCHECK_GE(num_sockets, 0);
+  DCHECK(!network_isolation_key.IsEmpty());
 }
 
 PrefetchRequest::PrefetchRequest(
@@ -85,6 +86,7 @@
       network_isolation_key(network_isolation_key),
       destination(destination) {
   DCHECK(base::FeatureList::IsEnabled(features::kLoadingPredictorPrefetch));
+  DCHECK(!network_isolation_key.IsEmpty());
 }
 
 PreconnectPrediction::PreconnectPrediction() = default;
diff --git a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc
index b622c73..69b90d3 100644
--- a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc
+++ b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc
@@ -4448,12 +4448,14 @@
   PrefetchProxyPrerenderBrowserTest& operator=(
       const PrefetchProxyPrerenderBrowserTest&) = delete;
 
-  void SetUp() override { PrefetchProxyBrowserTest::SetUp(); }
-
   void TearDown() override { PrefetchProxyBrowserTest::ResetFeatureList(); }
 
+  void SetUp() override {
+    prerender_test_helper_.SetUp(embedded_test_server());
+    PrefetchProxyBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
-    prerender_test_helper_.SetUpOnMainThread(embedded_test_server());
     host_resolver()->AddRule("*", "127.0.0.1");
     ASSERT_TRUE(embedded_test_server()->Start());
     PrefetchProxyBrowserTest::SetUpOnMainThread();
diff --git a/chrome/browser/prefs/chrome_pref_service_factory.cc b/chrome/browser/prefs/chrome_pref_service_factory.cc
index b9ec4fa..2eeb569a 100644
--- a/chrome/browser/prefs/chrome_pref_service_factory.cc
+++ b/chrome/browser/prefs/chrome_pref_service_factory.cc
@@ -184,7 +184,7 @@
 #if defined(OS_WIN)
     {31, prefs::kSwReporterReportingEnabled, EnforcementLevel::ENFORCE_ON_LOAD,
      PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
-    {32, prefs::kMediaCdmOrigin, EnforcementLevel::ENFORCE_ON_LOAD,
+    {32, prefs::kMediaCdmOriginData, EnforcementLevel::ENFORCE_ON_LOAD,
      PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
 #endif  // defined(OS_WIN)
 
diff --git a/chrome/browser/prerender/payment_request_for_prerender_browsertest.cc b/chrome/browser/prerender/payment_request_for_prerender_browsertest.cc
index 1a4e254..6cddba4 100644
--- a/chrome/browser/prerender/payment_request_for_prerender_browsertest.cc
+++ b/chrome/browser/prerender/payment_request_for_prerender_browsertest.cc
@@ -23,9 +23,9 @@
             &PaymentRequestForPrerenderBrowserTest::GetActiveWebContents,
             base::Unretained(this))) {}
 
-  void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(https_server());
-    PaymentRequestPlatformBrowserTestBase::SetUpOnMainThread();
+  void SetUp() override {
+    prerender_helper_.SetUp(https_server());
+    PaymentRequestPlatformBrowserTestBase::SetUp();
   }
 
   // PaymentRequestTestObserver
diff --git a/chrome/browser/prerender/prerender_browsertest.cc b/chrome/browser/prerender/prerender_browsertest.cc
index d7823ed..53d723e3 100644
--- a/chrome/browser/prerender/prerender_browsertest.cc
+++ b/chrome/browser/prerender/prerender_browsertest.cc
@@ -34,8 +34,12 @@
             base::BindRepeating(&PrerenderBrowserTest::GetActiveWebContents,
                                 base::Unretained(this))) {}
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    PlatformBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     host_resolver()->AddRule("*", "127.0.0.1");
     embedded_test_server()->ServeFilesFromDirectory(
         base::PathService::CheckedGet(chrome::DIR_TEST_DATA));
diff --git a/chrome/browser/printing/print_browsertest.cc b/chrome/browser/printing/print_browsertest.cc
index a302e7c..2e91262f 100644
--- a/chrome/browser/printing/print_browsertest.cc
+++ b/chrome/browser/printing/print_browsertest.cc
@@ -1158,8 +1158,12 @@
     PrintBrowserTest::SetUpCommandLine(cmd_line);
   }
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    PrintBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     ASSERT_TRUE(embedded_test_server()->Start());
   }
 
diff --git a/chrome/browser/privacy_budget/identifiability_study_state_unittest.cc b/chrome/browser/privacy_budget/identifiability_study_state_unittest.cc
index a5fabcf..f57d074 100644
--- a/chrome/browser/privacy_budget/identifiability_study_state_unittest.cc
+++ b/chrome/browser/privacy_budget/identifiability_study_state_unittest.cc
@@ -181,7 +181,7 @@
             settings->active_surfaces());
   EXPECT_EQ((IdentifiableSurfaceSet{kBlockedTypeSurface1}),
             settings->retired_surfaces());
-  EXPECT_EQ("289",
+  EXPECT_EQ("291",
             pref_service()->GetString(prefs::kPrivacyBudgetRetiredSurfaces));
 }
 
diff --git a/chrome/browser/privacy_budget/privacy_budget_browsertest.cc b/chrome/browser/privacy_budget/privacy_budget_browsertest.cc
index 2d79302..4b19c98 100644
--- a/chrome/browser/privacy_budget/privacy_budget_browsertest.cc
+++ b/chrome/browser/privacy_budget/privacy_budget_browsertest.cc
@@ -313,18 +313,27 @@
   // adjust this test to deal.
   ASSERT_EQ(1u, merged_entries.size());
 
+  auto& metrics = merged_entries.begin()->second->metrics;
+
   // (kCanvasReadback | input_digest << kTypeBits) = one of the merged_entries
   // If the value of the relevant merged entry changes, input_digest needs to
   // change. The new input_digest can be calculated by:
   // new_input_digest = new_ukm_entry >> kTypeBits
-  constexpr uint64_t input_digest = UINT64_C(61919955620835840);
-  EXPECT_THAT(merged_entries.begin()->second->metrics,
+  constexpr uint64_t input_digest = UINT64_C(33457614533296512);
+  EXPECT_THAT(metrics,
               IsSupersetOf({
                   Key(blink::IdentifiableSurface::FromTypeAndToken(
                           blink::IdentifiableSurface::Type::kCanvasReadback,
                           input_digest)
                           .ToUkmMetricHash()),
               }));
+
+  for (auto& metric : metrics) {
+    auto surface(blink::IdentifiableSurface::FromMetricHash(metric.first));
+    LOG(INFO) << "surface type " << static_cast<uint64_t>(surface.GetType())
+              << " surface input hash " << surface.GetInputHash() << " value "
+              << metric.second;
+  }
 }
 
 #if BUILDFLAG(FIELDTRIAL_TESTING_ENABLED)
diff --git a/chrome/browser/push_messaging/push_messaging_browsertest.cc b/chrome/browser/push_messaging/push_messaging_browsertest.cc
index e65f9818..d6236bf 100644
--- a/chrome/browser/push_messaging/push_messaging_browsertest.cc
+++ b/chrome/browser/push_messaging/push_messaging_browsertest.cc
@@ -2785,7 +2785,9 @@
   // PushMessagingBrowserTest:
   void SetUpOnMainThread() override {
     incognito_browser_ = CreateIncognitoBrowser();
-    prerender_helper_.SetUpOnMainThread(https_server());
+    // We SetUp here rather than in SetUp since the https_server isn't yet
+    // created at that time.
+    prerender_helper_.SetUp(https_server());
     PushMessagingBrowserTest::SetUpOnMainThread();
   }
   Browser* GetBrowser() const override { return incognito_browser_; }
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 79d09a0..e7fa86a 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -85,9 +85,9 @@
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
 #include "chrome/browser/ui/webui/history/foreign_session_handler.h"
-#include "chrome/browser/web_applications/components/app_icon_manager.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
 #include "chrome/browser/web_applications/system_web_apps/system_web_app_manager.h"
+#include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/common/chrome_constants.h"
diff --git a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html
index d1d6716..2f3201a 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html
+++ b/chrome/browser/resources/chromeos/emoji_picker/emoji_picker.html
@@ -24,6 +24,7 @@
     margin-top: 0;
     padding-top: var(--emoji-picker-top-padding);
     width: var(--emoji-picker-width);
+    background-color: var(--cros-bg-color);
   }
 
   .side-padding {
diff --git a/chrome/browser/resources/extensions/runtime_host_permissions.html b/chrome/browser/resources/extensions/runtime_host_permissions.html
index 37a128b2..d012499 100644
--- a/chrome/browser/resources/extensions/runtime_host_permissions.html
+++ b/chrome/browser/resources/extensions/runtime_host_permissions.html
@@ -5,19 +5,29 @@
   }
 
   #section-heading {
+    --md-select-width: 160px;
     align-items: center;
     display: flex;
-    justify-content: space-between;
   }
 
+  #section-heading-heading {
+    display: flex;
+    flex: 1;
+  }
+
+  #section-heading .link-icon-button {
+    margin-inline-start: 6px;
+  }
+  
   #host-access {
-    margin-inline-start: 20px;
+    margin-inline-start: 12px;
     width: 100%;
   }
 
   #hosts {
     margin-bottom: 0;
-    padding: 0;
+    padding-inline-start: calc(
+        var(--cr-section-indent-padding) - var(--cr-section-padding));
   }
 
   #hosts li {
@@ -52,47 +62,52 @@
 </style>
 <div id="permissions-mode">
   <div id="section-heading">
-    <span>$i18n{hostPermissionsHeading}</span>
-    <a class="link-icon-button" aria-label="$i18n{learnMore}"
-        href="$i18n{hostPermissionsLearnMoreLink}" target="_blank"
-        on-click="onLearnMoreClick_">
-      <iron-icon icon="cr:help-outline"></iron-icon>
-    </a>
+    <div id="section-heading-heading">
+      <span id="section-heading-text">$i18n{hostPermissionsHeading}</span>
+      <a class="link-icon-button" aria-label="$i18n{learnMore}"
+          href="$i18n{hostPermissionsLearnMoreLink}" target="_blank"
+          on-click="onLearnMoreClick_">
+        <iron-icon icon="cr:help-outline"></iron-icon>
+      </a>
+    </div>
+
+    <div>
+      <select id="host-access" class="md-select" on-change="onHostAccessChange_"
+          value="[[permissions.hostAccess]]"
+          aria-labelledby="section-heading-text">
+        <option value="[[HostAccess_.ON_CLICK]]">
+          $i18n{hostAccessOnClick}
+        </option>
+        <option value="[[HostAccess_.ON_SPECIFIC_SITES]]">
+          $i18n{hostAccessOnSpecificSites}
+        </option>
+        <option value="[[HostAccess_.ON_ALL_SITES]]">
+          $i18n{hostAccessOnAllSites}
+        </option>
+      </select>
+    </div>
   </div>
-  <cr-radio-group id="host-access" selected="[[permissions.hostAccess]]"
-      on-selected-changed="onHostAccessChange_">
-    <cr-radio-button name="[[HostAccess_.ON_CLICK]]">
-      $i18n{hostAccessOnClick}
-    </cr-radio-button>
-    <cr-radio-button name="[[HostAccess_.ON_SPECIFIC_SITES]]"
-        class="multi-row">
-      <div>
-        $i18n{hostAccessOnSpecificSites}
-      </div>
-      <template is="dom-if" if="[[showSpecificSites_(permissions.*)]]">
-        <ul id="hosts">
-          <template is="dom-repeat"
-              items="[[getRuntimeHosts_(permissions.hosts)]]">
-            <li>
-              <div>[[item]]</div>
-              <cr-icon-button class="icon-more-vert edit-host"
-                  on-click="onEditHostClick_"
-                  title="$i18n{hostPermissionsEdit}"></cr-icon-button>
-            </li>
-          </template>
-          <li>
-            <a id="add-host" is="action-link" on-click="onAddHostClick_">
-              $i18n{itemSiteAccessAddHost}
-            </a>
-          </li>
-        </ul>
-      </template>
-    </cr-radio-button>
-    <cr-radio-button name="[[HostAccess_.ON_ALL_SITES]]">
-      $i18n{hostAccessOnAllSites}
-    </cr-radio-button>
-  </cr-radio-group>
 </div>
+
+<template is="dom-if" if="[[showSpecificSites_(permissions.*)]]">
+  <ul id="hosts">
+    <template is="dom-repeat"
+        items="[[getRuntimeHosts_(permissions.hosts)]]">
+      <li>
+        <div>[[item]]</div>
+        <cr-icon-button class="icon-more-vert edit-host"
+            on-click="onEditHostClick_"
+            title="$i18n{hostPermissionsEdit}"></cr-icon-button>
+      </li>
+    </template>
+    <li>
+      <a id="add-host" is="action-link" on-click="onAddHostClick_">
+        $i18n{itemSiteAccessAddHost}
+      </a>
+    </li>
+  </ul>
+</template>
+
 <cr-action-menu id="hostActionMenu"
     role-description="$i18n{menu}"
     on-close="onActionMenuClose_">
diff --git a/chrome/browser/resources/extensions/runtime_host_permissions.ts b/chrome/browser/resources/extensions/runtime_host_permissions.ts
index 1a5e3b3..d77c69b 100644
--- a/chrome/browser/resources/extensions/runtime_host_permissions.ts
+++ b/chrome/browser/resources/extensions/runtime_host_permissions.ts
@@ -35,7 +35,7 @@
 interface ExtensionsRuntimeHostPermissionsElement {
   $: {
     hostActionMenu: CrActionMenuElement,
-    'host-access': CrRadioGroupElement,
+    'host-access': HTMLSelectElement,
   };
 }
 
@@ -140,8 +140,8 @@
   private revertingHostAccess_: boolean;
 
   private onHostAccessChange_() {
-    const group = this.$['host-access'];
-    const access = group.selected as chrome.developerPrivate.HostAccess;
+    const selectMenu = this.$['host-access'];
+    const access = selectMenu.value as chrome.developerPrivate.HostAccess;
 
     // Log a user action when the host access selection is changed by the user,
     // but not when reverting from a canceled change to another setting.
@@ -174,7 +174,7 @@
       //   This ensures there will be at least one, so that the host access
       //   is properly calculated.
       this.oldHostAccess_ = this.permissions.hostAccess;
-      this.doShowHostDialog_(group, null);
+      this.doShowHostDialog_(selectMenu, null);
     } else {
       this.delegate.setItemHostAccess(this.itemId, access);
     }
@@ -235,7 +235,7 @@
     if (this.oldHostAccess_) {
       assert(this.permissions.hostAccess === this.oldHostAccess_);
       this.revertingHostAccess_ = true;
-      this.$['host-access'].selected = this.oldHostAccess_;
+      this.$['host-access'].value = this.oldHostAccess_;
       this.revertingHostAccess_ = false;
       this.oldHostAccess_ = null;
     }
diff --git a/chrome/browser/resources/settings/appearance_page/home_url_input.js b/chrome/browser/resources/settings/appearance_page/home_url_input.js
index b7f1b44..82bb51a1 100644
--- a/chrome/browser/resources/settings/appearance_page/home_url_input.js
+++ b/chrome/browser/resources/settings/appearance_page/home_url_input.js
@@ -86,12 +86,6 @@
       return;
     }
 
-    // Ignore updates while the input is focused so that user input is not
-    // overwritten.
-    if (this.$.input.focused) {
-      return;
-    }
-
     this.setInputValueFromPref_();
   }
 
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.html b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.html
index b2a9de4..af489af 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.html
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.html
@@ -22,7 +22,8 @@
         <div id="tabModeText">
           <iron-icon icon="cr:info-outline"></iron-icon>
           [[getAppNameTabModeExplanation_(app)]]
-          <a href="https://support.google.com/chromebook?p=app_intent ">
+          <a href="https://support.google.com/chromebook?p=app_intent"
+              target="_blank">
             $i18n{appManagementIntentSharingTabLearnMore}
           </a>
         </div>
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.js
index 1d071494..7d8e28a 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notifications_subpage.js
@@ -3,8 +3,14 @@
 // found in the LICENSE file.
 
 import './app_notification_row.js';
-import '/os_apps_page/app_notification_handler.mojom-lite.js';
 import 'chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js';
+import 'chrome://resources/mojo/skia/public/mojom/image_info.mojom-lite.js';
+import 'chrome://resources/mojo/skia/public/mojom/bitmap.mojom-lite.js';
+import 'chrome://resources/mojo/url/mojom/url.mojom-lite.js';
+import '/app-management/file_path.mojom-lite.js';
+import '/app-management/image.mojom-lite.js';
+import '/app-management/types.mojom-lite.js';
+import '/os_apps_page/app_notification_handler.mojom-lite.js';
 
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn
index a34f989..3d560bb6 100644
--- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/BUILD.gn
@@ -52,6 +52,7 @@
     ":os_paired_bluetooth_list_item",
     "//third_party/polymer/v3_0/components-chromium/iron-list:iron-list",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_elements:cr_scrollable_behavior.m",
   ]
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.html b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.html
index de4e45a9..374815b 100644
--- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.html
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.html
@@ -6,11 +6,11 @@
 <!-- TODO(crbug.com/1010321): Populate with real data. -->
 <div id="container" class="layout vertical flex" scrollable
     no-bottom-scroll-border>
-  <iron-list items="[[devices_]]" scroll-target="container" preserve-focus>
+  <iron-list items="[[devices]]" scroll-target="container" preserve-focus>
     <template>
-      <!-- TODO(crbug.com/1010321): Add focus behavior,
-           fix not all elements rendering in large lists. -->
-      <os-settings-paired-bluetooth-list-item>
+      <!-- TODO(crbug.com/1010321): Fix focus behavior. -->
+      <os-settings-paired-bluetooth-list-item
+          tabindex="[[tabIndex]]">
       </os-settings-paired-bluetooth-list-item>
     </template>
   </iron-list>
diff --git a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.js b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.js
index 086cb74d..3a1b55e 100644
--- a/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.js
+++ b/chrome/browser/resources/settings/chromeos/os_bluetooth_page/os_paired_bluetooth_list.js
@@ -11,10 +11,20 @@
 import './os_paired_bluetooth_list_item.js';
 
 import '//resources/polymer/v3_0/iron-list/iron-list.js';
-import {html, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {CrScrollableBehavior, CrScrollableBehaviorInterface} from '//resources/cr_elements/cr_scrollable_behavior.m.js';
+import {html, mixinBehaviors, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+/**
+ * @constructor
+ * @extends {PolymerElement}
+ * @implements {CrScrollableBehaviorInterface}
+ */
+const SettingsPairedBluetoothListElementBase =
+    mixinBehaviors([CrScrollableBehavior], PolymerElement);
 
 /** @polymer */
-class SettingsPairedBluetoothListElement extends PolymerElement {
+class SettingsPairedBluetoothListElement extends
+    SettingsPairedBluetoothListElementBase {
   static get is() {
     return 'os-settings-paired-bluetooth-list';
   }
@@ -29,14 +39,20 @@
        * TODO(crbug.com/1010321): Use actual Device objects.
        * @private {Array<Object>}
        */
-      devices_: {
+      devices: {
         type: Array,
-        value() {
-          return [{}, {}, {}];
-        }
+        observer: 'onDevicesChanged_',
+        value: [],
       }
     };
   }
+
+  /** @private */
+  onDevicesChanged_() {
+    // CrScrollableBehaviorInterface method required for list items to be
+    // properly rendered when devices updates.
+    this.updateScrollableContents();
+  }
 }
 
 customElements.define(
diff --git a/chrome/browser/resources/whats_new/BUILD.gn b/chrome/browser/resources/whats_new/BUILD.gn
index b19cc46..60567b9 100644
--- a/chrome/browser/resources/whats_new/BUILD.gn
+++ b/chrome/browser/resources/whats_new/BUILD.gn
@@ -43,6 +43,11 @@
   ]
 }
 
+copy("copy_proxy") {
+  sources = [ "//chrome/browser/resources/whats_new/whats_new_proxy.ts" ]
+  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
+}
+
 ts_library("build_ts") {
   root_dir = target_gen_dir
   out_dir = "$target_gen_dir/$tsc_folder"
@@ -50,10 +55,14 @@
   in_files = [
     "whats_new_app.ts",
     "whats_new_error_page.ts",
+    "whats_new_proxy.ts",
   ]
   deps = [
     "//third_party/polymer/v3_0:library",
     "//ui/webui/resources:library",
   ]
-  extra_deps = [ ":web_components" ]
+  extra_deps = [
+    ":copy_proxy",
+    ":web_components",
+  ]
 }
diff --git a/chrome/browser/resources/whats_new/whats_new_app.ts b/chrome/browser/resources/whats_new/whats_new_app.ts
index bffd70e..0f348f40 100644
--- a/chrome/browser/resources/whats_new/whats_new_app.ts
+++ b/chrome/browser/resources/whats_new/whats_new_app.ts
@@ -6,10 +6,11 @@
 import './whats_new_error_page.js';
 import './strings.m.js';
 
-import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {html, microTask, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {WhatsNewProxyImpl} from './whats_new_proxy.js';
+
 export class WhatsNewAppElement extends PolymerElement {
   static get is() {
     return 'whats-new-app';
@@ -30,7 +31,7 @@
 
     const queryParams = new URLSearchParams(window.location.search);
     const isAutoOpen = queryParams.has('auto');
-    sendWithPromise('initialize', isAutoOpen).then(url => {
+    WhatsNewProxyImpl.getInstance().initialize(isAutoOpen).then(url => {
       if (!url) {
         this.showErrorPage_ = true;
         return;
diff --git a/chrome/browser/resources/whats_new/whats_new_proxy.ts b/chrome/browser/resources/whats_new/whats_new_proxy.ts
new file mode 100644
index 0000000..1e245f3a
--- /dev/null
+++ b/chrome/browser/resources/whats_new/whats_new_proxy.ts
@@ -0,0 +1,25 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
+
+export interface WhatsNewProxy {
+  initialize(isAutoOpen: boolean): Promise<string>;
+}
+
+export class WhatsNewProxyImpl implements WhatsNewProxy {
+  initialize(isAutoOpen: boolean): Promise<string> {
+    return sendWithPromise('initialize', isAutoOpen);
+  }
+
+  static getInstance(): WhatsNewProxy {
+    return instance || (instance = new WhatsNewProxyImpl());
+  }
+
+  static setInstance(obj: WhatsNewProxy) {
+    instance = obj;
+  }
+}
+
+let instance: WhatsNewProxy|null = null;
diff --git a/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc b/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc
index 7d800f2..67b0ef9 100644
--- a/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc
+++ b/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc
@@ -176,8 +176,12 @@
   ClientSideDetectionHostPrerenderBrowserTest& operator=(
       const ClientSideDetectionHostPrerenderBrowserTest&) = delete;
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    ClientSideDetectionHostBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     host_resolver()->AddRule("*", "127.0.0.1");
     ASSERT_TRUE(embedded_test_server()->Start());
     ClientSideDetectionHostBrowserTest::SetUpOnMainThread();
diff --git a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
index 35e73bd5..919c5473 100644
--- a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
@@ -3086,9 +3086,9 @@
   SafeBrowsingPrerenderBrowserTest& operator=(
       const SafeBrowsingPrerenderBrowserTest&) = delete;
 
-  void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
-    SafeBrowsingBlockingPageBrowserTest::SetUpOnMainThread();
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    SafeBrowsingBlockingPageBrowserTest::SetUp();
   }
 
   content::test::PrerenderTestHelper& prerender_helper() {
diff --git a/chrome/browser/search/background/ntp_background_data.cc b/chrome/browser/search/background/ntp_background_data.cc
index cbb3b61..d50b465 100644
--- a/chrome/browser/search/background/ntp_background_data.cc
+++ b/chrome/browser/search/background/ntp_background_data.cc
@@ -108,3 +108,12 @@
   error_type = ErrorType::NONE;
   net_error = 0;
 }
+
+CustomBackground::CustomBackground() = default;
+CustomBackground::CustomBackground(const CustomBackground&) = default;
+CustomBackground::CustomBackground(CustomBackground&&) = default;
+CustomBackground::~CustomBackground() = default;
+
+CustomBackground& CustomBackground::operator=(const CustomBackground&) =
+    default;
+CustomBackground& CustomBackground::operator=(CustomBackground&&) = default;
diff --git a/chrome/browser/search/background/ntp_background_data.h b/chrome/browser/search/background/ntp_background_data.h
index 1534093..29fc757 100644
--- a/chrome/browser/search/background/ntp_background_data.h
+++ b/chrome/browser/search/background/ntp_background_data.h
@@ -105,4 +105,30 @@
   ErrorType error_type;
 };
 
+// Represents a custom background on the new tab page.
+struct CustomBackground {
+  CustomBackground();
+  CustomBackground(const CustomBackground&);
+  CustomBackground(CustomBackground&&);
+  ~CustomBackground();
+
+  CustomBackground& operator=(const CustomBackground&);
+  CustomBackground& operator=(CustomBackground&&);
+
+  // Url of the custom background selected by the user.
+  GURL custom_background_url;
+
+  // First attribution string for custom background.
+  std::string custom_background_attribution_line_1;
+
+  // Second attribution string for custom background.
+  std::string custom_background_attribution_line_2;
+
+  // Url to learn more info about the custom background.
+  GURL custom_background_attribution_action_url;
+
+  // Id of the collection being used for "daily refresh".
+  std::string collection_id;
+};
+
 #endif  // CHROME_BROWSER_SEARCH_BACKGROUND_NTP_BACKGROUND_DATA_H_
diff --git a/chrome/browser/search/background/ntp_custom_background_service.cc b/chrome/browser/search/background/ntp_custom_background_service.cc
new file mode 100644
index 0000000..4a34535
--- /dev/null
+++ b/chrome/browser/search/background/ntp_custom_background_service.cc
@@ -0,0 +1,408 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/search/background/ntp_custom_background_service.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/task/post_task.h"
+#include "base/task/thread_pool.h"
+#include "base/time/clock.h"
+#include "base/time/default_clock.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search/background/ntp_background_service_factory.h"
+#include "chrome/browser/search/background/ntp_custom_background_service_observer.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/search/instant_types.h"
+#include "chrome/common/url_constants.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_thread.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/gurl.h"
+
+namespace {
+
+const char kNtpCustomBackgroundURL[] = "background_url";
+const char kNtpCustomBackgroundAttributionLine1[] = "attribution_line_1";
+const char kNtpCustomBackgroundAttributionLine2[] = "attribution_line_2";
+const char kNtpCustomBackgroundAttributionActionURL[] =
+    "attribution_action_url";
+const char kNtpCustomBackgroundCollectionId[] = "collection_id";
+const char kNtpCustomBackgroundResumeToken[] = "resume_token";
+const char kNtpCustomBackgroundRefreshTimestamp[] = "refresh_timestamp";
+
+base::DictionaryValue GetBackgroundInfoAsDict(
+    const GURL& background_url,
+    const std::string& attribution_line_1,
+    const std::string& attribution_line_2,
+    const GURL& action_url,
+    const absl::optional<std::string>& collection_id,
+    const absl::optional<std::string>& resume_token,
+    const absl::optional<int> refresh_timestamp) {
+  base::DictionaryValue background_info;
+  background_info.SetKey(kNtpCustomBackgroundURL,
+                         base::Value(background_url.spec()));
+  background_info.SetKey(kNtpCustomBackgroundAttributionLine1,
+                         base::Value(attribution_line_1));
+  background_info.SetKey(kNtpCustomBackgroundAttributionLine2,
+                         base::Value(attribution_line_2));
+  background_info.SetKey(kNtpCustomBackgroundAttributionActionURL,
+                         base::Value(action_url.spec()));
+  background_info.SetKey(kNtpCustomBackgroundCollectionId,
+                         base::Value(collection_id.value_or("")));
+  background_info.SetKey(kNtpCustomBackgroundResumeToken,
+                         base::Value(resume_token.value_or("")));
+  background_info.SetKey(kNtpCustomBackgroundRefreshTimestamp,
+                         base::Value(refresh_timestamp.value_or(0)));
+
+  return background_info;
+}
+
+base::Value NtpCustomBackgroundDefaults() {
+  base::Value defaults(base::Value::Type::DICTIONARY);
+  defaults.SetKey(kNtpCustomBackgroundURL,
+                  base::Value(base::Value::Type::STRING));
+  defaults.SetKey(kNtpCustomBackgroundAttributionLine1,
+                  base::Value(base::Value::Type::STRING));
+  defaults.SetKey(kNtpCustomBackgroundAttributionLine2,
+                  base::Value(base::Value::Type::STRING));
+  defaults.SetKey(kNtpCustomBackgroundAttributionActionURL,
+                  base::Value(base::Value::Type::STRING));
+  defaults.SetKey(kNtpCustomBackgroundCollectionId,
+                  base::Value(base::Value::Type::STRING));
+  defaults.SetKey(kNtpCustomBackgroundResumeToken,
+                  base::Value(base::Value::Type::STRING));
+  defaults.SetKey(kNtpCustomBackgroundRefreshTimestamp,
+                  base::Value(base::Value::Type::INTEGER));
+  return defaults;
+}
+
+void CopyFileToProfilePath(const base::FilePath& from_path,
+                           const base::FilePath& profile_path) {
+  base::CopyFile(from_path,
+                 profile_path.AppendASCII(
+                     chrome::kChromeUIUntrustedNewTabPageBackgroundFilename));
+}
+
+}  // namespace
+
+// static
+void NtpCustomBackgroundService::RegisterProfilePrefs(
+    PrefRegistrySimple* registry) {
+  registry->RegisterDictionaryPref(
+      prefs::kNtpCustomBackgroundDict, NtpCustomBackgroundDefaults(),
+      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+  registry->RegisterBooleanPref(prefs::kNtpCustomBackgroundLocalToDevice,
+                                false);
+}
+
+NtpCustomBackgroundService::NtpCustomBackgroundService(Profile* profile)
+    : profile_(profile),
+      pref_service_(profile_->GetPrefs()),
+      clock_(base::DefaultClock::GetInstance()),
+      background_updated_timestamp_(base::TimeTicks::Now()) {
+  background_service_ = NtpBackgroundServiceFactory::GetForProfile(profile_);
+  if (background_service_)
+    background_service_observation_.Observe(background_service_);
+
+  // Update theme info when the pref is changed via Sync.
+  pref_change_registrar_.Init(pref_service_);
+  pref_change_registrar_.Add(
+      prefs::kNtpCustomBackgroundDict,
+      base::BindRepeating(&NtpCustomBackgroundService::UpdateBackgroundFromSync,
+                          weak_ptr_factory_.GetWeakPtr()));
+}
+
+NtpCustomBackgroundService::~NtpCustomBackgroundService() = default;
+
+void NtpCustomBackgroundService::Shutdown() {
+  for (NtpCustomBackgroundServiceObserver& observer : observers_)
+    observer.OnNtpCustomBackgroundServiceShuttingDown();
+}
+
+void NtpCustomBackgroundService::OnCollectionInfoAvailable() {}
+
+void NtpCustomBackgroundService::OnCollectionImagesAvailable() {}
+
+void NtpCustomBackgroundService::OnNextCollectionImageAvailable() {
+  auto image = background_service_->next_image();
+  std::string attribution1;
+  std::string attribution2;
+  if (image.attribution.size() > 0)
+    attribution1 = image.attribution[0];
+  if (image.attribution.size() > 1)
+    attribution2 = image.attribution[1];
+
+  std::string resume_token = background_service_->next_image_resume_token();
+  int64_t timestamp = (clock_->Now() + base::TimeDelta::FromDays(1)).ToTimeT();
+
+  base::DictionaryValue background_info = GetBackgroundInfoAsDict(
+      image.image_url, attribution1, attribution2, image.attribution_action_url,
+      image.collection_id, resume_token, timestamp);
+
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  pref_service_->Set(prefs::kNtpCustomBackgroundDict, background_info);
+}
+
+void NtpCustomBackgroundService::OnNtpBackgroundServiceShuttingDown() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  background_service_observation_.Reset();
+  background_service_ = nullptr;
+}
+
+void NtpCustomBackgroundService::UpdateBackgroundFromSync() {
+  // Any incoming change to synced background data should clear the local image.
+  pref_service_->SetBoolean(prefs::kNtpCustomBackgroundLocalToDevice, false);
+  RemoveLocalBackgroundImageCopy();
+  NotifyAboutBackgrounds();
+}
+
+void NtpCustomBackgroundService::ResetCustomBackgroundInfo() {
+  SetCustomBackgroundInfo(GURL(), std::string(), std::string(), GURL(),
+                          std::string());
+}
+
+void NtpCustomBackgroundService::SetCustomBackgroundInfo(
+    const GURL& background_url,
+    const std::string& attribution_line_1,
+    const std::string& attribution_line_2,
+    const GURL& action_url,
+    const std::string& collection_id) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  if (IsCustomBackgroundDisabledByPolicy()) {
+    return;
+  }
+  bool is_backdrop_collection =
+      background_service_ &&
+      background_service_->IsValidBackdropCollection(collection_id);
+  bool is_backdrop_url =
+      background_service_ &&
+      background_service_->IsValidBackdropUrl(background_url);
+
+  bool need_forced_refresh =
+      pref_service_->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice) &&
+      pref_service_->FindPreference(prefs::kNtpCustomBackgroundDict)
+          ->IsDefaultValue();
+  pref_service_->SetBoolean(prefs::kNtpCustomBackgroundLocalToDevice, false);
+  RemoveLocalBackgroundImageCopy();
+
+  background_updated_timestamp_ = base::TimeTicks::Now();
+
+  if (!collection_id.empty() && is_backdrop_collection) {
+    background_service_->FetchNextCollectionImage(collection_id, absl::nullopt);
+  } else if (background_url.is_valid() && is_backdrop_url) {
+    base::DictionaryValue background_info = GetBackgroundInfoAsDict(
+        background_url, attribution_line_1, attribution_line_2, action_url,
+        absl::nullopt, absl::nullopt, absl::nullopt);
+    pref_service_->Set(prefs::kNtpCustomBackgroundDict, background_info);
+  } else {
+    pref_service_->ClearPref(prefs::kNtpCustomBackgroundDict);
+
+    // If this device was using a local image and did not have a non-local
+    // background saved, UpdateBackgroundFromSync will not fire. Therefore, we
+    // need to force a refresh here.
+    if (need_forced_refresh) {
+      NotifyAboutBackgrounds();
+    }
+  }
+}
+
+void NtpCustomBackgroundService::SelectLocalBackgroundImage(
+    const base::FilePath& path) {
+  if (IsCustomBackgroundDisabledByPolicy()) {
+    return;
+  }
+  base::ThreadPool::PostTaskAndReply(
+      FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
+      base::BindOnce(&CopyFileToProfilePath, path, profile_->GetPath()),
+      base::BindOnce(&NtpCustomBackgroundService::SetBackgroundToLocalResource,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void NtpCustomBackgroundService::ResetCustomBackgroundNtpTheme() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  pref_service_->ClearPref(prefs::kNtpCustomBackgroundDict);
+  pref_service_->SetBoolean(prefs::kNtpCustomBackgroundLocalToDevice, false);
+  RemoveLocalBackgroundImageCopy();
+}
+
+void NtpCustomBackgroundService::RefreshBackgroundIfNeeded() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  const base::DictionaryValue* background_info =
+      profile_->GetPrefs()->GetDictionary(prefs::kNtpCustomBackgroundDict);
+  int64_t refresh_timestamp = 0;
+  const base::Value* timestamp_value =
+      background_info->FindKey(kNtpCustomBackgroundRefreshTimestamp);
+  if (timestamp_value)
+    refresh_timestamp = timestamp_value->GetInt();
+  if (refresh_timestamp == 0)
+    return;
+
+  if (clock_->Now().ToTimeT() > refresh_timestamp) {
+    std::string collection_id =
+        background_info->FindKey(kNtpCustomBackgroundCollectionId)->GetString();
+    std::string resume_token =
+        background_info->FindKey(kNtpCustomBackgroundResumeToken)->GetString();
+    background_service_->FetchNextCollectionImage(collection_id, resume_token);
+  }
+}
+
+absl::optional<CustomBackground>
+NtpCustomBackgroundService::GetCustomBackground() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  if (pref_service_->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice)) {
+    auto custom_background = absl::make_optional<CustomBackground>();
+    // Add a timestamp to the url to prevent the browser from using a cached
+    // version when "Upload an image" is used multiple times.
+    std::string time_string = std::to_string(base::Time::Now().ToTimeT());
+    std::string local_string(chrome::kChromeUIUntrustedNewTabPageBackgroundUrl);
+    GURL timestamped_url(local_string + "?ts=" + time_string);
+    custom_background->custom_background_url = timestamped_url;
+    custom_background->custom_background_attribution_line_1 = std::string();
+    custom_background->custom_background_attribution_line_2 = std::string();
+    custom_background->custom_background_attribution_action_url = GURL();
+    return custom_background;
+  }
+
+  // Attempt to get custom background URL from preferences.
+  if (IsCustomBackgroundPrefValid()) {
+    auto custom_background = absl::make_optional<CustomBackground>();
+    const base::DictionaryValue* background_info =
+        pref_service_->GetDictionary(prefs::kNtpCustomBackgroundDict);
+    GURL custom_background_url(
+        background_info->FindKey(kNtpCustomBackgroundURL)->GetString());
+
+    std::string collection_id;
+    const base::Value* id_value =
+        background_info->FindKey(kNtpCustomBackgroundCollectionId);
+    if (id_value)
+      collection_id = id_value->GetString();
+
+    // Set custom background information in theme info (attributions are
+    // optional).
+    const base::Value* attribution_line_1 =
+        background_info->FindKey(kNtpCustomBackgroundAttributionLine1);
+    const base::Value* attribution_line_2 =
+        background_info->FindKey(kNtpCustomBackgroundAttributionLine2);
+    const base::Value* attribution_action_url =
+        background_info->FindKey(kNtpCustomBackgroundAttributionActionURL);
+    custom_background->custom_background_url = custom_background_url;
+    custom_background->collection_id = collection_id;
+
+    if (attribution_line_1) {
+      custom_background->custom_background_attribution_line_1 =
+          background_info->FindKey(kNtpCustomBackgroundAttributionLine1)
+              ->GetString();
+    }
+    if (attribution_line_2) {
+      custom_background->custom_background_attribution_line_2 =
+          background_info->FindKey(kNtpCustomBackgroundAttributionLine2)
+              ->GetString();
+    }
+    if (attribution_action_url) {
+      GURL action_url(
+          background_info->FindKey(kNtpCustomBackgroundAttributionActionURL)
+              ->GetString());
+
+      if (!action_url.SchemeIsCryptographic()) {
+        custom_background->custom_background_attribution_action_url = GURL();
+      } else {
+        custom_background->custom_background_attribution_action_url =
+            action_url;
+      }
+    }
+    return custom_background;
+  }
+
+  return absl::nullopt;
+}
+
+void NtpCustomBackgroundService::AddObserver(
+    NtpCustomBackgroundServiceObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void NtpCustomBackgroundService::RemoveObserver(
+    NtpCustomBackgroundServiceObserver* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+bool NtpCustomBackgroundService::IsCustomBackgroundDisabledByPolicy() {
+  // |prefs::kNtpCustomBackgroundDict| is managed by policy only if
+  // |policy::key::kNTPCustomBackgroundEnabled| is set to false and therefore
+  // should be empty.
+  bool managed =
+      pref_service_->IsManagedPreference(prefs::kNtpCustomBackgroundDict);
+  if (managed) {
+    DCHECK(pref_service_->GetDictionary(prefs::kNtpCustomBackgroundDict)
+               ->DictEmpty());
+  }
+  return managed;
+}
+
+bool NtpCustomBackgroundService::IsCustomBackgroundSet() {
+  return pref_service_->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice) ||
+         IsCustomBackgroundPrefValid();
+}
+
+void NtpCustomBackgroundService::AddValidBackdropUrlForTesting(
+    const GURL& url) const {
+  background_service_->AddValidBackdropUrlForTesting(url);
+}
+
+void NtpCustomBackgroundService::AddValidBackdropCollectionForTesting(
+    const std::string& collection_id) const {
+  background_service_->AddValidBackdropCollectionForTesting(collection_id);
+}
+
+void NtpCustomBackgroundService::SetNextCollectionImageForTesting(
+    const CollectionImage& image) const {
+  background_service_->SetNextCollectionImageForTesting(image);
+}
+
+void NtpCustomBackgroundService::SetClockForTesting(base::Clock* clock) {
+  clock_ = clock;
+}
+
+void NtpCustomBackgroundService::RemoveLocalBackgroundImageCopy() {
+  base::FilePath path = profile_->GetPath().AppendASCII(
+      chrome::kChromeUIUntrustedNewTabPageBackgroundFilename);
+  base::ThreadPool::PostTask(
+      FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
+      base::BindOnce(base::GetDeleteFileCallback(), path));
+}
+
+void NtpCustomBackgroundService::SetBackgroundToLocalResource() {
+  background_updated_timestamp_ = base::TimeTicks::Now();
+  pref_service_->SetBoolean(prefs::kNtpCustomBackgroundLocalToDevice, true);
+  NotifyAboutBackgrounds();
+}
+
+bool NtpCustomBackgroundService::IsCustomBackgroundPrefValid() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  const base::DictionaryValue* background_info =
+      profile_->GetPrefs()->GetDictionary(prefs::kNtpCustomBackgroundDict);
+  if (!background_info)
+    return false;
+
+  const base::Value* background_url =
+      background_info->FindKey(kNtpCustomBackgroundURL);
+  if (!background_url)
+    return false;
+
+  return GURL(background_url->GetString()).is_valid();
+}
+
+void NtpCustomBackgroundService::NotifyAboutBackgrounds() {
+  for (NtpCustomBackgroundServiceObserver& observer : observers_)
+    observer.OnCustomBackgroundImageUpdated();
+}
diff --git a/chrome/browser/search/background/ntp_custom_background_service.h b/chrome/browser/search/background/ntp_custom_background_service.h
new file mode 100644
index 0000000..ba8babb2
--- /dev/null
+++ b/chrome/browser/search/background/ntp_custom_background_service.h
@@ -0,0 +1,105 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SEARCH_BACKGROUND_NTP_CUSTOM_BACKGROUND_SERVICE_H_
+#define CHROME_BROWSER_SEARCH_BACKGROUND_NTP_CUSTOM_BACKGROUND_SERVICE_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
+#include "base/time/time.h"
+#include "chrome/browser/search/background/ntp_background_service.h"
+#include "chrome/browser/search/background/ntp_background_service_observer.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/prefs/pref_change_registrar.h"
+
+class NtpCustomBackgroundServiceObserver;
+class PrefRegistrySimple;
+class PrefService;
+class Profile;
+
+namespace base {
+class Clock;
+class FilePath;
+}  // namespace base
+
+// Manages custom backgrounds on the new tab page.
+class NtpCustomBackgroundService : public KeyedService,
+                                   public NtpBackgroundServiceObserver {
+ public:
+  static void RegisterProfilePrefs(PrefRegistrySimple* registry);
+
+  explicit NtpCustomBackgroundService(Profile* profile);
+  ~NtpCustomBackgroundService() override;
+
+  // KeyedService:
+  void Shutdown() override;
+
+  // NtpBackgroundServiceObserver:
+  void OnCollectionInfoAvailable() override;
+  void OnCollectionImagesAvailable() override;
+  void OnNextCollectionImageAvailable() override;
+  void OnNtpBackgroundServiceShuttingDown() override;
+
+  // Invoked when a background pref update is received via sync, triggering
+  // an update of theme info.
+  void UpdateBackgroundFromSync();
+
+  // Invoked when the background is reset on the NTP.
+  void ResetCustomBackgroundInfo();
+
+  // Invoked when a custom background is configured on the NTP.
+  void SetCustomBackgroundInfo(const GURL& background_url,
+                               const std::string& attribution_line_1,
+                               const std::string& attribution_line_2,
+                               const GURL& action_url,
+                               const std::string& collection_id);
+
+  // Invoked when a user selected the "Upload an image" option on the NTP.
+  void SelectLocalBackgroundImage(const base::FilePath& path);
+
+  // Marked virtual for mocking in tests.
+  virtual void ResetCustomBackgroundNtpTheme();
+
+  void RefreshBackgroundIfNeeded();
+
+  absl::optional<CustomBackground> GetCustomBackground();
+
+  // Adds/Removes NtpCustomBackgroundServiceObserver observers.
+  void AddObserver(NtpCustomBackgroundServiceObserver* observer);
+  void RemoveObserver(NtpCustomBackgroundServiceObserver* observer);
+
+  // Returns whether having a custom background is disabled by policy.
+  bool IsCustomBackgroundDisabledByPolicy();
+
+  // Returns whether a custom background has been set by the user.
+  bool IsCustomBackgroundSet();
+
+  void AddValidBackdropUrlForTesting(const GURL& url) const;
+  void AddValidBackdropCollectionForTesting(
+      const std::string& collection_id) const;
+  void SetNextCollectionImageForTesting(const CollectionImage& image) const;
+  void SetClockForTesting(base::Clock* clock);
+
+ private:
+  void RemoveLocalBackgroundImageCopy();
+  void SetBackgroundToLocalResource();
+  // Returns false if the custom background pref cannot be parsed, otherwise
+  // returns true.
+  bool IsCustomBackgroundPrefValid();
+  void NotifyAboutBackgrounds();
+
+  Profile* const profile_;
+  PrefService* pref_service_;
+  PrefChangeRegistrar pref_change_registrar_;
+  NtpBackgroundService* background_service_;
+  base::ScopedObservation<NtpBackgroundService, NtpBackgroundServiceObserver>
+      background_service_observation_{this};
+  base::Clock* clock_;
+  base::TimeTicks background_updated_timestamp_;
+  base::ObserverList<NtpCustomBackgroundServiceObserver> observers_;
+
+  base::WeakPtrFactory<NtpCustomBackgroundService> weak_ptr_factory_{this};
+};
+
+#endif  // CHROME_BROWSER_SEARCH_BACKGROUND_NTP_CUSTOM_BACKGROUND_SERVICE_H_
diff --git a/chrome/browser/search/background/ntp_custom_background_service_factory.cc b/chrome/browser/search/background/ntp_custom_background_service_factory.cc
new file mode 100644
index 0000000..1d98c04
--- /dev/null
+++ b/chrome/browser/search/background/ntp_custom_background_service_factory.cc
@@ -0,0 +1,38 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/search/background/ntp_custom_background_service_factory.h"
+
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search/background/ntp_custom_background_service.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/storage_partition.h"
+
+// static
+NtpCustomBackgroundService* NtpCustomBackgroundServiceFactory::GetForProfile(
+    Profile* profile) {
+  return static_cast<NtpCustomBackgroundService*>(
+      GetInstance()->GetServiceForBrowserContext(profile, true));
+}
+
+// static
+NtpCustomBackgroundServiceFactory*
+NtpCustomBackgroundServiceFactory::GetInstance() {
+  return base::Singleton<NtpCustomBackgroundServiceFactory>::get();
+}
+
+NtpCustomBackgroundServiceFactory::NtpCustomBackgroundServiceFactory()
+    : BrowserContextKeyedServiceFactory(
+          "NtpCustomBackgroundService",
+          BrowserContextDependencyManager::GetInstance()) {}
+
+NtpCustomBackgroundServiceFactory::~NtpCustomBackgroundServiceFactory() =
+    default;
+
+KeyedService* NtpCustomBackgroundServiceFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  return new NtpCustomBackgroundService(Profile::FromBrowserContext(context));
+}
diff --git a/chrome/browser/search/background/ntp_custom_background_service_factory.h b/chrome/browser/search/background/ntp_custom_background_service_factory.h
new file mode 100644
index 0000000..d911c1b
--- /dev/null
+++ b/chrome/browser/search/background/ntp_custom_background_service_factory.h
@@ -0,0 +1,37 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SEARCH_BACKGROUND_NTP_CUSTOM_BACKGROUND_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_SEARCH_BACKGROUND_NTP_CUSTOM_BACKGROUND_SERVICE_FACTORY_H_
+
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "content/public/browser/browser_context.h"
+
+class NtpCustomBackgroundService;
+class Profile;
+
+class NtpCustomBackgroundServiceFactory
+    : public BrowserContextKeyedServiceFactory {
+ public:
+  static NtpCustomBackgroundService* GetForProfile(Profile* profile);
+  static NtpCustomBackgroundServiceFactory* GetInstance();
+
+  NtpCustomBackgroundServiceFactory(const NtpCustomBackgroundServiceFactory&) =
+      delete;
+  NtpCustomBackgroundServiceFactory& operator=(
+      const NtpCustomBackgroundServiceFactory&) = delete;
+
+ private:
+  friend struct base::DefaultSingletonTraits<NtpCustomBackgroundServiceFactory>;
+
+  NtpCustomBackgroundServiceFactory();
+  ~NtpCustomBackgroundServiceFactory() override;
+
+  // BrowserContextKeyedServiceFactory:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* profile) const override;
+};
+
+#endif  // CHROME_BROWSER_SEARCH_BACKGROUND_NTP_CUSTOM_BACKGROUND_SERVICE_FACTORY_H_
diff --git a/chrome/browser/search/background/ntp_custom_background_service_observer.h b/chrome/browser/search/background/ntp_custom_background_service_observer.h
new file mode 100644
index 0000000..17ddcb4
--- /dev/null
+++ b/chrome/browser/search/background/ntp_custom_background_service_observer.h
@@ -0,0 +1,22 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SEARCH_BACKGROUND_NTP_CUSTOM_BACKGROUND_SERVICE_OBSERVER_H_
+#define CHROME_BROWSER_SEARCH_BACKGROUND_NTP_CUSTOM_BACKGROUND_SERVICE_OBSERVER_H_
+
+#include "base/observer_list_types.h"
+
+// Observer for NtpCustomBackgroundService.
+class NtpCustomBackgroundServiceObserver : public base::CheckedObserver {
+ public:
+  // Called when the custom background image is updated.
+  virtual void OnCustomBackgroundImageUpdated() = 0;
+
+  // Called when the NtpCustomBackgroundService is shutting down. Observers that
+  // might outlive the service should use this to unregister themselves, and
+  // clear out any pointers to the service they might hold.
+  virtual void OnNtpCustomBackgroundServiceShuttingDown() {}
+};
+
+#endif  // CHROME_BROWSER_SEARCH_BACKGROUND_NTP_CUSTOM_BACKGROUND_SERVICE_OBSERVER_H_
diff --git a/chrome/browser/search/background/ntp_custom_background_service_unittest.cc b/chrome/browser/search/background/ntp_custom_background_service_unittest.cc
new file mode 100644
index 0000000..f489dc1d
--- /dev/null
+++ b/chrome/browser/search/background/ntp_custom_background_service_unittest.cc
@@ -0,0 +1,465 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/search/background/ntp_custom_background_service.h"
+
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/task/thread_pool/thread_pool_instance.h"
+#include "base/test/mock_callback.h"
+#include "base/test/simple_test_clock.h"
+#include "build/build_config.h"
+#include "chrome/browser/search/background/ntp_custom_background_service_observer.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/search/instant_types.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+
+class MockNtpCustomBackgroundServiceObserver
+    : public NtpCustomBackgroundServiceObserver {
+ public:
+  MOCK_METHOD0(OnCustomBackgroundImageUpdated, void());
+  MOCK_METHOD0(OnNtpCustomBackgroundServiceShuttingDown, void());
+};
+
+base::DictionaryValue GetBackgroundInfoAsDict(const GURL& background_url) {
+  base::DictionaryValue background_info;
+  background_info.SetKey("background_url", base::Value(background_url.spec()));
+  background_info.SetKey("attribution_line_1", base::Value(std::string()));
+  background_info.SetKey("attribution_line_2", base::Value(std::string()));
+  background_info.SetKey("attribution_action_url", base::Value(std::string()));
+  background_info.SetKey("collection_id", base::Value(std::string()));
+  background_info.SetKey("resume_token", base::Value(std::string()));
+  background_info.SetKey("refresh_timestamp", base::Value(0));
+  return background_info;
+}
+
+base::Time GetReferenceTime() {
+  base::Time::Exploded exploded_reference_time;
+  exploded_reference_time.year = 2019;
+  exploded_reference_time.month = 1;
+  exploded_reference_time.day_of_month = 1;
+  exploded_reference_time.day_of_week = 1;
+  exploded_reference_time.hour = 0;
+  exploded_reference_time.minute = 0;
+  exploded_reference_time.second = 0;
+  exploded_reference_time.millisecond = 0;
+
+  base::Time out_time;
+  EXPECT_TRUE(
+      base::Time::FromLocalExploded(exploded_reference_time, &out_time));
+  return out_time;
+}
+
+}  // namespace
+
+class NtpCustomBackgroundServiceTest : public testing::Test {
+ public:
+  void SetUp() override {
+    custom_background_service_ =
+        std::make_unique<NtpCustomBackgroundService>(&profile_);
+    custom_background_service_->AddObserver(&observer_);
+  }
+
+ protected:
+  // NOTE: The initialization order of these members matters.
+  content::BrowserTaskEnvironment task_environment_;
+  TestingProfile profile_;
+  base::SimpleTestClock clock_;
+  MockNtpCustomBackgroundServiceObserver observer_;
+  std::unique_ptr<NtpCustomBackgroundService> custom_background_service_;
+};
+
+TEST_F(NtpCustomBackgroundServiceTest, SetCustomBackgroundURL) {
+  EXPECT_CALL(observer_, OnCustomBackgroundImageUpdated).Times(1);
+
+  ASSERT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+  const GURL kUrl("https://www.foo.com");
+
+  custom_background_service_->AddValidBackdropUrlForTesting(kUrl);
+  custom_background_service_->SetCustomBackgroundInfo(kUrl, "", "", GURL(), "");
+
+  auto custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_EQ(kUrl, custom_background->custom_background_url);
+  EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+}
+
+TEST_F(NtpCustomBackgroundServiceTest, SetCustomBackgroundURLInvalidURL) {
+  EXPECT_CALL(observer_, OnCustomBackgroundImageUpdated).Times(2);
+
+  ASSERT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+  const GURL kInvalidUrl("foo");
+  const GURL kValidUrl("https://www.foo.com");
+  custom_background_service_->AddValidBackdropUrlForTesting(kValidUrl);
+  custom_background_service_->SetCustomBackgroundInfo(kValidUrl, "", "", GURL(),
+                                                      "");
+
+  auto custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_EQ(kValidUrl.spec(), custom_background->custom_background_url.spec());
+
+  custom_background_service_->SetCustomBackgroundInfo(kInvalidUrl, "", "",
+                                                      GURL(), "");
+
+  custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_FALSE(custom_background.has_value());
+  EXPECT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+}
+
+TEST_F(NtpCustomBackgroundServiceTest, SetCustomBackgroundInfo) {
+  EXPECT_CALL(observer_, OnCustomBackgroundImageUpdated).Times(1);
+
+  ASSERT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+  const GURL kUrl("https://www.foo.com");
+  const std::string kAttributionLine1 = "foo";
+  const std::string kAttributionLine2 = "bar";
+  const GURL kActionUrl("https://www.bar.com");
+  custom_background_service_->AddValidBackdropUrlForTesting(kUrl);
+  custom_background_service_->SetCustomBackgroundInfo(
+      kUrl, kAttributionLine1, kAttributionLine2, kActionUrl, "");
+
+  auto custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_EQ(kUrl, custom_background->custom_background_url);
+  EXPECT_EQ(kAttributionLine1,
+            custom_background->custom_background_attribution_line_1);
+  EXPECT_EQ(kAttributionLine2,
+            custom_background->custom_background_attribution_line_2);
+  EXPECT_EQ(kActionUrl,
+            custom_background->custom_background_attribution_action_url);
+  EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+}
+
+TEST_F(NtpCustomBackgroundServiceTest, LocalBackgroundImageCopyCreated) {
+  EXPECT_CALL(observer_, OnCustomBackgroundImageUpdated).Times(1);
+  ASSERT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+
+  base::FilePath profile_path = profile_.GetPath();
+  base::FilePath path(profile_path.AppendASCII("test_file"));
+  base::FilePath copy_path(profile_path.AppendASCII(
+      chrome::kChromeUIUntrustedNewTabPageBackgroundFilename));
+
+  base::WriteFile(path, "background_image", 16);
+
+  custom_background_service_->SelectLocalBackgroundImage(path);
+
+  task_environment_.RunUntilIdle();
+
+  bool file_exists = base::PathExists(copy_path);
+
+  EXPECT_EQ(true, file_exists);
+  EXPECT_EQ(true, profile_.GetTestingPrefService()->GetBoolean(
+                      prefs::kNtpCustomBackgroundLocalToDevice));
+  EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+}
+
+TEST_F(NtpCustomBackgroundServiceTest,
+       SettingUrlRemovesLocalBackgroundImageCopy) {
+  EXPECT_CALL(observer_, OnCustomBackgroundImageUpdated).Times(1);
+  ASSERT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+  const GURL kUrl("https://www.foo.com");
+
+  base::FilePath profile_path = profile_.GetPath();
+  base::FilePath path(profile_path.AppendASCII(
+      chrome::kChromeUIUntrustedNewTabPageBackgroundFilename));
+
+  base::WriteFile(path, "background_image", 16);
+
+  custom_background_service_->AddValidBackdropUrlForTesting(kUrl);
+  custom_background_service_->SetCustomBackgroundInfo(kUrl, "", "", GURL(), "");
+
+  task_environment_.RunUntilIdle();
+
+  bool file_exists = base::PathExists(path);
+
+  EXPECT_EQ(false, file_exists);
+  EXPECT_EQ(false, profile_.GetTestingPrefService()->GetBoolean(
+                       prefs::kNtpCustomBackgroundLocalToDevice));
+  ASSERT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+}
+
+TEST_F(NtpCustomBackgroundServiceTest, UpdatingPrefUpdatesNtpTheme) {
+  EXPECT_CALL(observer_, OnCustomBackgroundImageUpdated).Times(2);
+  ASSERT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+  const GURL kUrlFoo("https://www.foo.com");
+  const GURL kUrlBar("https://www.bar.com");
+
+  sync_preferences::TestingPrefServiceSyncable* pref_service =
+      profile_.GetTestingPrefService();
+  pref_service->SetUserPref(
+      prefs::kNtpCustomBackgroundDict,
+      std::make_unique<base::Value>(GetBackgroundInfoAsDict(kUrlFoo)));
+
+  auto custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_EQ(kUrlFoo, custom_background->custom_background_url);
+  EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+
+  pref_service->SetUserPref(
+      prefs::kNtpCustomBackgroundDict,
+      std::make_unique<base::Value>(GetBackgroundInfoAsDict(kUrlBar)));
+
+  custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_EQ(kUrlBar, custom_background->custom_background_url);
+  EXPECT_EQ(false,
+            pref_service->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice));
+  EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+}
+
+TEST_F(NtpCustomBackgroundServiceTest, SetLocalImage) {
+  EXPECT_CALL(observer_, OnCustomBackgroundImageUpdated).Times(1);
+  ASSERT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+
+  sync_preferences::TestingPrefServiceSyncable* pref_service =
+      profile_.GetTestingPrefService();
+
+  base::FilePath profile_path = profile_.GetPath();
+  base::FilePath path(profile_path.AppendASCII(
+      chrome::kChromeUIUntrustedNewTabPageBackgroundFilename));
+  base::WriteFile(path, "background_image", 16);
+  base::ThreadPoolInstance::Get()->FlushForTesting();
+
+  custom_background_service_->SelectLocalBackgroundImage(path);
+  task_environment_.RunUntilIdle();
+
+  auto custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_TRUE(
+      base::StartsWith(custom_background->custom_background_url.spec(),
+                       chrome::kChromeUIUntrustedNewTabPageBackgroundUrl,
+                       base::CompareCase::SENSITIVE));
+  EXPECT_TRUE(
+      pref_service->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice));
+  EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+}
+
+TEST_F(NtpCustomBackgroundServiceTest, SyncPrefOverridesAndRemovesLocalImage) {
+  EXPECT_CALL(observer_, OnCustomBackgroundImageUpdated).Times(2);
+  ASSERT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+  const GURL kUrl("https://www.foo.com/");
+
+  sync_preferences::TestingPrefServiceSyncable* pref_service =
+      profile_.GetTestingPrefService();
+
+  base::FilePath profile_path = profile_.GetPath();
+  base::FilePath path(profile_path.AppendASCII(
+      chrome::kChromeUIUntrustedNewTabPageBackgroundFilename));
+  base::WriteFile(path, "background_image", 16);
+  base::ThreadPoolInstance::Get()->FlushForTesting();
+
+  custom_background_service_->SelectLocalBackgroundImage(path);
+  task_environment_.RunUntilIdle();
+
+  EXPECT_TRUE(
+      pref_service->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice));
+  EXPECT_TRUE(base::PathExists(path));
+
+  // Update custom_background info via Sync.
+  pref_service->SetUserPref(
+      prefs::kNtpCustomBackgroundDict,
+      std::make_unique<base::Value>(GetBackgroundInfoAsDict(kUrl)));
+  task_environment_.RunUntilIdle();
+
+  auto custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_EQ(kUrl, custom_background->custom_background_url);
+  EXPECT_FALSE(
+      pref_service->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice));
+  EXPECT_FALSE(base::PathExists(path));
+  EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+}
+
+TEST_F(NtpCustomBackgroundServiceTest, ValidateBackdropUrls) {
+  EXPECT_CALL(observer_, OnCustomBackgroundImageUpdated).Times(4);
+  ASSERT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+  const GURL kBackdropUrl1("https://www.foo.com");
+  const GURL kBackdropUrl2("https://www.bar.com");
+  const GURL kNonBackdropUrl1("https://www.test.com");
+  const GURL kNonBackdropUrl2("https://www.foo.com/path");
+
+  custom_background_service_->AddValidBackdropUrlForTesting(kBackdropUrl1);
+  custom_background_service_->AddValidBackdropUrlForTesting(kBackdropUrl2);
+
+  custom_background_service_->SetCustomBackgroundInfo(kBackdropUrl1, "", "",
+                                                      GURL(), "");
+  auto custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_EQ(kBackdropUrl1, custom_background->custom_background_url);
+  EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+
+  custom_background_service_->SetCustomBackgroundInfo(kNonBackdropUrl1, "", "",
+                                                      GURL(), "");
+  custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_FALSE(custom_background.has_value());
+  EXPECT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+
+  custom_background_service_->SetCustomBackgroundInfo(kBackdropUrl2, "", "",
+                                                      GURL(), "");
+  custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_EQ(kBackdropUrl2, custom_background->custom_background_url);
+  EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+
+  custom_background_service_->SetCustomBackgroundInfo(kNonBackdropUrl2, "", "",
+                                                      GURL(), "");
+  custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_FALSE(custom_background.has_value());
+  EXPECT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+}
+
+TEST_F(NtpCustomBackgroundServiceTest, LocalImageDoesNotHaveAttribution) {
+  EXPECT_CALL(observer_, OnCustomBackgroundImageUpdated).Times(2);
+  ASSERT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+  const GURL kUrl("https://www.foo.com");
+  const std::string kAttributionLine1 = "foo";
+  const std::string kAttributionLine2 = "bar";
+  const GURL kActionUrl("https://www.bar.com");
+
+  sync_preferences::TestingPrefServiceSyncable* pref_service =
+      profile_.GetTestingPrefService();
+  custom_background_service_->AddValidBackdropUrlForTesting(kUrl);
+  custom_background_service_->SetCustomBackgroundInfo(
+      kUrl, kAttributionLine1, kAttributionLine2, kActionUrl, "");
+
+  auto custom_background = custom_background_service_->GetCustomBackground();
+  ASSERT_EQ(kAttributionLine1,
+            custom_background->custom_background_attribution_line_1);
+  ASSERT_EQ(kAttributionLine2,
+            custom_background->custom_background_attribution_line_2);
+  ASSERT_EQ(kActionUrl,
+            custom_background->custom_background_attribution_action_url);
+  ASSERT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+
+  base::FilePath profile_path = profile_.GetPath();
+  base::FilePath path(profile_path.AppendASCII(
+      chrome::kChromeUIUntrustedNewTabPageBackgroundFilename));
+  base::WriteFile(path, "background_image", 16);
+  base::ThreadPoolInstance::Get()->FlushForTesting();
+
+  custom_background_service_->SelectLocalBackgroundImage(path);
+  task_environment_.RunUntilIdle();
+
+  custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_TRUE(
+      base::StartsWith(custom_background->custom_background_url.spec(),
+                       chrome::kChromeUIUntrustedNewTabPageBackgroundUrl,
+                       base::CompareCase::SENSITIVE));
+  EXPECT_TRUE(
+      pref_service->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice));
+  EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+  EXPECT_EQ("", custom_background->custom_background_attribution_line_1);
+  EXPECT_EQ("", custom_background->custom_background_attribution_line_2);
+  EXPECT_EQ(GURL(),
+            custom_background->custom_background_attribution_action_url);
+}
+
+TEST_F(NtpCustomBackgroundServiceTest, SetCustomBackgroundCollectionId) {
+  EXPECT_CALL(observer_, OnCustomBackgroundImageUpdated).Times(2);
+  ASSERT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+  const std::string kInvalidId("aarrtt");
+  const std::string kValidId("art");
+
+  // A valid id should update the pref/background.
+  CollectionImage image;
+  image.collection_id = kValidId;
+  image.image_url = GURL("https://www.test.com/");
+  custom_background_service_->SetNextCollectionImageForTesting(image);
+
+  custom_background_service_->AddValidBackdropCollectionForTesting(kValidId);
+  custom_background_service_->SetCustomBackgroundInfo(GURL(), "", "", GURL(),
+                                                      kValidId);
+  task_environment_.RunUntilIdle();
+
+  auto custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_EQ(kValidId, custom_background->collection_id);
+  EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+
+  // An invalid id should clear the pref/background.
+  CollectionImage image2;
+  custom_background_service_->SetNextCollectionImageForTesting(image2);
+  custom_background_service_->SetCustomBackgroundInfo(GURL(), "", "", GURL(),
+                                                      kInvalidId);
+  task_environment_.RunUntilIdle();
+
+  custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_FALSE(custom_background.has_value());
+  EXPECT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+}
+
+TEST_F(NtpCustomBackgroundServiceTest,
+       CollectionIdTakePriorityOverBackgroundURL) {
+  EXPECT_CALL(observer_, OnCustomBackgroundImageUpdated).Times(1);
+  ASSERT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+  const std::string kValidId("art");
+  const GURL kUrl("https://www.foo.com/");
+
+  CollectionImage image;
+  image.collection_id = kValidId;
+  image.image_url = GURL("https://www.test.com/");
+  custom_background_service_->SetNextCollectionImageForTesting(image);
+  custom_background_service_->AddValidBackdropUrlForTesting(kUrl);
+  custom_background_service_->AddValidBackdropCollectionForTesting(kValidId);
+
+  custom_background_service_->SetCustomBackgroundInfo(kUrl, "", "", GURL(),
+                                                      kValidId);
+  task_environment_.RunUntilIdle();
+
+  auto custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_EQ(kValidId, custom_background->collection_id);
+  EXPECT_EQ("https://www.test.com/", custom_background->custom_background_url);
+  EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+}
+
+TEST_F(NtpCustomBackgroundServiceTest, RefreshesBackgroundAfter24Hours) {
+  EXPECT_CALL(observer_, OnCustomBackgroundImageUpdated).Times(2);
+  ASSERT_FALSE(custom_background_service_->IsCustomBackgroundSet());
+  const std::string kValidId("art");
+  const GURL kImageUrl1("https://www.test.com/1/");
+  const GURL kImageUrl2("https://www.test.com/2/");
+
+  custom_background_service_->SetClockForTesting(&clock_);
+  clock_.SetNow(GetReferenceTime());
+
+  // A valid id should update the pref/background.
+  CollectionImage image;
+  image.collection_id = kValidId;
+  image.image_url = kImageUrl1;
+  custom_background_service_->SetNextCollectionImageForTesting(image);
+
+  custom_background_service_->AddValidBackdropCollectionForTesting(kValidId);
+  custom_background_service_->SetCustomBackgroundInfo(GURL(), "", "", GURL(),
+                                                      kValidId);
+  task_environment_.RunUntilIdle();
+
+  auto custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_EQ(kValidId, custom_background->collection_id);
+  EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+
+  CollectionImage image2;
+  image2.collection_id = kValidId;
+  image2.image_url = kImageUrl2;
+  custom_background_service_->SetNextCollectionImageForTesting(image2);
+
+  // Should not refresh background.
+  custom_background_service_->RefreshBackgroundIfNeeded();
+  task_environment_.RunUntilIdle();
+  custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_EQ(kValidId, custom_background->collection_id);
+  EXPECT_EQ(kImageUrl1, custom_background->custom_background_url);
+  EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+
+  clock_.Advance(base::TimeDelta::FromHours(25));
+
+  // Should refresh background after >24 hours.
+  custom_background_service_->RefreshBackgroundIfNeeded();
+  task_environment_.RunUntilIdle();
+  custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_EQ(kValidId, custom_background->collection_id);
+  EXPECT_EQ(kImageUrl2, custom_background->custom_background_url);
+  EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+}
diff --git a/chrome/browser/search/instant_service.cc b/chrome/browser/search/instant_service.cc
index 3160d60..ef5deb4 100644
--- a/chrome/browser/search/instant_service.cc
+++ b/chrome/browser/search/instant_service.cc
@@ -22,7 +22,7 @@
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/ntp_tiles/chrome_most_visited_sites_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/search/background/ntp_background_service_factory.h"
+#include "chrome/browser/search/background/ntp_custom_background_service_factory.h"
 #include "chrome/browser/search/chrome_colors/chrome_colors_service.h"
 #include "chrome/browser/search/instant_service_factory.h"
 #include "chrome/browser/search/instant_service_observer.h"
@@ -58,79 +58,12 @@
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/color_utils.h"
 
-namespace {
-
-const char kNtpCustomBackgroundURL[] = "background_url";
-const char kNtpCustomBackgroundAttributionLine1[] = "attribution_line_1";
-const char kNtpCustomBackgroundAttributionLine2[] = "attribution_line_2";
-const char kNtpCustomBackgroundAttributionActionURL[] =
-    "attribution_action_url";
-const char kNtpCustomBackgroundCollectionId[] = "collection_id";
-const char kNtpCustomBackgroundResumeToken[] = "resume_token";
-const char kNtpCustomBackgroundRefreshTimestamp[] = "refresh_timestamp";
-
-base::DictionaryValue GetBackgroundInfoAsDict(
-    const GURL& background_url,
-    const std::string& attribution_line_1,
-    const std::string& attribution_line_2,
-    const GURL& action_url,
-    const absl::optional<std::string>& collection_id,
-    const absl::optional<std::string>& resume_token,
-    const absl::optional<int> refresh_timestamp) {
-  base::DictionaryValue background_info;
-  background_info.SetKey(kNtpCustomBackgroundURL,
-                         base::Value(background_url.spec()));
-  background_info.SetKey(kNtpCustomBackgroundAttributionLine1,
-                         base::Value(attribution_line_1));
-  background_info.SetKey(kNtpCustomBackgroundAttributionLine2,
-                         base::Value(attribution_line_2));
-  background_info.SetKey(kNtpCustomBackgroundAttributionActionURL,
-                         base::Value(action_url.spec()));
-  background_info.SetKey(kNtpCustomBackgroundCollectionId,
-                         base::Value(collection_id.value_or("")));
-  background_info.SetKey(kNtpCustomBackgroundResumeToken,
-                         base::Value(resume_token.value_or("")));
-  background_info.SetKey(kNtpCustomBackgroundRefreshTimestamp,
-                         base::Value(refresh_timestamp.value_or(0)));
-
-  return background_info;
-}
-
-base::Value NtpCustomBackgroundDefaults() {
-  base::Value defaults(base::Value::Type::DICTIONARY);
-  defaults.SetKey(kNtpCustomBackgroundURL,
-                  base::Value(base::Value::Type::STRING));
-  defaults.SetKey(kNtpCustomBackgroundAttributionLine1,
-                  base::Value(base::Value::Type::STRING));
-  defaults.SetKey(kNtpCustomBackgroundAttributionLine2,
-                  base::Value(base::Value::Type::STRING));
-  defaults.SetKey(kNtpCustomBackgroundAttributionActionURL,
-                  base::Value(base::Value::Type::STRING));
-  defaults.SetKey(kNtpCustomBackgroundCollectionId,
-                  base::Value(base::Value::Type::STRING));
-  defaults.SetKey(kNtpCustomBackgroundResumeToken,
-                  base::Value(base::Value::Type::STRING));
-  defaults.SetKey(kNtpCustomBackgroundRefreshTimestamp,
-                  base::Value(base::Value::Type::INTEGER));
-  return defaults;
-}
-
-void CopyFileToProfilePath(const base::FilePath& from_path,
-                           const base::FilePath& profile_path) {
-  base::CopyFile(from_path,
-                 profile_path.AppendASCII(
-                     chrome::kChromeUIUntrustedNewTabPageBackgroundFilename));
-}
-
-}  // namespace
-
 InstantService::InstantService(Profile* profile)
     : profile_(profile),
       most_visited_info_(std::make_unique<InstantMostVisitedInfo>()),
       pref_service_(profile_->GetPrefs()),
       native_theme_(ui::NativeTheme::GetInstanceForNativeUi()),
-      background_updated_timestamp_(base::TimeTicks::Now()),
-      clock_(base::DefaultClock::GetInstance()) {
+      background_updated_timestamp_(base::TimeTicks::Now()) {
   // The initialization below depends on a typical set of browser threads. Skip
   // it if we are running in a unit test without the full suite.
   if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI))
@@ -150,7 +83,8 @@
         this, ntp_tiles::kMaxNumMostVisited);
   }
 
-  background_service_ = NtpBackgroundServiceFactory::GetForProfile(profile_);
+  custom_background_service_ =
+      NtpCustomBackgroundServiceFactory::GetForProfile(profile_);
 
   // Listen for theme installation.
   ThemeServiceFactory::GetForProfile(profile_)->AddObserver(this);
@@ -169,17 +103,10 @@
   content::URLDataSource::Add(profile_,
                               std::make_unique<MostVisitedIframeSource>());
 
-  // Update theme info when the pref is changed via Sync.
-  pref_change_registrar_.Init(pref_service_);
-  pref_change_registrar_.Add(
-      prefs::kNtpCustomBackgroundDict,
-      base::BindRepeating(&InstantService::UpdateBackgroundFromSync,
-                          weak_ptr_factory_.GetWeakPtr()));
-
   theme_observation_.Observe(native_theme_);
 
-  if (background_service_)
-    background_service_observation_.Observe(background_service_);
+  if (custom_background_service_)
+    custom_background_service_observation_.Observe(custom_background_service_);
 }
 
 InstantService::~InstantService() = default;
@@ -236,20 +163,14 @@
   NotifyAboutNtpTheme();
 }
 
-void InstantService::UpdateBackgroundFromSync() {
-  // Any incoming change to synced background data should clear the local image.
-  pref_service_->SetBoolean(prefs::kNtpCustomBackgroundLocalToDevice, false);
-  RemoveLocalBackgroundImageCopy();
-  UpdateNtpTheme();
-}
-
 void InstantService::UpdateMostVisitedInfo() {
   NotifyAboutMostVisitedInfo();
 }
 
 void InstantService::ResetCustomBackgroundInfo() {
-  SetCustomBackgroundInfo(GURL(), std::string(), std::string(), GURL(),
-                          std::string());
+  if (custom_background_service_) {
+    custom_background_service_->ResetCustomBackgroundInfo();
+  }
 }
 
 void InstantService::SetCustomBackgroundInfo(
@@ -258,64 +179,23 @@
     const std::string& attribution_line_2,
     const GURL& action_url,
     const std::string& collection_id) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (IsCustomBackgroundDisabledByPolicy()) {
-    return;
-  }
-  bool is_backdrop_collection =
-      background_service_ &&
-      background_service_->IsValidBackdropCollection(collection_id);
-  bool is_backdrop_url =
-      background_service_ &&
-      background_service_->IsValidBackdropUrl(background_url);
-
-  bool need_forced_refresh =
-      pref_service_->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice) &&
-      pref_service_->FindPreference(prefs::kNtpCustomBackgroundDict)
-          ->IsDefaultValue();
-  pref_service_->SetBoolean(prefs::kNtpCustomBackgroundLocalToDevice, false);
-  RemoveLocalBackgroundImageCopy();
-
-  background_updated_timestamp_ = base::TimeTicks::Now();
-
-  if (!collection_id.empty() && is_backdrop_collection) {
-    background_service_->FetchNextCollectionImage(collection_id, absl::nullopt);
-  } else if (background_url.is_valid() && is_backdrop_url) {
-    base::DictionaryValue background_info = GetBackgroundInfoAsDict(
+  if (custom_background_service_) {
+    custom_background_service_->SetCustomBackgroundInfo(
         background_url, attribution_line_1, attribution_line_2, action_url,
-        absl::nullopt, absl::nullopt, absl::nullopt);
-    pref_service_->Set(prefs::kNtpCustomBackgroundDict, background_info);
-  } else {
-    pref_service_->ClearPref(prefs::kNtpCustomBackgroundDict);
-
-    // If this device was using a local image and did not have a non-local
-    // background saved, UpdateBackgroundFromSync will not fire. Therefore, we
-    // need to force a refresh here.
-    if (need_forced_refresh) {
-      UpdateNtpTheme();
-    }
+        collection_id);
   }
 }
 
-void InstantService::SetBackgroundToLocalResource() {
-  background_updated_timestamp_ = base::TimeTicks::Now();
-  pref_service_->SetBoolean(prefs::kNtpCustomBackgroundLocalToDevice, true);
-  UpdateNtpTheme();
-}
-
 void InstantService::SelectLocalBackgroundImage(const base::FilePath& path) {
-  if (IsCustomBackgroundDisabledByPolicy()) {
-    return;
+  if (custom_background_service_) {
+    custom_background_service_->SelectLocalBackgroundImage(path);
   }
-  base::ThreadPool::PostTaskAndReply(
-      FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
-      base::BindOnce(&CopyFileToProfilePath, path, profile_->GetPath()),
-      base::BindOnce(&InstantService::SetBackgroundToLocalResource,
-                     weak_ptr_factory_.GetWeakPtr()));
 }
 
 NtpTheme* InstantService::GetInitializedNtpTheme() {
-  RefreshBackgroundIfNeeded();
+  if (custom_background_service_) {
+    custom_background_service_->RefreshBackgroundIfNeeded();
+  }
 
   if (!theme_)
     BuildNtpTheme();
@@ -338,30 +218,15 @@
   ThemeServiceFactory::GetForProfile(profile_)->RemoveObserver(this);
 }
 
-void InstantService::OnNextCollectionImageAvailable() {
-  auto image = background_service_->next_image();
-  std::string attribution1;
-  std::string attribution2;
-  if (image.attribution.size() > 0)
-    attribution1 = image.attribution[0];
-  if (image.attribution.size() > 1)
-    attribution2 = image.attribution[1];
-
-  std::string resume_token = background_service_->next_image_resume_token();
-  int64_t timestamp = (clock_->Now() + base::TimeDelta::FromDays(1)).ToTimeT();
-
-  base::DictionaryValue background_info = GetBackgroundInfoAsDict(
-      image.image_url, attribution1, attribution2, image.attribution_action_url,
-      image.collection_id, resume_token, timestamp);
-
+void InstantService::OnCustomBackgroundImageUpdated() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  pref_service_->Set(prefs::kNtpCustomBackgroundDict, background_info);
+  UpdateNtpTheme();
 }
 
-void InstantService::OnNtpBackgroundServiceShuttingDown() {
+void InstantService::OnNtpCustomBackgroundServiceShuttingDown() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  background_service_observation_.Reset();
-  background_service_ = nullptr;
+  custom_background_service_observation_.Reset();
+  custom_background_service_ = nullptr;
 }
 
 void InstantService::Observe(int type,
@@ -511,84 +376,32 @@
     return;
   }
 
-  if (pref_service_->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice)) {
-    // Add a timestamp to the url to prevent the browser from using a cached
-    // version when "Upload an image" is used multiple times.
-    std::string time_string = std::to_string(base::Time::Now().ToTimeT());
-    std::string local_string(chrome::kChromeUIUntrustedNewTabPageBackgroundUrl);
-    GURL timestamped_url(local_string + "?ts=" + time_string);
-    GetInitializedNtpTheme()->custom_background_url = timestamped_url;
-    GetInitializedNtpTheme()->custom_background_attribution_line_1 =
-        std::string();
-    GetInitializedNtpTheme()->custom_background_attribution_line_2 =
-        std::string();
-    GetInitializedNtpTheme()->custom_background_attribution_action_url = GURL();
-    return;
-  }
+  auto custom_background =
+      custom_background_service_
+          ? custom_background_service_->GetCustomBackground()
+          : absl::optional<CustomBackground>();
 
-  // Attempt to get custom background URL from preferences.
-  GURL custom_background_url;
-  if (!IsCustomBackgroundPrefValid(custom_background_url)) {
+  if (!custom_background) {
     ResetCustomBackgroundNtpTheme();
     return;
   }
 
-  ApplyCustomBackgroundNtpTheme();
-}
-
-void InstantService::ApplyCustomBackgroundNtpTheme() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  const base::DictionaryValue* background_info =
-      pref_service_->GetDictionary(prefs::kNtpCustomBackgroundDict);
-  GURL custom_background_url(
-      background_info->FindKey(kNtpCustomBackgroundURL)->GetString());
-
-  std::string collection_id;
-  const base::Value* id_value =
-      background_info->FindKey(kNtpCustomBackgroundCollectionId);
-  if (id_value)
-    collection_id = id_value->GetString();
-
-  // Set custom background information in theme info (attributions are
-  // optional).
-  const base::Value* attribution_line_1 =
-      background_info->FindKey(kNtpCustomBackgroundAttributionLine1);
-  const base::Value* attribution_line_2 =
-      background_info->FindKey(kNtpCustomBackgroundAttributionLine2);
-  const base::Value* attribution_action_url =
-      background_info->FindKey(kNtpCustomBackgroundAttributionActionURL);
-  NtpTheme* theme = GetInitializedNtpTheme();
-  theme->custom_background_url = custom_background_url;
-  theme->collection_id = collection_id;
-
-  if (attribution_line_1) {
-    theme->custom_background_attribution_line_1 =
-        background_info->FindKey(kNtpCustomBackgroundAttributionLine1)
-            ->GetString();
-  }
-  if (attribution_line_2) {
-    theme->custom_background_attribution_line_2 =
-        background_info->FindKey(kNtpCustomBackgroundAttributionLine2)
-            ->GetString();
-  }
-  if (attribution_action_url) {
-    GURL action_url(
-        background_info->FindKey(kNtpCustomBackgroundAttributionActionURL)
-            ->GetString());
-
-    if (!action_url.SchemeIsCryptographic()) {
-      theme->custom_background_attribution_action_url = GURL();
-    } else {
-      theme->custom_background_attribution_action_url = action_url;
-    }
-  }
+  GetInitializedNtpTheme()->custom_background_url =
+      custom_background->custom_background_url;
+  GetInitializedNtpTheme()->custom_background_attribution_line_1 =
+      custom_background->custom_background_attribution_line_1;
+  GetInitializedNtpTheme()->custom_background_attribution_line_2 =
+      custom_background->custom_background_attribution_line_2;
+  GetInitializedNtpTheme()->custom_background_attribution_action_url =
+      custom_background->custom_background_attribution_action_url;
+  GetInitializedNtpTheme()->collection_id = custom_background->collection_id;
 }
 
 void InstantService::ResetCustomBackgroundNtpTheme() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  pref_service_->ClearPref(prefs::kNtpCustomBackgroundDict);
-  pref_service_->SetBoolean(prefs::kNtpCustomBackgroundLocalToDevice, false);
-  RemoveLocalBackgroundImageCopy();
+  if (custom_background_service_) {
+    custom_background_service_->ResetCustomBackgroundNtpTheme();
+  }
   FallbackToDefaultNtpTheme();
 }
 
@@ -602,78 +415,37 @@
 }
 
 bool InstantService::IsCustomBackgroundDisabledByPolicy() {
-  // |prefs::kNtpCustomBackgroundDict| is managed by policy only if
-  // |policy::key::kNTPCustomBackgroundEnabled| is set to false and therefore
-  // should be empty.
-  bool managed =
-      pref_service_->IsManagedPreference(prefs::kNtpCustomBackgroundDict);
-  if (managed) {
-    DCHECK(
-        pref_service_->GetDictionary(prefs::kNtpCustomBackgroundDict)->DictEmpty());
-  }
-  return managed;
+  return custom_background_service_ &&
+         custom_background_service_->IsCustomBackgroundDisabledByPolicy();
 }
 
 bool InstantService::IsCustomBackgroundSet() {
-  if (pref_service_->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice))
-    return true;
-
-  GURL custom_background_url;
-  if (!IsCustomBackgroundPrefValid(custom_background_url))
-    return false;
-
-  return true;
+  return custom_background_service_ &&
+         custom_background_service_->IsCustomBackgroundSet();
 }
 
 void InstantService::ResetToDefault() {
   ResetCustomBackgroundNtpTheme();
 }
 
-bool InstantService::IsCustomBackgroundPrefValid(GURL& custom_background_url) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  const base::DictionaryValue* background_info =
-      profile_->GetPrefs()->GetDictionary(prefs::kNtpCustomBackgroundDict);
-  if (!background_info)
-    return false;
-
-  const base::Value* background_url =
-      background_info->FindKey(kNtpCustomBackgroundURL);
-  if (!background_url)
-    return false;
-
-  custom_background_url = GURL(background_url->GetString());
-  return custom_background_url.is_valid();
-}
-
-void InstantService::RemoveLocalBackgroundImageCopy() {
-  base::FilePath path = profile_->GetPath().AppendASCII(
-      chrome::kChromeUIUntrustedNewTabPageBackgroundFilename);
-  base::ThreadPool::PostTask(
-      FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
-      base::BindOnce(base::GetDeleteFileCallback(), path));
-}
-
 void InstantService::AddValidBackdropUrlForTesting(const GURL& url) const {
-  background_service_->AddValidBackdropUrlForTesting(url);
+  custom_background_service_->AddValidBackdropUrlForTesting(url);
 }
 
 void InstantService::AddValidBackdropCollectionForTesting(
     const std::string& collection_id) const {
-  background_service_->AddValidBackdropCollectionForTesting(collection_id);
+  custom_background_service_->AddValidBackdropCollectionForTesting(
+      collection_id);
 }
 
 void InstantService::SetNextCollectionImageForTesting(
     const CollectionImage& image) const {
-  background_service_->SetNextCollectionImageForTesting(image);
+  custom_background_service_->SetNextCollectionImageForTesting(image);
 }
 
 // static
 void InstantService::RegisterProfilePrefs(PrefRegistrySimple* registry) {
-  registry->RegisterDictionaryPref(
-      prefs::kNtpCustomBackgroundDict, NtpCustomBackgroundDefaults(),
-      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-  registry->RegisterBooleanPref(prefs::kNtpCustomBackgroundLocalToDevice,
-                                false);
+  NtpCustomBackgroundService::RegisterProfilePrefs(registry);
 }
 
 // static
@@ -695,29 +467,8 @@
          instant_service->IsInstantProcess(render_process_id);
 }
 
-void InstantService::RefreshBackgroundIfNeeded() {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  const base::DictionaryValue* background_info =
-      profile_->GetPrefs()->GetDictionary(prefs::kNtpCustomBackgroundDict);
-  int64_t refresh_timestamp = 0;
-  const base::Value* timestamp_value =
-      background_info->FindKey(kNtpCustomBackgroundRefreshTimestamp);
-  if (timestamp_value)
-    refresh_timestamp = timestamp_value->GetInt();
-  if (refresh_timestamp == 0)
-    return;
-
-  if (clock_->Now().ToTimeT() > refresh_timestamp) {
-    std::string collection_id =
-        background_info->FindKey(kNtpCustomBackgroundCollectionId)->GetString();
-    std::string resume_token =
-        background_info->FindKey(kNtpCustomBackgroundResumeToken)->GetString();
-    background_service_->FetchNextCollectionImage(collection_id, resume_token);
-  }
-}
-
 void InstantService::SetClockForTesting(base::Clock* clock) {
-  clock_ = clock;
+  custom_background_service_->SetClockForTesting(clock);
 }
 
 void InstantService::SetNtpElementsNtpTheme() {
diff --git a/chrome/browser/search/instant_service.h b/chrome/browser/search/instant_service.h
index 420ac54..6c9774f 100644
--- a/chrome/browser/search/instant_service.h
+++ b/chrome/browser/search/instant_service.h
@@ -17,8 +17,8 @@
 #include "base/observer_list.h"
 #include "base/scoped_observation.h"
 #include "build/build_config.h"
-#include "chrome/browser/search/background/ntp_background_service.h"
-#include "chrome/browser/search/background/ntp_background_service_observer.h"
+#include "chrome/browser/search/background/ntp_custom_background_service.h"
+#include "chrome/browser/search/background/ntp_custom_background_service_observer.h"
 #include "chrome/browser/themes/theme_service_observer.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/keyed_service/core/keyed_service.h"
@@ -56,7 +56,7 @@
 // necessary information (most visited tiles and theme info) updated in those
 // renderer processes.
 class InstantService : public KeyedService,
-                       public NtpBackgroundServiceObserver,
+                       public NtpCustomBackgroundServiceObserver,
                        public content::NotificationObserver,
                        public ntp_tiles::MostVisitedSites::Observer,
                        public ui::NativeThemeObserver,
@@ -108,10 +108,6 @@
   // Invoked to update theme information for the NTP.
   virtual void UpdateNtpTheme();
 
-  // Invoked when a background pref update is received via sync, triggering
-  // an update of theme info.
-  void UpdateBackgroundFromSync();
-
   // Invoked by the InstantController to update most visited items details for
   // NTP.
   void UpdateMostVisitedInfo();
@@ -172,11 +168,9 @@
   // KeyedService:
   void Shutdown() override;
 
-  // NtpBackgroundServiceObserver:
-  void OnCollectionInfoAvailable() override {}
-  void OnCollectionImagesAvailable() override {}
-  void OnNextCollectionImageAvailable() override;
-  void OnNtpBackgroundServiceShuttingDown() override;
+  // NtpCustomBackgroundServiceObserver:
+  void OnCustomBackgroundImageUpdated() override;
+  void OnNtpCustomBackgroundServiceShuttingDown() override;
 
   // content::NotificationObserver:
   void Observe(int type,
@@ -202,32 +196,17 @@
 
   void ApplyOrResetCustomBackgroundNtpTheme();
 
-  void ApplyCustomBackgroundNtpTheme();
-
   // Marked virtual for mocking in tests.
   virtual void ResetCustomBackgroundNtpTheme();
 
   void FallbackToDefaultNtpTheme();
 
-  void RemoveLocalBackgroundImageCopy();
-
-  // Returns false if the custom background pref cannot be parsed, otherwise
-  // returns true and sets custom_background_url to the value in the pref.
-  bool IsCustomBackgroundPrefValid(GURL& custom_background_url);
-
-  // Update the background pref to point to
-  // chrome://new-tab-page/background.jpg.
-  void SetBackgroundToLocalResource();
-
   void SetClockForTesting(base::Clock* clock);
 
   base::TimeTicks GetBackgroundUpdatedTimestampForTesting() {
     return background_updated_timestamp_;
   }
 
-  // Requests a new background image if it hasn't been updated in >24 hours.
-  void RefreshBackgroundIfNeeded();
-
   // Sets NTP elements theme info that are overridden when custom
   // background is used.
   void SetNtpElementsNtpTheme();
@@ -258,17 +237,16 @@
   base::ScopedObservation<ui::NativeTheme, ui::NativeThemeObserver>
       theme_observation_{this};
 
-  base::ScopedObservation<NtpBackgroundService, NtpBackgroundServiceObserver>
-      background_service_observation_{this};
+  base::ScopedObservation<NtpCustomBackgroundService,
+                          NtpCustomBackgroundServiceObserver>
+      custom_background_service_observation_{this};
 
   ui::NativeTheme* native_theme_;
 
-  NtpBackgroundService* background_service_;
+  NtpCustomBackgroundService* custom_background_service_;
 
   base::TimeTicks background_updated_timestamp_;
 
-  base::Clock* clock_;
-
   base::WeakPtrFactory<InstantService> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(InstantService);
diff --git a/chrome/browser/search/instant_service_unittest.cc b/chrome/browser/search/instant_service_unittest.cc
index 4c95607..e280067 100644
--- a/chrome/browser/search/instant_service_unittest.cc
+++ b/chrome/browser/search/instant_service_unittest.cc
@@ -11,7 +11,6 @@
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/test/mock_callback.h"
 #include "build/build_config.h"
-#include "chrome/browser/search/background/ntp_background_service.h"
 #include "chrome/browser/search/instant_service_observer.h"
 #include "chrome/browser/search/instant_unittest_base.h"
 #include "chrome/browser/themes/test/theme_service_changed_waiter.h"
@@ -41,35 +40,6 @@
   MOCK_METHOD1(MostVisitedInfoChanged, void(const InstantMostVisitedInfo&));
 };
 
-base::DictionaryValue GetBackgroundInfoAsDict(const GURL& background_url) {
-  base::DictionaryValue background_info;
-  background_info.SetKey("background_url", base::Value(background_url.spec()));
-  background_info.SetKey("attribution_line_1", base::Value(std::string()));
-  background_info.SetKey("attribution_line_2", base::Value(std::string()));
-  background_info.SetKey("attribution_action_url", base::Value(std::string()));
-  background_info.SetKey("collection_id", base::Value(std::string()));
-  background_info.SetKey("resume_token", base::Value(std::string()));
-  background_info.SetKey("refresh_timestamp", base::Value(0));
-  return background_info;
-}
-
-base::Time GetReferenceTime() {
-  base::Time::Exploded exploded_reference_time;
-  exploded_reference_time.year = 2019;
-  exploded_reference_time.month = 1;
-  exploded_reference_time.day_of_month = 1;
-  exploded_reference_time.day_of_week = 1;
-  exploded_reference_time.hour = 0;
-  exploded_reference_time.minute = 0;
-  exploded_reference_time.second = 0;
-  exploded_reference_time.millisecond = 0;
-
-  base::Time out_time;
-  EXPECT_TRUE(
-      base::Time::FromLocalExploded(exploded_reference_time, &out_time));
-  return out_time;
-}
-
 class MockInstantService : public InstantService {
  public:
   explicit MockInstantService(Profile* profile) : InstantService(profile) {}
@@ -98,53 +68,6 @@
   EXPECT_EQ(ntp_tiles::TileTitleSource::TITLE_TAG, items[0].title_source);
 }
 
-TEST_F(InstantServiceTest, SetCustomBackgroundURL) {
-  ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
-  const GURL kUrl("https://www.foo.com");
-
-  instant_service_->AddValidBackdropUrlForTesting(kUrl);
-  instant_service_->SetCustomBackgroundInfo(kUrl, "", "", GURL(), "");
-
-  NtpTheme* theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_EQ(kUrl, theme->custom_background_url);
-  EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
-}
-
-TEST_F(InstantServiceTest, SetCustomBackgroundURLInvalidURL) {
-  ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
-  const GURL kInvalidUrl("foo");
-  const GURL kValidUrl("https://www.foo.com");
-  instant_service_->AddValidBackdropUrlForTesting(kValidUrl);
-  instant_service_->SetCustomBackgroundInfo(kValidUrl, "", "", GURL(), "");
-
-  NtpTheme* theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_EQ(kValidUrl.spec(), theme->custom_background_url.spec());
-
-  instant_service_->SetCustomBackgroundInfo(kInvalidUrl, "", "", GURL(), "");
-
-  theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_EQ("", theme->custom_background_url.spec());
-  EXPECT_FALSE(instant_service_->IsCustomBackgroundSet());
-}
-
-TEST_F(InstantServiceTest, SetCustomBackgroundInfo) {
-  ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
-  const GURL kUrl("https://www.foo.com");
-  const std::string kAttributionLine1 = "foo";
-  const std::string kAttributionLine2 = "bar";
-  const GURL kActionUrl("https://www.bar.com");
-  instant_service_->AddValidBackdropUrlForTesting(kUrl);
-  instant_service_->SetCustomBackgroundInfo(kUrl, kAttributionLine1,
-                                            kAttributionLine2, kActionUrl, "");
-
-  NtpTheme* theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_EQ(kUrl, theme->custom_background_url);
-  EXPECT_EQ(kAttributionLine1, theme->custom_background_attribution_line_1);
-  EXPECT_EQ(kAttributionLine2, theme->custom_background_attribution_line_2);
-  EXPECT_EQ(kActionUrl, theme->custom_background_attribution_action_url);
-  EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
-}
-
 TEST_F(InstantServiceTest, ChangingSearchProviderClearsNtpThemeAndPref) {
   ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
   const GURL kUrl("https://www.foo.com");
@@ -185,28 +108,6 @@
   EXPECT_FALSE(instant_service_->IsCustomBackgroundSet());
 }
 
-TEST_F(InstantServiceTest, LocalBackgroundImageCopyCreated) {
-  ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
-
-  base::FilePath profile_path = profile()->GetPath();
-  base::FilePath path(profile_path.AppendASCII("test_file"));
-  base::FilePath copy_path(profile_path.AppendASCII(
-      chrome::kChromeUIUntrustedNewTabPageBackgroundFilename));
-
-  base::WriteFile(path, "background_image", 16);
-
-  instant_service_->SelectLocalBackgroundImage(path);
-
-  task_environment()->RunUntilIdle();
-
-  bool file_exists = base::PathExists(copy_path);
-
-  EXPECT_EQ(true, file_exists);
-  EXPECT_EQ(true, profile()->GetTestingPrefService()->GetBoolean(
-                      prefs::kNtpCustomBackgroundLocalToDevice));
-  EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
-}
-
 TEST_F(InstantServiceTest,
        ChangingSearchProviderRemovesLocalBackgroundImageCopy) {
   ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
@@ -229,30 +130,6 @@
   EXPECT_FALSE(instant_service_->IsCustomBackgroundSet());
 }
 
-TEST_F(InstantServiceTest, SettingUrlRemovesLocalBackgroundImageCopy) {
-  ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
-  const GURL kUrl("https://www.foo.com");
-
-  base::FilePath profile_path = profile()->GetPath();
-  base::FilePath path(profile_path.AppendASCII(
-      chrome::kChromeUIUntrustedNewTabPageBackgroundFilename));
-
-  base::WriteFile(path, "background_image", 16);
-
-  instant_service_->AddValidBackdropUrlForTesting(kUrl);
-  instant_service_->SetCustomBackgroundInfo(kUrl, "", "", GURL(), "");
-  instant_service_->UpdateNtpTheme();
-
-  task_environment()->RunUntilIdle();
-
-  bool file_exists = base::PathExists(path);
-
-  EXPECT_EQ(false, file_exists);
-  EXPECT_EQ(false, profile()->GetTestingPrefService()->GetBoolean(
-                       prefs::kNtpCustomBackgroundLocalToDevice));
-  ASSERT_TRUE(instant_service_->IsCustomBackgroundSet());
-}
-
 TEST_F(InstantServiceTest, CustomBackgroundAttributionActionUrlReset) {
   ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
   const GURL kUrl("https://www.foo.com");
@@ -292,124 +169,6 @@
   EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
 }
 
-TEST_F(InstantServiceTest, UpdatingPrefUpdatesNtpTheme) {
-  ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
-  const GURL kUrlFoo("https://www.foo.com");
-  const GURL kUrlBar("https://www.bar.com");
-
-  sync_preferences::TestingPrefServiceSyncable* pref_service =
-      profile()->GetTestingPrefService();
-  pref_service->SetUserPref(
-      prefs::kNtpCustomBackgroundDict,
-      std::make_unique<base::Value>(GetBackgroundInfoAsDict(kUrlFoo)));
-
-  NtpTheme* theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_EQ(kUrlFoo, theme->custom_background_url);
-  EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
-
-  pref_service->SetUserPref(
-      prefs::kNtpCustomBackgroundDict,
-      std::make_unique<base::Value>(GetBackgroundInfoAsDict(kUrlBar)));
-
-  theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_EQ(kUrlBar, theme->custom_background_url);
-  EXPECT_EQ(false,
-            pref_service->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice));
-  EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
-}
-
-TEST_F(InstantServiceTest, SetLocalImage) {
-  ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
-
-  sync_preferences::TestingPrefServiceSyncable* pref_service =
-      profile()->GetTestingPrefService();
-
-  base::FilePath profile_path = profile()->GetPath();
-  base::FilePath path(profile_path.AppendASCII(
-      chrome::kChromeUIUntrustedNewTabPageBackgroundFilename));
-  base::WriteFile(path, "background_image", 16);
-  base::ThreadPoolInstance::Get()->FlushForTesting();
-
-  instant_service_->SelectLocalBackgroundImage(path);
-  task_environment()->RunUntilIdle();
-
-  NtpTheme* theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_TRUE(
-      base::StartsWith(theme->custom_background_url.spec(),
-                       chrome::kChromeUIUntrustedNewTabPageBackgroundUrl,
-                       base::CompareCase::SENSITIVE));
-  EXPECT_TRUE(
-      pref_service->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice));
-  EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
-}
-
-TEST_F(InstantServiceTest, SyncPrefOverridesAndRemovesLocalImage) {
-  ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
-  const GURL kUrl("https://www.foo.com/");
-
-  sync_preferences::TestingPrefServiceSyncable* pref_service =
-      profile()->GetTestingPrefService();
-
-  base::FilePath profile_path = profile()->GetPath();
-  base::FilePath path(profile_path.AppendASCII(
-      chrome::kChromeUIUntrustedNewTabPageBackgroundFilename));
-  base::WriteFile(path, "background_image", 16);
-  base::ThreadPoolInstance::Get()->FlushForTesting();
-
-  instant_service_->SelectLocalBackgroundImage(path);
-  task_environment()->RunUntilIdle();
-
-  EXPECT_TRUE(
-      pref_service->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice));
-  EXPECT_TRUE(base::PathExists(path));
-
-  // Update theme info via Sync.
-  pref_service->SetUserPref(
-      prefs::kNtpCustomBackgroundDict,
-      std::make_unique<base::Value>(GetBackgroundInfoAsDict(kUrl)));
-  task_environment()->RunUntilIdle();
-
-  NtpTheme* theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_EQ(kUrl, theme->custom_background_url);
-  EXPECT_FALSE(
-      pref_service->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice));
-  EXPECT_FALSE(base::PathExists(path));
-  EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
-}
-
-TEST_F(InstantServiceTest, ValidateBackdropUrls) {
-  ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
-  const GURL kBackdropUrl1("https://www.foo.com");
-  const GURL kBackdropUrl2("https://www.bar.com");
-  const GURL kNonBackdropUrl1("https://www.test.com");
-  const GURL kNonBackdropUrl2("https://www.foo.com/path");
-
-  instant_service_->AddValidBackdropUrlForTesting(kBackdropUrl1);
-  instant_service_->AddValidBackdropUrlForTesting(kBackdropUrl2);
-
-  instant_service_->SetCustomBackgroundInfo(kBackdropUrl1, "", "", GURL(), "");
-  NtpTheme* theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_EQ(kBackdropUrl1, theme->custom_background_url);
-  EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
-
-  instant_service_->SetCustomBackgroundInfo(kNonBackdropUrl1, "", "", GURL(),
-                                            "");
-  theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_EQ(GURL(), theme->custom_background_url);
-  EXPECT_FALSE(instant_service_->IsCustomBackgroundSet());
-
-  instant_service_->SetCustomBackgroundInfo(kBackdropUrl2, "", "", GURL(), "");
-  theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_EQ(kBackdropUrl2, theme->custom_background_url);
-  EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
-
-  instant_service_->SetCustomBackgroundInfo(kNonBackdropUrl2, "", "", GURL(),
-                                            "");
-  theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_EQ(GURL(), theme->custom_background_url);
-  EXPECT_FALSE(instant_service_->IsCustomBackgroundSet());
-}
-
 TEST_F(InstantServiceTest, TestNoNtpTheme) {
   instant_service_->theme_ = nullptr;
   EXPECT_NE(nullptr, instant_service_->GetInitializedNtpTheme());
@@ -421,12 +180,6 @@
   EXPECT_NE(nullptr, instant_service_->theme_);
 }
 
-TEST_F(InstantServiceTest, TestResetToDefault) {
-  MockInstantService mock_instant_service_(profile());
-  EXPECT_CALL(mock_instant_service_, ResetCustomBackgroundNtpTheme());
-  mock_instant_service_.ResetToDefault();
-}
-
 class InstantServiceThemeTest : public InstantServiceTest {
  public:
   InstantServiceThemeTest() {}
@@ -440,144 +193,6 @@
   DISALLOW_COPY_AND_ASSIGN(InstantServiceThemeTest);
 };
 
-TEST_F(InstantServiceTest, LocalImageDoesNotHaveAttribution) {
-  ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
-  const GURL kUrl("https://www.foo.com");
-  const std::string kAttributionLine1 = "foo";
-  const std::string kAttributionLine2 = "bar";
-  const GURL kActionUrl("https://www.bar.com");
-
-  sync_preferences::TestingPrefServiceSyncable* pref_service =
-      profile()->GetTestingPrefService();
-  SetUserSelectedDefaultSearchProvider("{google:baseURL}");
-  instant_service_->AddValidBackdropUrlForTesting(kUrl);
-  instant_service_->SetCustomBackgroundInfo(kUrl, kAttributionLine1,
-                                            kAttributionLine2, kActionUrl, "");
-
-  NtpTheme* theme = instant_service_->GetInitializedNtpTheme();
-  ASSERT_EQ(kAttributionLine1, theme->custom_background_attribution_line_1);
-  ASSERT_EQ(kAttributionLine2, theme->custom_background_attribution_line_2);
-  ASSERT_EQ(kActionUrl, theme->custom_background_attribution_action_url);
-  ASSERT_TRUE(instant_service_->IsCustomBackgroundSet());
-
-  base::FilePath profile_path = profile()->GetPath();
-  base::FilePath path(profile_path.AppendASCII(
-      chrome::kChromeUIUntrustedNewTabPageBackgroundFilename));
-  base::WriteFile(path, "background_image", 16);
-  base::ThreadPoolInstance::Get()->FlushForTesting();
-
-  instant_service_->SelectLocalBackgroundImage(path);
-  task_environment()->RunUntilIdle();
-
-  theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_TRUE(
-      base::StartsWith(theme->custom_background_url.spec(),
-                       chrome::kChromeUIUntrustedNewTabPageBackgroundUrl,
-                       base::CompareCase::SENSITIVE));
-  EXPECT_TRUE(
-      pref_service->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice));
-  EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
-  EXPECT_EQ("", theme->custom_background_attribution_line_1);
-  EXPECT_EQ("", theme->custom_background_attribution_line_2);
-  EXPECT_EQ(GURL(), theme->custom_background_attribution_action_url);
-}
-
-TEST_F(InstantServiceTest, SetCustomBackgroundCollectionId) {
-  ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
-  const std::string kInvalidId("aarrtt");
-  const std::string kValidId("art");
-
-  // A valid id should update the pref/background.
-  CollectionImage image;
-  image.collection_id = kValidId;
-  image.image_url = GURL("https://www.test.com/");
-  instant_service_->SetNextCollectionImageForTesting(image);
-
-  instant_service_->AddValidBackdropCollectionForTesting(kValidId);
-  instant_service_->SetCustomBackgroundInfo(GURL(), "", "", GURL(), kValidId);
-  task_environment()->RunUntilIdle();
-
-  NtpTheme* theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_EQ(kValidId, theme->collection_id);
-  EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
-
-  // An invalid id should clear the pref/background.
-  CollectionImage image2;
-  instant_service_->SetNextCollectionImageForTesting(image2);
-  instant_service_->SetCustomBackgroundInfo(GURL(), "", "", GURL(), kInvalidId);
-  task_environment()->RunUntilIdle();
-
-  theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_EQ(std::string(), theme->collection_id);
-  EXPECT_FALSE(instant_service_->IsCustomBackgroundSet());
-}
-
-TEST_F(InstantServiceTest, CollectionIdTakePriorityOverBackgroundURL) {
-  ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
-  const std::string kValidId("art");
-  const GURL kUrl("https://www.foo.com/");
-
-  CollectionImage image;
-  image.collection_id = kValidId;
-  image.image_url = GURL("https://www.test.com/");
-  instant_service_->SetNextCollectionImageForTesting(image);
-  instant_service_->AddValidBackdropUrlForTesting(kUrl);
-  instant_service_->AddValidBackdropCollectionForTesting(kValidId);
-
-  instant_service_->SetCustomBackgroundInfo(kUrl, "", "", GURL(), kValidId);
-  task_environment()->RunUntilIdle();
-
-  NtpTheme* theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_EQ(kValidId, theme->collection_id);
-  EXPECT_EQ("https://www.test.com/", theme->custom_background_url);
-  EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
-}
-
-TEST_F(InstantServiceTest, RefreshesBackgroundAfter24Hours) {
-  ASSERT_FALSE(instant_service_->IsCustomBackgroundSet());
-  const std::string kValidId("art");
-  const GURL kImageUrl1("https://www.test.com/1/");
-  const GURL kImageUrl2("https://www.test.com/2/");
-
-  instant_service_->SetClockForTesting(clock_);
-  clock_->SetNow(GetReferenceTime());
-
-  // A valid id should update the pref/background.
-  CollectionImage image;
-  image.collection_id = kValidId;
-  image.image_url = kImageUrl1;
-  instant_service_->SetNextCollectionImageForTesting(image);
-
-  instant_service_->AddValidBackdropCollectionForTesting(kValidId);
-  instant_service_->SetCustomBackgroundInfo(GURL(), "", "", GURL(), kValidId);
-  task_environment()->RunUntilIdle();
-
-  NtpTheme* theme = instant_service_->GetInitializedNtpTheme();
-  EXPECT_EQ(kValidId, theme->collection_id);
-  EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
-
-  CollectionImage image2;
-  image2.collection_id = kValidId;
-  image2.image_url = kImageUrl2;
-  instant_service_->SetNextCollectionImageForTesting(image2);
-
-  // Should not refresh background.
-  theme = instant_service_->GetInitializedNtpTheme();
-  task_environment()->RunUntilIdle();
-  EXPECT_EQ(kValidId, theme->collection_id);
-  EXPECT_EQ(kImageUrl1, theme->custom_background_url);
-  EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
-
-  clock_->Advance(base::TimeDelta::FromHours(25));
-
-  // Should refresh background after >24 hours.
-  theme = instant_service_->GetInitializedNtpTheme();
-  task_environment()->RunUntilIdle();
-  EXPECT_EQ(kValidId, theme->collection_id);
-  EXPECT_EQ(kImageUrl2, theme->custom_background_url);
-  EXPECT_TRUE(instant_service_->IsCustomBackgroundSet());
-}
-
 TEST_F(InstantServiceTest, SetNTPElementsNtpTheme) {
   const auto& theme_provider =
       ThemeService::GetThemeProviderForProfile(profile());
diff --git a/chrome/browser/service_sandbox_type.h b/chrome/browser/service_sandbox_type.h
index 09bea014..abdb5dc 100644
--- a/chrome/browser/service_sandbox_type.h
+++ b/chrome/browser/service_sandbox_type.h
@@ -228,19 +228,4 @@
 }
 #endif  // defined(OS_WIN)
 
-// sharing::mojom::Sharing
-#if !defined(OS_MAC)
-namespace sharing {
-namespace mojom {
-class Sharing;
-}
-}  // namespace sharing
-
-template <>
-inline sandbox::policy::SandboxType
-content::GetServiceSandboxType<sharing::mojom::Sharing>() {
-  return sandbox::policy::SandboxType::kService;
-}
-#endif  // !defined(OS_MAC)
-
 #endif  // CHROME_BROWSER_SERVICE_SANDBOX_TYPE_H_
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetPropertyModelBuilder.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetPropertyModelBuilder.java
index c56cfd10..1319572 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetPropertyModelBuilder.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetPropertyModelBuilder.java
@@ -66,6 +66,7 @@
     private static final String IMAGE_TYPE = "image/";
     // Variations parameter name for the comma-separated list of third-party activity names.
     private static final String PARAM_SHARING_HUB_THIRD_PARTY_APPS = "sharing-hub-third-party-apps";
+    private static final String SHARING_HUB_APPS_KEY = "ChromeSharingHub";
 
     static final HashSet<Integer> ALL_CONTENT_TYPES_FOR_TEST = new HashSet<>(
             Arrays.asList(ContentType.LINK_PAGE_VISIBLE, ContentType.LINK_PAGE_NOT_VISIBLE,
@@ -274,7 +275,7 @@
 
     private List<String> getThirdPartyActivityNames() {
         String param = ChromeFeatureList.getFieldTrialParamByFeature(
-                ChromeFeatureList.CHROME_SHARING_HUB, PARAM_SHARING_HUB_THIRD_PARTY_APPS);
+                SHARING_HUB_APPS_KEY, PARAM_SHARING_HUB_THIRD_PARTY_APPS);
         if (param.isEmpty()) {
             return FALLBACK_ACTIVITIES;
         }
diff --git a/chrome/browser/ssl/security_state_tab_helper_browsertest.cc b/chrome/browser/ssl/security_state_tab_helper_browsertest.cc
index 9ad086ab..3ce656c 100644
--- a/chrome/browser/ssl/security_state_tab_helper_browsertest.cc
+++ b/chrome/browser/ssl/security_state_tab_helper_browsertest.cc
@@ -2074,9 +2074,13 @@
   SecurityStateTabHelperPrerenderTest& operator=(
       const SecurityStateTabHelperPrerenderTest&) = delete;
 
+  void SetUp() override {
+    prerender_helper_.SetUp(&https_server_);
+    SecurityStateTabHelperTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     web_contents_ = browser()->tab_strip_model()->GetActiveWebContents();
-    prerender_helper_.SetUpOnMainThread(&https_server_);
     SecurityStateTabHelperTest::SetUpOnMainThread();
   }
 
diff --git a/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.cc b/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.cc
index ee2d33b76..6c8a7db 100644
--- a/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.cc
+++ b/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.cc
@@ -331,9 +331,9 @@
 SubresourceFilterPrerenderingBrowserTest::
     ~SubresourceFilterPrerenderingBrowserTest() = default;
 
-void SubresourceFilterPrerenderingBrowserTest::SetUpOnMainThread() {
-  prerender_helper_.SetUpOnMainThread(embedded_test_server());
-  SubresourceFilterListInsertingBrowserTest::SetUpOnMainThread();
+void SubresourceFilterPrerenderingBrowserTest::SetUp() {
+  prerender_helper_.SetUp(embedded_test_server());
+  SubresourceFilterListInsertingBrowserTest::SetUp();
 }
 
 }  // namespace subresource_filter
diff --git a/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.h b/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.h
index 36394e48..2a604e4 100644
--- a/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.h
+++ b/chrome/browser/subresource_filter/subresource_filter_browser_test_harness.h
@@ -226,7 +226,7 @@
   SubresourceFilterPrerenderingBrowserTest();
   ~SubresourceFilterPrerenderingBrowserTest() override;
 
-  void SetUpOnMainThread() override;
+  void SetUp() override;
 
  protected:
   content::test::PrerenderTestHelper prerender_helper_;
diff --git a/chrome/browser/sync/sessions/sync_sessions_router_tab_helper_browsertest.cc b/chrome/browser/sync/sessions/sync_sessions_router_tab_helper_browsertest.cc
index 7f2c634c..6f5b2b5 100644
--- a/chrome/browser/sync/sessions/sync_sessions_router_tab_helper_browsertest.cc
+++ b/chrome/browser/sync/sessions/sync_sessions_router_tab_helper_browsertest.cc
@@ -81,9 +81,13 @@
             base::Unretained(this))) {}
   ~SyncSessionsRouterTabHelperBrowserTest() override = default;
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    InProcessBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     web_contents_ = browser()->tab_strip_model()->GetActiveWebContents();
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     ASSERT_TRUE(embedded_test_server()->Start());
   }
 
diff --git a/chrome/browser/sync/test/integration/apps_helper.cc b/chrome/browser/sync/test/integration/apps_helper.cc
index 71afd76..8963677 100644
--- a/chrome/browser/sync/test/integration/apps_helper.cc
+++ b/chrome/browser/sync/test/integration/apps_helper.cc
@@ -213,8 +213,7 @@
     // registry.
     ASSERT_TRUE(web_app::WebAppProvider::Get(profile)
                     ->registrar()
-                    .AsWebAppRegistrar()
-                    ->GetAppsFromSyncAndPendingInstallation()
+                    .GetAppsFromSyncAndPendingInstallation()
                     .empty());
 
     std::set<web_app::AppId> apps_in_sync_uninstall =
diff --git a/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc b/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
index 5cd74ae..38a63af 100644
--- a/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/ui/browser_dialogs.h"
 #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/web_application_info.h"
 #include "chrome/browser/web_applications/os_integration_manager.h"
 #include "chrome/browser/web_applications/test/test_os_integration_manager.h"
@@ -28,6 +27,7 @@
 #include "chrome/browser/web_applications/web_app_install_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
+#include "chrome/browser/web_applications/web_app_shortcut_manager.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/webapps/browser/installable/installable_metrics.h"
diff --git a/chrome/browser/sync/test/integration/two_client_web_apps_sync_test.cc b/chrome/browser/sync/test/integration/two_client_web_apps_sync_test.cc
index 6642537..fa5067a 100644
--- a/chrome/browser/sync/test/integration/two_client_web_apps_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_web_apps_sync_test.cc
@@ -15,11 +15,11 @@
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
-#include "chrome/browser/web_applications/components/app_icon_manager.h"
 #include "chrome/browser/web_applications/components/web_app_utils.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
 #include "chrome/browser/web_applications/os_integration_manager.h"
 #include "chrome/browser/web_applications/test/web_app_install_observer.h"
+#include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/common/chrome_features.h"
diff --git a/chrome/browser/tab_contents/view_source_browsertest.cc b/chrome/browser/tab_contents/view_source_browsertest.cc
index 5b70222..7f2c3a7 100644
--- a/chrome/browser/tab_contents/view_source_browsertest.cc
+++ b/chrome/browser/tab_contents/view_source_browsertest.cc
@@ -776,9 +776,9 @@
   content::WebContents* target() const { return target_; }
   void set_target(content::WebContents* target) { target_ = target; }
 
-  void SetUpOnMainThread() override {
-    ViewSourceTest::SetUpOnMainThread();
-    prerender_test_helper().SetUpOnMainThread(embedded_test_server());
+  void SetUp() override {
+    prerender_test_helper().SetUp(embedded_test_server());
+    ViewSourceTest::SetUp();
   }
 
  private:
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 95bf5cc..4322785 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2229,8 +2229,6 @@
       "views/crostini/crostini_update_component_view.h",
       "views/crostini/crostini_update_filesystem_view.cc",
       "views/crostini/crostini_update_filesystem_view.h",
-      "views/extensions/print_job_confirmation_dialog_view.cc",
-      "views/extensions/print_job_confirmation_dialog_view.h",
       "views/extensions/request_file_system_dialog_view.cc",
       "views/extensions/request_file_system_dialog_view.h",
       "views/frame/browser_frame_ash.cc",
@@ -2250,6 +2248,8 @@
       "web_applications/file_stream_data_pipe_getter.h",
 
       # On chromeos, file manager extension handles the file open/save dialog.
+      "tabs/existing_window_sub_menu_model_chromeos.cc",
+      "tabs/existing_window_sub_menu_model_chromeos.h",
       "views/select_file_dialog_extension.cc",
       "views/select_file_dialog_extension.h",
       "views/select_file_dialog_extension_factory.cc",
@@ -2950,9 +2950,11 @@
     ]
   }
 
-  if (is_chromeos_ash || is_chromeos_lacros) {
+  if (is_chromeos) {
     sources += [
       "platform_keys_certificate_selector_chromeos.h",
+      "views/extensions/print_job_confirmation_dialog_view.cc",
+      "views/extensions/print_job_confirmation_dialog_view.h",
       "views/frame/browser_frame_header_chromeos.cc",
       "views/frame/browser_frame_header_chromeos.h",
       "views/frame/browser_non_client_frame_view_chromeos.cc",
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
index 3e8e13f..cc84c90 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
@@ -297,6 +297,8 @@
      *         AutocompleteController associated with the supplied profile.
      */
     static AutocompleteController getForProfile(Profile profile) {
+        assert profile != null : "AutocompleteController cannot be created for null profile";
+        if (profile == null) return null;
         return AutocompleteControllerJni.get().getForProfile(profile);
     }
 
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java
index 0bf5053..879327f 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinator.java
@@ -379,7 +379,7 @@
      *         match built with the user's default search engine, or a NAVIGATION match.
      */
     @Deprecated
-    public static AutocompleteMatch classify(Profile profile, String query) {
+    public static AutocompleteMatch classify(@NonNull Profile profile, @NonNull String query) {
         return AutocompleteController.getForProfile(profile).classify(query, false);
     }
 
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandler.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandler.java
index 4cbdfef..3826b94 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandler.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandler.java
@@ -546,11 +546,13 @@
                 }
             }
 
-            AutocompleteMatch match =
-                    AutocompleteCoordinator.classify(mProfileSupplier.get(), topResultQuery);
+            AutocompleteMatch match = null;
+            if (mProfileSupplier.hasValue()) {
+                match = AutocompleteCoordinator.classify(mProfileSupplier.get(), topResultQuery);
+            }
 
-            String url = match.getUrl().getSpec();
-            if (match.isSearchSuggestion()) {
+            String url;
+            if (match == null || match.isSearchSuggestion()) {
                 url = TemplateUrlServiceFactory.get()
                               .getUrlForVoiceSearchQuery(topResultQuery)
                               .getSpec();
@@ -563,6 +565,8 @@
                     assert !url.contains("#") : "URL must not contain a fragment.";
                     url += "&hl=" + topResult.getLanguage();
                 }
+            } else {
+                url = match.getUrl().getSpec();
             }
 
             mDelegate.loadUrlFromVoice(url);
@@ -688,12 +692,21 @@
             // If the string appears to be a URL, then use it instead of the string returned from
             // the voice engine.
             String culledString = strings.get(i).replaceAll(" ", "");
-            AutocompleteMatch match =
-                    AutocompleteCoordinator.classify(mProfileSupplier.get(), culledString);
-            String url = match.isSearchSuggestion() ? null : match.getUrl().getSpec();
+
+            AutocompleteMatch match = null;
+            if (mProfileSupplier.hasValue()) {
+                match = AutocompleteCoordinator.classify(mProfileSupplier.get(), culledString);
+            }
+
+            String urlOrSearchQuery;
+            if (match == null || match.isSearchSuggestion()) {
+                urlOrSearchQuery = strings.get(i);
+            } else {
+                urlOrSearchQuery = culledString;
+            }
+
             String language = languages == null ? null : languages.get(i);
-            results.add(new VoiceResult(
-                    url == null ? strings.get(i) : culledString, confidences[i], language));
+            results.add(new VoiceResult(urlOrSearchQuery, confidences[i], language));
         }
         return results;
     }
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 844bed9..3357aa7 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -1432,6 +1432,17 @@
         Chrome has saved you <ph name="gigabytes">%1$d<ex>10</ex></ph> GB
       </message>
 
+       <!-- Dangerous download -->
+      <message name="IDS_DANGEROUS_DOWNLOAD_DIALOG_CONFIRM_TEXT" desc="Text label for the confirm button on the dangerous download dialog for user to confirm the download.">
+        Download anyway
+      </message>
+      <message name="IDS_DANGEROUS_DOWNLOAD_DIALOG_TEXT" desc="Warning message shown on a dialog to ask user to validate the download of a dangerous file.">
+        Do you want to download <ph name="FILE_NAME">%1$s<ex>malware.exe</ex></ph> anyway?
+      </message>
+      <message name="IDS_DANGEROUS_DOWNLOAD_DIALOG_TITLE" desc="Title of the dialog for asking user to confirm a dangerous download">
+        File might be harmful
+      </message>
+
       <!-- Download later -->
       <message name="IDS_DOWNLOAD_LATER_DIALOG_TITLE" desc="Title of the download later dialog that let the user to choose the time to download.">
         Download later instead?
@@ -2884,8 +2895,12 @@
       <message name="IDS_DISCOVER_NO_CARDS_INSTRUCTIONS" desc="Message body for the article feed on the New Tab Page. Instructs the user to come back later when new feed content is available. Please translate 'stories' to match its use in TC ID 792300740424433300. Also see TC ID 7331961212006720987 for the corresponding message in Discover.">Check back later for new stories</message>
       <message name="IDS_DISCOVER_CANT_REFRESH" desc="Message title for the article feed on the New Tab Page. Shown when feed content is not available because there is no internet connection. Please use the branded term for Discover, as listed under Product Names in the Google Glossary Manager (TC ID 1799975766543019278). Also see TC ID 6114827482740570724 for the corresponding message in Discover.">Can't refresh Discover</message>
       <message name="IDS_DISCOVER_CANT_REFRESH_INSTRUCTIONS" desc="Message body for the article feed on the New Tab Page. Instructs the user to wait and try to refresh the feed once the internet connection returns. Please translate 'stories' to match its use in TC ID 792300740424433300. Also see TC ID 6114827482740570724 for the corresponding message in Discover.">Check back later for new stories</message>
-      <message name="IDS_WEB_FEED_NO_CARDS_TITLE" desc="Message title for the user-customizable web feed on the New Tab Page. Shown when the user either has not followed any websites or when the user has followed some websites but the sites do not have any new articles. Please translate 'stories' to match its use in TC ID 792300740424433300.">You'll find stories here</message>
-      <message name="IDS_WEB_FEED_NO_CARDS_INSTRUCTIONS" desc="Message body for the user-customizable web feed on the New Tab Page. Shown when the user either has not followed any websites or when the user has followed some websites but the sites do not have any new articles. Instructs the user to follow more sites or check back later. Please translate 'Follow' (with capital F) to match its use in TC ID 4108314971463891922.">To follow a site, go to the site, open the Chrome menu, and tap Follow.</message>
+      <message name="IDS_WEB_FEED_NO_CARDS_TITLE" desc="Message title for the user-customizable web feed on the New Tab Page. Shown when the user either has not followed any websites or when the user has followed some websites but the sites do not have any new articles.">
+        Keep up with your favorite sites
+      </message>
+      <message name="IDS_WEB_FEED_NO_CARDS_INSTRUCTIONS" desc="Message body for the user-customizable web feed on the New Tab Page. Shown when the user either has not followed any websites or when the user has followed some websites but the sites do not have any new articles. Instructs the user to follow more sites or check back later. Please translate 'Follow' (with capital F) to match its use in TC ID 4108314971463891922.">
+        To follow a site, go to the site, open the Chrome menu, and tap Follow.
+      </message>
       <message name="IDS_FEED_SWIPE_REFRESH_IPH" desc="In-product help that prompts the user to use pull-down gesture to refresh the feed content.">
         For new stories, pull down to refresh
       </message>
@@ -3732,37 +3747,37 @@
       <message name="IDS_WEB_FEED_FOLLOW_LOADING_DESCRIPTION" desc="The content description of the loading spinner after the user presses Follow and is waiting for a response.">
         Following...
       </message>
-      <message name="IDS_WEB_FEED_FOLLOW_SUCCESS_SNACKBAR_MESSAGE" desc="The snackbar message after a successful Web Feed follow.">
-        Followed <ph name="SITE_NAME">%1$s<ex>Google</ex></ph>
+      <message name="IDS_WEB_FEED_FOLLOW_SUCCESS_SNACKBAR_MESSAGE" desc="The snackbar message after a successful Web Feed follow request.">
+        Following <ph name="SITE_NAME">%1$s<ex>Reuters</ex></ph>
       </message>
-      <message name="IDS_WEB_FEED_FOLLOW_SUCCESS_SNACKBAR_ACTION" desc="The snackbar action after a successful Web Feed follow.">
+      <message name="IDS_WEB_FEED_FOLLOW_SUCCESS_SNACKBAR_ACTION" desc="The snackbar action after a successful Web Feed follow request.">
         Go to feed
       </message>
-      <message name="IDS_WEB_FEED_FOLLOW_GENERIC_FAILURE_SNACKBAR_MESSAGE" desc="The generic snackbar message after an unsuccessful Web Feed follow.">
+      <message name="IDS_WEB_FEED_FOLLOW_GENERIC_FAILURE_SNACKBAR_MESSAGE" desc="The generic snackbar message after an unsuccessful Web Feed follow request.">
         Can’t follow. Something went wrong.
       </message>
-      <message name="IDS_WEB_FEED_UNFOLLOW_GENERIC_FAILURE_SNACKBAR_MESSAGE" desc="The generic snackbar message after an unsuccessful Web Feed unfollow.">
+      <message name="IDS_WEB_FEED_UNFOLLOW_GENERIC_FAILURE_SNACKBAR_MESSAGE" desc="The generic snackbar message after an unsuccessful Web Feed unfollow request.">
         Can’t unfollow. Something went wrong.
       </message>
-      <message name="IDS_WEB_FEED_GENERIC_FAILURE_SNACKBAR_ACTION" desc="The snackbar action after an unsuccessful Web Feed follow/unfollow.">
+      <message name="IDS_WEB_FEED_GENERIC_FAILURE_SNACKBAR_ACTION" desc="The snackbar action after an unsuccessful Web Feed follow or unfollow request.">
         Try again
       </message>
-      <message name="IDS_WEB_FEED_UNFOLLOW_SUCCESS_SNACKBAR_MESSAGE" desc="The snackbar message after a successful Web Feed unfollow.">
-        Unfollowed <ph name="SITE_NAME">%1$s<ex>Google</ex></ph>
+      <message name="IDS_WEB_FEED_UNFOLLOW_SUCCESS_SNACKBAR_MESSAGE" desc="The snackbar message after a successful Web Feed unfollow request.">
+        Unfollowed <ph name="SITE_NAME">%1$s<ex>Reuters</ex></ph>
       </message>
-      <message name="IDS_WEB_FEED_UNFOLLOW_SUCCESS_SNACKBAR_ACTION" desc="The snackbar action after a successful Web Feed unfollow.">
+      <message name="IDS_WEB_FEED_UNFOLLOW_SUCCESS_SNACKBAR_ACTION" desc="The snackbar action after a successful Web Feed unfollow request.">
         Refollow
       </message>
-      <message name="IDS_WEB_FEED_POST_FOLLOW_DIALOG_TITLE" desc="The snackbar action after a successful Web Feed unfollow.">
-        You’re following <ph name="SITE_NAME">%1$s<ex>Google</ex></ph>
+      <message name="IDS_WEB_FEED_POST_FOLLOW_DIALOG_TITLE" desc="The title of the dialog presented a few times after a successful Web Feed follow request.">
+        You’re following <ph name="SITE_NAME">%1$s<ex>Reuters</ex></ph>
       </message>
-      <message name="IDS_WEB_FEED_POST_FOLLOW_DIALOG_STORIES_READY_DESCRIPTION" desc="The snackbar action after a successful Web Feed unfollow.">
-        You’ll now see stories from <ph name="SITE_NAME">%1$s<ex>Google</ex></ph> when you open a new tab. You can control the sites you’re following in Manage Follows.
+      <message name="IDS_WEB_FEED_POST_FOLLOW_DIALOG_STORIES_READY_DESCRIPTION" desc="The body text of the dialog presented a few times after a successful Web Feed follow request, when content from that Web Feed is immediately available. Please use the branded term for Discover, as listed under Product Names in the Google Glossary Manager (TC ID 1799975766543019278).">
+        You'll now see stories from <ph name="SITE_NAME">%1$s<ex>Reuters</ex></ph> when you open a new tab. Sites you follow are saved in your Google account. You can manage them in Discover settings.
       </message>
-      <message name="IDS_WEB_FEED_POST_FOLLOW_DIALOG_STORIES_NOT_READY_DESCRIPTION" desc="The snackbar action after a successful Web Feed unfollow.">
-        Soon, you’ll see stories from <ph name="SITE_NAME">%1$s<ex>Google</ex></ph> when you open a new tab. You can control the sites you’re following in Manage Follows.
+      <message name="IDS_WEB_FEED_POST_FOLLOW_DIALOG_STORIES_NOT_READY_DESCRIPTION" desc="The body text of the dialog presented a few times after a successful Web Feed follow request, when content from that Web Feed is not readily available. Please use the branded term for Discover, as listed under Product Names in the Google Glossary Manager (TC ID 1799975766543019278).">
+        Soon, you’ll see stories from <ph name="SITE_NAME">%1$s<ex>Reuters</ex></ph> when you open a new tab. Sites you follow are saved in your Google account. You can manage them in Discover settings.
       </message>
-      <message name="IDS_WEB_FEED_POST_FOLLOW_DIALOG_OPEN_A_NEW_TAB" desc="The snackbar action after a successful Web Feed unfollow.">
+      <message name="IDS_WEB_FEED_POST_FOLLOW_DIALOG_OPEN_A_NEW_TAB" desc="The main action of the dialog presented a few times after a successful Web Feed follow request, when content from from that Web Feed is immediately available.">
         Open a new tab
       </message>
 
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DANGEROUS_DOWNLOAD_DIALOG_CONFIRM_TEXT.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DANGEROUS_DOWNLOAD_DIALOG_CONFIRM_TEXT.png.sha1
new file mode 100644
index 0000000..9d8862a
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DANGEROUS_DOWNLOAD_DIALOG_CONFIRM_TEXT.png.sha1
@@ -0,0 +1 @@
+1f67cb182dca90131ad25ea0304c63e5c2298028
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DANGEROUS_DOWNLOAD_DIALOG_TEXT.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DANGEROUS_DOWNLOAD_DIALOG_TEXT.png.sha1
new file mode 100644
index 0000000..9d8862a
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DANGEROUS_DOWNLOAD_DIALOG_TEXT.png.sha1
@@ -0,0 +1 @@
+1f67cb182dca90131ad25ea0304c63e5c2298028
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DANGEROUS_DOWNLOAD_DIALOG_TITLE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DANGEROUS_DOWNLOAD_DIALOG_TITLE.png.sha1
new file mode 100644
index 0000000..9d8862a
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_DANGEROUS_DOWNLOAD_DIALOG_TITLE.png.sha1
@@ -0,0 +1 @@
+1f67cb182dca90131ad25ea0304c63e5c2298028
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_FOLLOW_SUCCESS_SNACKBAR_MESSAGE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_FOLLOW_SUCCESS_SNACKBAR_MESSAGE.png.sha1
index 1294d42..c90f58b 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_FOLLOW_SUCCESS_SNACKBAR_MESSAGE.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_FOLLOW_SUCCESS_SNACKBAR_MESSAGE.png.sha1
@@ -1 +1 @@
-40ccb741a3d10bc6a88557846d8f529678fe4578
\ No newline at end of file
+fa0905b1a1d4f36cf5fc04b5d34469b9f49adecc
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_NO_CARDS_TITLE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_NO_CARDS_TITLE.png.sha1
index 0716a0a..4b047b2 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_NO_CARDS_TITLE.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_NO_CARDS_TITLE.png.sha1
@@ -1 +1 @@
-71823e8d3b5e2d20280a80ba358e1d8402f5672f
\ No newline at end of file
+aeadab80489f12da5d9ebb0963ffea755e52c940
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_POST_FOLLOW_DIALOG_STORIES_NOT_READY_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_POST_FOLLOW_DIALOG_STORIES_NOT_READY_DESCRIPTION.png.sha1
index 3fa2fac5..6940474c 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_POST_FOLLOW_DIALOG_STORIES_NOT_READY_DESCRIPTION.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_POST_FOLLOW_DIALOG_STORIES_NOT_READY_DESCRIPTION.png.sha1
@@ -1 +1 @@
-e1d5abd09820296a84f0e95c1b524f79b5422575
\ No newline at end of file
+e7ecf10548622ef60b89e9d8818f30d1305e03f1
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_POST_FOLLOW_DIALOG_STORIES_READY_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_POST_FOLLOW_DIALOG_STORIES_READY_DESCRIPTION.png.sha1
index 4b1bcf4..1ba3a43 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_POST_FOLLOW_DIALOG_STORIES_READY_DESCRIPTION.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_POST_FOLLOW_DIALOG_STORIES_READY_DESCRIPTION.png.sha1
@@ -1 +1 @@
-c5b422a780eb5e56f1755b4af248e5f08328451a
\ No newline at end of file
+d2b99a9720ff773661c7e6f38cf811cc7c500809
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_POST_FOLLOW_DIALOG_TITLE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_POST_FOLLOW_DIALOG_TITLE.png.sha1
index 4b1bcf4..1ba3a43 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_POST_FOLLOW_DIALOG_TITLE.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_POST_FOLLOW_DIALOG_TITLE.png.sha1
@@ -1 +1 @@
-c5b422a780eb5e56f1755b4af248e5f08328451a
\ No newline at end of file
+d2b99a9720ff773661c7e6f38cf811cc7c500809
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_UNFOLLOW_SUCCESS_SNACKBAR_MESSAGE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_UNFOLLOW_SUCCESS_SNACKBAR_MESSAGE.png.sha1
index a1f0cf2..c036c12 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_UNFOLLOW_SUCCESS_SNACKBAR_MESSAGE.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_UNFOLLOW_SUCCESS_SNACKBAR_MESSAGE.png.sha1
@@ -1 +1 @@
-a41c25000109842d635d7ed3cf9d4f4da270e478
\ No newline at end of file
+cf16261bbb30ed03076e60f0c220a7fb92d93641
\ No newline at end of file
diff --git a/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java b/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java
index 8dea045f..e270d71 100644
--- a/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java
+++ b/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java
@@ -10,7 +10,6 @@
 import androidx.annotation.NonNull;
 
 import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.base.supplier.BooleanSupplier;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.tab.CurrentTabObserver;
@@ -46,7 +45,7 @@
     private final CurrentTabObserver mTabObserver;
 
     private final Supplier<Integer> mActivityThemeColorSupplier;
-    private final BooleanSupplier mIsTabletSupplier;
+    private final boolean mIsTablet;
 
     /** Whether the theme should apply while in dark mode. */
     private final boolean mAllowThemingInNightMode;
@@ -61,13 +60,13 @@
      * @param context {@link Context} to access resource.
      * @param tabSupplier Supplier of the current tab.
      * @param activityThemeColorSupplier Supplier of activity theme color.
-     * @param isTabletSupplier Supplier of a boolean indicating we're on a tablet device.
+     * @param isTablet Whether the current activity is being run on a tablet.
      * @param allowThemingInNightMode Whether the tab theme should be used when the device is in
      *                                night mode.
      * @param allowBrightThemeColors Whether the tab allows bright theme colors.
      */
     public TopUiThemeColorProvider(Context context, ObservableSupplier<Tab> tabSupplier,
-            Supplier<Integer> activityThemeColorSupplier, BooleanSupplier isTabletSupplier,
+            Supplier<Integer> activityThemeColorSupplier, boolean isTablet,
             boolean allowThemingInNightMode, boolean allowBrightThemeColors) {
         super(context);
         mTabObserver = new CurrentTabObserver(tabSupplier,
@@ -86,7 +85,7 @@
                     if (tab != null) updateColor(tab, tab.getThemeColor(), false);
                 });
         mActivityThemeColorSupplier = activityThemeColorSupplier;
-        mIsTabletSupplier = isTabletSupplier;
+        mIsTablet = isTablet;
         mAllowThemingInNightMode = allowThemingInNightMode;
         mAllowBrightThemeColors = allowBrightThemeColors;
     }
@@ -166,8 +165,8 @@
         boolean disallowDueToNightMode =
                 !mAllowThemingInNightMode && ColorUtils.inNightMode(tab.getContext());
 
-        return tab.isThemingAllowed() && !mIsTabletSupplier.getAsBoolean()
-                && !disallowDueToNightMode && !tab.isNativePage() && !tab.isIncognito();
+        return tab.isThemingAllowed() && !mIsTablet && !disallowDueToNightMode
+                && !tab.isNativePage() && !tab.isIncognito();
     }
 
     /**
diff --git a/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc b/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc
index 6d3bd23e..ce2265e 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc
+++ b/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc
@@ -41,10 +41,10 @@
 #include "chrome/browser/ui/app_list/md_icon_normalizer.h"
 #include "chrome/browser/ui/app_list/test/fake_app_list_model_updater.h"
 #include "chrome/browser/ui/app_list/test/test_app_list_controller_delegate.h"
-#include "chrome/browser/web_applications/components/app_icon_manager.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
 #include "chrome/browser/web_applications/test/test_web_app_provider.h"
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
+#include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_features.h"
diff --git a/chrome/browser/ui/ash/shelf/app_service/app_service_instance_registry_helper.cc b/chrome/browser/ui/ash/shelf/app_service/app_service_instance_registry_helper.cc
index fc0e4812..d805c9e 100644
--- a/chrome/browser/ui/ash/shelf/app_service/app_service_instance_registry_helper.cc
+++ b/chrome/browser/ui/ash/shelf/app_service/app_service_instance_registry_helper.cc
@@ -97,7 +97,7 @@
     // When the user drags a tab to a new browser, or to an other browser, the
     // top window could be changed, so the relation for the tap window and the
     // browser window.
-    UpdateTabWindow(app_id, window);
+    UpdateTabInstance(app_id, apps::Instance::InstanceKey(window));
 
     // If the app is active, it should be started, running, and visible.
     apps::InstanceState state = static_cast<apps::InstanceState>(
@@ -125,14 +125,14 @@
   // error for inconsistent app_id.
   const std::string old_app_id = GetAppId(window);
   if (!old_app_id.empty() && app_id != old_app_id) {
-    RemoveTabWindow(old_app_id, window);
+    RemoveTabInstance(old_app_id, apps::Instance::InstanceKey(window));
     OnInstances(old_app_id, window, std::string(),
                 apps::InstanceState::kDestroyed);
   }
 
   // The tab window could be dragged to a new browser, and the top window could
   // be changed, so clear the old top window first, then add the new top window.
-  UpdateTabWindow(app_id, window);
+  UpdateTabInstance(app_id, apps::Instance::InstanceKey(window));
   apps::InstanceState state = static_cast<apps::InstanceState>(
       apps::InstanceState::kStarted | apps::InstanceState::kRunning);
 
@@ -149,7 +149,7 @@
   if (app_id.empty())
     return;
 
-  RemoveTabWindow(app_id, window);
+  RemoveTabInstance(app_id, apps::Instance::InstanceKey(window));
   OnInstances(app_id, window, std::string(), apps::InstanceState::kDestroyed);
 }
 
@@ -159,7 +159,7 @@
     if (!chrome::FindBrowserWithWindow(window)) {
       // The tabs in the browser should be closed, and tab windows have been
       // removed from |browser_window_to_tab_window_|.
-      DCHECK(!base::Contains(browser_window_to_tab_window_, window));
+      DCHECK(!base::Contains(browser_window_to_tab_instances_, window));
 
       // The browser is removed if the window can't be found, so update the
       // Chrome window instance as destroyed.
@@ -249,8 +249,9 @@
     // state for the tab window to keep one instance for the Web app.
     auto windows = GetWindows(shelf_id.app_id);
     for (auto* it : windows) {
-      auto tab_it = tab_window_to_browser_window_.find(it);
-      if (tab_it == tab_window_to_browser_window_.end() ||
+      apps::Instance::InstanceKey instance_key(it);
+      auto tab_it = tab_instance_to_browser_window_.find(instance_key);
+      if (tab_it == tab_instance_to_browser_window_.end() ||
           tab_it->second != window) {
         continue;
       }
@@ -258,7 +259,7 @@
       // When the user drags a tab to a new browser, or to an other browser, the
       // top window could be changed, so update the relation for the tap window
       // and the browser window.
-      UpdateTabWindow(shelf_id.app_id, it);
+      UpdateTabInstance(shelf_id.app_id, instance_key);
 
       apps::InstanceState state = CalculateVisibilityState(it, visible);
       OnInstances(shelf_id.app_id, it, shelf_id.launch_id, state);
@@ -270,17 +271,17 @@
   apps::InstanceState state = CalculateVisibilityState(window, visible);
   OnInstances(extension_misc::kChromeAppId, window, std::string(), state);
 
-  if (!base::Contains(browser_window_to_tab_window_, window))
+  if (!base::Contains(browser_window_to_tab_instances_, window))
     return;
 
   // For Chrome browser app windows, sets the state for each tab window instance
   // in this browser.
-  for (auto* it : browser_window_to_tab_window_[window]) {
-    const std::string app_id = GetAppId(it);
+  for (const auto& it : browser_window_to_tab_instances_[window]) {
+    const std::string app_id = GetAppId(it.Window());
     if (app_id.empty())
       continue;
-    apps::InstanceState state = CalculateVisibilityState(it, visible);
-    OnInstances(app_id, it, std::string(), state);
+    apps::InstanceState state = CalculateVisibilityState(it.Window(), visible);
+    OnInstances(app_id, it.Window(), std::string(), state);
   }
 }
 
@@ -299,9 +300,9 @@
         continue;
 
       // When the user drags a tab to a new browser, or to an other browser, the
-      // top window could be changed, so the relation for the tap window and the
+      // top window could be changed, so the relation for the tab window and the
       // browser window.
-      UpdateTabWindow(shelf_id.app_id, it);
+      UpdateTabInstance(shelf_id.app_id, apps::Instance::InstanceKey(it));
 
       apps::InstanceState state = CalculateActivatedState(it, active);
       OnInstances(shelf_id.app_id, it, shelf_id.launch_id, state);
@@ -313,7 +314,7 @@
   apps::InstanceState state = CalculateActivatedState(window, active);
   OnInstances(extension_misc::kChromeAppId, window, std::string(), state);
 
-  if (!base::Contains(browser_window_to_tab_window_, window))
+  if (!base::Contains(browser_window_to_tab_instances_, window))
     return;
 
   // For the Chrome browser, when the window is activated, the active tab is set
@@ -342,7 +343,7 @@
     // When the user drags a tab to a new browser, or to an other browser, the
     // top window could be changed, so the relation for the tap window and the
     // browser window.
-    UpdateTabWindow(app_id, contents_window);
+    UpdateTabInstance(app_id, apps::Instance::InstanceKey(contents_window));
 
     OnInstances(app_id, contents_window, std::string(), state);
     return;
@@ -350,12 +351,12 @@
 
   // For Chrome browser app windows, sets the state for each tab window instance
   // in this browser.
-  for (auto* it : browser_window_to_tab_window_[window]) {
-    const std::string app_id = GetAppId(it);
+  for (const auto& it : browser_window_to_tab_instances_[window]) {
+    const std::string app_id = GetAppId(it.Window());
     if (app_id.empty())
       continue;
-    apps::InstanceState state = CalculateActivatedState(it, active);
-    OnInstances(app_id, it, std::string(), state);
+    apps::InstanceState state = CalculateActivatedState(it.Window(), active);
+    OnInstances(app_id, it.Window(), std::string(), state);
   }
 }
 
@@ -499,39 +500,40 @@
   return apps::InstanceState::kUnknown;
 }
 
-void AppServiceInstanceRegistryHelper::AddTabWindow(const std::string& app_id,
-                                                    aura::Window* window) {
+void AppServiceInstanceRegistryHelper::AddTabInstance(
+    const std::string& app_id,
+    const apps::Instance::InstanceKey& instance_key) {
   if (app_id == extension_misc::kChromeAppId)
     return;
 
-  aura::Window* top_level_window = window->GetToplevelWindow();
-  browser_window_to_tab_window_[top_level_window].insert(window);
-  tab_window_to_browser_window_[window] = top_level_window;
+  aura::Window* top_level_window = instance_key.Window()->GetToplevelWindow();
+  browser_window_to_tab_instances_[top_level_window].insert(instance_key);
+  tab_instance_to_browser_window_[instance_key] = top_level_window;
 }
 
-void AppServiceInstanceRegistryHelper::RemoveTabWindow(
+void AppServiceInstanceRegistryHelper::RemoveTabInstance(
     const std::string& app_id,
-    aura::Window* window) {
+    const apps::Instance::InstanceKey& instance_key) {
   if (app_id == extension_misc::kChromeAppId)
     return;
 
-  auto it = tab_window_to_browser_window_.find(window);
-  if (it == tab_window_to_browser_window_.end())
+  auto it = tab_instance_to_browser_window_.find(instance_key);
+  if (it == tab_instance_to_browser_window_.end())
     return;
 
   aura::Window* top_level_window = it->second;
 
-  auto browser_it = browser_window_to_tab_window_.find(top_level_window);
-  DCHECK(browser_it != browser_window_to_tab_window_.end());
-  browser_it->second.erase(window);
+  auto browser_it = browser_window_to_tab_instances_.find(top_level_window);
+  DCHECK(browser_it != browser_window_to_tab_instances_.end());
+  browser_it->second.erase(instance_key);
   if (browser_it->second.empty())
-    browser_window_to_tab_window_.erase(browser_it);
-  tab_window_to_browser_window_.erase(it);
+    browser_window_to_tab_instances_.erase(browser_it);
+  tab_instance_to_browser_window_.erase(it);
 }
 
-void AppServiceInstanceRegistryHelper::UpdateTabWindow(
+void AppServiceInstanceRegistryHelper::UpdateTabInstance(
     const std::string& app_id,
-    aura::Window* window) {
-  RemoveTabWindow(app_id, window);
-  AddTabWindow(app_id, window);
+    const apps::Instance::InstanceKey& instance_key) {
+  RemoveTabInstance(app_id, instance_key);
+  AddTabInstance(app_id, instance_key);
 }
diff --git a/chrome/browser/ui/ash/shelf/app_service/app_service_instance_registry_helper.h b/chrome/browser/ui/ash/shelf/app_service/app_service_instance_registry_helper.h
index 176456a..616fad2 100644
--- a/chrome/browser/ui/ash/shelf/app_service/app_service_instance_registry_helper.h
+++ b/chrome/browser/ui/ash/shelf/app_service/app_service_instance_registry_helper.h
@@ -110,13 +110,16 @@
   // no |window| in InstanceRegistry, returns apps::InstanceState::kUnknown.
   apps::InstanceState GetState(aura::Window* window) const;
 
-  // Adds the tab's |window| to |browser_window_to_tab_window_|.
-  void AddTabWindow(const std::string& app_id, aura::Window* window);
-  // Removes the tab's |window| from |browser_window_to_tab_window_|.
-  void RemoveTabWindow(const std::string& app_id, aura::Window* window);
-  // updates the relation for the tab's |window| and
-  // |browser_window_to_tab_window_|.
-  void UpdateTabWindow(const std::string& app_id, aura::Window* window);
+  // Adds the tab's |instance_key| to |browser_window_to_tab_instance_|.
+  void AddTabInstance(const std::string& app_id,
+                      const apps::Instance::InstanceKey& instance_key);
+  // Removes the tab's |instance_key| from |browser_window_to_tab_instance_|.
+  void RemoveTabInstance(const std::string& app_id,
+                         const apps::Instance::InstanceKey& instance_key);
+  // updates the relation for the tab's |instance_key| and
+  // |browser_window_to_tab_instance_|.
+  void UpdateTabInstance(const std::string& app_id,
+                         const apps::Instance::InstanceKey& instance_key);
 
   AppServiceAppWindowShelfController* controller_ = nullptr;
 
@@ -125,14 +128,15 @@
   // Used to get app info for tabs.
   std::unique_ptr<ShelfControllerHelper> shelf_controller_helper_;
 
-  // Maps the browser window to tab windows in the browser. When the browser
-  // window is inactive or invisible, tab windows in the browser should be
+  // Maps the browser window to tab instances in the browser. When the browser
+  // window is inactive or invisible, tab instances in the browser should be
   // updated accordingly as well.
-  std::map<aura::Window*, std::set<aura::Window*>>
-      browser_window_to_tab_window_;
+  std::map<aura::Window*, std::set<apps::Instance::InstanceKey>>
+      browser_window_to_tab_instances_;
 
-  // Maps the tab window to the browser window in the browser.
-  std::map<aura::Window*, aura::Window*> tab_window_to_browser_window_;
+  // Maps the tab instance to the browser window in the browser.
+  std::map<apps::Instance::InstanceKey, aura::Window*>
+      tab_instance_to_browser_window_;
 
   DISALLOW_COPY_AND_ASSIGN(AppServiceInstanceRegistryHelper);
 };
diff --git a/chrome/browser/ui/ash/shelf/browser_app_status_observer.h b/chrome/browser/ui/ash/shelf/browser_app_status_observer.h
index 1a1a51f..9b9a183 100644
--- a/chrome/browser/ui/ash/shelf/browser_app_status_observer.h
+++ b/chrome/browser/ui/ash/shelf/browser_app_status_observer.h
@@ -13,6 +13,8 @@
 class WebContents;
 }
 
+typedef uint32_t WebContentsId;
+
 // An instance of a browser-based app. Can represent either of:
 // - apps running inside Browser->WebContents,
 // - actual browser instances (a single browser window). In this case `contents`
@@ -23,7 +25,8 @@
 
   std::string app_id;
   Browser* browser;
-  content::WebContents* contents;
+  content::WebContents* web_contents;
+  WebContentsId web_contents_id;
   bool visible;
   bool active;
 };
diff --git a/chrome/browser/ui/ash/shelf/browser_apps_tracker.cc b/chrome/browser/ui/ash/shelf/browser_apps_tracker.cc
index 2d81c7a0..3c92608 100644
--- a/chrome/browser/ui/ash/shelf/browser_apps_tracker.cc
+++ b/chrome/browser/ui/ash/shelf/browser_apps_tracker.cc
@@ -146,6 +146,17 @@
   return it == app_instances_.end() ? nullptr : it->second.get();
 }
 
+const BrowserAppInstance* BrowserAppsTracker::GetAppInstanceByWebContentsId(
+    WebContentsId web_contents_id) const {
+  for (const auto& pair : app_instances_) {
+    const auto& app_instance = pair.second;
+    if (app_instance->web_contents_id == web_contents_id) {
+      return app_instance.get();
+    }
+  }
+  return nullptr;
+}
+
 const BrowserAppInstance* BrowserAppsTracker::GetChromeInstance(
     Browser* browser) const {
   auto it = chrome_instances_.find(browser);
@@ -424,12 +435,12 @@
 void BrowserAppsTracker::CreateAppInstance(std::string app_id,
                                            Browser* browser,
                                            content::WebContents* contents) {
-  // TODO(crbug.com/1203992): generate WebContents ID here
   CreateInstance(app_instances_, contents,
                  base::WrapUnique(new BrowserAppInstance{
                      std::move(app_id),
                      browser,
                      contents,
+                     ++last_web_contents_id_,
                      IsAppVisible(browser, contents),
                      IsAppActive(browser, contents),
                  }));
@@ -450,7 +461,8 @@
                  base::WrapUnique(new BrowserAppInstance{
                      extension_misc::kChromeAppId,
                      browser,
-                     nullptr,
+                     nullptr /* contents */,
+                     0 /* web_contents_id */,
                      IsBrowserVisible(browser),
                      IsBrowserActive(browser),
                  }));
@@ -484,9 +496,9 @@
   DCHECK(browser);
   bool visible;
   bool active;
-  if (instance.contents) {
-    visible = IsAppVisible(browser, instance.contents);
-    active = IsAppActive(browser, instance.contents);
+  if (instance.web_contents) {
+    visible = IsAppVisible(browser, instance.web_contents);
+    active = IsAppActive(browser, instance.web_contents);
   } else {
     visible = IsBrowserVisible(browser);
     active = IsBrowserActive(browser);
diff --git a/chrome/browser/ui/ash/shelf/browser_apps_tracker.h b/chrome/browser/ui/ash/shelf/browser_apps_tracker.h
index 8b98178..af52a38 100644
--- a/chrome/browser/ui/ash/shelf/browser_apps_tracker.h
+++ b/chrome/browser/ui/ash/shelf/browser_apps_tracker.h
@@ -57,6 +57,10 @@
   const BrowserAppInstance* GetAppInstance(
       content::WebContents* contents) const;
 
+  // Get app instance running in a |contents|. Returns null if no app is found.
+  const BrowserAppInstance* GetAppInstanceByWebContentsId(
+      WebContentsId web_contents_id) const;
+
   // Get Chrome instance running in |browser|. Returns null if not found.
   const BrowserAppInstance* GetChromeInstance(Browser* browser) const;
 
@@ -186,6 +190,8 @@
   std::map<Browser*, std::unique_ptr<BrowserAppInstance>> chrome_instances_;
 
   base::ObserverList<BrowserAppStatusObserver, true>::Unchecked observers_;
+
+  WebContentsId last_web_contents_id_{0};
 };
 
 #endif  // CHROME_BROWSER_UI_ASH_SHELF_BROWSER_APPS_TRACKER_H_
diff --git a/chrome/browser/ui/ash/shelf/browser_apps_tracker_browsertest.cc b/chrome/browser/ui/ash/shelf/browser_apps_tracker_browsertest.cc
index dedbe74..a4a6a36 100644
--- a/chrome/browser/ui/ash/shelf/browser_apps_tracker_browsertest.cc
+++ b/chrome/browser/ui/ash/shelf/browser_apps_tracker_browsertest.cc
@@ -42,7 +42,8 @@
         name,
         instance.app_id,
         instance.browser,
-        instance.contents,
+        instance.web_contents,
+        instance.web_contents_id,
         instance.visible,
         instance.active,
     };
@@ -56,7 +57,8 @@
   std::string name;
   std::string app_id;
   Browser* browser;
-  content::WebContents* contents;
+  content::WebContents* web_contents;
+  uint32_t web_contents_id;
   bool visible;
   bool active;
 };
@@ -69,8 +71,9 @@
 
 bool operator==(const TestInstance& e1, const TestInstance& e2) {
   return e1.name == e2.name && e1.app_id == e2.app_id &&
-         e1.browser == e2.browser && e1.contents == e2.contents &&
-         e1.visible == e2.visible && e1.active == e2.active;
+         e1.browser == e2.browser && e1.web_contents == e2.web_contents &&
+         e1.web_contents_id == e2.web_contents_id && e1.visible == e2.visible &&
+         e1.active == e2.active;
 }
 
 bool operator!=(const TestInstance& e1, const TestInstance& e2) {
@@ -82,8 +85,9 @@
     return os << "none";
   }
   os << e.name << "(app_id=" << e.app_id << ", browser=" << e.browser;
-  if (e.contents) {
-    os << ", tab=" << e.contents;
+  if (e.web_contents) {
+    os << ", tab=" << e.web_contents;
+    os << ", tab_id=" << e.web_contents_id;
   }
   os << ", " << (e.visible ? "visible" : "hidden");
   os << ", " << (e.active ? "active" : "inactive");
@@ -245,8 +249,8 @@
     browser = CreateBrowser();
     tab_app1 = InsertForegroundTab(browser, "https://a.example.org");
     recorder.Verify({
-        {"added", kChromeAppId, browser, nullptr, kVisible, kActive},
-        {"added", kAppAId, browser, tab_app1, kVisible, kActive},
+        {"added", kChromeAppId, browser, nullptr, 0, kVisible, kActive},
+        {"added", kAppAId, browser, tab_app1, 1, kVisible, kActive},
     });
   }
 
@@ -257,8 +261,8 @@
 
     tab_app2 = InsertForegroundTab(browser, "https://b.example.org");
     recorder.Verify({
-        {"updated", kAppAId, browser, tab_app1, kVisible, kInactive},
-        {"added", kAppBId, browser, tab_app2, kVisible, kActive},
+        {"updated", kAppAId, browser, tab_app1, 1, kVisible, kInactive},
+        {"added", kAppBId, browser, tab_app2, 2, kVisible, kActive},
     });
   }
 
@@ -269,7 +273,7 @@
 
     InsertForegroundTab(browser, "https://c.example.org");
     recorder.Verify({
-        {"updated", kAppBId, browser, tab_app2, kVisible, kInactive},
+        {"updated", kAppBId, browser, tab_app2, 2, kVisible, kInactive},
     });
   }
 
@@ -290,15 +294,15 @@
 
     recorder.Verify({
         // tab 4 opened: no events for tab 3 as it has no app
-        {"added", kAppAId, browser, tab_app4, kVisible, kActive},
+        {"added", kAppAId, browser, tab_app4, 3, kVisible, kActive},
         // tab 5 opened: tab 4 deactivates
-        {"updated", kAppAId, browser, tab_app4, kVisible, kInactive},
-        {"added", kAppBId, browser, tab_app5, kVisible, kActive},
+        {"updated", kAppAId, browser, tab_app4, 3, kVisible, kInactive},
+        {"added", kAppBId, browser, tab_app5, 4, kVisible, kActive},
         // tab 5 closed: tab 4 reactivates
-        {"removed", kAppBId, browser, tab_app5, kVisible, kActive},
-        {"updated", kAppAId, browser, tab_app4, kVisible, kActive},
+        {"removed", kAppBId, browser, tab_app5, 4, kVisible, kActive},
+        {"updated", kAppAId, browser, tab_app4, 3, kVisible, kActive},
         // tab closed: no events for tab 3 as it has no app
-        {"removed", kAppAId, browser, tab_app4, kVisible, kActive},
+        {"removed", kAppAId, browser, tab_app4, 3, kVisible, kActive},
     });
   }
 
@@ -309,9 +313,9 @@
 
     browser->tab_strip_model()->CloseAllTabs();
     recorder.Verify({
-        {"removed", kAppBId, browser, tab_app2, kVisible, kInactive},
-        {"removed", kAppAId, browser, tab_app1, kVisible, kInactive},
-        {"removed", kChromeAppId, browser, nullptr, kVisible, kActive},
+        {"removed", kAppBId, browser, tab_app2, 2, kVisible, kInactive},
+        {"removed", kAppAId, browser, tab_app1, 1, kVisible, kInactive},
+        {"removed", kChromeAppId, browser, nullptr, 0, kVisible, kActive},
     });
   }
 }
@@ -328,7 +332,7 @@
 
     NavigateActiveTab(browser, "https://a.example.org");
     recorder.Verify({
-        {"added", kAppAId, browser, tab, kVisible, kActive},
+        {"added", kAppAId, browser, tab, 1, kVisible, kActive},
     });
   }
 
@@ -339,8 +343,8 @@
 
     NavigateActiveTab(browser, "https://b.example.org");
     recorder.Verify({
-        {"removed", kAppAId, browser, tab, kVisible, kActive},
-        {"added", kAppBId, browser, tab, kVisible, kActive},
+        {"removed", kAppAId, browser, tab, 1, kVisible, kActive},
+        {"added", kAppBId, browser, tab, 2, kVisible, kActive},
     });
   }
 
@@ -351,7 +355,7 @@
 
     NavigateActiveTab(browser, "https://c.example.org");
     recorder.Verify({
-        {"removed", kAppBId, browser, tab, kVisible, kActive},
+        {"removed", kAppBId, browser, tab, 2, kVisible, kActive},
     });
   }
 
@@ -362,7 +366,7 @@
 
     NavigateActiveTab(browser, "https://b.example.org");
     recorder.Verify({
-        {"added", kAppBId, browser, tab, kVisible, kActive},
+        {"added", kAppBId, browser, tab, 3, kVisible, kActive},
     });
   }
 
@@ -373,7 +377,7 @@
 
     NavigateActiveTab(browser, "https://example.com");
     recorder.Verify({
-        {"removed", kAppBId, browser, tab, kVisible, kActive},
+        {"removed", kAppBId, browser, tab, 3, kVisible, kActive},
     });
   }
 
@@ -384,7 +388,7 @@
 
     NavigateActiveTab(browser, "https://b.example.org");
     recorder.Verify({
-        {"added", kAppBId, browser, tab, kVisible, kActive},
+        {"added", kAppBId, browser, tab, 4, kVisible, kActive},
     });
   }
 }
@@ -401,7 +405,7 @@
     browser = CreateAppBrowser(kAppAId);
     tab = InsertForegroundTab(browser, "https://a.example.org");
     recorder.Verify({
-        {"added", kAppAId, browser, tab, kVisible, kActive},
+        {"added", kAppAId, browser, tab, 1, kVisible, kActive},
     });
   }
 
@@ -412,7 +416,7 @@
 
     browser->tab_strip_model()->CloseAllTabs();
     recorder.Verify({
-        {"removed", kAppAId, browser, tab, kVisible, kActive},
+        {"removed", kAppAId, browser, tab, 1, kVisible, kActive},
     });
   }
 }
@@ -431,7 +435,7 @@
 
     browser->tab_strip_model()->ActivateTabAt(0);
     recorder.Verify({
-        {"updated", kAppAId, browser, tab0, kVisible, kActive},
+        {"updated", kAppAId, browser, tab0, 1, kVisible, kActive},
     });
   }
 
@@ -442,8 +446,8 @@
 
     browser->tab_strip_model()->ActivateTabAt(1);
     recorder.Verify({
-        {"updated", kAppBId, browser, tab1, kVisible, kActive},
-        {"updated", kAppAId, browser, tab0, kVisible, kInactive},
+        {"updated", kAppBId, browser, tab1, 2, kVisible, kActive},
+        {"updated", kAppAId, browser, tab0, 1, kVisible, kInactive},
     });
   }
 
@@ -454,7 +458,7 @@
 
     browser->tab_strip_model()->ActivateTabAt(2);
     recorder.Verify({
-        {"updated", kAppBId, browser, tab1, kVisible, kInactive},
+        {"updated", kAppBId, browser, tab1, 2, kVisible, kInactive},
     });
   }
 }
@@ -475,9 +479,9 @@
 
     browser->window()->GetNativeWindow()->Hide();
     recorder.Verify({
-        {"updated", kChromeAppId, browser, nullptr, kHidden, kInactive},
-        {"updated", kAppAId, browser, bg_tab, kHidden, kInactive},
-        {"updated", kAppBId, browser, fg_tab, kHidden, kInactive},
+        {"updated", kChromeAppId, browser, nullptr, 0, kHidden, kInactive},
+        {"updated", kAppAId, browser, bg_tab, 1, kHidden, kInactive},
+        {"updated", kAppBId, browser, fg_tab, 2, kHidden, kInactive},
     });
   }
 
@@ -488,9 +492,9 @@
 
     browser->window()->GetNativeWindow()->Show();
     recorder.Verify({
-        {"updated", kChromeAppId, browser, nullptr, kVisible, kInactive},
-        {"updated", kAppAId, browser, bg_tab, kVisible, kInactive},
-        {"updated", kAppBId, browser, fg_tab, kVisible, kInactive},
+        {"updated", kChromeAppId, browser, nullptr, 0, kVisible, kInactive},
+        {"updated", kAppAId, browser, bg_tab, 1, kVisible, kInactive},
+        {"updated", kAppBId, browser, fg_tab, 2, kVisible, kInactive},
     });
   }
 }
@@ -518,11 +522,11 @@
     browser1->window()->Activate();
     recorder.Verify({
         // deactivated first
-        {"updated", kChromeAppId, browser2, nullptr, kVisible, kInactive},
-        {"updated", kAppBId, browser2, fg_tab2, kVisible, kInactive},
+        {"updated", kChromeAppId, browser2, nullptr, 0, kVisible, kInactive},
+        {"updated", kAppBId, browser2, fg_tab2, 4, kVisible, kInactive},
         // then activated
-        {"updated", kChromeAppId, browser1, nullptr, kVisible, kActive},
-        {"updated", kAppBId, browser1, fg_tab1, kVisible, kActive},
+        {"updated", kChromeAppId, browser1, nullptr, 0, kVisible, kActive},
+        {"updated", kAppBId, browser1, fg_tab1, 2, kVisible, kActive},
     });
   }
 
@@ -534,11 +538,11 @@
     browser2->window()->Activate();
     recorder.Verify({
         // deactivated first
-        {"updated", kChromeAppId, browser1, nullptr, kVisible, kInactive},
-        {"updated", kAppBId, browser1, fg_tab1, kVisible, kInactive},
+        {"updated", kChromeAppId, browser1, nullptr, 0, kVisible, kInactive},
+        {"updated", kAppBId, browser1, fg_tab1, 2, kVisible, kInactive},
         // then activated
-        {"updated", kChromeAppId, browser2, nullptr, kVisible, kActive},
-        {"updated", kAppBId, browser2, fg_tab2, kVisible, kActive},
+        {"updated", kChromeAppId, browser2, nullptr, 0, kVisible, kActive},
+        {"updated", kAppBId, browser2, fg_tab2, 4, kVisible, kActive},
     });
   }
 }
@@ -580,18 +584,18 @@
   recorder.Verify({
       // background tab in the dragged-from browser gets activated when the
       // active tab is detached
-      {"updated", kAppAId, browser2, bg_tab2, kVisible, kActive},
+      {"updated", kAppAId, browser2, bg_tab2, 4, kVisible, kActive},
       // dragged-from browser goes into background
-      {"updated", kChromeAppId, browser2, nullptr, kVisible, kInactive},
-      {"updated", kAppAId, browser2, bg_tab2, kVisible, kInactive},
+      {"updated", kChromeAppId, browser2, nullptr, 0, kVisible, kInactive},
+      {"updated", kAppAId, browser2, bg_tab2, 4, kVisible, kInactive},
       // dragged-into browser window goes into foreground
-      {"updated", kChromeAppId, browser1, nullptr, kVisible, kActive},
-      {"updated", kAppBId, browser1, fg_tab1, kVisible, kActive},
+      {"updated", kChromeAppId, browser1, nullptr, 0, kVisible, kActive},
+      {"updated", kAppBId, browser1, fg_tab1, 2, kVisible, kActive},
       // previously foreground tab in the dragged-into browser goes into
       // background when the dragged tab is attached to the new browser
-      {"updated", kAppBId, browser1, fg_tab1, kVisible, kInactive},
+      {"updated", kAppBId, browser1, fg_tab1, 2, kVisible, kInactive},
       // dragged tab gets reparented and becomes active in the new browser
-      {"updated", kAppBId, browser1, fg_tab2, kVisible, kActive},
+      {"updated", kAppBId, browser1, fg_tab2, 5, kVisible, kActive},
   });
 }
 
@@ -626,27 +630,27 @@
   auto* b3_tab1_app = tracker_->GetAppInstance(b3_tab1);
 
   EXPECT_EQ(TestInstance::Create(b1_app),
-            (TestInstance{"snapshot", kChromeAppId, browser1, nullptr, kVisible,
-                          kInactive}));
+            (TestInstance{"snapshot", kChromeAppId, browser1, nullptr, 0,
+                          kVisible, kInactive}));
   EXPECT_EQ(TestInstance::Create(b1_tab1_app),
-            (TestInstance{"snapshot", kAppAId, browser1, b1_tab1, kVisible,
+            (TestInstance{"snapshot", kAppAId, browser1, b1_tab1, 1, kVisible,
                           kInactive}));
   EXPECT_EQ(TestInstance::Create(b1_tab2_app), TestInstance{});
   EXPECT_EQ(TestInstance::Create(b1_tab3_app),
-            (TestInstance{"snapshot", kAppBId, browser1, b1_tab3, kVisible,
+            (TestInstance{"snapshot", kAppBId, browser1, b1_tab3, 2, kVisible,
                           kInactive}));
 
   EXPECT_EQ(TestInstance::Create(b2_app),
-            (TestInstance{"snapshot", kChromeAppId, browser2, nullptr, kVisible,
-                          kInactive}));
+            (TestInstance{"snapshot", kChromeAppId, browser2, nullptr, 0,
+                          kVisible, kInactive}));
   EXPECT_EQ(TestInstance::Create(b2_tab1_app), TestInstance{});
   EXPECT_EQ(TestInstance::Create(b2_tab2_app),
-            (TestInstance{"snapshot", kAppBId, browser2, b2_tab2, kVisible,
+            (TestInstance{"snapshot", kAppBId, browser2, b2_tab2, 3, kVisible,
                           kInactive}));
 
   EXPECT_EQ(TestInstance::Create(b3_app), TestInstance{});
   EXPECT_EQ(TestInstance::Create(b3_tab1_app),
-            (TestInstance{"snapshot", kAppBId, browser3, b3_tab1, kVisible,
+            (TestInstance{"snapshot", kAppBId, browser3, b3_tab1, 4, kVisible,
                           kActive}));
 
   EXPECT_EQ(tracker_->GetAppInstancesByAppId(kAppAId),
@@ -662,6 +666,12 @@
   EXPECT_TRUE(tracker_->IsAppRunning(kChromeAppId));
   EXPECT_FALSE(tracker_->IsAppRunning("non-existent-app"));
 
+  EXPECT_EQ(TestInstance::Create(tracker_->GetAppInstanceByWebContentsId(1)),
+            (TestInstance{"snapshot", kAppAId, browser1, b1_tab1, 1, kVisible,
+                          kInactive}));
+  EXPECT_EQ(TestInstance::Create(tracker_->GetAppInstanceByWebContentsId(10)),
+            TestInstance{});
+
   // App A is closed, B and Chrome are still running.
   browser1->tab_strip_model()->CloseAllTabs();
 
@@ -697,8 +707,8 @@
 
     app_id = InstallWebApp("https://c.example.org");
     recorder.Verify({
-        {"added", app_id, browser1, tab1, kVisible, kInactive},
-        {"added", app_id, browser1, tab3, kVisible, kActive},
+        {"added", app_id, browser1, tab1, 1, kVisible, kInactive},
+        {"added", app_id, browser1, tab3, 2, kVisible, kActive},
     });
   }
 
@@ -708,8 +718,8 @@
 
     UninstallWebApp(app_id);
     recorder.Verify({
-        {"removed", app_id, browser1, tab1, kVisible, kInactive},
-        {"removed", app_id, browser1, tab3, kVisible, kActive},
+        {"removed", app_id, browser1, tab1, 1, kVisible, kInactive},
+        {"removed", app_id, browser1, tab3, 2, kVisible, kActive},
     });
   }
 }
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_browsertest.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_browsertest.cc
index 6bdbaf7a..d12d467 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_browsertest.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_browsertest.cc
@@ -77,7 +77,6 @@
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #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"
@@ -88,6 +87,7 @@
 #include "chrome/browser/web_applications/test/web_app_install_observer.h"
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/browser/web_applications/web_app_shortcut_manager.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
diff --git a/chrome/browser/ui/browser_dialogs.h b/chrome/browser/ui/browser_dialogs.h
index bde6962..770c1b4 100644
--- a/chrome/browser/ui/browser_dialogs.h
+++ b/chrome/browser/ui/browser_dialogs.h
@@ -226,7 +226,7 @@
 // user interaction.
 void SetAutoAcceptPWAInstallConfirmationForTesting(bool auto_accept);
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if defined(OS_CHROMEOS)
 
 // Shows the print job confirmation dialog bubble anchored to the toolbar icon
 // for the extension.
@@ -241,7 +241,7 @@
                                     const std::u16string& printer_name,
                                     base::OnceCallback<void(bool)> callback);
 
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // defined(OS_CHROMEOS)
 
 #if defined(OS_MAC)
 
diff --git a/chrome/browser/ui/javascript_dialogs/javascript_dialog_browsertest.cc b/chrome/browser/ui/javascript_dialogs/javascript_dialog_browsertest.cc
index 23c5c660..12b2ee6 100644
--- a/chrome/browser/ui/javascript_dialogs/javascript_dialog_browsertest.cc
+++ b/chrome/browser/ui/javascript_dialogs/javascript_dialog_browsertest.cc
@@ -386,9 +386,14 @@
                                 base::Unretained(this))) {
     feature_list_.InitAndEnableFeature(blink::features::kPrerender2);
   }
+
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    JavaScriptDialogTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     web_contents_ = browser()->tab_strip_model()->GetActiveWebContents();
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     JavaScriptDialogTest::SetUpOnMainThread();
   }
 
diff --git a/chrome/browser/ui/passwords/well_known_change_password_navigation_throttle_browsertest.cc b/chrome/browser/ui/passwords/well_known_change_password_navigation_throttle_browsertest.cc
index c2ea42e6..e744ef77 100644
--- a/chrome/browser/ui/passwords/well_known_change_password_navigation_throttle_browsertest.cc
+++ b/chrome/browser/ui/passwords/well_known_change_password_navigation_throttle_browsertest.cc
@@ -493,8 +493,12 @@
     return browser()->tab_strip_model()->GetActiveWebContents();
   }
 
+  void SetUp() override {
+    prerender_helper_.SetUp(test_server_.get());
+    WellKnownChangePasswordNavigationThrottleBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(test_server_.get());
     Initialize();
   }
 
diff --git a/chrome/browser/ui/search_engines/search_engine_tab_helper_browsertest.cc b/chrome/browser/ui/search_engines/search_engine_tab_helper_browsertest.cc
index 9d89add..3d7147f 100644
--- a/chrome/browser/ui/search_engines/search_engine_tab_helper_browsertest.cc
+++ b/chrome/browser/ui/search_engines/search_engine_tab_helper_browsertest.cc
@@ -160,8 +160,12 @@
             base::Unretained(this))) {}
   ~SearchEngineTabHelperPrerenderingBrowserTest() override = default;
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    InProcessBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     ASSERT_TRUE(test_server_handle_ =
                     embedded_test_server()->StartAndReturnHandle());
   }
diff --git a/chrome/browser/ui/startup/launch_mode_recorder.h b/chrome/browser/ui/startup/launch_mode_recorder.h
index 789ea94..4a6de12 100644
--- a/chrome/browser/ui/startup/launch_mode_recorder.h
+++ b/chrome/browser/ui/startup/launch_mode_recorder.h
@@ -14,8 +14,10 @@
 //   enumeration.
 enum class LaunchMode {
   kToBeDecided = 0,  // Possibly direct launch or via a shortcut.
-  // Launched as an installed web application in a standalone window.
-  kAsWebAppInWindow = 1,
+  // Launched as an installed web application in a standalone window through any
+  // method outside of a platform shortcut or command-line launch..
+  // kAsWebAppInWindow = 1,     Deprecated in favor of kAsWebAppInWindowByUrl
+  //                            and kAsWebAppInWindowByAppId
   kWithUrls = 2,             // Launched with urls in the cmd line.
   kOther = 3,                // Not launched from a shortcut.
   kShortcutNoName = 4,       // Launched from shortcut but no name available.
@@ -40,7 +42,11 @@
   // Launched as an installed web application in a browser tab.
   kAsWebAppInTab = 21,
   kUnknownWebApp = 22,  // The requested web application was not installed.
-  kMaxValue = kUnknownWebApp,
+  kAsWebAppInWindowByUrl = 23,    // Launched the app by url with --app switch.
+  kAsWebAppInWindowByAppId = 24,  // Launched app by id with --app-id switch.
+  kAsWebAppInWindowOther = 25,    // Launched app by any method other than
+  // through the command-line or from a platform shortcut.
+  kMaxValue = kAsWebAppInWindowOther,
 };
 
 class LaunchModeRecorder {
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc
index 1f80f14..19ca291f 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -363,6 +363,7 @@
 
 void FinalizeWebAppLaunch(
     std::unique_ptr<LaunchModeRecorder> launch_mode_recorder,
+    absl::optional<LaunchMode> app_launch_mode,
     Browser* browser,
     apps::mojom::LaunchContainer container) {
   if (!browser)
@@ -373,7 +374,7 @@
     switch (container) {
       case apps::mojom::LaunchContainer::kLaunchContainerWindow:
         DCHECK(browser->is_type_app());
-        mode = LaunchMode::kAsWebAppInWindow;
+        mode = app_launch_mode.value_or(LaunchMode::kAsWebAppInWindowOther);
         break;
       case apps::mojom::LaunchContainer::kLaunchContainerTab:
         DCHECK(!browser->is_type_app());
@@ -436,7 +437,8 @@
             /*url_handler_launch_url=*/absl::nullopt,
             /*protocol_handler_launch_url=*/absl::nullopt,
             base::BindOnce(&FinalizeWebAppLaunch,
-                           std::move(launch_mode_recorder)));
+                           std::move(launch_mode_recorder),
+                           LaunchMode::kAsWebAppInWindowByAppId));
     return true;
   }
 
@@ -458,7 +460,7 @@
           apps::OpenExtensionAppShortcutWindow(profile, url);
       if (web_contents) {
         FinalizeWebAppLaunch(
-            std::move(launch_mode_recorder),
+            std::move(launch_mode_recorder), LaunchMode::kAsWebAppInWindowByUrl,
             chrome::FindBrowserWithWebContents(web_contents),
             apps::mojom::LaunchContainer::kLaunchContainerWindow);
         return true;
@@ -492,7 +494,7 @@
   return web_app::startup::MaybeLaunchUrlHandlerWebAppFromCmd(
       command_line, cur_dir, std::move(on_urls_unhandled_cb),
       base::BindOnce(&FinalizeWebAppLaunch,
-                     std::make_unique<LaunchModeRecorder>()));
+                     std::make_unique<LaunchModeRecorder>(), absl::nullopt));
 }
 #endif
 
@@ -848,7 +850,7 @@
   web_app::startup::MaybeLaunchUrlHandlerWebAppFromUrls(
       urls, std::move(on_urls_unhandled_cb),
       base::BindOnce(&FinalizeWebAppLaunch,
-                     std::make_unique<LaunchModeRecorder>()));
+                     std::make_unique<LaunchModeRecorder>(), absl::nullopt));
 }
 #endif  // defined(OS_MAC)
 
@@ -1136,7 +1138,7 @@
           command_line, cur_dir, privacy_safe_profile, last_used_profile,
           last_opened_profiles,
           base::BindOnce(&FinalizeWebAppLaunch,
-                         std::make_unique<LaunchModeRecorder>()),
+                         std::make_unique<LaunchModeRecorder>(), absl::nullopt),
           std::move(startup_callback))) {
     return true;
   }
diff --git a/chrome/browser/ui/startup/startup_browser_creator.h b/chrome/browser/ui/startup/startup_browser_creator.h
index 223a891..fc7c81c 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.h
+++ b/chrome/browser/ui/startup/startup_browser_creator.h
@@ -27,7 +27,8 @@
 
 namespace web_app {
 FORWARD_DECLARE_TEST(WebAppEngagementBrowserTest, CommandLineTab);
-FORWARD_DECLARE_TEST(WebAppEngagementBrowserTest, CommandLineWindow);
+FORWARD_DECLARE_TEST(WebAppEngagementBrowserTest, CommandLineWindowByUrl);
+FORWARD_DECLARE_TEST(WebAppEngagementBrowserTest, CommandLineWindowByAppId);
 }  // namespace web_app
 
 // Indicates how Chrome should start up the first profile.
@@ -204,7 +205,9 @@
   FRIEND_TEST_ALL_PREFIXES(web_app::WebAppEngagementBrowserTest,
                            CommandLineTab);
   FRIEND_TEST_ALL_PREFIXES(web_app::WebAppEngagementBrowserTest,
-                           CommandLineWindow);
+                           CommandLineWindowByUrl);
+  FRIEND_TEST_ALL_PREFIXES(web_app::WebAppEngagementBrowserTest,
+                           CommandLineWindowByAppId);
   FRIEND_TEST_ALL_PREFIXES(StartupBrowserCreatorTest,
                            LastUsedProfilesWithWebApp);
 
diff --git a/chrome/browser/ui/tab_ui_helper_browsertest.cc b/chrome/browser/ui/tab_ui_helper_browsertest.cc
index 2fbd03d..506ae976 100644
--- a/chrome/browser/ui/tab_ui_helper_browsertest.cc
+++ b/chrome/browser/ui/tab_ui_helper_browsertest.cc
@@ -27,11 +27,11 @@
 
   void SetUp() override {
     feature_list_.InitAndEnableFeature(blink::features::kPrerender2);
+    prerender_test_helper_.SetUp(embedded_test_server());
     InProcessBrowserTest::SetUp();
   }
 
   void SetUpOnMainThread() override {
-    prerender_test_helper_.SetUpOnMainThread(embedded_test_server());
     host_resolver()->AddRule("*", "127.0.0.1");
     ASSERT_TRUE(embedded_test_server()->Start());
   }
diff --git a/chrome/browser/ui/tabs/existing_base_sub_menu_model.cc b/chrome/browser/ui/tabs/existing_base_sub_menu_model.cc
index 18190e9..2a51a678 100644
--- a/chrome/browser/ui/tabs/existing_base_sub_menu_model.cc
+++ b/chrome/browser/ui/tabs/existing_base_sub_menu_model.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/tabs/existing_base_sub_menu_model.h"
 
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "ui/base/resource/resource_bundle.h"
 
 ExistingBaseSubMenuModel::ExistingBaseSubMenuModel(
     ui::SimpleMenuModel::Delegate* parent_delegate,
@@ -23,6 +24,15 @@
   return false;
 }
 
+const gfx::FontList* ExistingBaseSubMenuModel::GetLabelFontListAt(
+    int index) const {
+  if (GetTypeAt(index) == ui::MenuModel::TYPE_TITLE) {
+    return &ui::ResourceBundle::GetSharedInstance().GetFontList(
+        ui::ResourceBundle::BoldFont);
+  }
+  return nullptr;
+}
+
 bool ExistingBaseSubMenuModel::IsCommandIdChecked(int command_id) const {
   return false;
 }
@@ -39,7 +49,7 @@
     ExecuteNewCommand(event_flags);
     return;
   }
-  ExecuteExistingCommand(command_id - min_command_id_ - 1);
+  ExecuteExistingCommand(command_id_to_target_index_[command_id]);
 }
 
 ExistingBaseSubMenuModel::~ExistingBaseSubMenuModel() = default;
@@ -69,25 +79,34 @@
   AddSeparator(ui::NORMAL_SEPARATOR);
 
   // Start command ids after the parent menu's ids to avoid collisions.
-  int group_index = min_command_id_ + 1;
+  int command_id = min_command_id_ + 1;
   for (auto item : menu_item_infos) {
-    if (group_index > min_command_id_ + max_size)
+    if (command_id > min_command_id_ + max_size)
       break;
 
-    if (item.image.has_value()) {
-      AddItemWithIcon(group_index, item.text, item.image.value());
+    if (item.target_index.has_value()) {
+      command_id_to_target_index_[command_id] = item.target_index.value();
+      if (item.image.has_value()) {
+        AddItemWithIcon(command_id++, item.text, item.image.value());
+      } else {
+        AddItem(command_id++, item.text);
+      }
     } else {
-      AddItem(group_index, item.text);
+      AddTitle(item.text);
     }
 
     SetMayHaveMnemonicsAt(GetItemCount() - 1, item.may_have_mnemonics);
-    group_index++;
   }
 }
 
+void ExistingBaseSubMenuModel::ClearMenu() {
+  Clear();
+  command_id_to_target_index_.clear();
+}
+
 void ExistingBaseSubMenuModel::ExecuteNewCommand(int event_flags) {}
 
-void ExistingBaseSubMenuModel::ExecuteExistingCommand(int command_index) {}
+void ExistingBaseSubMenuModel::ExecuteExistingCommand(int target_index) {}
 
 int ExistingBaseSubMenuModel::GetContextIndex() const {
   return model_->GetIndexOfWebContents(context_contents_);
diff --git a/chrome/browser/ui/tabs/existing_base_sub_menu_model.h b/chrome/browser/ui/tabs/existing_base_sub_menu_model.h
index 692bb1d..ecf9474 100644
--- a/chrome/browser/ui/tabs/existing_base_sub_menu_model.h
+++ b/chrome/browser/ui/tabs/existing_base_sub_menu_model.h
@@ -7,6 +7,7 @@
 
 #include <stddef.h>
 
+#include "base/containers/flat_map.h"
 #include "base/macros.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/models/simple_menu_model.h"
@@ -36,6 +37,7 @@
   // ui::SimpleMenuModel
   bool GetAcceleratorForCommandId(int command_id,
                                   ui::Accelerator* accelerator) const override;
+  const gfx::FontList* GetLabelFontListAt(int index) const override;
 
   // ui::SimpleMenuModel::Delegate
   bool IsCommandIdChecked(int command_id) const override;
@@ -48,6 +50,10 @@
 
   ~ExistingBaseSubMenuModel() override;
 
+  const base::flat_map<int, int>& command_id_to_target_index_for_testing() {
+    return command_id_to_target_index_;
+  }
+
  protected:
   struct MenuItemInfo {
     explicit MenuItemInfo(const std::u16string menu_text);
@@ -56,12 +62,17 @@
     ~MenuItemInfo();
 
     // The text for an entry in the sub menu.
-    const std::u16string text;
+    std::u16string text;
 
     // The optional image for an entry in the sub menu.
     absl::optional<ui::ImageModel> image;
 
     bool may_have_mnemonics = true;
+
+    // The target index for this item. E.g. tab group index or browser
+    // index. If this field is not provided then the entry for this item will be
+    // a title and have no corresponding command.
+    absl::optional<int> target_index;
   };
 
   // Helper method to create consistent submenus.|new_text| is the label to add
@@ -70,6 +81,9 @@
   // object model.
   void Build(int new_text, std::vector<MenuItemInfo> menu_item_infos);
 
+  // Clears the MenuModel and |command_id_to_target_index_|.
+  void ClearMenu();
+
   // Helper method for checking if the command is to add a tab to a new object
   // model.
   bool IsNewCommand(int command_id) const {
@@ -81,8 +95,8 @@
   virtual void ExecuteNewCommand(int event_flags);
 
   // Performs the action for adding the tab to an existing object model (e.g.
-  // group or window).
-  virtual void ExecuteExistingCommand(int command_index);
+  // group or window) at |target_index|.
+  virtual void ExecuteExistingCommand(int target_index);
 
   // Maximum number of entries for a submenu.
   static constexpr int max_size = 200;
@@ -94,10 +108,15 @@
   int GetContextIndex() const;
 
  private:
-  ui::SimpleMenuModel::Delegate* parent_delegate_;
-  TabStripModel* model_;
-  content::WebContents* context_contents_;
-  int min_command_id_;
+  ui::SimpleMenuModel::Delegate* const parent_delegate_;
+  TabStripModel* const model_;
+  const content::WebContents* const context_contents_;
+  const int min_command_id_;
+
+  // Stores a mapping from a menu item's command id to its target index (e.g.
+  // tab-group index or browser index).
+  base::flat_map<int, int> command_id_to_target_index_;
+
   DISALLOW_COPY_AND_ASSIGN(ExistingBaseSubMenuModel);
 };
 
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 d8cce6f4..96950e9 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
@@ -36,7 +36,10 @@
   constexpr int kIconSize = 14;
   std::vector<MenuItemInfo> menu_item_infos;
 
-  for (tab_groups::TabGroupId group : GetOrderedTabGroupsInSubMenu()) {
+  std::vector<tab_groups::TabGroupId> ordered_tab_groups =
+      GetOrderedTabGroupsInSubMenu();
+  for (size_t i = 0; i < ordered_tab_groups.size(); ++i) {
+    tab_groups::TabGroupId group = ordered_tab_groups[i];
     const TabGroup* tab_group = model->group_model()->GetTabGroup(group);
     const std::u16string group_title = tab_group->visual_data()->title();
     const std::u16string displayed_title =
@@ -49,6 +52,7 @@
         kTabGroupIcon, tp.GetColor(color_id), kIconSize);
     menu_item_infos.emplace_back(MenuItemInfo{displayed_title, image_model});
     menu_item_infos.back().may_have_mnemonics = false;
+    menu_item_infos.back().target_index = static_cast<int>(i);
   }
   Build(IDS_TAB_CXMENU_SUBMENU_NEW_GROUP, menu_item_infos);
 }
@@ -85,16 +89,16 @@
                                     event_flags);
 }
 
-void ExistingTabGroupSubMenuModel::ExecuteExistingCommand(int command_index) {
+void ExistingTabGroupSubMenuModel::ExecuteExistingCommand(int target_index) {
   base::RecordAction(base::UserMetricsAction("TabContextMenu_NewTabInGroup"));
 
-  if (static_cast<size_t>(command_index) >=
+  if (static_cast<size_t>(target_index) >=
       model()->group_model()->ListTabGroups().size())
     return;
   if (!model()->ContainsIndex(GetContextIndex()))
     return;
   model()->ExecuteAddToExistingGroupCommand(
-      GetContextIndex(), GetOrderedTabGroupsInSubMenu()[command_index]);
+      GetContextIndex(), GetOrderedTabGroupsInSubMenu()[target_index]);
 }
 
 // static
diff --git a/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.h b/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.h
index bd321ad1..22fba76 100644
--- a/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.h
+++ b/chrome/browser/ui/tabs/existing_tab_group_sub_menu_model.h
@@ -33,7 +33,7 @@
  private:
   // ExistingBaseSubMenuModel
   void ExecuteNewCommand(int event_flags) override;
-  void ExecuteExistingCommand(int command_index) override;
+  void ExecuteExistingCommand(int target_index) override;
 
   // Returns the group ids that appear in the submenu in the order that they
   // appear in the tab strip model, so that the user sees an ordered display.
diff --git a/chrome/browser/ui/tabs/existing_window_sub_menu_model.cc b/chrome/browser/ui/tabs/existing_window_sub_menu_model.cc
index 90d531b..62a0098 100644
--- a/chrome/browser/ui/tabs/existing_window_sub_menu_model.cc
+++ b/chrome/browser/ui/tabs/existing_window_sub_menu_model.cc
@@ -4,20 +4,45 @@
 
 #include "chrome/browser/ui/tabs/existing_window_sub_menu_model.h"
 
-#include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/tabs/tab_menu_model_delegate.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/grit/generated_resources.h"
-#include "ui/base/models/menu_separator_types.h"
-#include "ui/gfx/paint_vector_icon.h"
-#include "ui/native_theme/native_theme.h"
-#include "ui/views/controls/menu/menu_config.h"
-#include "ui/views/vector_icons.h"
+#include "ui/base/accelerators/accelerator.h"
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chrome/browser/ui/tabs/existing_window_sub_menu_model_chromeos.h"
+#endif
+
+namespace {
+
+// The max length for a window title.
+static constexpr int kWindowTitleForMenuMaxWidth = 400;
+
+}  // namespace
+
+// static:
+std::unique_ptr<ExistingWindowSubMenuModel> ExistingWindowSubMenuModel::Create(
+    ui::SimpleMenuModel::Delegate* parent_delegate,
+    TabMenuModelDelegate* tab_menu_model_delegate,
+    TabStripModel* model,
+    int context_index) {
+  // TODO(crbug.com/1236618): Implement this for Lacros.
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  return std::make_unique<chromeos::ExistingWindowSubMenuModelChromeOS>(
+      GetPassKey(), parent_delegate, tab_menu_model_delegate, model,
+      context_index);
+#else
+  return std::make_unique<ExistingWindowSubMenuModel>(
+      GetPassKey(), parent_delegate, tab_menu_model_delegate, model,
+      context_index);
+#endif
+}
 
 ExistingWindowSubMenuModel::ExistingWindowSubMenuModel(
+    base::PassKey<ExistingWindowSubMenuModel> passkey,
     ui::SimpleMenuModel::Delegate* parent_delegate,
     TabMenuModelDelegate* tab_menu_model_delegate,
     TabStripModel* model,
@@ -26,17 +51,9 @@
                                model,
                                context_index,
                                kMinExistingWindowCommandId) {
-  static constexpr int kWindowTitleForMenuMaxWidth = 400;
-  std::vector<MenuItemInfo> menu_item_infos;
-  auto existing_browsers =
-      tab_menu_model_delegate->GetExistingWindowsForMoveMenu();
-
-  for (auto* browser : existing_browsers) {
-    menu_item_infos.emplace_back(MenuItemInfo{
-        browser->GetWindowTitleForMaxWidth(kWindowTitleForMenuMaxWidth)});
-    menu_item_infos.back().may_have_mnemonics = false;
-  }
-  Build(IDS_TAB_CXMENU_MOVETOANOTHERNEWWINDOW, menu_item_infos);
+  Build(IDS_TAB_CXMENU_MOVETOANOTHERNEWWINDOW,
+        BuildMenuItemInfoVectorForBrowsers(
+            tab_menu_model_delegate->GetExistingWindowsForMoveMenu()));
 }
 
 ExistingWindowSubMenuModel::~ExistingWindowSubMenuModel() = default;
@@ -67,16 +84,38 @@
   return true;
 }
 
-// static
+// static:
 bool ExistingWindowSubMenuModel::ShouldShowSubmenu(Profile* profile) {
   return chrome::GetTabbedBrowserCount(profile) > 1;
 }
 
+// static:
+base::PassKey<ExistingWindowSubMenuModel>
+ExistingWindowSubMenuModel::GetPassKey() {
+  return base::PassKey<ExistingWindowSubMenuModel>();
+}
+
+// static:
+std::vector<ExistingBaseSubMenuModel::MenuItemInfo>
+ExistingWindowSubMenuModel::BuildMenuItemInfoVectorForBrowsers(
+    const std::vector<Browser*>& existing_browsers) {
+  std::vector<MenuItemInfo> menu_item_infos;
+  for (size_t i = 0; i < existing_browsers.size(); ++i) {
+    Browser* browser = existing_browsers[i];
+    auto window_title =
+        browser->GetWindowTitleForMaxWidth(kWindowTitleForMenuMaxWidth);
+    menu_item_infos.emplace_back(window_title);
+    menu_item_infos.back().may_have_mnemonics = false;
+    menu_item_infos.back().target_index = static_cast<int>(i);
+  }
+  return menu_item_infos;
+}
+
 void ExistingWindowSubMenuModel::ExecuteNewCommand(int event_flags) {
   parent_delegate()->ExecuteCommand(TabStripModel::CommandMoveTabsToNewWindow,
                                     event_flags);
 }
 
-void ExistingWindowSubMenuModel::ExecuteExistingCommand(int command_index) {
-  model()->ExecuteAddToExistingWindowCommand(GetContextIndex(), command_index);
+void ExistingWindowSubMenuModel::ExecuteExistingCommand(int target_index) {
+  model()->ExecuteAddToExistingWindowCommand(GetContextIndex(), target_index);
 }
diff --git a/chrome/browser/ui/tabs/existing_window_sub_menu_model.h b/chrome/browser/ui/tabs/existing_window_sub_menu_model.h
index a7ce4f8f..f95cad2 100644
--- a/chrome/browser/ui/tabs/existing_window_sub_menu_model.h
+++ b/chrome/browser/ui/tabs/existing_window_sub_menu_model.h
@@ -7,16 +7,32 @@
 
 #include <stddef.h>
 
-#include "base/macros.h"
+#include "base/types/pass_key.h"
 #include "chrome/browser/ui/tabs/existing_base_sub_menu_model.h"
 
+class Browser;
 class Profile;
 class TabStripModel;
 class TabMenuModelDelegate;
 
+namespace ui {
+class Accelerator;
+}  // namespace ui
+
 class ExistingWindowSubMenuModel : public ExistingBaseSubMenuModel {
  public:
-  ExistingWindowSubMenuModel(ui::SimpleMenuModel::Delegate* parent_delegate,
+  // Factory function for creating a platform-specific
+  // ExistingWindowSubMenuModel.
+  static std::unique_ptr<ExistingWindowSubMenuModel> Create(
+      ui::SimpleMenuModel::Delegate* parent_delegate,
+      TabMenuModelDelegate* tab_menu_model_delegate,
+      TabStripModel* model,
+      int context_index);
+
+  // Clients shouldn't directly create instances of ExistingWindowSubMenuModel,
+  // but rather use ExistingWindowSubMenuModel::Create().
+  ExistingWindowSubMenuModel(base::PassKey<ExistingWindowSubMenuModel> passkey,
+                             ui::SimpleMenuModel::Delegate* parent_delegate,
                              TabMenuModelDelegate* tab_menu_model_delegate,
                              TabStripModel* model,
                              int context_index);
@@ -25,11 +41,11 @@
       delete;
   ~ExistingWindowSubMenuModel() override;
 
-  // ui::SimpleMenuModel
+  // ui::SimpleMenuModel:
   bool GetAcceleratorForCommandId(int command_id,
                                   ui::Accelerator* accelerator) const override;
 
-  // ui::SimpleMenuModel::Delegate
+  // ui::SimpleMenuModel::Delegate:
   bool IsCommandIdChecked(int command_id) const override;
   bool IsCommandIdEnabled(int command_id) const override;
 
@@ -38,10 +54,20 @@
   // |model|; |model| must outlive this instance.
   static bool ShouldShowSubmenu(Profile* profile);
 
+ protected:
+  // Retrieves a base::Passkey which can be used to construct an instance of
+  // ExistingWindowSubMenuModel.
+  static base::PassKey<ExistingWindowSubMenuModel> GetPassKey();
+
+  // Builds a vector of MenuItemInfo structs for the given browsers.
+  static std::vector<ExistingWindowSubMenuModel::MenuItemInfo>
+  BuildMenuItemInfoVectorForBrowsers(
+      const std::vector<Browser*>& existing_browsers);
+
  private:
-  // ExistingBaseSubMenuModel
+  // ExistingBaseSubMenuModel:
   void ExecuteNewCommand(int event_flags) override;
-  void ExecuteExistingCommand(int command_index) override;
+  void ExecuteExistingCommand(int target_index) override;
 };
 
 #endif  // CHROME_BROWSER_UI_TABS_EXISTING_WINDOW_SUB_MENU_MODEL_H_
diff --git a/chrome/browser/ui/tabs/existing_window_sub_menu_model_chromeos.cc b/chrome/browser/ui/tabs/existing_window_sub_menu_model_chromeos.cc
new file mode 100644
index 0000000..f20a36fe
--- /dev/null
+++ b/chrome/browser/ui/tabs/existing_window_sub_menu_model_chromeos.cc
@@ -0,0 +1,95 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/tabs/existing_window_sub_menu_model_chromeos.h"
+
+#include "ash/public/cpp/desks_helper.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/tabs/tab_menu_model_delegate.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/base/models/menu_model.h"
+
+namespace chromeos {
+
+namespace {
+
+int GetDeskIndexForBrowser(Browser* browser, int num_desks) {
+  int desk_index;
+  CHECK(base::StringToInt(browser->window()->GetWorkspace(), &desk_index));
+  CHECK_LT(desk_index, num_desks);
+  return desk_index;
+}
+
+}  // namespace
+
+ExistingWindowSubMenuModelChromeOS::ExistingWindowSubMenuModelChromeOS(
+    base::PassKey<ExistingWindowSubMenuModel> passkey,
+    ui::SimpleMenuModel::Delegate* parent_delegate,
+    TabMenuModelDelegate* tab_menu_model_delegate,
+    TabStripModel* model,
+    int context_index)
+    : ExistingWindowSubMenuModel(ExistingWindowSubMenuModel::GetPassKey(),
+                                 parent_delegate,
+                                 tab_menu_model_delegate,
+                                 model,
+                                 context_index),
+      desks_helper_(ash::DesksHelper::Get()) {
+  // If we shouldn't group by desk, ExistingWindowSubMenuModel's ctor has
+  // already built the menu.
+  if (!ShouldGroupByDesk())
+    return;
+
+  ClearMenu();
+  BuildMenuGroupedByDesk(
+      tab_menu_model_delegate->GetExistingWindowsForMoveMenu());
+}
+
+ExistingWindowSubMenuModelChromeOS::~ExistingWindowSubMenuModelChromeOS() =
+    default;
+
+void ExistingWindowSubMenuModelChromeOS::BuildMenuGroupedByDesk(
+    const std::vector<Browser*>& existing_browsers) {
+  // Get the vector of MenuItemInfo for |existing_browsers| and then group them
+  // by desk.
+  const int num_desks = desks_helper_->GetNumberOfDesks();
+  std::vector<std::vector<ExistingBaseSubMenuModel::MenuItemInfo>>
+      grouped_by_desk_menu_item_infos(num_desks);
+  for (const ExistingBaseSubMenuModel::MenuItemInfo& item :
+       BuildMenuItemInfoVectorForBrowsers(existing_browsers)) {
+    const int desk = GetDeskIndexForBrowser(
+        existing_browsers[item.target_index.value()], num_desks);
+    grouped_by_desk_menu_item_infos[desk].push_back(item);
+  }
+
+  // Now flatten the 2D vector based on its grouping and insert the desk
+  // headings before each group.
+  std::vector<MenuItemInfo> sorted_menu_items_with_headings;
+  for (size_t desk = 0; desk < grouped_by_desk_menu_item_infos.size(); ++desk) {
+    const std::vector<MenuItemInfo>& desk_items =
+        grouped_by_desk_menu_item_infos[desk];
+    if (desk_items.empty())
+      continue;
+
+    // Create a MenuItemInfo for this desk.
+    ExistingBaseSubMenuModel::MenuItemInfo desk_heading(
+        desks_helper_->GetDeskName(desk));
+    desk_heading.may_have_mnemonics = false;
+
+    sorted_menu_items_with_headings.push_back(desk_heading);
+    sorted_menu_items_with_headings.insert(
+        sorted_menu_items_with_headings.end(), desk_items.begin(),
+        desk_items.end());
+  }
+
+  Build(IDS_TAB_CXMENU_MOVETOANOTHERNEWWINDOW, sorted_menu_items_with_headings);
+}
+
+bool ExistingWindowSubMenuModelChromeOS::ShouldGroupByDesk() {
+  constexpr int kMinNumOfDesks = 2;
+  return desks_helper_->GetNumberOfDesks() >= kMinNumOfDesks;
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/ui/tabs/existing_window_sub_menu_model_chromeos.h b/chrome/browser/ui/tabs/existing_window_sub_menu_model_chromeos.h
new file mode 100644
index 0000000..2336e01
--- /dev/null
+++ b/chrome/browser/ui/tabs/existing_window_sub_menu_model_chromeos.h
@@ -0,0 +1,53 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_TABS_EXISTING_WINDOW_SUB_MENU_MODEL_CHROMEOS_H_
+#define CHROME_BROWSER_UI_TABS_EXISTING_WINDOW_SUB_MENU_MODEL_CHROMEOS_H_
+
+#include <stddef.h>
+
+#include "base/types/pass_key.h"
+#include "chrome/browser/ui/tabs/existing_window_sub_menu_model.h"
+
+class Browser;
+class TabStripModel;
+class TabMenuModelDelegate;
+
+namespace ash {
+class DesksHelper;
+}  // namespace ash
+
+namespace chromeos {
+
+// ExistingWindowSubMenuModel implementation for chromeos specific code.
+class ExistingWindowSubMenuModelChromeOS : public ExistingWindowSubMenuModel {
+ public:
+  ExistingWindowSubMenuModelChromeOS(
+      base::PassKey<ExistingWindowSubMenuModel> passkey,
+      ui::SimpleMenuModel::Delegate* parent_delegate,
+      TabMenuModelDelegate* tab_menu_model_delegate,
+      TabStripModel* model,
+      int context_index);
+  ExistingWindowSubMenuModelChromeOS(
+      const ExistingWindowSubMenuModelChromeOS&) = delete;
+  ExistingWindowSubMenuModelChromeOS& operator=(
+      const ExistingWindowSubMenuModelChromeOS&) = delete;
+  ~ExistingWindowSubMenuModelChromeOS() override;
+
+ private:
+  // Builds the submenu, grouping browsers from |existing_browsers| by desks.
+  // Browsers within desk groupings maintain the originally provided order. I.e.
+  // if browsers were provided in MRU order, then within desk groupings browsers
+  // will be in MRU order.
+  void BuildMenuGroupedByDesk(const std::vector<Browser*>& existing_browsers);
+
+  // Returns true if there are at least 2 desks.
+  bool ShouldGroupByDesk();
+
+  const ash::DesksHelper* const desks_helper_;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_UI_TABS_EXISTING_WINDOW_SUB_MENU_MODEL_CHROMEOS_H_
diff --git a/chrome/browser/ui/tabs/existing_window_sub_menu_model_unittest.cc b/chrome/browser/ui/tabs/existing_window_sub_menu_model_unittest.cc
index 285549c..e3de11e 100644
--- a/chrome/browser/ui/tabs/existing_window_sub_menu_model_unittest.cc
+++ b/chrome/browser/ui/tabs/existing_window_sub_menu_model_unittest.cc
@@ -13,6 +13,12 @@
 #include "chrome/test/base/test_browser_window.h"
 #include "ui/gfx/text_elider.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "ash/public/cpp/autotest_desks_api.h"
+#include "chrome/browser/ui/tabs/existing_window_sub_menu_model_chromeos.h"
+#include "chrome/browser/ui/tabs/tab_menu_model_delegate.h"
+#endif
+
 namespace {
 
 class ExistingWindowSubMenuModelTest : public BrowserWithTestWindowTest {
@@ -21,6 +27,9 @@
 
  protected:
   std::unique_ptr<Browser> CreateTestBrowser(bool incognito, bool popup);
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  std::unique_ptr<Browser> CreateTestBrowserOnWorkspace(std::string desk_index);
+#endif
   void AddTabWithTitle(Browser* browser, std::string title);
 
   static void CheckBrowserTitle(const std::u16string& title,
@@ -44,6 +53,18 @@
   return browser;
 }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+std::unique_ptr<Browser>
+ExistingWindowSubMenuModelTest::CreateTestBrowserOnWorkspace(
+    std::string desk_index) {
+  auto browser = CreateTestBrowser(/*incognito=*/false, /*popup=*/false);
+  auto* test_browser_window =
+      static_cast<TestBrowserWindow*>(browser->window());
+  test_browser_window->set_workspace(desk_index);
+  return browser;
+}
+#endif
+
 void ExistingWindowSubMenuModelTest::AddTabWithTitle(Browser* browser,
                                                      std::string title) {
   AddTab(browser, GURL("about:blank"));
@@ -156,34 +177,34 @@
   AddTabWithTitle(browser_4.get(), kLongTabTitleExample);
 
   // Create menu from browser 1.
-  ExistingWindowSubMenuModel menu1(nullptr,
-                                   browser()->tab_menu_model_delegate(),
-                                   browser()->tab_strip_model(), 0);
-  ASSERT_EQ(5, menu1.GetItemCount());
-  CheckBrowserTitle(menu1.GetLabelAt(2), kLongTabTitleExample, 3);
-  CheckBrowserTitle(menu1.GetLabelAt(3), "Browser 3 Tab 2", 2);
-  CheckBrowserTitle(menu1.GetLabelAt(4), kLongTabTitleExample, 1);
+  auto menu1 = ExistingWindowSubMenuModel::Create(
+      nullptr, browser()->tab_menu_model_delegate(),
+      browser()->tab_strip_model(), 0);
+  ASSERT_EQ(5, menu1->GetItemCount());
+  CheckBrowserTitle(menu1->GetLabelAt(2), kLongTabTitleExample, 3);
+  CheckBrowserTitle(menu1->GetLabelAt(3), "Browser 3 Tab 2", 2);
+  CheckBrowserTitle(menu1->GetLabelAt(4), kLongTabTitleExample, 1);
 
   // Create menu from browser 2.
-  ExistingWindowSubMenuModel menu2(nullptr,
-                                   browser_2->tab_menu_model_delegate(),
-                                   browser_2->tab_strip_model(), 0);
-  ASSERT_EQ(5, menu2.GetItemCount());
-  CheckBrowserTitle(menu2.GetLabelAt(2), kLongTabTitleExample, 3);
-  CheckBrowserTitle(menu2.GetLabelAt(3), "Browser 3 Tab 2", 2);
-  CheckBrowserTitle(menu2.GetLabelAt(4), "Browser 1", 1);
+  auto menu2 = ExistingWindowSubMenuModel::Create(
+      nullptr, browser_2->tab_menu_model_delegate(),
+      browser_2->tab_strip_model(), 0);
+  ASSERT_EQ(5, menu2->GetItemCount());
+  CheckBrowserTitle(menu2->GetLabelAt(2), kLongTabTitleExample, 3);
+  CheckBrowserTitle(menu2->GetLabelAt(3), "Browser 3 Tab 2", 2);
+  CheckBrowserTitle(menu2->GetLabelAt(4), "Browser 1", 1);
 
   // Rearrange the MRU and re-test.
   BrowserList::SetLastActive(browser());
   BrowserList::SetLastActive(browser_2.get());
 
-  ExistingWindowSubMenuModel menu3(nullptr,
-                                   browser_3->tab_menu_model_delegate(),
-                                   browser_3->tab_strip_model(), 0);
-  ASSERT_EQ(5, menu3.GetItemCount());
-  CheckBrowserTitle(menu3.GetLabelAt(2), kLongTabTitleExample, 1);
-  CheckBrowserTitle(menu3.GetLabelAt(3), "Browser 1", 1);
-  CheckBrowserTitle(menu3.GetLabelAt(4), kLongTabTitleExample, 3);
+  auto menu3 = ExistingWindowSubMenuModel::Create(
+      nullptr, browser_3->tab_menu_model_delegate(),
+      browser_3->tab_strip_model(), 0);
+  ASSERT_EQ(5, menu3->GetItemCount());
+  CheckBrowserTitle(menu3->GetLabelAt(2), kLongTabTitleExample, 1);
+  CheckBrowserTitle(menu3->GetLabelAt(3), "Browser 1", 1);
+  CheckBrowserTitle(menu3->GetLabelAt(4), kLongTabTitleExample, 3);
 
   // Clean up.
   chrome::CloseTab(browser_2.get());
@@ -214,18 +235,19 @@
   const std::u16string kIncognitoBrowser2ExpectedTitle = u"Incognito Browser 2";
 
   // Test that a non-incognito browser only shows non-incognito windows.
-  ExistingWindowSubMenuModel menu(nullptr, browser()->tab_menu_model_delegate(),
-                                  browser()->tab_strip_model(), 0);
-  ASSERT_EQ(4, menu.GetItemCount());
-  ASSERT_EQ(kBrowser3ExpectedTitle, menu.GetLabelAt(2));
-  ASSERT_EQ(kBrowser2ExpectedTitle, menu.GetLabelAt(3));
+  auto menu = ExistingWindowSubMenuModel::Create(
+      nullptr, browser()->tab_menu_model_delegate(),
+      browser()->tab_strip_model(), 0);
+  ASSERT_EQ(4, menu->GetItemCount());
+  ASSERT_EQ(kBrowser3ExpectedTitle, menu->GetLabelAt(2));
+  ASSERT_EQ(kBrowser2ExpectedTitle, menu->GetLabelAt(3));
 
   // Test that a incognito browser only shows incognito windows.
-  ExistingWindowSubMenuModel menu_incognito(
+  auto menu_incognito = ExistingWindowSubMenuModel::Create(
       nullptr, incognito_browser_1->tab_menu_model_delegate(),
       incognito_browser_1->tab_strip_model(), 0);
-  ASSERT_EQ(3, menu_incognito.GetItemCount());
-  ASSERT_EQ(kIncognitoBrowser2ExpectedTitle, menu_incognito.GetLabelAt(2));
+  ASSERT_EQ(3, menu_incognito->GetItemCount());
+  ASSERT_EQ(kIncognitoBrowser2ExpectedTitle, menu_incognito->GetLabelAt(2));
 
   // Clean up.
   chrome::CloseTab(browser_2.get());
@@ -251,11 +273,12 @@
   const std::u16string kBrowser3ExpectedTitle = u"Browser 3";
 
   // Test that popups do not show.
-  ExistingWindowSubMenuModel menu(nullptr, browser()->tab_menu_model_delegate(),
-                                  browser()->tab_strip_model(), 0);
-  ASSERT_EQ(4, menu.GetItemCount());
-  ASSERT_EQ(kBrowser3ExpectedTitle, menu.GetLabelAt(2));
-  ASSERT_EQ(kBrowser2ExpectedTitle, menu.GetLabelAt(3));
+  auto menu = ExistingWindowSubMenuModel::Create(
+      nullptr, browser()->tab_menu_model_delegate(),
+      browser()->tab_strip_model(), 0);
+  ASSERT_EQ(4, menu->GetItemCount());
+  ASSERT_EQ(kBrowser3ExpectedTitle, menu->GetLabelAt(2));
+  ASSERT_EQ(kBrowser2ExpectedTitle, menu->GetLabelAt(3));
 
   // Clean up.
   chrome::CloseTab(browser_2.get());
@@ -264,4 +287,134 @@
   chrome::CloseTab(popup_browser_2.get());
 }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// Ensure that when there are multiple desks the browsers are grouped by which
+// desk they belong to.
+TEST_F(ExistingWindowSubMenuModelTest, BuildSubmenuGroupedByDesks) {
+  const std::string kBrowser2TabTitle("Browser 2 Tab 1");
+  const std::string kBrowser3TabTitle("Browser 3 Tab 1");
+  const std::string kBrowser4TabTitle("Browser 4 Tab 1");
+  const std::string kBrowser5TabTitle("Browser 5 Tab 1");
+  const std::string kBrowser6TabTitle("Browser 6 Tab 1");
+  const std::string kBrowser7TabTitle("Browser 7 Tab 1");
+
+  // Create 4 desks so we have 5 in total.
+  ash::AutotestDesksApi().CreateNewDesk();
+  ash::AutotestDesksApi().CreateNewDesk();
+  ash::AutotestDesksApi().CreateNewDesk();
+  ash::AutotestDesksApi().CreateNewDesk();
+
+  // Add some browsers and put them in each desk.
+  BrowserList::SetLastActive(browser());
+  std::unique_ptr<Browser> browser_2(CreateTestBrowserOnWorkspace("0"));
+  std::unique_ptr<Browser> browser_3(CreateTestBrowserOnWorkspace("1"));
+  std::unique_ptr<Browser> browser_4(CreateTestBrowserOnWorkspace("1"));
+  std::unique_ptr<Browser> browser_5(CreateTestBrowserOnWorkspace("2"));
+  std::unique_ptr<Browser> browser_6(CreateTestBrowserOnWorkspace("2"));
+  std::unique_ptr<Browser> browser_7(CreateTestBrowserOnWorkspace("3"));
+
+  // Add tabs.
+  AddTabWithTitle(browser_2.get(), kBrowser2TabTitle);
+  AddTabWithTitle(browser_3.get(), kBrowser3TabTitle);
+  AddTabWithTitle(browser_4.get(), kBrowser4TabTitle);
+  AddTabWithTitle(browser_5.get(), kBrowser5TabTitle);
+  AddTabWithTitle(browser_6.get(), kBrowser6TabTitle);
+  AddTabWithTitle(browser_7.get(), kBrowser7TabTitle);
+
+  // Scramble their MRU order by activating them. The MRU order should be:
+  // [b7, b5, b4, b2, b3, b6] (left-most is MRU).
+  BrowserList::SetLastActive(browser_6.get());
+  BrowserList::SetLastActive(browser_3.get());
+  BrowserList::SetLastActive(browser_2.get());
+  BrowserList::SetLastActive(browser_4.get());
+  BrowserList::SetLastActive(browser_5.get());
+  BrowserList::SetLastActive(browser_7.get());
+
+  const std::vector<Browser*> kExpectedMRUOrder{
+      browser_7.get(), browser_5.get(), browser_4.get(),
+      browser_2.get(), browser_3.get(), browser_6.get()};
+  const auto& mru_ordered_windows =
+      browser()->tab_menu_model_delegate()->GetExistingWindowsForMoveMenu();
+  ASSERT_EQ(6u, mru_ordered_windows.size());
+  ASSERT_EQ(mru_ordered_windows, kExpectedMRUOrder);
+
+  // Create the menu from browser 1. The labels should be grouped by desk and
+  // respect MRU order within each desk grouping. Also a label shouldn't be made
+  // for the 5th desk since no browsers are in it.
+  auto menu1 = ExistingWindowSubMenuModel::Create(
+      nullptr, browser()->tab_menu_model_delegate(),
+      browser()->tab_strip_model(), 0);
+  ASSERT_EQ(12, menu1->GetItemCount());
+  EXPECT_EQ(u"Desk 1", menu1->GetLabelAt(2));
+  CheckBrowserTitle(menu1->GetLabelAt(3), kBrowser2TabTitle, 1);
+  EXPECT_EQ(u"Desk 2", menu1->GetLabelAt(4));
+  CheckBrowserTitle(menu1->GetLabelAt(5), kBrowser4TabTitle, 1);
+  CheckBrowserTitle(menu1->GetLabelAt(6), kBrowser3TabTitle, 1);
+  EXPECT_EQ(u"Desk 3", menu1->GetLabelAt(7));
+  CheckBrowserTitle(menu1->GetLabelAt(8), kBrowser5TabTitle, 1);
+  CheckBrowserTitle(menu1->GetLabelAt(9), kBrowser6TabTitle, 1);
+  EXPECT_EQ(u"Desk 4", menu1->GetLabelAt(10));
+  CheckBrowserTitle(menu1->GetLabelAt(11), kBrowser7TabTitle, 1);
+
+  // Clean up.
+  chrome::CloseTab(browser_2.get());
+  chrome::CloseTab(browser_3.get());
+  chrome::CloseTab(browser_4.get());
+  chrome::CloseTab(browser_5.get());
+  chrome::CloseTab(browser_6.get());
+  chrome::CloseTab(browser_7.get());
+}
+
+// Tests out that executing the commands in the submenu grouped by desks work
+// properly.
+TEST_F(ExistingWindowSubMenuModelTest, EnsureGroupedByDesksCommands) {
+  // Create 2 desks so we have 3 in total.
+  ash::AutotestDesksApi().CreateNewDesk();
+  ash::AutotestDesksApi().CreateNewDesk();
+
+  // Add some browsers and put them in desks.
+  std::unique_ptr<Browser> browser_2(CreateTestBrowserOnWorkspace("0"));
+  std::unique_ptr<Browser> browser_3(CreateTestBrowserOnWorkspace("1"));
+  std::unique_ptr<Browser> browser_4(CreateTestBrowserOnWorkspace("1"));
+  std::unique_ptr<Browser> browser_5(CreateTestBrowserOnWorkspace("2"));
+
+  // Scramble the MRU order by activating them. The MRU order should be:
+  // [b4, b2, b3, b5] (left-most is MRU).
+  BrowserList::SetLastActive(browser_5.get());
+  BrowserList::SetLastActive(browser_3.get());
+  BrowserList::SetLastActive(browser_2.get());
+  BrowserList::SetLastActive(browser_4.get());
+
+  const std::vector<Browser*> kExpectedMRUOrder{
+      browser_4.get(), browser_2.get(), browser_3.get(), browser_5.get()};
+  const auto& mru_ordered_windows =
+      browser()->tab_menu_model_delegate()->GetExistingWindowsForMoveMenu();
+  ASSERT_EQ(4u, mru_ordered_windows.size());
+  ASSERT_EQ(mru_ordered_windows, kExpectedMRUOrder);
+
+  // Create the menu from browser 1 and ensure that the command indexes properly
+  // map to their browser indices.
+  auto menu1 = ExistingWindowSubMenuModel::Create(
+      nullptr, browser()->tab_menu_model_delegate(),
+      browser()->tab_strip_model(), 0);
+  const auto& command_id_to_target_index =
+      static_cast<chromeos::ExistingWindowSubMenuModelChromeOS*>(menu1.get())
+          ->command_id_to_target_index_for_testing();
+
+  // A vector of the expected mappings. The first element of each pair is the
+  // commdand id. The second element of each pair is the browser index.
+  const std::vector<std::pair<int, int>> kExpectedMappings{
+      {1002, 1}, {1003, 0}, {1004, 2}, {1005, 3}};
+  for (const auto& pair : kExpectedMappings) {
+    EXPECT_EQ(pair.second, command_id_to_target_index.at(pair.first));
+  }
+
+  // Clean up.
+  chrome::CloseTab(browser_2.get());
+  chrome::CloseTab(browser_3.get());
+  chrome::CloseTab(browser_4.get());
+  chrome::CloseTab(browser_5.get());
+}
+#endif
+
 }  // namespace
diff --git a/chrome/browser/ui/tabs/tab_menu_model.cc b/chrome/browser/ui/tabs/tab_menu_model.cc
index dd999e97..a4295f4 100644
--- a/chrome/browser/ui/tabs/tab_menu_model.cc
+++ b/chrome/browser/ui/tabs/tab_menu_model.cc
@@ -87,9 +87,8 @@
 
   if (ExistingWindowSubMenuModel::ShouldShowSubmenu(tab_strip->profile())) {
     // Create submenu with existing windows
-    add_to_existing_window_submenu_ =
-        std::make_unique<ExistingWindowSubMenuModel>(
-            delegate(), tab_menu_model_delegate_, tab_strip, index);
+    add_to_existing_window_submenu_ = ExistingWindowSubMenuModel::Create(
+        delegate(), tab_menu_model_delegate_, tab_strip, index);
     AddSubMenu(TabStripModel::CommandMoveToExistingWindow,
                l10n_util::GetPluralStringFUTF16(
                    IDS_TAB_CXMENU_MOVETOANOTHERWINDOW, num_tabs),
diff --git a/chrome/browser/ui/toolbar/media_router_contextual_menu.cc b/chrome/browser/ui/toolbar/media_router_contextual_menu.cc
index ded569b..874ed545 100644
--- a/chrome/browser/ui/toolbar/media_router_contextual_menu.cc
+++ b/chrome/browser/ui/toolbar/media_router_contextual_menu.cc
@@ -12,7 +12,6 @@
 #include "base/strings/strcat.h"
 #include "build/build_config.h"
 #include "chrome/app/chrome_command_ids.h"
-#include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/media/router/mojo/media_router_mojo_impl.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
@@ -176,9 +175,6 @@
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 void MediaRouterContextualMenu::ReportIssue() {
-  if (!base::FeatureList::IsEnabled(media_router::kCastFeedbackDialog)) {
-    return;
-  }
   ShowSingletonTab(
       browser_,
       GURL(base::StrCat({"chrome://", chrome::kChromeUICastFeedbackHost})));
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc b/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc
index a85a196f..db4a4df 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc
+++ b/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc
@@ -226,19 +226,6 @@
   SetClipPath(clip_path);
 }
 
-gfx::Rect AutofillPopupBaseView::GetWindowBounds() const {
-  views::Widget* widget = views::Widget::GetTopLevelWidgetForNativeView(
-      delegate()->container_view());
-  if (widget)
-    return widget->GetWindowBoundsInScreen();
-
-  // If the widget is null, simply return an empty rect. The most common reason
-  // to end up here is that the NativeView has been destroyed externally, which
-  // can happen at any time. This happens fairly commonly on Windows (e.g., at
-  // shutdown) in particular.
-  return gfx::Rect();
-}
-
 gfx::Rect AutofillPopupBaseView::GetContentAreaBounds() const {
   content::WebContents* web_contents = delegate()->GetWebContents();
   if (web_contents)
@@ -263,14 +250,14 @@
   // area so that the user notices the presence of the popup.
   int item_height =
       children().size() > 0 ? children()[0]->GetPreferredSize().height() : 0;
-  if (!CanShowDropdownHere(item_height, GetContentAreaBounds(),
-                           element_bounds)) {
+  const gfx::Rect content_area_bounds = GetContentAreaBounds();
+  if (!CanShowDropdownHere(item_height, content_area_bounds, element_bounds)) {
     HideController(PopupHidingReason::kInsufficientSpace);
     return false;
   }
 
   gfx::Rect popup_bounds = CalculatePopupBounds(
-      preferred_size, GetWindowBounds(), element_bounds, delegate()->IsRTL());
+      preferred_size, content_area_bounds, element_bounds, delegate()->IsRTL());
   // Account for the scroll view's border so that the content has enough space.
   popup_bounds.Inset(-GetWidget()->GetRootView()->GetInsets());
   GetWidget()->SetBounds(popup_bounds);
@@ -328,7 +315,6 @@
 ADD_READONLY_PROPERTY_METADATA(SkColor, FooterBackgroundColor)
 ADD_READONLY_PROPERTY_METADATA(SkColor, SeparatorColor)
 ADD_READONLY_PROPERTY_METADATA(SkColor, WarningColor)
-ADD_READONLY_PROPERTY_METADATA(gfx::Rect, WindowBounds)
 ADD_READONLY_PROPERTY_METADATA(gfx::Rect, ContentAreaBounds)
 END_METADATA
 
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_base_view.h b/chrome/browser/ui/views/autofill/autofill_popup_base_view.h
index 62b71250..13f2218 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_base_view.h
+++ b/chrome/browser/ui/views/autofill/autofill_popup_base_view.h
@@ -74,9 +74,6 @@
   // boundaries. Should be overridden together with CreateBorder.
   void UpdateClipPath();
 
-  // Returns the bounds of the containing window in screen space.
-  gfx::Rect GetWindowBounds() const;
-
   // Returns the bounds of the content area in screen space.
   gfx::Rect GetContentAreaBounds() const;
 
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
index f3c65e0..556ed85 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
+++ b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
@@ -1383,7 +1383,6 @@
   gfx::Size preferred_size = CalculatePreferredSize();
   gfx::Rect popup_bounds;
 
-  const gfx::Rect window_bounds = GetWindowBounds();
 
   // When a bubble border is shown, the contents area (inside the shadow) is
   // supposed to be aligned with input element boundaries.
@@ -1399,13 +1398,13 @@
           ? body_container_->children()[0]->GetPreferredSize().height()
           : 0;
 
-  if (!CanShowDropdownHere(item_height, GetContentAreaBounds(),
-                           element_bounds)) {
+  const gfx::Rect content_area_bounds = GetContentAreaBounds();
+  if (!CanShowDropdownHere(item_height, content_area_bounds, element_bounds)) {
     controller_->Hide(PopupHidingReason::kInsufficientSpace);
     return false;
   }
 
-  CalculatePopupYAndHeight(preferred_size.height(), window_bounds,
+  CalculatePopupYAndHeight(preferred_size.height(), content_area_bounds,
                            element_bounds, &popup_bounds);
 
   // Adjust the width to compensate for a scroll bar, if necessary, and for
@@ -1422,8 +1421,8 @@
   }
   preferred_size.set_width(AdjustWidth(preferred_size.width() + scroll_width));
 
-  CalculatePopupXAndWidth(preferred_size.width(), window_bounds, element_bounds,
-                          controller_->IsRTL(), &popup_bounds);
+  CalculatePopupXAndWidth(preferred_size.width(), content_area_bounds,
+                          element_bounds, controller_->IsRTL(), &popup_bounds);
 
   if (BoundsOverlapWithAnyOpenPrompt(popup_bounds,
                                      controller_->GetWebContents())) {
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_utils.cc b/chrome/browser/ui/views/autofill/autofill_popup_view_utils.cc
index 8bded50..ef2f520 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_view_utils.cc
+++ b/chrome/browser/ui/views/autofill/autofill_popup_view_utils.cc
@@ -12,17 +12,18 @@
 #include "ui/views/widget/widget.h"
 
 void CalculatePopupXAndWidth(int popup_preferred_width,
-                             const gfx::Rect& window_bounds,
+                             const gfx::Rect& content_area_bounds,
                              const gfx::Rect& element_bounds,
                              bool is_rtl,
                              gfx::Rect* popup_bounds) {
-  int right_growth_start =
-      base::clamp(element_bounds.x(), window_bounds.x(), window_bounds.right());
-  int left_growth_end = base::clamp(element_bounds.right(), window_bounds.x(),
-                                    window_bounds.right());
+  int right_growth_start = base::clamp(
+      element_bounds.x(), content_area_bounds.x(), content_area_bounds.right());
+  int left_growth_end =
+      base::clamp(element_bounds.right(), content_area_bounds.x(),
+                  content_area_bounds.right());
 
-  int right_available = window_bounds.right() - right_growth_start;
-  int left_available = left_growth_end - window_bounds.x();
+  int right_available = content_area_bounds.right() - right_growth_start;
+  int left_available = left_growth_end - content_area_bounds.x();
 
   int popup_width = std::min(popup_preferred_width,
                              std::max(left_available, right_available));
@@ -45,16 +46,17 @@
 }
 
 void CalculatePopupYAndHeight(int popup_preferred_height,
-                              const gfx::Rect& window_bounds,
+                              const gfx::Rect& content_area_bounds,
                               const gfx::Rect& element_bounds,
                               gfx::Rect* popup_bounds) {
-  int top_growth_end = base::clamp(element_bounds.y(), window_bounds.y(),
-                                   window_bounds.bottom());
-  int bottom_growth_start = base::clamp(
-      element_bounds.bottom(), window_bounds.y(), window_bounds.bottom());
+  int top_growth_end = base::clamp(element_bounds.y(), content_area_bounds.y(),
+                                   content_area_bounds.bottom());
+  int bottom_growth_start =
+      base::clamp(element_bounds.bottom(), content_area_bounds.y(),
+                  content_area_bounds.bottom());
 
-  int top_available = top_growth_end - window_bounds.y();
-  int bottom_available = window_bounds.bottom() - bottom_growth_start;
+  int top_available = top_growth_end - content_area_bounds.y();
+  int bottom_available = content_area_bounds.bottom() - bottom_growth_start;
 
   popup_bounds->set_height(popup_preferred_height);
   popup_bounds->set_y(top_growth_end);
@@ -65,21 +67,22 @@
         gfx::Rect(popup_bounds->x(), element_bounds.bottom(),
                   popup_bounds->width(), bottom_available));
   } else {
-    popup_bounds->AdjustToFit(gfx::Rect(popup_bounds->x(), window_bounds.y(),
+    popup_bounds->AdjustToFit(gfx::Rect(popup_bounds->x(),
+                                        content_area_bounds.y(),
                                         popup_bounds->width(), top_available));
   }
 }
 
 gfx::Rect CalculatePopupBounds(const gfx::Size& desired_size,
-                               const gfx::Rect& window_bounds,
+                               const gfx::Rect& content_area_bounds,
                                const gfx::Rect& element_bounds,
                                bool is_rtl) {
   gfx::Rect popup_bounds;
 
-  CalculatePopupXAndWidth(desired_size.width(), window_bounds, element_bounds,
-                          is_rtl, &popup_bounds);
-  CalculatePopupYAndHeight(desired_size.height(), window_bounds, element_bounds,
-                           &popup_bounds);
+  CalculatePopupXAndWidth(desired_size.width(), content_area_bounds,
+                          element_bounds, is_rtl, &popup_bounds);
+  CalculatePopupYAndHeight(desired_size.height(), content_area_bounds,
+                           element_bounds, &popup_bounds);
 
   return popup_bounds;
 }
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_utils.h b/chrome/browser/ui/views/autofill/autofill_popup_view_utils.h
index f2694fc..27b0b4e 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_view_utils.h
+++ b/chrome/browser/ui/views/autofill/autofill_popup_view_utils.h
@@ -16,7 +16,7 @@
 // direction it's supposed to grow (either to the left or to the right).
 // Components |y| and |height| of |popup_bounds| are not changed.
 void CalculatePopupXAndWidth(int popup_preferred_width,
-                             const gfx::Rect& window_bounds,
+                             const gfx::Rect& content_area_bounds,
                              const gfx::Rect& element_bounds,
                              bool is_rtl,
                              gfx::Rect* popup_bounds);
@@ -26,14 +26,14 @@
 // direction it's supposed to grow (either up or down). Components |x| and
 // |width| of |popup_bounds| are not changed.
 void CalculatePopupYAndHeight(int popup_preferred_height,
-                              const gfx::Rect& window_bounds,
+                              const gfx::Rect& content_area_bounds,
                               const gfx::Rect& element_bounds,
                               gfx::Rect* popup_bounds);
 
 // Convenience method which handles both the vertical and horizontal bounds
 // and returns a new Rect.
 gfx::Rect CalculatePopupBounds(const gfx::Size& desired_size,
-                               const gfx::Rect& window_bounds,
+                               const gfx::Rect& content_area_bounds,
                                const gfx::Rect& element_bounds,
                                bool is_rtl);
 
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_utils_unittest.cc b/chrome/browser/ui/views/autofill/autofill_popup_view_utils_unittest.cc
index ee3dc86..a2886a66 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_view_utils_unittest.cc
+++ b/chrome/browser/ui/views/autofill/autofill_popup_view_utils_unittest.cc
@@ -16,7 +16,7 @@
 
   struct {
     gfx::Rect element_bounds;
-    gfx::Rect window_bounds;
+    gfx::Rect content_area_bounds;
     gfx::Rect expected_popup_bounds_ltr;
     // Non-empty only when it differs from the ltr expectation.
     gfx::Rect expected_popup_bounds_rtl;
@@ -78,14 +78,14 @@
 
   for (size_t i = 0; i < base::size(test_cases); ++i) {
     gfx::Rect actual_popup_bounds =
-        CalculatePopupBounds(preferred_size, test_cases[i].window_bounds,
+        CalculatePopupBounds(preferred_size, test_cases[i].content_area_bounds,
                              test_cases[i].element_bounds, /* is_rtl= */ false);
     EXPECT_EQ(test_cases[i].expected_popup_bounds_ltr.ToString(),
               actual_popup_bounds.ToString())
         << "Popup bounds failed to match for ltr test " << i;
 
     actual_popup_bounds =
-        CalculatePopupBounds(preferred_size, test_cases[i].window_bounds,
+        CalculatePopupBounds(preferred_size, test_cases[i].content_area_bounds,
                              test_cases[i].element_bounds, /* is_rtl= */ true);
     gfx::Rect expected_popup_bounds = test_cases[i].expected_popup_bounds_rtl;
     if (expected_popup_bounds.IsEmpty())
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc
index 743a87a6..231ca34 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc
@@ -27,8 +27,8 @@
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "content/public/test/theme_change_waiter.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
 #include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "ui/base/theme_provider.h"
 
 class BrowserNonClientFrameViewBrowserTest
@@ -52,10 +52,13 @@
   // longer be hosted apps when BMO ships.
   void InstallAndLaunchBookmarkApp(
       absl::optional<GURL> app_url = absl::nullopt) {
-    blink::Manifest manifest;
+    blink::mojom::Manifest manifest;
     manifest.start_url = app_url.value_or(GetAppURL());
     manifest.scope = manifest.start_url.GetWithoutFilename();
-    manifest.theme_color = app_theme_color_;
+    if (app_theme_color_) {
+      manifest.has_theme_color = true;
+      manifest.theme_color = *app_theme_color_;
+    }
 
     auto web_app_info = std::make_unique<WebApplicationInfo>();
     GURL manifest_url = embedded_test_server()->GetURL("/manifest");
diff --git a/chrome/browser/ui/views/frame/webui_tab_strip_container_view.cc b/chrome/browser/ui/views/frame/webui_tab_strip_container_view.cc
index 211f962..915ae85 100644
--- a/chrome/browser/ui/views/frame/webui_tab_strip_container_view.cc
+++ b/chrome/browser/ui/views/frame/webui_tab_strip_container_view.cc
@@ -859,8 +859,15 @@
     tab_groups::TabGroupId group) {
   ConvertPointToScreen(this, &point);
   rect.set_origin(point);
-  TabGroupEditorBubbleView::Show(browser_view_->browser(), group, nullptr, rect,
-                                 this);
+  editor_bubble_widget_ = TabGroupEditorBubbleView::Show(
+      browser_view_->browser(), group, nullptr, rect, this);
+  scoped_widget_observation_.Observe(editor_bubble_widget_);
+}
+
+void WebUITabStripContainerView::HideEditDialogForGroup() {
+  if (editor_bubble_widget_)
+    editor_bubble_widget_->CloseWithReason(
+        BrowserFrame::ClosedReason::kUnspecified);
 }
 
 TabStripUILayout WebUITabStripContainerView::GetLayout() {
@@ -958,6 +965,14 @@
     tab_contents_container_ = nullptr;
 }
 
+void WebUITabStripContainerView::OnWidgetDestroying(views::Widget* widget) {
+  if (widget != editor_bubble_widget_)
+    return;
+
+  scoped_widget_observation_.Reset();
+  editor_bubble_widget_ = nullptr;
+}
+
 bool WebUITabStripContainerView::SetPaneFocusAndFocusDefault() {
   // Make sure the pane first receives focus, then send a WebUI event to the
   // front-end so the correct HTML element receives focus.
diff --git a/chrome/browser/ui/views/frame/webui_tab_strip_container_view.h b/chrome/browser/ui/views/frame/webui_tab_strip_container_view.h
index b5bb74964..ff765408 100644
--- a/chrome/browser/ui/views/frame/webui_tab_strip_container_view.h
+++ b/chrome/browser/ui/views/frame/webui_tab_strip_container_view.h
@@ -23,6 +23,7 @@
 #include "ui/views/accessible_pane_view.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_observer.h"
 
 #if !BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
 #error
@@ -48,6 +49,7 @@
                                    public gfx::AnimationDelegate,
                                    public views::AccessiblePaneView,
                                    public views::ViewObserver,
+                                   public views::WidgetObserver,
                                    public content::WebContentsObserver {
  public:
   WebUITabStripContainerView(BrowserView* browser_view,
@@ -126,6 +128,7 @@
   void ShowEditDialogForGroupAtPoint(gfx::Point point,
                                      gfx::Rect rect,
                                      tab_groups::TabGroupId group) override;
+  void HideEditDialogForGroup() override;
   TabStripUILayout GetLayout() override;
   SkColor GetColor(int id) const override;
   SkColor GetSystemColor(ui::NativeTheme::ColorId id) const override;
@@ -144,6 +147,9 @@
   void OnViewBoundsChanged(View* observed_view) override;
   void OnViewIsDeleting(View* observed_view) override;
 
+  // views::WidgetObserver:
+  void OnWidgetDestroying(views::Widget* widget) override;
+
   // views::AccessiblePaneView
   bool SetPaneFocusAndFocusDefault() override;
 
@@ -184,6 +190,10 @@
 
   base::ScopedMultiSourceObservation<views::View, views::ViewObserver>
       view_observations_{this};
+  base::ScopedObservation<views::Widget, views::WidgetObserver>
+      scoped_widget_observation_{this};
+
+  views::Widget* editor_bubble_widget_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_FRAME_WEBUI_TAB_STRIP_CONTAINER_VIEW_H_
diff --git a/chrome/browser/ui/views/hats/hats_browsertest.cc b/chrome/browser/ui/views/hats/hats_browsertest.cc
index 55bca522..08b1ae15 100644
--- a/chrome/browser/ui/views/hats/hats_browsertest.cc
+++ b/chrome/browser/ui/views/hats/hats_browsertest.cc
@@ -26,6 +26,7 @@
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/pref_names.h"
+#include "chrome/test/base/scoped_browser_locale.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/version_info/version_info.h"
 #include "content/public/test/browser_test.h"
@@ -42,6 +43,10 @@
     {"Test Field 2", false},
     {"Test Field 3", true}};
 
+// The locale expected by the test survey. This value is checked in
+// hats_next_mock.html for tests that expect a loaded response.
+const std::string kTestLocale = "lt";
+
 }  // namespace
 
 class MockHatsNextWebDialog : public HatsNextWebDialog {
@@ -131,6 +136,8 @@
   const std::string kLastMajorVersion =
       std::string(kHatsSurveyTriggerTesting) + ".last_major_version";
 
+  ScopedBrowserLocale browser_locale(kTestLocale);
+
   auto* dialog = new MockHatsNextWebDialog(
       browser(), kHatsNextSurveyTriggerIDTesting,
       embedded_test_server()->GetURL("/hats/hats_next_mock.html"),
@@ -205,6 +212,8 @@
   ASSERT_TRUE(embedded_test_server()->Start());
   base::HistogramTester histogram_tester;
 
+  ScopedBrowserLocale browser_locale(kTestLocale);
+
   EXPECT_CALL(*hats_service(), HatsNextDialogClosed);
   auto* dialog = new MockHatsNextWebDialog(
       browser(), kHatsNextSurveyTriggerIDTesting,
@@ -360,6 +369,8 @@
   browser()->profile()->GetZoomLevelPrefs()->SetDefaultZoomLevelPref(
       blink::PageZoomFactorToZoomLevel(5.0f));
 
+  ScopedBrowserLocale browser_locale(kTestLocale);
+
   ASSERT_TRUE(embedded_test_server()->Start());
   auto* dialog = new MockHatsNextWebDialog(
       browser(), kHatsNextSurveyTriggerIDTesting,
diff --git a/chrome/browser/ui/views/hats/hats_next_web_dialog.cc b/chrome/browser/ui/views/hats/hats_next_web_dialog.cc
index a4a9f8d..69eac93 100644
--- a/chrome/browser/ui/views/hats/hats_next_web_dialog.cc
+++ b/chrome/browser/ui/views/hats/hats_next_web_dialog.cc
@@ -9,6 +9,7 @@
 #include "base/base64url.h"
 #include "base/json/json_writer.h"
 #include "base/metrics/histogram_functions.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_destroyer.h"
 #include "chrome/browser/ui/hats/hats_service.h"
@@ -230,10 +231,22 @@
   param_url = net::AppendQueryParameter(param_url, "product_specific_data",
                                         product_specific_data_json);
 
+  // The HaTS backend service accepts a list of preferred languages, although
+  // only the application locale is provided here to ensure that the survey
+  // matches the native UI language.
+  base::ListValue language_list;
+  language_list.AppendString(g_browser_process->GetApplicationLocale());
+
+  std::string language_list_json;
+  base::JSONWriter::Write(language_list, &language_list_json);
+  param_url =
+      net::AppendQueryParameter(param_url, "languages", language_list_json);
+
   if (base::FeatureList::IsEnabled(
           features::kHappinessTrackingSurveysForDesktopDemo)) {
     param_url = net::AppendQueryParameter(param_url, "enable_testing", "true");
   }
+
   return param_url;
 }
 
diff --git a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
index f80c538..da38056 100644
--- a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
@@ -1633,12 +1633,12 @@
   void SetUp() override {
     scoped_feature_list_.InitAndEnableFeature(blink::features::kPrerender2);
     reputation::InitializeSafetyTipConfig();
+    prerender_helper_.SetUp(embedded_test_server());
     InProcessBrowserTest::SetUp();
   }
 
   void SetUpOnMainThread() override {
     web_contents_ = browser()->tab_strip_model()->GetActiveWebContents();
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     host_resolver()->AddRule("*", "127.0.0.1");
     ASSERT_TRUE(embedded_test_server()->Start());
   }
diff --git a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc
index 5f2f4e9d..21732d9 100644
--- a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc
+++ b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.cc
@@ -128,14 +128,6 @@
           IDS_BROWSER_SHARING_SCREENSHOT_DIALOG_EDIT_BUTTON_LABEL));
   edit_button->SetHorizontalAlignment(gfx::ALIGN_LEFT);
 
-  // Share button.
-  auto share_button = std::make_unique<views::MdTextButton>(
-      base::BindRepeating(&ScreenshotCapturedBubble::ShareButtonPressed,
-                          base::Unretained(this)),
-      l10n_util::GetStringUTF16(
-          IDS_BROWSER_SHARING_SCREENSHOT_DIALOG_SHARE_BUTTON_LABEL));
-  share_button->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
-
   // Download button.
   auto download_button = std::make_unique<views::MdTextButton>(
       base::BindRepeating(&ScreenshotCapturedBubble::DownloadButtonPressed,
@@ -159,18 +151,11 @@
 
   int kPaddingEditShareButtonPx =
       kImageWidthPx - edit_button->CalculatePreferredSize().width() -
-      share_button->CalculatePreferredSize().width() -
       download_button->CalculatePreferredSize().width();
   // Spacing between the edit and share buttons.
   control_columns->AddPaddingColumn(views::GridLayout::kFixedSize,
                                     kPaddingEditShareButtonPx);
 
-  // Column for share button.
-  control_columns->AddColumn(
-      views::GridLayout::TRAILING, views::GridLayout::CENTER, 1.0,
-      views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
-  layout->StartRow(views::GridLayout::kFixedSize, kDownloadRowColumnSetId);
-
   // Column for download button
   control_columns->AddColumn(
       views::GridLayout::TRAILING, views::GridLayout::CENTER, 1.0,
@@ -178,7 +163,6 @@
   layout->StartRow(views::GridLayout::kFixedSize, kDownloadRowColumnSetId);
 
   edit_button_ = layout->AddView(std::move(edit_button));
-  share_button_ = layout->AddView(std::move(share_button));
   download_button_ = layout->AddView(std::move(download_button));
   // End controls row
 }
@@ -236,10 +220,6 @@
   download_manager->DownloadUrl(std::move(params));
 }
 
-void ScreenshotCapturedBubble::ShareButtonPressed() {
-  NOTIMPLEMENTED();
-}
-
 void ScreenshotCapturedBubble::EditButtonPressed() {
   NOTIMPLEMENTED();
 }
diff --git a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h
index f4959216..c538465 100644
--- a/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h
+++ b/chrome/browser/ui/views/sharing_hub/screenshot/screenshot_captured_bubble.h
@@ -51,8 +51,6 @@
 
   void EditButtonPressed();
 
-  void ShareButtonPressed();
-
   const gfx::Image& image_;
 
   content::WebContents* web_contents_;
@@ -61,7 +59,6 @@
   views::ImageView* image_view_ = nullptr;
   views::MdTextButton* download_button_ = nullptr;
   views::LabelButton* edit_button_ = nullptr;
-  views::LabelButton* share_button_ = nullptr;
 };
 
 }  // namespace sharing_hub
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc
index b3cd3291..bf3f369 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest.cc
@@ -72,8 +72,6 @@
   WebAppIntegrationBrowserTestBase helper_;
 };
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
     WebAppIntegration_InstOmniboxSiteA_WindowCreated_InstPlcyTabShctSiteA_NavSiteA_LaunchIconShown) {
@@ -100,10 +98,7 @@
   helper_.CheckLaunchIconShown();
   helper_.AfterStateCheckAction();
 }
-#endif
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
     WebAppIntegration_InstOmniboxSiteA_WindowCreated_InstPlcyTabShctSiteA_UninstallPlcySiteA)
@@ -127,10 +122,7 @@
   helper_.UninstallPolicyApp("SiteA");
   helper_.AfterStateChangeAction();
 }
-#endif
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
     WebAppIntegration_InstOmniboxSiteA_WindowCreated_NavSiteA_InstIconNotShown_LaunchIconShown) {
@@ -157,7 +149,6 @@
   helper_.CheckLaunchIconShown();
   helper_.AfterStateCheckAction();
 }
-#endif
 
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
@@ -236,8 +227,6 @@
   helper_.AfterStateCheckAction();
 }
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
     WebAppIntegration_InstOmniboxSiteA_WindowCreated_InstPlcyWinShctSiteA_UninstallPlcySiteA)
@@ -245,9 +234,9 @@
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
-  helper_.BeforeStateCheckAction();
+  helper_.BeforeStateChangeAction();
   helper_.InstallOmniboxIcon("SiteA");
-  helper_.AfterStateCheckAction();
+  helper_.AfterStateChangeAction();
 
   helper_.BeforeStateCheckAction();
   helper_.CheckWindowCreated();
@@ -261,10 +250,7 @@
   helper_.UninstallPolicyApp("SiteA");
   helper_.AfterStateChangeAction();
 }
-#endif
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
     WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InstPlcyTabShctSiteA_NavSiteA_LaunchIconShown)
@@ -292,10 +278,7 @@
   helper_.CheckLaunchIconShown();
   helper_.AfterStateCheckAction();
 }
-#endif
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
     WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InstPlcyTabShctSiteA_UninstallPlcySiteA)
@@ -319,10 +302,7 @@
   helper_.UninstallPolicyApp("SiteA");
   helper_.AfterStateChangeAction();
 }
-#endif
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
     WebAppIntegration_InstMenuOptionSiteA_WindowCreated_NavSiteA_InstIconNotShown_LaunchIconShown)
@@ -350,10 +330,7 @@
   helper_.CheckLaunchIconShown();
   helper_.AfterStateCheckAction();
 }
-#endif
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
     WebAppIntegration_InstMenuOptionSiteA_WindowCreated_ClosePWA_MnfstUpdateDsplMinimalSiteA)
@@ -377,10 +354,7 @@
   helper_.ManifestUpdateDisplayMinimal("SiteA");
   helper_.AfterStateChangeAction();
 }
-#endif
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
     WebAppIntegration_InstMenuOptionSiteA_WindowCreated_SetOpenTabSiteA_NavSiteA_InstIconShown)
@@ -408,10 +382,7 @@
   helper_.CheckInstallIconShown();
   helper_.AfterStateCheckAction();
 }
-#endif
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
   WebAppIntegration_InstMenuOptionSiteA_WindowCreated_NavSiteAFoo_InstIconNotShown_LaunchIconShown)
@@ -439,10 +410,7 @@
   helper_.CheckLaunchIconShown();
   helper_.AfterStateCheckAction();
 }
-#endif
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
   WebAppIntegrationBrowserTest,
   WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InstPlcyWinShctSiteA_UninstallPlcySiteA)
@@ -460,13 +428,12 @@
 
   helper_.BeforeStateChangeAction();
   helper_.InstallPolicyAppWindowedShortcut("SiteA");
-  helper_.BeforeStateChangeAction();
+  helper_.AfterStateChangeAction();
 
   helper_.BeforeStateChangeAction();
   helper_.UninstallPolicyApp("SiteA");
   helper_.AfterStateChangeAction();
 }
-#endif
 
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
@@ -578,8 +545,6 @@
   helper_.AfterStateCheckAction();
 }
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
     WebAppIntegration_InstPlcyTabShctSiteA_InstOmniboxSiteA_WindowCreated_UninstallPlcySiteA)
@@ -603,10 +568,7 @@
   helper_.UninstallPolicyApp("SiteA");
   helper_.AfterStateChangeAction();
 }
-#endif
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
     WebAppIntegration_InstPlcyTabShctSiteA_InstMenuOptionSiteA_WindowCreated_UninstallPlcySiteA)
@@ -630,7 +592,6 @@
   helper_.UninstallPolicyApp("SiteA");
   helper_.AfterStateChangeAction();
 }
-#endif
 
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
@@ -670,8 +631,6 @@
   helper_.AfterStateChangeAction();
 }
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
     WebAppIntegration_InstPlcyTabShctSiteC_NavSiteC_InstIconNotShown_LaunchIconShown)
@@ -692,10 +651,9 @@
   helper_.AfterStateCheckAction();
 
   helper_.BeforeStateCheckAction();
-  helper_.CheckLaunchIconNotShown();
+  helper_.CheckLaunchIconShown();
   helper_.AfterStateCheckAction();
 }
-#endif
 
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
@@ -757,8 +715,6 @@
   helper_.AfterStateCheckAction();
 }
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(WebAppIntegrationBrowserTest,
                        WebAppIntegration_InstMenuOptionSiteAFoo_ClosePWA) {
   // Test contents are generated by script. Please do not modify!
@@ -772,10 +728,7 @@
   helper_.ClosePwa();
   helper_.AfterStateChangeAction();
 }
-#endif
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
     WebAppIntegration_InstMenuOptionSiteAFoo_NavSiteABar_InstIconShown_LaunchIconShown)
@@ -799,7 +752,6 @@
   helper_.CheckLaunchIconNotShown();
   helper_.AfterStateCheckAction();
 }
-#endif
 
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
@@ -898,8 +850,6 @@
   helper_.AfterStateCheckAction();
 }
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
     WebAppIntegration_InstMenuOptionSiteA_WindowCreated_NavSiteB_InstIconShown_LaunchIconShown)
@@ -927,7 +877,6 @@
   helper_.CheckLaunchIconNotShown();
   helper_.AfterStateCheckAction();
 }
-#endif
 
 IN_PROC_BROWSER_TEST_F(
     WebAppIntegrationBrowserTest,
@@ -967,8 +916,6 @@
   helper_.AfterStateCheckAction();
 }
 
-// TODO(jarrydg@): Fix the following test failure/crash.
-#if false
 IN_PROC_BROWSER_TEST_F(WebAppIntegrationBrowserTest,
                        WebAppIntegration_InstMenuOptionSiteB_NavSiteB_LaunchIconShown) {
   // Test contents are generated by script. Please do not modify!
@@ -986,6 +933,5 @@
   helper_.CheckLaunchIconShown();
   helper_.AfterStateCheckAction();
 }
-#endif
 
 }  // namespace web_app
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.cc b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.cc
index 8db0604..ef02fd4 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_base.cc
@@ -15,6 +15,7 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
@@ -353,14 +354,18 @@
 void WebAppIntegrationBrowserTestBase::InstallMenuOption(
     const std::string& action_scope) {
   MaybeNavigateTabbedBrowserInScope(action_scope);
-  chrome::SetAutoAcceptWebAppDialogForTesting(
-      /*auto_accept=*/true,
-      /*auto_open_in_window=*/true);
   chrome::SetAutoAcceptPWAInstallConfirmationForTesting(/*auto_accept=*/true);
+  content::WindowedNotificationObserver app_loaded_observer(
+      content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
+      content::NotificationService::AllSources());
   WebAppInstallObserver observer(profile());
   CHECK(chrome::ExecuteCommand(browser(), IDC_INSTALL_PWA));
   active_app_id_ = observer.AwaitNextInstall();
-  chrome::SetAutoAcceptWebAppDialogForTesting(false, false);
+  app_loaded_observer.Wait();
+  chrome::SetAutoAcceptPWAInstallConfirmationForTesting(/*auto_accept=*/false);
+  auto* browser_list = BrowserList::GetInstance();
+  app_browser_ = browser_list->GetLastActive();
+  DCHECK(AppBrowserController::IsWebApp(app_browser_));
 }
 
 void WebAppIntegrationBrowserTestBase::InstallLocally() {
@@ -579,6 +584,13 @@
           run_loop.Quit();
         }
       }));
+  // If there are still install sources, the app might not be fully uninstalled,
+  // so this will listen for the removal of the policy install source.
+  GetProvider()->install_finalizer().SetRemoveSourceCallbackForTesting(
+      base::BindLambdaForTesting([&](const AppId& app_id) {
+        if (policy_app->id == app_id)
+          run_loop.Quit();
+      }));
   {
     ListPrefUpdate update(profile()->GetPrefs(),
                           prefs::kWebAppInstallForceList);
diff --git a/chrome/browser/ui/web_applications/share_target_utils.cc b/chrome/browser/ui/web_applications/share_target_utils.cc
index 257d236..fe4fd90 100644
--- a/chrome/browser/ui/web_applications/share_target_utils.cc
+++ b/chrome/browser/ui/web_applications/share_target_utils.cc
@@ -52,15 +52,21 @@
 
   std::string extracted_text = *intent.share_text;
   GURL extracted_url;
-  size_t last_space = extracted_text.find_last_of(' ');
-  if (last_space == std::string::npos) {
+  size_t separator_pos = extracted_text.find_last_of(' ');
+  size_t newline_pos = extracted_text.find_last_of('\n');
+  if (newline_pos != std::string::npos &&
+      (separator_pos == std::string::npos || separator_pos < newline_pos)) {
+    separator_pos = newline_pos;
+  }
+
+  if (separator_pos == std::string::npos) {
     extracted_url = GURL(extracted_text);
     if (extracted_url.is_valid())
       extracted_text.clear();
   } else {
-    extracted_url = GURL(extracted_text.substr(last_space + 1));
+    extracted_url = GURL(extracted_text.substr(separator_pos + 1));
     if (extracted_url.is_valid())
-      extracted_text.erase(last_space);
+      extracted_text.erase(separator_pos);
   }
 
   if (!share_target.params.text.empty() && !extracted_text.empty())
diff --git a/chrome/browser/ui/web_applications/share_target_utils_unittest.cc b/chrome/browser/ui/web_applications/share_target_utils_unittest.cc
index 170837d..7350590 100644
--- a/chrome/browser/ui/web_applications/share_target_utils_unittest.cc
+++ b/chrome/browser/ui/web_applications/share_target_utils_unittest.cc
@@ -72,6 +72,36 @@
   }
 }
 
+TEST(ShareTargetUtils, ExtractTextUrl) {
+  apps::ShareTarget share_target;
+  share_target.params.text = "body";
+  share_target.params.url = "link";
+
+  {
+    apps::mojom::Intent intent;
+    intent.share_text = "One line\nhttps://example.org/";
+    std::vector<SharedField> expected = {{"body", "One line"},
+                                         {"link", "https://example.org/"}};
+    EXPECT_EQ(ExtractSharedFields(share_target, intent), expected);
+  }
+
+  {
+    apps::mojom::Intent intent;
+    intent.share_text = "Two\nlines\nhttps://example.org/";
+    std::vector<SharedField> expected = {{"body", "Two\nlines"},
+                                         {"link", "https://example.org/"}};
+    EXPECT_EQ(ExtractSharedFields(share_target, intent), expected);
+  }
+
+  {
+    apps::mojom::Intent intent;
+    intent.share_text = "Many\nmany\nlines https://example.org/";
+    std::vector<SharedField> expected = {{"body", "Many\nmany\nlines"},
+                                         {"link", "https://example.org/"}};
+    EXPECT_EQ(ExtractSharedFields(share_target, intent), expected);
+  }
+}
+
 TEST(ShareTargetUtils, ExtractTitleTextUrl) {
   apps::mojom::Intent intent;
   intent.share_title = "Browse";
diff --git a/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc b/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc
index 4fa19b58..a0e54ba 100644
--- a/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc
+++ b/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc
@@ -27,14 +27,14 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/toolbar/app_menu_model.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
-#include "chrome/browser/web_applications/components/app_icon_manager.h"
 #include "chrome/browser/web_applications/components/external_install_options.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/components/install_finalizer.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_application_info.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/test/service_worker_registration_waiter.h"
+#include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "chrome/browser/web_applications/web_app_install_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
diff --git a/chrome/browser/ui/web_applications/web_app_browser_controller.cc b/chrome/browser/ui/web_applications/web_app_browser_controller.cc
index e527bb73..b0cb4f4 100644
--- a/chrome/browser/ui/web_applications/web_app_browser_controller.cc
+++ b/chrome/browser/ui/web_applications/web_app_browser_controller.cc
@@ -18,10 +18,10 @@
 #include "chrome/browser/ui/web_applications/web_app_dialog_manager.h"
 #include "chrome/browser/ui/web_applications/web_app_launch_utils.h"
 #include "chrome/browser/ui/web_applications/web_app_ui_manager_impl.h"
-#include "chrome/browser/web_applications/components/app_icon_manager.h"
 #include "chrome/browser/web_applications/components/app_registry_controller.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/web_app_icon_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/common/chrome_features.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
diff --git a/chrome/browser/ui/web_applications/web_app_browser_controller.h b/chrome/browser/ui/web_applications/web_app_browser_controller.h
index 81f83cbe..eeb121e 100644
--- a/chrome/browser/ui/web_applications/web_app_browser_controller.h
+++ b/chrome/browser/ui/web_applications/web_app_browser_controller.h
@@ -40,7 +40,7 @@
 // Class to encapsulate logic to control the browser UI for
 // web apps.
 // App information is obtained from the WebAppRegistrar.
-// Icon information is obtained from the AppIconManager.
+// Icon information is obtained from the WebAppIconManager.
 // Note: Much of the functionality in HostedAppBrowserController
 // will move to this class.
 class WebAppBrowserController : public AppBrowserController,
diff --git a/chrome/browser/ui/web_applications/web_app_browsertest.cc b/chrome/browser/ui/web_applications/web_app_browsertest.cc
index e6d1c02..c6ee16a 100644
--- a/chrome/browser/ui/web_applications/web_app_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_browsertest.cc
@@ -217,9 +217,10 @@
 IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, ThemeColor) {
   {
     const SkColor theme_color = SkColorSetA(SK_ColorBLUE, 0xF0);
-    blink::Manifest manifest;
+    blink::mojom::Manifest manifest;
     manifest.start_url = GURL(kExampleURL);
     manifest.scope = GURL(kExampleURL);
+    manifest.has_theme_color = true;
     manifest.theme_color = theme_color;
     auto web_app_info = std::make_unique<WebApplicationInfo>();
     web_app::UpdateWebAppInfoFromManifest(manifest, GURL(kExampleManifestURL),
@@ -246,9 +247,10 @@
 }
 
 IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, BackgroundColor) {
-  blink::Manifest manifest;
+  blink::mojom::Manifest manifest;
   manifest.start_url = GURL(kExampleURL);
   manifest.scope = GURL(kExampleURL);
+  manifest.has_background_color = true;
   manifest.background_color = SkColorSetA(SK_ColorBLUE, 0xF0);
   auto web_app_info = std::make_unique<WebApplicationInfo>();
   web_app::UpdateWebAppInfoFromManifest(manifest, GURL(kExampleManifestURL),
diff --git a/chrome/browser/ui/web_applications/web_app_engagement_browsertest.cc b/chrome/browser/ui/web_applications/web_app_engagement_browsertest.cc
index d384959..2915cb7 100644
--- a/chrome/browser/ui/web_applications/web_app_engagement_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_engagement_browsertest.cc
@@ -17,13 +17,14 @@
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/startup/startup_browser_creator.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
 #include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
 #include "chrome/browser/web_applications/components/external_install_options.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.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_application_info.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/services/app_service/public/mojom/types.mojom-shared.h"
@@ -470,7 +471,58 @@
   TestEngagementEventsAfterLaunch(histograms, browser());
 }
 
-IN_PROC_BROWSER_TEST_F(WebAppEngagementBrowserTest, CommandLineWindow) {
+IN_PROC_BROWSER_TEST_F(WebAppEngagementBrowserTest, CommandLineWindowByUrl) {
+  base::HistogramTester tester;
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  // There should be one browser to start with.
+  unsigned int expected_browsers = 1;
+  const int expected_tabs = 1;
+  EXPECT_EQ(expected_browsers, chrome::GetBrowserCount(browser()->profile()));
+  EXPECT_EQ(expected_tabs, browser()->tab_strip_model()->count());
+
+  const GURL example_url(
+      embedded_test_server()->GetURL("/banners/manifest_test_page.html"));
+
+  auto result_code = ExternallyManagedAppManagerInstall(
+      browser()->profile(), CreateInstallOptions(example_url));
+  ASSERT_EQ(InstallResultCode::kSuccessNewInstall, result_code);
+  absl::optional<AppId> app_id = FindAppWithUrlInScope(example_url);
+  ASSERT_TRUE(app_id);
+  content::WindowedNotificationObserver app_loaded_observer(
+      content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
+      content::NotificationService::AllSources());
+
+  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
+  command_line.AppendSwitchASCII(switches::kApp, example_url.spec());
+
+  // The app should open as a window.
+  EXPECT_TRUE(StartupBrowserCreator().ProcessCmdLineImpl(
+      command_line, base::FilePath(), /*process_startup=*/false,
+      browser()->profile(), {}));
+  app_loaded_observer.Wait();
+
+  Browser* const app_browser = BrowserList::GetInstance()->GetLastActive();
+  EXPECT_TRUE(app_browser->is_type_app());
+
+  {
+    // From c/b/ui/startup/launch_mode_recorder.h:
+    constexpr char kLaunchModesHistogram[] = "Launch.Modes";
+    const base::HistogramBase::Sample LM_AS_WEBAPP_WINDOW_BY_URL = 23;
+
+    tester.ExpectUniqueSample(kLaunchModesHistogram, LM_AS_WEBAPP_WINDOW_BY_URL,
+                              1);
+  }
+
+  // Check that the number of browsers and tabs is correct.
+  expected_browsers++;
+
+  EXPECT_EQ(expected_browsers, chrome::GetBrowserCount(browser()->profile()));
+  EXPECT_EQ(expected_tabs, browser()->tab_strip_model()->count());
+  EXPECT_EQ(expected_tabs, app_browser->tab_strip_model()->count());
+}
+
+IN_PROC_BROWSER_TEST_F(WebAppEngagementBrowserTest, CommandLineWindowByAppId) {
   base::HistogramTester tester;
   ASSERT_TRUE(embedded_test_server()->Start());
 
@@ -504,13 +556,15 @@
   Browser* const app_browser = BrowserList::GetInstance()->GetLastActive();
   EXPECT_EQ(app_browser->type(), Browser::TYPE_APP);
   EXPECT_TRUE(app_browser->app_controller());
+  EXPECT_TRUE(AppBrowserController::IsWebApp(app_browser));
 
   {
-    // From startup_browser_creator_impl.cc:
+    // From c/b/ui/startup/launch_mode_recorder.h:
     constexpr char kLaunchModesHistogram[] = "Launch.Modes";
-    const base::HistogramBase::Sample LM_AS_WEBAPP_IN_WINDOW = 1;
+    const base::HistogramBase::Sample LM_AS_WEBAPP_WINDOW_BY_APP_ID = 24;
 
-    tester.ExpectUniqueSample(kLaunchModesHistogram, LM_AS_WEBAPP_IN_WINDOW, 1);
+    tester.ExpectUniqueSample(kLaunchModesHistogram,
+                              LM_AS_WEBAPP_WINDOW_BY_APP_ID, 1);
   }
 
   // Check that the number of browsers and tabs is correct.
diff --git a/chrome/browser/ui/web_applications/web_app_file_handling_browsertest.cc b/chrome/browser/ui/web_applications/web_app_file_handling_browsertest.cc
index ebdee42..e5e47f11 100644
--- a/chrome/browser/ui/web_applications/web_app_file_handling_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_file_handling_browsertest.cc
@@ -490,16 +490,14 @@
     web_app_info->scope = web_app_info->start_url;
     web_app_info->title = u"Many File Handlers";
 
-    std::vector<blink::Manifest::FileHandler> file_handlers;
+    std::vector<blink::mojom::ManifestFileHandlerPtr> file_handlers;
     for (unsigned i = 0; i < kNumHandlers; ++i) {
-      const std::u16string name =
-          base::UTF8ToUTF16(base::StringPrintf("n%u", i));
-      std::map<std::u16string, std::vector<std::u16string>> accept;
-      accept[base::UTF8ToUTF16(mime_type(i))] = {
+      auto handler = blink::mojom::ManifestFileHandler::New();
+      handler->action = action_url(i);
+      handler->name = base::UTF8ToUTF16(base::StringPrintf("n%u", i));
+      handler->accept[base::UTF8ToUTF16(mime_type(i))] = {
           base::UTF8ToUTF16(extension(i))};
-      file_handlers.push_back({action_url(i), name,
-                               std::vector<blink::Manifest::ImageResource>(),
-                               std::move(accept)});
+      file_handlers.push_back(std::move(handler));
     }
     web_app_info->file_handlers =
         CreateFileHandlersFromManifest(file_handlers, web_app_info->scope);
diff --git a/chrome/browser/ui/webui/realbox/realbox_handler.cc b/chrome/browser/ui/webui/realbox/realbox_handler.cc
index c1c41fe4..d0554796 100644
--- a/chrome/browser/ui/webui/realbox/realbox_handler.cc
+++ b/chrome/browser/ui/webui/realbox/realbox_handler.cc
@@ -162,7 +162,7 @@
         RealboxHandler::AutocompleteMatchVectorIconToResourceName(
             match.GetVectorIcon(is_bookmarked));
     mojom_match->image_dominant_color = match.image_dominant_color;
-    mojom_match->image_url = match.ImageUrl().spec();
+    mojom_match->image_url = match.image_url.spec();
     mojom_match->fill_into_edit = match.fill_into_edit;
     mojom_match->inline_autocompletion = match.inline_autocompletion;
     mojom_match->is_search_type = AutocompleteMatch::IsSearchType(match.type);
@@ -179,9 +179,10 @@
                                 {match.contents, additional_text.value()}, u" ")
                           : match.contents,
           ImageLineToString16(match.answer->second_line()));
+      mojom_match->image_url = match.ImageUrl().spec();
     }
     mojom_match->is_rich_suggestion =
-        !match.ImageUrl().is_empty() ||
+        !mojom_match->image_url.empty() ||
         match.type == AutocompleteMatchType::CALCULATOR ||
         (match.answer.has_value() &&
          base::FeatureList::IsEnabled(omnibox::kNtpRealboxSuggestionAnswers));
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler.cc b/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler.cc
index 5c707fc..d4aa2fd6 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler.cc
@@ -3,19 +3,53 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler.h"
+
 #include "ash/public/cpp/message_center_ash.h"
 #include "base/logging.h"
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/services/app_service/public/cpp/types_util.h"
+#include "components/services/app_service/public/mojom/types.mojom.h"
 
 namespace chromeos {
 namespace settings {
 
+namespace {
+app_notification::mojom::AppPtr CreateAppPtr(const apps::AppUpdate& update) {
+  apps::mojom::PermissionPtr permission_copy;
+  for (const auto& permission : update.Permissions()) {
+    if ((static_cast<app_management::mojom::PwaPermissionType>(
+             permission->permission_id) ==
+         app_management::mojom::PwaPermissionType::NOTIFICATIONS) ||
+        (static_cast<app_management::mojom::ArcPermissionType>(
+             permission->permission_id) ==
+         app_management::mojom::ArcPermissionType::NOTIFICATIONS)) {
+      permission_copy = permission->Clone();
+      break;
+    }
+  }
+
+  auto app = app_notification::mojom::App::New();
+  app->id = update.AppId();
+  app->title = update.Name();
+  app->notification_permission = std::move(permission_copy);
+
+  return app;
+}
+}  // namespace
+
 using app_notification::mojom::AppNotificationsHandler;
 using app_notification::mojom::AppNotificationsObserver;
 
-AppNotificationHandler ::AppNotificationHandler() {
+AppNotificationHandler::AppNotificationHandler(
+    apps::AppServiceProxyChromeOs* app_service_proxy)
+    : app_service_proxy_(app_service_proxy) {
   if (ash::MessageCenterAsh::Get()) {
     ash::MessageCenterAsh::Get()->AddObserver(this);
   }
+  // app_service_proxy_ = apps::AppServiceProxyFactory::GetForProfile(profile_);
+  Observe(&app_service_proxy_->AppRegistryCache());
 }
 
 AppNotificationHandler::~AppNotificationHandler() {
@@ -47,5 +81,52 @@
   ash::MessageCenterAsh::Get()->SetQuietMode(in_quiet_mode);
 }
 
+void AppNotificationHandler::GetApps() {
+  std::vector<app_notification::mojom::AppPtr> apps;
+  app_service_proxy_->AppRegistryCache().ForEachApp(
+      [&apps](const apps::AppUpdate& update) {
+        if (update.ShowInManagement() != apps::mojom::OptionalBool::kTrue ||
+            !apps_util::IsInstalled(update.Readiness())) {
+          return;
+        }
+
+        // This statement only adds apps to the list if they are
+        // of app_type kArc or kWeb.
+        if (update.AppType() == apps::mojom::AppType::kArc) {
+          for (const auto& permission : update.Permissions()) {
+            if (static_cast<app_management::mojom::ArcPermissionType>(
+                    permission->permission_id) ==
+                app_management::mojom::ArcPermissionType::NOTIFICATIONS) {
+              apps.push_back(CreateAppPtr(update));
+              break;
+            }
+          }
+        } else if (update.AppType() == apps::mojom::AppType::kWeb) {
+          for (const auto& permission : update.Permissions()) {
+            if (static_cast<app_management::mojom::PwaPermissionType>(
+                    permission->permission_id) ==
+                app_management::mojom::PwaPermissionType::NOTIFICATIONS) {
+              apps.push_back(CreateAppPtr(update));
+              break;
+            }
+          }
+        }
+      });
+  apps_ = std::move(apps);
+}
+
+void AppNotificationHandler::OnAppUpdate(const apps::AppUpdate& update) {
+  // Each time an update is observed the entire list of apps is refetched.
+  // 'update' is a required parameter from the AppRegistryCache::Observer,
+  // but is not used in this implementation.
+  apps_.clear();
+  GetApps();
+}
+
+void AppNotificationHandler::OnAppRegistryCacheWillBeDestroyed(
+    apps::AppRegistryCache* cache) {
+  Observe(nullptr);
+}
+
 }  // namespace settings
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler.h b/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler.h
index e5810197e..6553ae13 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler.h
+++ b/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler.h
@@ -6,19 +6,27 @@
 #define CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_OS_APPS_PAGE_APP_NOTIFICATION_HANDLER_H_
 
 #include "ash/public/cpp/message_center_ash.h"
+#include "chrome/browser/ui/webui/app_management/app_management.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_apps_page/mojom/app_notification_handler.mojom.h"
+#include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote_set.h"
 
+namespace apps {
+class AppServiceProxyChromeOs;
+}  // namespace apps
+
 namespace chromeos {
 namespace settings {
 
 class AppNotificationHandler
     : public app_notification::mojom::AppNotificationsHandler,
-      public ash::MessageCenterAsh::Observer {
+      public ash::MessageCenterAsh::Observer,
+      public apps::AppRegistryCache::Observer {
  public:
-  AppNotificationHandler();
+  explicit AppNotificationHandler(
+      apps::AppServiceProxyChromeOs* app_service_proxy);
   ~AppNotificationHandler() override;
 
   // app_notification::mojom::AppNotificationHandler:
@@ -39,11 +47,21 @@
   // settings::mojom::AppNotificationHandler:
   void SetQuietMode(bool in_quiet_mode) override;
 
+  // apps::AppRegistryCache::Observer:
+  void OnAppUpdate(const apps::AppUpdate& update) override;
+  void OnAppRegistryCacheWillBeDestroyed(
+      apps::AppRegistryCache* cache) override;
+
+  void GetApps();
+
   bool in_quiet_mode_;
 
   mojo::RemoteSet<app_notification::mojom::AppNotificationsObserver>
       observer_list_;
 
+  apps::AppServiceProxyChromeOs* app_service_proxy_;
+  std::vector<app_notification::mojom::AppPtr> apps_;
+
   mojo::Receiver<app_notification::mojom::AppNotificationsHandler> receiver_{
       this};
 };
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler_unittest.cc b/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler_unittest.cc
index e4eefad..e23a697 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/os_apps_page/app_notification_handler_unittest.cc
@@ -6,9 +6,17 @@
 
 #include <memory>
 #include <utility>
+#include <vector>
 
 #include "ash/public/cpp/message_center_ash.h"
 #include "base/logging.h"
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/ui/webui/app_management/app_management.mojom.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/services/app_service/public/cpp/app_registry_cache.h"
+#include "components/services/app_service/public/mojom/types.mojom.h"
+#include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace chromeos {
@@ -31,16 +39,22 @@
 
 class AppNotificationHandlerTest : public testing::Test {
  public:
-  AppNotificationHandlerTest() = default;
+  AppNotificationHandlerTest()
+      : task_environment_(content::BrowserTaskEnvironment::REAL_IO_THREAD),
+        profile_(std::make_unique<TestingProfile>()) {}
   ~AppNotificationHandlerTest() override = default;
 
   void SetUp() override {
     ash::MessageCenterAsh::SetForTesting(&message_center_ash_);
-    handler_ = std::make_unique<AppNotificationHandler>();
+    app_service_proxy_ =
+        std::make_unique<apps::AppServiceProxyChromeOs>(profile_.get());
+    handler_ =
+        std::make_unique<AppNotificationHandler>(app_service_proxy_.get());
   }
 
   void TearDown() override {
     handler_.reset();
+    app_service_proxy_.reset();
     ash::MessageCenterAsh::SetForTesting(nullptr);
   }
 
@@ -51,8 +65,56 @@
     handler_->SetQuietMode(quiet_mode_enabled);
   }
 
+  void CreateAndStoreFakeApp(
+      std::string fake_id,
+      apps::mojom::AppType app_type,
+      std::uint32_t permission_type,
+      apps::mojom::PermissionValueType permission_value_type) {
+    std::vector<apps::mojom::PermissionPtr> fake_permissions;
+    apps::mojom::PermissionPtr fake_permission = apps::mojom::Permission::New();
+    fake_permission->permission_id = permission_type;
+    fake_permission->value_type = permission_value_type;
+    fake_permission->value = /*True=*/1;
+    fake_permission->is_managed = false;
+
+    fake_permissions.push_back(fake_permission.Clone());
+
+    std::vector<apps::mojom::AppPtr> fake_apps;
+    apps::mojom::AppPtr fake_app = apps::mojom::App::New();
+    fake_app->app_type = app_type;
+    fake_app->app_id = fake_id;
+    fake_app->show_in_management = apps::mojom::OptionalBool::kTrue;
+    fake_app->readiness = apps::mojom::Readiness::kReady;
+    fake_app->permissions = std::move(fake_permissions);
+
+    fake_apps.push_back(fake_app.Clone());
+
+    UpdateAppRegistryCache(fake_apps, app_type);
+  }
+
+  void UpdateAppRegistryCache(std::vector<apps::mojom::AppPtr>& fake_apps,
+                              apps::mojom::AppType app_type) {
+    app_service_proxy_->AppRegistryCache().OnApps(std::move(fake_apps),
+                                                  app_type, false);
+  }
+
+  bool CheckIfFakeAppInList(std::string fake_id) {
+    bool app_found = false;
+
+    for (app_notification::mojom::AppPtr const& app : handler_->apps_) {
+      if (app->id.compare(fake_id) == 0) {
+        app_found = true;
+        break;
+      }
+    }
+    return app_found;
+  }
+
  private:
   std::unique_ptr<AppNotificationHandler> handler_;
+  content::BrowserTaskEnvironment task_environment_;
+  std::unique_ptr<TestingProfile> profile_;
+  std::unique_ptr<apps::AppServiceProxyChromeOs> app_service_proxy_;
   FakeMessageCenterAsh message_center_ash_;
 };
 
@@ -76,5 +138,47 @@
   EXPECT_FALSE(GetHandlerQuietModeState());
 }
 
+// Tests the filtering of the GetApps() function
+// by creating multiple fake apps with different parameters
+// and confirming that GetApps() only adds the correct ones.
+// GetApps() should only add kArc and kWeb apps
+// with the NOTIFICATIONS permission.
+TEST_F(AppNotificationHandlerTest, TestGetAppsFiltering) {
+  CreateAndStoreFakeApp(
+      "arcAppWithNotifications", apps::mojom::AppType::kArc,
+      static_cast<std::uint32_t>(
+          app_management::mojom::ArcPermissionType::NOTIFICATIONS),
+      apps::mojom::PermissionValueType::kBool);
+
+  CreateAndStoreFakeApp(
+      "webAppWithNotifications", apps::mojom::AppType::kWeb,
+      static_cast<std::uint32_t>(
+          app_management::mojom::PwaPermissionType::NOTIFICATIONS),
+      apps::mojom::PermissionValueType::kBool);
+
+  CreateAndStoreFakeApp("arcAppWithCamera", apps::mojom::AppType::kArc,
+                        static_cast<std::uint32_t>(
+                            app_management::mojom::ArcPermissionType::CAMERA),
+                        apps::mojom::PermissionValueType::kBool);
+
+  CreateAndStoreFakeApp(
+      "webAppWithGeolocation", apps::mojom::AppType::kWeb,
+      static_cast<std::uint32_t>(
+          app_management::mojom::PwaPermissionType::GEOLOCATION),
+      apps::mojom::PermissionValueType::kBool);
+
+  CreateAndStoreFakeApp(
+      "pluginVmAppWithPrinting", apps::mojom::AppType::kPluginVm,
+      static_cast<std::uint32_t>(
+          app_management::mojom::PluginVmPermissionType::PRINTING),
+      apps::mojom::PermissionValueType::kBool);
+
+  EXPECT_TRUE(CheckIfFakeAppInList("arcAppWithNotifications"));
+  EXPECT_TRUE(CheckIfFakeAppInList("webAppWithNotifications"));
+  EXPECT_FALSE(CheckIfFakeAppInList("arcAppWithCamera"));
+  EXPECT_FALSE(CheckIfFakeAppInList("webAppWithGeolocation"));
+  EXPECT_FALSE(CheckIfFakeAppInList("pluginVmAppWithPrinting"));
+}
+
 }  // namespace settings
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_apps_page/mojom/BUILD.gn b/chrome/browser/ui/webui/settings/chromeos/os_apps_page/mojom/BUILD.gn
index c653d74b..1ac2122 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_apps_page/mojom/BUILD.gn
+++ b/chrome/browser/ui/webui/settings/chromeos/os_apps_page/mojom/BUILD.gn
@@ -7,5 +7,8 @@
 mojom("mojom") {
   sources = [ "app_notification_handler.mojom" ]
 
-  public_deps = [ "//mojo/public/mojom/base" ]
+  public_deps = [
+    "//components/services/app_service/public/mojom",
+    "//mojo/public/mojom/base",
+  ]
 }
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_apps_page/mojom/app_notification_handler.mojom b/chrome/browser/ui/webui/settings/chromeos/os_apps_page/mojom/app_notification_handler.mojom
index 16667bc..7116aa0 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_apps_page/mojom/app_notification_handler.mojom
+++ b/chrome/browser/ui/webui/settings/chromeos/os_apps_page/mojom/app_notification_handler.mojom
@@ -4,6 +4,24 @@
 
 module chromeos.settings.app_notification.mojom;
 
+import "components/services/app_service/public/mojom/types.mojom";
+
+// Implementation of App
+// Contains the app's id, title, and only the notification permission, as this
+// is the only permission used in the AppNotificationsHandler.
+// Only represents Apps with an app_type of kArc or kWeb (Pwa).
+struct App {
+  // Unique identifier of the App.
+  string id;
+
+  // The title of the app,
+  // this field may be null when this struct is used to signal updates.
+  string? title;
+
+  // Contains the current permission state of the App's notification.
+  apps.mojom.Permission notification_permission;
+};
+
 // Browser interface.
 // Interface for for fetching and setting App notification
 // properties in OSSettings.
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_manager.cc b/chrome/browser/ui/webui/settings/chromeos/os_settings_manager.cc
index 1d4b167..e06b749 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_settings_manager.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_manager.cc
@@ -55,7 +55,8 @@
                                           sections_.get(),
                                           hierarchy_.get(),
                                           local_search_service_proxy)),
-      app_notification_handler_(std::make_unique<AppNotificationHandler>()) {}
+      app_notification_handler_(
+          std::make_unique<AppNotificationHandler>(app_service_proxy)) {}
 
 OsSettingsManager::~OsSettingsManager() = default;
 
diff --git a/chrome/browser/ui/webui/settings/settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc
index 22f00008..d1067d8 100644
--- a/chrome/browser/ui/webui/settings/settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui.cc
@@ -256,7 +256,7 @@
     (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
   AddSettingsPageUIHandler(std::make_unique<UrlHandlersHandler>(
       g_browser_process->local_state(), profile,
-      GetRegistrarForProfile(profile).AsWebAppRegistrar()));
+      &GetRegistrarForProfile(profile)));
 #endif
 
 #if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
diff --git a/chrome/browser/ui/webui/tab_strip/tab_strip_ui_browsertest.cc b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_browsertest.cc
index 598260e..2a0c9e1 100644
--- a/chrome/browser/ui/webui/tab_strip/tab_strip_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_browsertest.cc
@@ -48,6 +48,7 @@
   MOCK_METHOD0(CloseContextMenu, void());
   MOCK_METHOD3(ShowEditDialogForGroupAtPoint,
                void(gfx::Point, gfx::Rect, tab_groups::TabGroupId));
+  MOCK_METHOD0(HideEditDialogForGroup, void());
   MOCK_METHOD0(GetLayout, TabStripUILayout());
   MOCK_CONST_METHOD1(GetColor, SkColor(int));
   MOCK_CONST_METHOD1(GetSystemColor, SkColor(ui::NativeTheme::ColorId));
diff --git a/chrome/browser/ui/webui/tab_strip/tab_strip_ui_embedder.h b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_embedder.h
index 1bec1de..ab0f4c9 100644
--- a/chrome/browser/ui/webui/tab_strip/tab_strip_ui_embedder.h
+++ b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_embedder.h
@@ -34,6 +34,7 @@
   virtual void ShowEditDialogForGroupAtPoint(gfx::Point point,
                                              gfx::Rect rect,
                                              tab_groups::TabGroupId group) = 0;
+  virtual void HideEditDialogForGroup() = 0;
 
   virtual TabStripUILayout GetLayout() = 0;
 
diff --git a/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.cc b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.cc
index b206386..b7f1397 100644
--- a/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.cc
+++ b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler.cc
@@ -259,6 +259,7 @@
     }
 
     case TabGroupChange::kClosed: {
+      embedder_->HideEditDialogForGroup();
       FireWebUIListener("tab-group-closed",
                         base::Value(change.group.ToString()));
       break;
diff --git a/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler_unittest.cc b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler_unittest.cc
index 6b982c2a..637db592 100644
--- a/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler_unittest.cc
+++ b/chrome/browser/ui/webui/tab_strip/tab_strip_ui_handler_unittest.cc
@@ -54,6 +54,7 @@
                                      gfx::Rect rect,
                                      tab_groups::TabGroupId group_id) override {
   }
+  void HideEditDialogForGroup() override {}
   TabStripUILayout GetLayout() override { return TabStripUILayout(); }
   SkColor GetColor(int id) const override { return SK_ColorWHITE; }
   SkColor GetSystemColor(ui::NativeTheme::ColorId id) const override {
diff --git a/chrome/browser/ui/webui/test_data_source.cc b/chrome/browser/ui/webui/test_data_source.cc
index 15a78cb..4df3ebf1 100644
--- a/chrome/browser/ui/webui/test_data_source.cc
+++ b/chrome/browser/ui/webui/test_data_source.cc
@@ -99,6 +99,8 @@
                  network::mojom::CSPDirectiveName::RequireTrustedTypesFor ||
              directive == network::mojom::CSPDirectiveName::TrustedTypes) {
     return std::string();
+  } else if (directive == network::mojom::CSPDirectiveName::FrameAncestors) {
+    return "frame-ancestors chrome://* 'self';";
   }
 
   return content::URLDataSource::GetContentSecurityPolicy(directive);
diff --git a/chrome/browser/ui/webui/whats_new/whats_new_ui.cc b/chrome/browser/ui/webui/whats_new/whats_new_ui.cc
index ca27e4f..f274e40 100644
--- a/chrome/browser/ui/webui/whats_new/whats_new_ui.cc
+++ b/chrome/browser/ui/webui/whats_new/whats_new_ui.cc
@@ -42,7 +42,7 @@
   // Allow embedding of iframe from chrome.com
   source->OverrideContentSecurityPolicy(
       network::mojom::CSPDirectiveName::ChildSrc,
-      base::StringPrintf("child-src https: %s;",
+      base::StringPrintf("child-src chrome://test https: %s;",
                          whats_new::kChromeWhatsNewURLShort));
   return source;
 }
diff --git a/chrome/browser/ui/zoom/zoom_controller_browsertest.cc b/chrome/browser/ui/zoom/zoom_controller_browsertest.cc
index 4aadcbb..ef7f0ee 100644
--- a/chrome/browser/ui/zoom/zoom_controller_browsertest.cc
+++ b/chrome/browser/ui/zoom/zoom_controller_browsertest.cc
@@ -313,8 +313,12 @@
             base::Unretained(this))) {}
   ~ZoomControllerForPrerenderingTest() override = default;
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    ZoomControllerBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     host_resolver()->AddRule("*", "127.0.0.1");
     ASSERT_TRUE(embedded_test_server()->Start());
 
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 9d5ad1d..49572a0 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -14,17 +14,12 @@
 
 source_set("web_applications") {
   sources = [
-    "app_shortcut_manager.cc",
-    "components/app_icon_manager.cc",
-    "components/app_icon_manager.h",
     "components/app_registrar_observer.h",
     "components/app_registry_controller.cc",
     "components/app_registry_controller.h",
-    "components/app_shortcut_manager.h",
     "components/external_install_options.cc",
     "components/external_install_options.h",
     "components/externally_installed_web_app_prefs.h",
-    "components/externally_managed_app_manager.h",
     "components/file_handler_manager.h",
     "components/file_handling_permission_context.cc",
     "components/file_handling_permission_context.h",
@@ -98,6 +93,7 @@
     "externally_managed_app_install_task.cc",
     "externally_managed_app_install_task.h",
     "externally_managed_app_manager.cc",
+    "externally_managed_app_manager.h",
     "externally_managed_app_manager_impl.cc",
     "externally_managed_app_manager_impl.h",
     "externally_managed_app_registration_task.cc",
@@ -473,7 +469,6 @@
   testonly = true
 
   sources = [
-    "components/externally_managed_app_manager_unittest.cc",
     "components/file_handler_manager_unittest.cc",
     "components/install_finalizer_unittest.cc",
     "components/preinstalled_app_install_features_unittest.cc",
@@ -490,6 +485,7 @@
     "components/web_app_utils_unittest.cc",
     "daily_metrics_helper_unittest.cc",
     "externally_managed_app_manager_impl_unittest.cc",
+    "externally_managed_app_manager_unittest.cc",
     "isolation_prefs_utils_unittest.cc",
     "manifest_update_task_unittest.cc",
     "os_integration_manager_unittest.cc",
diff --git a/chrome/browser/web_applications/README.md b/chrome/browser/web_applications/README.md
index 55b4216..c055976 100644
--- a/chrome/browser/web_applications/README.md
+++ b/chrome/browser/web_applications/README.md
@@ -191,7 +191,7 @@
 manifest data it specifies" with a few inbetweens.
 
 
-### [`ExternallyManagedAppManager`](components/externally_managed_app_manager.h)
+### [`ExternallyManagedAppManager`](externally_managed_app_manager.h)
 
 This is for all installs that are not initiated by the user. This includes
 [preinstalled apps](preinstalled_web_app_manager.h),
diff --git a/chrome/browser/web_applications/app_service/web_apps_chromeos.cc b/chrome/browser/web_applications/app_service/web_apps_chromeos.cc
index 5b9d594..c1f5e66 100644
--- a/chrome/browser/web_applications/app_service/web_apps_chromeos.cc
+++ b/chrome/browser/web_applications/app_service/web_apps_chromeos.cc
@@ -26,13 +26,13 @@
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/web_applications/web_app_dialog_manager.h"
 #include "chrome/browser/ui/web_applications/web_app_ui_manager_impl.h"
-#include "chrome/browser/web_applications/components/app_icon_manager.h"
 #include "chrome/browser/web_applications/components/install_finalizer.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"
 #include "chrome/browser/web_applications/components/web_app_utils.h"
 #include "chrome/browser/web_applications/system_web_apps/system_web_app_manager.h"
+#include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/browser/web_applications/web_app_sync_bridge.h"
diff --git a/chrome/browser/web_applications/app_service/web_apps_publisher_host.cc b/chrome/browser/web_applications/app_service/web_apps_publisher_host.cc
index c040050..407da58 100644
--- a/chrome/browser/web_applications/app_service/web_apps_publisher_host.cc
+++ b/chrome/browser/web_applications/app_service/web_apps_publisher_host.cc
@@ -13,9 +13,9 @@
 #include "chrome/browser/apps/app_service/app_icon_factory.h"
 #include "chrome/browser/apps/app_service/menu_item_constants.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/web_applications/components/app_icon_manager.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app.h"
+#include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/common/chrome_features.h"
diff --git a/chrome/browser/web_applications/app_shortcut_manager.cc b/chrome/browser/web_applications/app_shortcut_manager.cc
deleted file mode 100644
index 872ee9a..0000000
--- a/chrome/browser/web_applications/app_shortcut_manager.cc
+++ /dev/null
@@ -1,253 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/web_applications/components/app_shortcut_manager.h"
-
-#include <utility>
-
-#include "base/callback.h"
-#include "base/feature_list.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/no_destructor.h"
-#include "base/strings/utf_string_conversions.h"
-#include "build/build_config.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/web_applications/components/app_icon_manager.h"
-#include "chrome/browser/web_applications/web_app_registrar.h"
-#include "chrome/common/chrome_features.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
-
-namespace web_app {
-
-namespace {
-
-// UMA metric name for shortcuts creation result.
-constexpr const char* kCreationResultMetric =
-    "WebApp.Shortcuts.Creation.Result";
-
-// Result of shortcuts creation process.
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused.
-enum class CreationResult {
-  kSuccess = 0,
-  kFailToCreateShortcut = 1,
-  kMaxValue = kFailToCreateShortcut
-};
-
-AppShortcutManager::ShortcutCallback& GetShortcutUpdateCallbackForTesting() {
-  static base::NoDestructor<AppShortcutManager::ShortcutCallback> callback;
-  return *callback;
-}
-
-}  // namespace
-
-AppShortcutManager::AppShortcutManager(Profile* profile) : profile_(profile) {}
-
-AppShortcutManager::~AppShortcutManager() = default;
-
-void AppShortcutManager::SetSubsystems(AppIconManager* icon_manager,
-                                       WebAppRegistrar* registrar) {
-  icon_manager_ = icon_manager;
-  registrar_ = registrar;
-}
-
-void AppShortcutManager::UpdateShortcuts(const AppId& app_id,
-                                         base::StringPiece old_name) {
-  if (!CanCreateShortcuts())
-    return;
-
-  GetShortcutInfoForApp(
-      app_id, base::BindOnce(
-                  &AppShortcutManager::OnShortcutInfoRetrievedUpdateShortcuts,
-                  weak_ptr_factory_.GetWeakPtr(), base::UTF8ToUTF16(old_name)));
-}
-
-void AppShortcutManager::GetAppExistingShortCutLocation(
-    ShortcutLocationCallback callback,
-    std::unique_ptr<ShortcutInfo> shortcut_info) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  // Ownership of |shortcut_info| moves to the Reply, which is guaranteed to
-  // outlive the const reference.
-  const ShortcutInfo& shortcut_info_ref = *shortcut_info;
-  internals::GetShortcutIOTaskRunner()->PostTaskAndReplyWithResult(
-      FROM_HERE,
-      base::BindOnce(&internals::GetAppExistingShortCutLocationImpl,
-                     std::cref(shortcut_info_ref)),
-      base::BindOnce(
-          [](std::unique_ptr<ShortcutInfo> shortcut_info,
-             ShortcutLocationCallback callback, ShortcutLocations locations) {
-            DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-            shortcut_info.reset();
-            std::move(callback).Run(locations);
-          },
-          std::move(shortcut_info), std::move(callback)));
-}
-
-void AppShortcutManager::SetShortcutUpdateCallbackForTesting(
-    base::OnceCallback<void(const ShortcutInfo*)> callback) {
-  GetShortcutUpdateCallbackForTesting() = std::move(callback);  // IN-TEST
-}
-
-bool AppShortcutManager::CanCreateShortcuts() const {
-#if defined(OS_CHROMEOS)
-  return false;
-#else
-  return true;
-#endif
-}
-
-void AppShortcutManager::SuppressShortcutsForTesting() {
-  suppress_shortcuts_for_testing_ = true;
-}
-
-void AppShortcutManager::CreateShortcuts(const AppId& app_id,
-                                         bool add_to_desktop,
-                                         CreateShortcutsCallback callback) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK(CanCreateShortcuts());
-
-  GetShortcutInfoForApp(
-      app_id, base::BindOnce(
-                  &AppShortcutManager::OnShortcutInfoRetrievedCreateShortcuts,
-                  weak_ptr_factory_.GetWeakPtr(), add_to_desktop,
-                  base::BindOnce(&AppShortcutManager::OnShortcutsCreated,
-                                 weak_ptr_factory_.GetWeakPtr(), app_id,
-                                 std::move(callback))));
-}
-
-void AppShortcutManager::DeleteShortcuts(
-    const AppId& app_id,
-    const base::FilePath& shortcuts_data_dir,
-    std::unique_ptr<ShortcutInfo> shortcut_info,
-    DeleteShortcutsCallback callback) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK(CanCreateShortcuts());
-
-  internals::ScheduleDeletePlatformShortcuts(
-      shortcuts_data_dir, std::move(shortcut_info),
-      base::BindOnce(&AppShortcutManager::OnShortcutsDeleted,
-                     weak_ptr_factory_.GetWeakPtr(), app_id,
-                     std::move(callback)));
-}
-
-void AppShortcutManager::ReadAllShortcutsMenuIconsAndRegisterShortcutsMenu(
-    const AppId& app_id,
-    RegisterShortcutsMenuCallback callback) {
-  icon_manager_->ReadAllShortcutsMenuIcons(
-      app_id,
-      base::BindOnce(
-          &AppShortcutManager::OnShortcutsMenuIconsReadRegisterShortcutsMenu,
-          weak_ptr_factory_.GetWeakPtr(), app_id, std::move(callback)));
-}
-
-void AppShortcutManager::RegisterShortcutsMenuWithOs(
-    const AppId& app_id,
-    const std::vector<WebApplicationShortcutsMenuItemInfo>&
-        shortcuts_menu_item_infos,
-    const ShortcutsMenuIconBitmaps& shortcuts_menu_icon_bitmaps) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (!web_app::ShouldRegisterShortcutsMenuWithOs() ||
-      suppress_shortcuts_for_testing()) {
-    return;
-  }
-
-  std::unique_ptr<ShortcutInfo> shortcut_info = BuildShortcutInfo(app_id);
-  if (!shortcut_info)
-    return;
-
-  // |shortcut_data_dir| is located in per-app OS integration resources
-  // directory. See GetOsIntegrationResourcesDirectoryForApp function for more
-  // info.
-  base::FilePath shortcut_data_dir =
-      internals::GetShortcutDataDir(*shortcut_info);
-  web_app::RegisterShortcutsMenuWithOs(
-      shortcut_info->extension_id, shortcut_info->profile_path,
-      shortcut_data_dir, shortcuts_menu_item_infos,
-      shortcuts_menu_icon_bitmaps);
-}
-
-void AppShortcutManager::UnregisterShortcutsMenuWithOs(const AppId& app_id) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (!web_app::ShouldRegisterShortcutsMenuWithOs())
-    return;
-
-  web_app::UnregisterShortcutsMenuWithOs(app_id, profile_->GetPath());
-}
-
-void AppShortcutManager::OnShortcutsCreated(const AppId& app_id,
-                                            CreateShortcutsCallback callback,
-                                            bool success) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  UMA_HISTOGRAM_ENUMERATION(kCreationResultMetric,
-                            success ? CreationResult::kSuccess
-                                    : CreationResult::kFailToCreateShortcut);
-  std::move(callback).Run(success);
-}
-
-void AppShortcutManager::OnShortcutsDeleted(const AppId& app_id,
-                                            DeleteShortcutsCallback callback,
-                                            bool success) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  std::move(callback).Run(success);
-}
-
-void AppShortcutManager::OnShortcutInfoRetrievedCreateShortcuts(
-    bool add_to_desktop,
-    CreateShortcutsCallback callback,
-    std::unique_ptr<ShortcutInfo> info) {
-  if (suppress_shortcuts_for_testing_) {
-    std::move(callback).Run(/*shortcut_created=*/true);
-    return;
-  }
-
-  if (info == nullptr) {
-    std::move(callback).Run(/*shortcut_created=*/false);
-    return;
-  }
-
-  base::FilePath shortcut_data_dir = internals::GetShortcutDataDir(*info);
-
-  ShortcutLocations locations;
-  locations.on_desktop = add_to_desktop;
-  locations.applications_menu_location = APP_MENU_LOCATION_SUBDIR_CHROMEAPPS;
-
-  internals::ScheduleCreatePlatformShortcuts(
-      std::move(shortcut_data_dir), locations, SHORTCUT_CREATION_BY_USER,
-      std::move(info), std::move(callback));
-}
-
-void AppShortcutManager::OnShortcutsMenuIconsReadRegisterShortcutsMenu(
-    const AppId& app_id,
-    RegisterShortcutsMenuCallback callback,
-    ShortcutsMenuIconBitmaps shortcuts_menu_icon_bitmaps) {
-  std::vector<WebApplicationShortcutsMenuItemInfo> shortcuts_menu_item_infos =
-      registrar_->GetAppShortcutsMenuItemInfos(app_id);
-  if (!shortcuts_menu_item_infos.empty()) {
-    RegisterShortcutsMenuWithOs(app_id, shortcuts_menu_item_infos,
-                                shortcuts_menu_icon_bitmaps);
-  }
-
-  std::move(callback).Run(/*shortcuts_menu_registered=*/true);
-}
-
-void AppShortcutManager::OnShortcutInfoRetrievedUpdateShortcuts(
-    std::u16string old_name,
-    std::unique_ptr<ShortcutInfo> shortcut_info) {
-  if (GetShortcutUpdateCallbackForTesting())
-    std::move(GetShortcutUpdateCallbackForTesting()).Run(shortcut_info.get());
-
-  if (suppress_shortcuts_for_testing() || !shortcut_info)
-    return;
-
-  base::FilePath shortcut_data_dir =
-      internals::GetShortcutDataDir(*shortcut_info);
-  internals::PostShortcutIOTask(
-      base::BindOnce(&internals::UpdatePlatformShortcuts,
-                     std::move(shortcut_data_dir), std::move(old_name)),
-      std::move(shortcut_info));
-}
-
-}  // namespace web_app
diff --git a/chrome/browser/web_applications/components/app_icon_manager.cc b/chrome/browser/web_applications/components/app_icon_manager.cc
deleted file mode 100644
index 628cf37..0000000
--- a/chrome/browser/web_applications/components/app_icon_manager.cc
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/web_applications/components/app_icon_manager.h"
-
-#include <map>
-#include "chrome/browser/web_applications/components/web_application_info.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-
-namespace web_app {
-
-namespace {
-
-void WrapReadIconCallback(AppIconManager::ReadIconCallback callback,
-                          IconPurpose ignored,
-                          SkBitmap bitmap) {
-  std::move(callback).Run(std::move(bitmap));
-}
-
-void WrapReadCompressedIconCallback(
-    AppIconManager::ReadCompressedIconCallback callback,
-    IconPurpose ignored,
-    std::vector<uint8_t> data) {
-  std::move(callback).Run(std::move(data));
-}
-
-}  // namespace
-
-WebAppIconManager* AppIconManager::AsWebAppIconManager() {
-  return nullptr;
-}
-
-void AppIconManager::ReadSmallestIconAny(const AppId& app_id,
-                                         SquareSizePx min_icon_size,
-                                         ReadIconCallback callback) const {
-  ReadIconWithPurposeCallback wrapped =
-      base::BindOnce(WrapReadIconCallback, std::move(callback));
-  ReadSmallestIcon(app_id, {IconPurpose::ANY}, min_icon_size,
-                   std::move(wrapped));
-}
-
-void AppIconManager::ReadSmallestCompressedIconAny(
-    const AppId& app_id,
-    SquareSizePx min_icon_size,
-    ReadCompressedIconCallback callback) const {
-  ReadCompressedIconWithPurposeCallback wrapped =
-      base::BindOnce(WrapReadCompressedIconCallback, std::move(callback));
-  ReadSmallestCompressedIcon(app_id, {IconPurpose::ANY}, min_icon_size,
-                             std::move(wrapped));
-}
-
-// static
-void AppIconManager::WrapReadIconWithPurposeCallback(
-    ReadIconWithPurposeCallback callback,
-    IconPurpose purpose,
-    SkBitmap bitmap) {
-  std::move(callback).Run(purpose, std::move(bitmap));
-}
-
-}  // namespace web_app
diff --git a/chrome/browser/web_applications/components/app_icon_manager.h b/chrome/browser/web_applications/components/app_icon_manager.h
deleted file mode 100644
index cb76e06..0000000
--- a/chrome/browser/web_applications/components/app_icon_manager.h
+++ /dev/null
@@ -1,132 +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_WEB_APPLICATIONS_COMPONENTS_APP_ICON_MANAGER_H_
-#define CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_APP_ICON_MANAGER_H_
-
-#include <cstdint>
-#include <map>
-#include <vector>
-
-#include "base/callback_forward.h"
-#include "chrome/browser/web_applications/components/web_app_id.h"
-#include "chrome/browser/web_applications/components/web_application_info.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-
-namespace web_app {
-
-class WebAppIconManager;
-
-// Exclusively used from the UI thread.
-class AppIconManager {
- public:
-  AppIconManager() = default;
-  AppIconManager(const AppIconManager&) = delete;
-  AppIconManager& operator=(const AppIconManager&) = delete;
-  virtual ~AppIconManager() = default;
-
-  virtual void Start() = 0;
-  virtual void Shutdown() = 0;
-
-  virtual WebAppIconManager* AsWebAppIconManager();
-
-  // Returns false if any icon in |icon_sizes_in_px| is missing from downloaded
-  // icons for a given app and |purpose|.
-  virtual bool HasIcons(const AppId& app_id,
-                        IconPurpose purpose,
-                        const SortedSizesPx& icon_sizes_in_px) const = 0;
-  struct IconSizeAndPurpose {
-    SquareSizePx size_px = 0;
-    IconPurpose purpose = IconPurpose::ANY;
-  };
-  // For each of |purposes|, in the given order, looks for an icon with size at
-  // least |min_icon_size|. Returns information on the first icon found.
-  virtual absl::optional<IconSizeAndPurpose> FindIconMatchBigger(
-      const AppId& app_id,
-      const std::vector<IconPurpose>& purposes,
-      SquareSizePx min_size) const = 0;
-  // Returns whether there is a downloaded icon of at least |min_size| for any
-  // of the given |purposes|.
-  virtual bool HasSmallestIcon(const AppId& app_id,
-                               const std::vector<IconPurpose>& purposes,
-                               SquareSizePx min_size) const = 0;
-
-  using ReadIconsCallback =
-      base::OnceCallback<void(std::map<SquareSizePx, SkBitmap> icon_bitmaps)>;
-  // Reads specified icon bitmaps for an app and |purpose|. Returns empty map in
-  // |callback| if IO error.
-  virtual void ReadIcons(const AppId& app_id,
-                         IconPurpose purpose,
-                         const SortedSizesPx& icon_sizes,
-                         ReadIconsCallback callback) const = 0;
-
-  using ReadShortcutsMenuIconsCallback = base::OnceCallback<void(
-      ShortcutsMenuIconBitmaps shortcuts_menu_icon_bitmaps)>;
-
-  // Reads bitmaps for all shortcuts menu icons for an app. Returns a vector of
-  // map<SquareSizePx, SkBitmap>. The index of a map in the vector is the same
-  // as that of its corresponding shortcut in the manifest's shortcuts vector.
-  // Returns empty vector in |callback| if we hit any error.
-  virtual void ReadAllShortcutsMenuIcons(
-      const AppId& app_id,
-      ReadShortcutsMenuIconsCallback callback) const = 0;
-
-  // TODO (crbug.com/1102701): Callback with const ref instead of value.
-  using ReadIconBitmapsCallback =
-      base::OnceCallback<void(IconBitmaps icon_bitmaps)>;
-  // Reads all icon bitmaps for an app. Returns empty |icon_bitmaps| in
-  // |callback| if IO error.
-  virtual void ReadAllIcons(const AppId& app_id,
-                            ReadIconBitmapsCallback callback) const = 0;
-
-  using ReadIconWithPurposeCallback =
-      base::OnceCallback<void(IconPurpose, SkBitmap)>;
-  // For each of |purposes|, in the given order, looks for an icon with size at
-  // least |min_icon_size|. Returns the first icon found, as a bitmap. Returns
-  // an empty SkBitmap in |callback| if IO error.
-  virtual void ReadSmallestIcon(const AppId& app_id,
-                                const std::vector<IconPurpose>& purposes,
-                                SquareSizePx min_icon_size,
-                                ReadIconWithPurposeCallback callback) const = 0;
-
-  using ReadIconCallback = base::OnceCallback<void(SkBitmap)>;
-  // Convenience method for |ReadSmallestIcon| with IconPurpose::ANY only.
-  void ReadSmallestIconAny(const AppId& app_id,
-                           SquareSizePx min_icon_size,
-                           ReadIconCallback callback) const;
-
-  using ReadCompressedIconWithPurposeCallback =
-      base::OnceCallback<void(IconPurpose, std::vector<uint8_t> data)>;
-  // For each of |purposes|, in the given order, looks for an icon with size at
-  // least |min_icon_size|. Returns the first icon found, compressed as PNG.
-  // Returns empty |data| in |callback| if IO error.
-  virtual void ReadSmallestCompressedIcon(
-      const AppId& app_id,
-      const std::vector<IconPurpose>& purposes,
-      SquareSizePx min_icon_size,
-      ReadCompressedIconWithPurposeCallback callback) const = 0;
-
-  using ReadCompressedIconCallback =
-      base::OnceCallback<void(std::vector<uint8_t> data)>;
-  // Convenience method for |ReadSmallestCompressedIcon| with IconPurpose::ANY
-  // only.
-  void ReadSmallestCompressedIconAny(const AppId& app_id,
-                                     SquareSizePx min_icon_size,
-                                     ReadCompressedIconCallback callback) const;
-
-  // Returns a square icon of gfx::kFaviconSize px, or an empty bitmap if not
-  // found.
-  virtual SkBitmap GetFavicon(const AppId& app_id) const = 0;
-
- protected:
-  static void WrapReadIconWithPurposeCallback(
-      ReadIconWithPurposeCallback callback,
-      IconPurpose purpose,
-      SkBitmap bitmap);
-};
-
-}  // namespace web_app
-
-#endif  // CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_APP_ICON_MANAGER_H_
diff --git a/chrome/browser/web_applications/components/app_shim_registry_mac.h b/chrome/browser/web_applications/components/app_shim_registry_mac.h
index 609535b..7ba7443 100644
--- a/chrome/browser/web_applications/components/app_shim_registry_mac.h
+++ b/chrome/browser/web_applications/components/app_shim_registry_mac.h
@@ -25,8 +25,8 @@
 // paths (e.g, the result of calling Profile::GetPath).
 //
 // This class should arguably be a extensions::ExtensionRegistryObserver. It
-// is not (and instead is called by AppShortcutManager, which is one), because
-// apps are in the process of being disentangled from extensions.
+// is not (and instead is called by WebAppShortcutManager, which is one),
+// because apps are in the process of being disentangled from extensions.
 class AppShimRegistry {
  public:
   AppShimRegistry(const AppShimRegistry& other) = delete;
diff --git a/chrome/browser/web_applications/components/app_shortcut_manager.h b/chrome/browser/web_applications/components/app_shortcut_manager.h
deleted file mode 100644
index 10c1031..0000000
--- a/chrome/browser/web_applications/components/app_shortcut_manager.h
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_APP_SHORTCUT_MANAGER_H_
-#define CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_APP_SHORTCUT_MANAGER_H_
-
-#include <memory>
-#include <vector>
-
-#include "base/callback_forward.h"
-#include "base/memory/weak_ptr.h"
-#include "chrome/browser/web_applications/components/web_app_id.h"
-#include "chrome/browser/web_applications/components/web_app_shortcut.h"
-#include "chrome/browser/web_applications/components/web_app_shortcuts_menu.h"
-#include "chrome/browser/web_applications/components/web_application_info.h"
-
-class Profile;
-
-namespace web_app {
-
-class AppIconManager;
-class WebAppRegistrar;
-struct ShortcutInfo;
-
-using ShortcutLocationCallback =
-    base::OnceCallback<void(ShortcutLocations shortcut_locations)>;
-
-// This class manages creation/update/deletion of OS shortcuts for web
-// applications.
-//
-// TODO(crbug.com/860581): Migrate functions from
-// web_app_extension_shortcut.(h|cc) and
-// platform_apps/shortcut_manager.(h|cc) to AppShortcutManager and
-// its subclasses.
-class AppShortcutManager {
- public:
-  explicit AppShortcutManager(Profile* profile);
-  AppShortcutManager(const AppShortcutManager&) = delete;
-  AppShortcutManager& operator=(const AppShortcutManager&) = delete;
-  virtual ~AppShortcutManager();
-
-  void SetSubsystems(AppIconManager* icon_manager, WebAppRegistrar* registrar);
-
-  void Start();
-  void Shutdown();
-
-  // Tells the AppShortcutManager that no shortcuts should actually be written
-  // to the disk.
-  void SuppressShortcutsForTesting();
-
-  bool CanCreateShortcuts() const;
-  void CreateShortcuts(const AppId& app_id,
-                       bool add_to_desktop,
-                       CreateShortcutsCallback callback);
-  // Fetch already-updated shortcut data and deploy to OS integration.
-  void UpdateShortcuts(const AppId& app_id, base::StringPiece old_name);
-  void DeleteShortcuts(const AppId& app_id,
-                       const base::FilePath& shortcuts_data_dir,
-                       std::unique_ptr<ShortcutInfo> shortcut_info,
-                       DeleteShortcutsCallback callback);
-
-  // Posts a task on the IO thread to gather existing shortcut locations
-  // according to |shortcut_info|. The result will be passed into |callback|.
-  // virtual for testing.
-  virtual void GetAppExistingShortCutLocation(
-      ShortcutLocationCallback callback,
-      std::unique_ptr<ShortcutInfo> shortcut_info);
-
-  // TODO(crbug.com/1098471): Move this into web_app_shortcuts_menu_win.cc when
-  // a callback is integrated into the Shortcuts Menu registration flow.
-  using RegisterShortcutsMenuCallback = base::OnceCallback<void(bool success)>;
-  // Registers a shortcuts menu for a web app after reading its shortcuts menu
-  // icons from disk.
-  //
-  // TODO(crbug.com/1098471): Consider unifying this method and
-  // RegisterShortcutsMenuWithOs() below.
-  void ReadAllShortcutsMenuIconsAndRegisterShortcutsMenu(
-      const AppId& app_id,
-      RegisterShortcutsMenuCallback callback);
-
-  // Registers a shortcuts menu for the web app's icon with the OS.
-  //
-  // TODO(crbug.com/1098471): Add a callback as part of the Shortcuts Menu
-  // registration flow.
-  void RegisterShortcutsMenuWithOs(
-      const AppId& app_id,
-      const std::vector<WebApplicationShortcutsMenuItemInfo>&
-          shortcuts_menu_item_infos,
-      const ShortcutsMenuIconBitmaps& shortcuts_menu_icon_bitmaps);
-
-  void UnregisterShortcutsMenuWithOs(const AppId& app_id);
-
-  // Builds initial ShortcutInfo without |ShortcutInfo::favicon| being read.
-  virtual std::unique_ptr<ShortcutInfo> BuildShortcutInfo(
-      const AppId& app_id) = 0;
-
-  // The result of a call to GetShortcutInfo.
-  using GetShortcutInfoCallback =
-      base::OnceCallback<void(std::unique_ptr<ShortcutInfo>)>;
-  // Asynchronously gets the information required to create a shortcut for
-  // |app_id| including all the icon bitmaps. Returns nullptr if app_id is
-  // uninstalled or becomes uninstalled during the asynchronous read of icons.
-  virtual void GetShortcutInfoForApp(const AppId& app_id,
-                                     GetShortcutInfoCallback callback) = 0;
-
-  using ShortcutCallback = base::OnceCallback<void(const ShortcutInfo*)>;
-  static void SetShortcutUpdateCallbackForTesting(ShortcutCallback callback);
-
- protected:
-  void OnShortcutsCreated(const AppId& app_id,
-                          CreateShortcutsCallback callback,
-                          bool success);
-  void OnShortcutsDeleted(const AppId& app_id,
-                          DeleteShortcutsCallback callback,
-                          bool success);
-
-  WebAppRegistrar* registrar() { return registrar_; }
-  Profile* profile() { return profile_; }
-  bool suppress_shortcuts_for_testing() const {
-    return suppress_shortcuts_for_testing_;
-  }
-
- private:
-  void OnShortcutInfoRetrievedCreateShortcuts(
-      bool add_to_desktop,
-      CreateShortcutsCallback callback,
-      std::unique_ptr<ShortcutInfo> info);
-
-  void OnShortcutInfoRetrievedUpdateShortcuts(
-      std::u16string old_name,
-      std::unique_ptr<ShortcutInfo> info);
-
-  void OnShortcutsMenuIconsReadRegisterShortcutsMenu(
-      const AppId& app_id,
-      RegisterShortcutsMenuCallback callback,
-      ShortcutsMenuIconBitmaps shortcuts_menu_icon_bitmaps);
-
-  bool suppress_shortcuts_for_testing_ = false;
-
-  WebAppRegistrar* registrar_ = nullptr;
-  AppIconManager* icon_manager_ = nullptr;
-  Profile* const profile_;
-
-  base::WeakPtrFactory<AppShortcutManager> weak_ptr_factory_{this};
-
-};
-
-}  // namespace web_app
-
-#endif  // CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_APP_SHORTCUT_MANAGER_H_
diff --git a/chrome/browser/web_applications/components/file_handler_manager.h b/chrome/browser/web_applications/components/file_handler_manager.h
index 8365d1f..f4b1de6 100644
--- a/chrome/browser/web_applications/components/file_handler_manager.h
+++ b/chrome/browser/web_applications/components/file_handler_manager.h
@@ -8,8 +8,8 @@
 #include <set>
 #include <vector>
 
-#include "chrome/browser/web_applications/components/app_shortcut_manager.h"
 #include "chrome/browser/web_applications/components/web_app_id.h"
+#include "chrome/browser/web_applications/web_app_shortcut_manager.h"
 #include "components/services/app_service/public/cpp/file_handler.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
diff --git a/chrome/browser/web_applications/components/install_finalizer.h b/chrome/browser/web_applications/components/install_finalizer.h
index fbf3003..de7ab33a 100644
--- a/chrome/browser/web_applications/components/install_finalizer.h
+++ b/chrome/browser/web_applications/components/install_finalizer.h
@@ -121,6 +121,9 @@
                            bool shortcut_created,
                            content::WebContents* web_contents);
 
+  virtual void SetRemoveSourceCallbackForTesting(
+      base::RepeatingCallback<void(const AppId&)>) = 0;
+
   virtual void Start() {}
   virtual void Shutdown() {}
 
diff --git a/chrome/browser/web_applications/components/web_app_data_retriever.cc b/chrome/browser/web_applications/components/web_app_data_retriever.cc
index 253f536f..a95707bf 100644
--- a/chrome/browser/web_applications/components/web_app_data_retriever.cc
+++ b/chrome/browser/web_applications/components/web_app_data_retriever.cc
@@ -24,6 +24,8 @@
 #include "content/public/browser/web_contents.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/common/manifest/manifest_util.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/skia/include/core/SkColor.h"
 
 namespace web_app {
@@ -183,13 +185,13 @@
 
   Observe(nullptr);
 
-  DCHECK(data.manifest_url.is_valid() || data.manifest.IsEmpty());
+  DCHECK(data.manifest_url.is_valid() || blink::IsEmptyManifest(data.manifest));
 
   const bool is_installable = data.NoBlockingErrors();
   DCHECK(!is_installable || data.valid_manifest);
-  absl::optional<blink::Manifest> opt_manifest;
-  if (!data.manifest.IsEmpty())
-    opt_manifest = data.manifest;
+  blink::mojom::ManifestPtr opt_manifest;
+  if (!blink::IsEmptyManifest(data.manifest))
+    opt_manifest = data.manifest.Clone();
 
   std::move(check_installability_callback_)
       .Run(std::move(opt_manifest), data.manifest_url, data.valid_manifest,
@@ -216,7 +218,7 @@
     std::move(get_web_app_info_callback_).Run(nullptr);
   } else if (check_installability_callback_) {
     std::move(check_installability_callback_)
-        .Run(/*manifest=*/absl::nullopt, /*manifest_url=*/GURL(),
+        .Run(/*manifest=*/nullptr, /*manifest_url=*/GURL(),
              /*valid_manifest_for_web_app=*/false,
              /*is_installable=*/false);
   } else if (get_icons_callback_) {
diff --git a/chrome/browser/web_applications/components/web_app_data_retriever.h b/chrome/browser/web_applications/components/web_app_data_retriever.h
index a8bc1a2..e6f79c5 100644
--- a/chrome/browser/web_applications/components/web_app_data_retriever.h
+++ b/chrome/browser/web_applications/components/web_app_data_retriever.h
@@ -18,14 +18,11 @@
 #include "content/public/browser/web_contents_observer.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 
 class GURL;
 struct WebApplicationInfo;
 
-namespace blink {
-struct Manifest;
-}
-
 namespace content {
 class WebContents;
 }
@@ -48,7 +45,7 @@
   // If manifest is present then it is non-empty.
   // |manifest_url| is empty if manifest is empty.
   using CheckInstallabilityCallback =
-      base::OnceCallback<void(absl::optional<blink::Manifest> manifest,
+      base::OnceCallback<void(blink::mojom::ManifestPtr opt_manifest,
                               const GURL& manifest_url,
                               bool valid_manifest_for_web_app,
                               bool is_installable)>;
diff --git a/chrome/browser/web_applications/components/web_app_data_retriever_unittest.cc b/chrome/browser/web_applications/components/web_app_data_retriever_unittest.cc
index 784ba32..e3c83e4 100644
--- a/chrome/browser/web_applications/components/web_app_data_retriever_unittest.cc
+++ b/chrome/browser/web_applications/components/web_app_data_retriever_unittest.cc
@@ -31,7 +31,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 
 namespace web_app {
 
@@ -298,25 +298,24 @@
   web_contents_tester()->NavigateAndCommit(GURL("https://foo.example"));
 
   {
-    auto manifest = std::make_unique<blink::Manifest>();
     webapps::FakeInstallableManager::CreateForWebContentsWithManifest(
-        web_contents(), webapps::NO_MANIFEST, GURL(), std::move(manifest));
+        web_contents(), webapps::NO_MANIFEST, GURL(),
+        blink::mojom::Manifest::New());
   }
 
   base::RunLoop run_loop;
   WebAppDataRetriever retriever;
   retriever.CheckInstallabilityAndRetrieveManifest(
       web_contents(), /*bypass_service_worker_check=*/false,
-      base::BindLambdaForTesting([&](absl::optional<blink::Manifest> manifest,
-                                     const GURL& manifest_url,
-                                     bool valid_manifest_for_web_app,
-                                     bool is_installable) {
-        EXPECT_FALSE(manifest);
-        EXPECT_EQ(manifest_url, GURL());
-        EXPECT_FALSE(valid_manifest_for_web_app);
-        EXPECT_FALSE(is_installable);
-        run_loop.Quit();
-      }));
+      base::BindLambdaForTesting(
+          [&](blink::mojom::ManifestPtr opt_manifest, const GURL& manifest_url,
+              bool valid_manifest_for_web_app, bool is_installable) {
+            EXPECT_FALSE(opt_manifest);
+            EXPECT_EQ(manifest_url, GURL());
+            EXPECT_FALSE(valid_manifest_for_web_app);
+            EXPECT_FALSE(is_installable);
+            run_loop.Quit();
+          }));
   DeleteContents();
   run_loop.Run();
 }
@@ -369,14 +368,15 @@
   const std::u16string manifest_short_name = u"Short Name from Manifest";
   const std::u16string manifest_name = u"Name from Manifest";
   const GURL manifest_scope = GURL("https://example.com/scope");
-  const absl::optional<SkColor> manifest_theme_color = 0xAABBCCDD;
+  const SkColor manifest_theme_color = 0xAABBCCDD;
 
   {
-    auto manifest = std::make_unique<blink::Manifest>();
+    auto manifest = blink::mojom::Manifest::New();
     manifest->short_name = manifest_short_name;
     manifest->name = manifest_name;
     manifest->start_url = manifest_start_url;
     manifest->scope = manifest_scope;
+    manifest->has_theme_color = true;
     manifest->theme_color = manifest_theme_color;
 
     webapps::FakeInstallableManager::CreateForWebContentsWithManifest(
@@ -392,15 +392,15 @@
   retriever.CheckInstallabilityAndRetrieveManifest(
       web_contents(), /*bypass_service_worker_check=*/false,
       base::BindLambdaForTesting(
-          [&](absl::optional<blink::Manifest> result, const GURL& manifest_url,
+          [&](blink::mojom::ManifestPtr opt_manifest, const GURL& manifest_url,
               bool valid_manifest_for_web_app, bool is_installable) {
             EXPECT_TRUE(is_installable);
 
-            EXPECT_EQ(manifest_short_name, result->short_name);
-            EXPECT_EQ(manifest_name, result->name);
-            EXPECT_EQ(manifest_start_url, result->start_url);
-            EXPECT_EQ(manifest_scope, result->scope);
-            EXPECT_EQ(manifest_theme_color, result->theme_color);
+            EXPECT_EQ(manifest_short_name, opt_manifest->short_name);
+            EXPECT_EQ(manifest_name, opt_manifest->name);
+            EXPECT_EQ(manifest_start_url, opt_manifest->start_url);
+            EXPECT_EQ(manifest_scope, opt_manifest->scope);
+            EXPECT_EQ(manifest_theme_color, opt_manifest->theme_color);
 
             EXPECT_EQ(manifest_url, GURL("https://example.com/manifest"));
 
@@ -416,9 +416,9 @@
   SetFakeWebPageMetadataAgent();
 
   {
-    auto manifest = std::make_unique<blink::Manifest>();
     webapps::FakeInstallableManager::CreateForWebContentsWithManifest(
-        web_contents(), webapps::NO_MANIFEST, GURL(), std::move(manifest));
+        web_contents(), webapps::NO_MANIFEST, GURL(),
+        blink::mojom::Manifest::New());
   }
 
   base::RunLoop run_loop;
@@ -429,7 +429,7 @@
   retriever.CheckInstallabilityAndRetrieveManifest(
       web_contents(), /*bypass_service_worker_check=*/false,
       base::BindLambdaForTesting(
-          [&](absl::optional<blink::Manifest> result, const GURL& manifest_url,
+          [&](blink::mojom::ManifestPtr opt_manifest, const GURL& manifest_url,
               bool valid_manifest_for_web_app, bool is_installable) {
             EXPECT_FALSE(is_installable);
             EXPECT_FALSE(valid_manifest_for_web_app);
diff --git a/chrome/browser/web_applications/components/web_app_helpers.h b/chrome/browser/web_applications/components/web_app_helpers.h
index 7094189..26da43c 100644
--- a/chrome/browser/web_applications/components/web_app_helpers.h
+++ b/chrome/browser/web_applications/components/web_app_helpers.h
@@ -9,7 +9,7 @@
 
 #include "chrome/browser/web_applications/components/web_app_id.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 
 class GURL;
 class Profile;
@@ -49,7 +49,7 @@
     const absl::optional<std::string>& manifest_id,
     const GURL& start_url);
 
-AppId GenerateAppIdFromManifest(const blink::Manifest& manifest);
+AppId GenerateAppIdFromManifest(const blink::mojom::Manifest& manifest);
 
 // Returns whether the given |app_url| is a valid web app url.
 bool IsValidWebAppUrl(const GURL& app_url);
diff --git a/chrome/browser/web_applications/components/web_app_install_utils.cc b/chrome/browser/web_applications/components/web_app_install_utils.cc
index 25a44b6..0a11019 100644
--- a/chrome/browser/web_applications/components/web_app_install_utils.cc
+++ b/chrome/browser/web_applications/components/web_app_install_utils.cc
@@ -14,7 +14,6 @@
 #include "base/time/time.h"
 #include "chrome/browser/banners/app_banner_manager_desktop.h"
 #include "chrome/browser/web_applications/components/web_app_icon_generator.h"
-#include "chrome/browser/web_applications/components/web_application_info.h"
 #include "chrome/common/chrome_features.h"
 #include "components/services/app_service/public/cpp/share_target.h"
 #include "components/webapps/browser/banners/app_banner_manager.h"
@@ -22,7 +21,7 @@
 #include "components/webapps/browser/installable/installable_metrics.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/features.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
 namespace web_app {
@@ -193,24 +192,25 @@
 }
 
 apps::UrlHandlers ToWebAppUrlHandlers(
-    const std::vector<blink::Manifest::UrlHandler>& url_handlers) {
+    const std::vector<blink::mojom::ManifestUrlHandlerPtr>& url_handlers) {
   apps::UrlHandlers apps_url_handlers;
   for (const auto& url_handler : url_handlers) {
-    apps_url_handlers.emplace_back(url_handler.origin,
-                                   url_handler.has_origin_wildcard);
+    DCHECK(url_handler);
+    apps_url_handlers.emplace_back(url_handler->origin,
+                                   url_handler->has_origin_wildcard);
   }
   return apps_url_handlers;
 }
 
 std::vector<apps::ProtocolHandlerInfo> ToWebAppProtocolHandlers(
-    const std::vector<blink::Manifest::ProtocolHandler>&
+    const std::vector<blink::mojom::ManifestProtocolHandlerPtr>&
         manifest_protocol_handlers) {
   std::vector<apps::ProtocolHandlerInfo> protocol_handlers;
   for (const auto& manifest_protocol_handler : manifest_protocol_handlers) {
     apps::ProtocolHandlerInfo protocol_handler;
     protocol_handler.protocol =
-        base::UTF16ToUTF8(manifest_protocol_handler.protocol);
-    protocol_handler.url = manifest_protocol_handler.url;
+        base::UTF16ToUTF8(manifest_protocol_handler->protocol);
+    protocol_handler.url = manifest_protocol_handler->url;
     protocol_handlers.push_back(std::move(protocol_handler));
   }
   return protocol_handlers;
@@ -219,15 +219,17 @@
 }  // namespace
 
 apps::FileHandlers CreateFileHandlersFromManifest(
-    const std::vector<blink::Manifest::FileHandler>& manifest_file_handlers,
+    const std::vector<blink::mojom::ManifestFileHandlerPtr>&
+        manifest_file_handlers,
     const GURL& app_scope) {
   apps::FileHandlers web_app_file_handlers;
 
   for (const auto& manifest_file_handler : manifest_file_handlers) {
+    DCHECK(manifest_file_handler);
     apps::FileHandler web_app_file_handler;
-    web_app_file_handler.action = manifest_file_handler.action;
+    web_app_file_handler.action = manifest_file_handler->action;
 
-    for (const auto& it : manifest_file_handler.accept) {
+    for (const auto& it : manifest_file_handler->accept) {
       apps::FileHandler::AcceptEntry web_app_accept_entry;
       web_app_accept_entry.mime_type = base::UTF16ToUTF8(it.first);
       for (const auto& manifest_file_extension : it.second) {
@@ -251,7 +253,7 @@
   return web_app_file_handlers;
 }
 
-void UpdateWebAppInfoFromManifest(const blink::Manifest& manifest,
+void UpdateWebAppInfoFromManifest(const blink::mojom::Manifest& manifest,
                                   const GURL& manifest_url,
                                   WebApplicationInfo* web_app_info) {
   // Give the full length name priority if it's not empty.
@@ -273,14 +275,14 @@
   if (manifest.scope.is_valid())
     web_app_info->scope = manifest.scope;
 
-  if (manifest.theme_color) {
+  if (manifest.has_theme_color) {
     web_app_info->theme_color =
-        SkColorSetA(*manifest.theme_color, SK_AlphaOPAQUE);
+        SkColorSetA(SkColor(manifest.theme_color), SK_AlphaOPAQUE);
   }
 
-  if (manifest.background_color) {
+  if (manifest.has_background_color) {
     web_app_info->background_color =
-        SkColorSetA(*manifest.background_color, SK_AlphaOPAQUE);
+        SkColorSetA(SkColor(manifest.background_color), SK_AlphaOPAQUE);
   }
 
   if (manifest.display != DisplayMode::kUndefined)
diff --git a/chrome/browser/web_applications/components/web_app_install_utils.h b/chrome/browser/web_applications/components/web_app_install_utils.h
index 14e03716..b9c3f23 100644
--- a/chrome/browser/web_applications/components/web_app_install_utils.h
+++ b/chrome/browser/web_applications/components/web_app_install_utils.h
@@ -10,13 +10,11 @@
 #include <vector>
 
 #include "chrome/browser/web_applications/components/web_app_constants.h"
+#include "chrome/browser/web_applications/components/web_application_info.h"
 #include "components/services/app_service/public/cpp/file_handler.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 #include "url/gurl.h"
 
-struct WebApplicationInfo;
-class SkBitmap;
-
 namespace content {
 class WebContents;
 }
@@ -36,13 +34,14 @@
 
 // Converts from the manifest type to the Chrome type.
 apps::FileHandlers CreateFileHandlersFromManifest(
-    const std::vector<blink::Manifest::FileHandler>& manifest_file_handlers,
+    const std::vector<blink::mojom::ManifestFileHandlerPtr>&
+        manifest_file_handlers,
     const GURL& app_scope);
 
 // Update the given WebApplicationInfo with information from the manifest.
 // Will sanitise the manifest fields to be suitable for installation to prevent
 // sites from using arbitrarily large amounts of disk space.
-void UpdateWebAppInfoFromManifest(const blink::Manifest& manifest,
+void UpdateWebAppInfoFromManifest(const blink::mojom::Manifest& manifest,
                                   const GURL& manifest_url,
                                   WebApplicationInfo* web_app_info);
 
@@ -50,11 +49,10 @@
 std::vector<GURL> GetValidIconUrlsToDownload(
     const WebApplicationInfo& web_app_info);
 
-// A map of icon urls to the bitmaps provided by that url.
-using IconsMap = std::map<GURL, std::vector<SkBitmap>>;
-
 // Populate shortcut item icon maps in WebApplicationInfo using the IconsMap.
 // This ignores icons that might have already existed in `web_app_info`.
+// TODO(estade): also save bitmaps in `icons_map` that are relevant to file
+// handling in `web_app_info->other_icon_bitmaps`.
 void PopulateShortcutItemIcons(WebApplicationInfo* web_app_info,
                                const IconsMap& icons_map);
 
diff --git a/chrome/browser/web_applications/components/web_app_install_utils_unittest.cc b/chrome/browser/web_applications/components/web_app_install_utils_unittest.cc
index d91bb1e..5432aa52 100644
--- a/chrome/browser/web_applications/components/web_app_install_utils_unittest.cc
+++ b/chrome/browser/web_applications/components/web_app_install_utils_unittest.cc
@@ -18,7 +18,7 @@
 #include "chrome/common/chrome_features.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/features.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -54,7 +54,7 @@
   info.url = kAppIcon1;
   web_app_info.icon_infos.push_back(info);
 
-  blink::Manifest manifest;
+  blink::mojom::Manifest manifest;
   const GURL kAppUrl("http://www.chromium.org/index.html");
   manifest.start_url = kAppUrl;
   manifest.scope = kAppUrl.GetWithoutFilename();
@@ -62,38 +62,37 @@
 
   const GURL kFileHandlingIcon("fav1.png");
   {
-    blink::Manifest::FileHandler handler;
-    handler.action = GURL("http://example.com/open-files");
-    handler.accept[u"image/png"].push_back(u".png");
-    handler.name = u"Images";
+    auto handler = blink::mojom::ManifestFileHandler::New();
+    handler->action = GURL("http://example.com/open-files");
+    handler->accept[u"image/png"].push_back(u".png");
+    handler->name = u"Images";
     {
       blink::Manifest::ImageResource icon;
       icon.src = kFileHandlingIcon;
       icon.purpose = {Purpose::ANY, Purpose::MONOCHROME};
-      handler.icons.push_back(icon);
+      handler->icons.push_back(icon);
     }
-    manifest.file_handlers.push_back(handler);
+    manifest.file_handlers.push_back(std::move(handler));
   }
 
   {
-    blink::Manifest::ProtocolHandler protocol_handler;
-    protocol_handler.protocol = u"mailto";
-    protocol_handler.url = GURL("http://example.com/handle=%s");
-    manifest.protocol_handlers.push_back(protocol_handler);
+    auto protocol_handler = blink::mojom::ManifestProtocolHandler::New();
+    protocol_handler->protocol = u"mailto";
+    protocol_handler->url = GURL("http://example.com/handle=%s");
+    manifest.protocol_handlers.push_back(std::move(protocol_handler));
   }
 
   {
-    blink::Manifest::UrlHandler url_handler;
-    url_handler.origin =
+    auto url_handler = blink::mojom::ManifestUrlHandler::New();
+    url_handler->origin =
         url::Origin::Create(GURL("https://url_handlers_origin.com/"));
-    url_handler.has_origin_wildcard = false;
-    manifest.url_handlers.push_back(url_handler);
+    url_handler->has_origin_wildcard = false;
+    manifest.url_handlers.push_back(std::move(url_handler));
   }
 
   {
     // Ensure an empty NoteTaking struct is ignored.
-    blink::Manifest::NoteTaking note_taking;
-    manifest.note_taking = note_taking;
+    manifest.note_taking = blink::mojom::ManifestNoteTaking::New();
   }
 
   const GURL kAppManifestUrl("http://www.chromium.org/manifest.json");
@@ -137,9 +136,9 @@
 
   {
     // Update with a valid new_note_url.
-    blink::Manifest::NoteTaking note_taking;
-    note_taking.new_note_url = GURL("http://example.com/new-note-url");
-    manifest.note_taking = note_taking;
+    auto note_taking = blink::mojom::ManifestNoteTaking::New();
+    note_taking->new_note_url = GURL("http://example.com/new-note-url");
+    manifest.note_taking = std::move(note_taking);
   }
 
   UpdateWebAppInfoFromManifest(manifest, kAppManifestUrl, &web_app_info);
@@ -159,11 +158,11 @@
 
   // Check file handlers were updated.
   ASSERT_EQ(1u, web_app_info.file_handlers.size());
-  auto file_handler = web_app_info.file_handlers;
-  ASSERT_EQ(1u, file_handler[0].accept.size());
-  EXPECT_EQ(file_handler[0].accept[0].mime_type, "image/png");
-  EXPECT_EQ(manifest.file_handlers[0].action, file_handler[0].action);
-  EXPECT_TRUE(file_handler[0].accept[0].file_extensions.contains(".png"));
+  auto file_handler = web_app_info.file_handlers[0];
+  ASSERT_EQ(1u, file_handler.accept.size());
+  EXPECT_EQ(file_handler.accept[0].mime_type, "image/png");
+  EXPECT_EQ(manifest.file_handlers[0]->action, file_handler.action);
+  EXPECT_TRUE(file_handler.accept[0].file_extensions.contains(".png"));
 
   // Check protocol handlers were updated.
   EXPECT_EQ(1u, web_app_info.protocol_handlers.size());
@@ -184,8 +183,8 @@
 TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest_EmptyName) {
   WebApplicationInfo web_app_info;
 
-  blink::Manifest manifest;
-  manifest.name = std::u16string();
+  blink::mojom::Manifest manifest;
+  manifest.name = absl::nullopt;
   manifest.short_name = kAppShortName;
 
   UpdateWebAppInfoFromManifest(
@@ -195,7 +194,7 @@
 
 // Test that maskable icons are parsed as separate icon_infos from the manifest.
 TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest_MaskableIcon) {
-  blink::Manifest manifest;
+  blink::mojom::Manifest manifest;
   blink::Manifest::ImageResource icon;
   icon.src = GURL("fav1.png");
   // Produces 2 separate icon_infos.
@@ -223,7 +222,7 @@
 
 TEST(WebAppInstallUtils,
      UpdateWebAppInfoFromManifest_MaskableIconOnly_UsesManifestIcons) {
-  blink::Manifest manifest;
+  blink::mojom::Manifest manifest;
   blink::Manifest::ImageResource icon;
   icon.src = GURL("fav1.png");
   icon.purpose = {Purpose::MASKABLE};
@@ -241,7 +240,7 @@
 }
 
 TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest_ShareTarget) {
-  blink::Manifest manifest;
+  blink::mojom::Manifest manifest;
   WebApplicationInfo web_app_info;
 
   {
@@ -327,7 +326,7 @@
   info.url = kAppIcon1;
   web_app_info.icon_infos.push_back(info);
 
-  blink::Manifest manifest;
+  blink::mojom::Manifest manifest;
   const GURL kAppUrl("http://www.chromium.org/index.html");
   manifest.start_url = kAppUrl;
   manifest.scope = kAppUrl.GetWithoutFilename();
@@ -335,32 +334,32 @@
 
   const GURL kFileHandlingIcon("fav1.png");
   {
-    blink::Manifest::FileHandler handler;
-    handler.action = GURL("http://example.com/open-files");
-    handler.accept[u"image/png"].push_back(u".png");
-    handler.name = u"Images";
+    auto handler = blink::mojom::ManifestFileHandler::New();
+    handler->action = GURL("http://example.com/open-files");
+    handler->accept[u"image/png"].push_back(u".png");
+    handler->name = u"Images";
     {
       blink::Manifest::ImageResource icon;
       icon.src = kFileHandlingIcon;
       icon.purpose = {Purpose::ANY, Purpose::MONOCHROME};
-      handler.icons.push_back(icon);
+      handler->icons.push_back(icon);
     }
-    manifest.file_handlers.push_back(handler);
+    manifest.file_handlers.push_back(std::move(handler));
   }
 
   {
-    blink::Manifest::ProtocolHandler protocol_handler;
-    protocol_handler.protocol = u"mailto";
-    protocol_handler.url = GURL("http://example.com/handle=%s");
-    manifest.protocol_handlers.push_back(protocol_handler);
+    auto protocol_handler = blink::mojom::ManifestProtocolHandler::New();
+    protocol_handler->protocol = u"mailto";
+    protocol_handler->url = GURL("http://example.com/handle=%s");
+    manifest.protocol_handlers.push_back(std::move(protocol_handler));
   }
 
   {
-    blink::Manifest::UrlHandler url_handler;
-    url_handler.origin =
+    auto url_handler = blink::mojom::ManifestUrlHandler::New();
+    url_handler->origin =
         url::Origin::Create(GURL("https://url_handlers_origin.com/"));
-    url_handler.has_origin_wildcard = true;
-    manifest.url_handlers.push_back(url_handler);
+    url_handler->has_origin_wildcard = true;
+    manifest.url_handlers.push_back(std::move(url_handler));
   }
   WebApplicationInfo web_app_info_original{web_app_info};
 
@@ -465,11 +464,11 @@
 
   // Check file handlers were updated.
   ASSERT_EQ(1u, web_app_info.file_handlers.size());
-  auto file_handler = web_app_info.file_handlers;
-  ASSERT_EQ(1u, file_handler[0].accept.size());
-  EXPECT_EQ(file_handler[0].accept[0].mime_type, "image/png");
-  EXPECT_EQ(manifest.file_handlers[0].action, file_handler[0].action);
-  EXPECT_TRUE(file_handler[0].accept[0].file_extensions.contains(".png"));
+  auto file_handler = web_app_info.file_handlers[0];
+  ASSERT_EQ(1u, file_handler.accept.size());
+  EXPECT_EQ(file_handler.accept[0].mime_type, "image/png");
+  EXPECT_EQ(manifest.file_handlers[0]->action, file_handler.action);
+  EXPECT_TRUE(file_handler.accept[0].file_extensions.contains(".png"));
 
   // Check protocol handlers were updated.
   EXPECT_EQ(1u, web_app_info.protocol_handlers.size());
@@ -487,7 +486,7 @@
 
 // Tests that we limit the number of icons declared by a site.
 TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifestTooManyIcons) {
-  blink::Manifest manifest;
+  blink::mojom::Manifest manifest;
   for (int i = 0; i < 50; ++i) {
     blink::Manifest::ImageResource icon;
     icon.src = GURL("fav1.png");
@@ -504,7 +503,7 @@
 
 // Tests that we limit the number of shortcut icons declared by a site.
 TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifestTooManyShortcutIcons) {
-  blink::Manifest manifest;
+  blink::mojom::Manifest manifest;
   for (unsigned int i = 0; i < kNumTestIcons; ++i) {
     blink::Manifest::ShortcutItem shortcut_item;
     shortcut_item.name = kShortcutItemName + base::NumberToString16(i);
@@ -535,7 +534,7 @@
 
 // Tests that we limit the size of icons declared by a site.
 TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifestIconsTooLarge) {
-  blink::Manifest manifest;
+  blink::mojom::Manifest manifest;
   for (int i = 1; i <= 20; ++i) {
     blink::Manifest::ImageResource icon;
     icon.src = GURL("fav1.png");
@@ -557,7 +556,7 @@
 
 // Tests that we limit the size of shortcut icons declared by a site.
 TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifestShortcutIconsTooLarge) {
-  blink::Manifest manifest;
+  blink::mojom::Manifest manifest;
   for (int i = 1; i <= 20; ++i) {
     blink::Manifest::ShortcutItem shortcut_item;
     shortcut_item.name = kShortcutItemName + base::NumberToString16(i);
@@ -724,7 +723,7 @@
 
 TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest_InvalidManifestUrl) {
   WebApplicationInfo web_app_info;
-  blink::Manifest manifest;
+  blink::mojom::Manifest manifest;
 
   UpdateWebAppInfoFromManifest(manifest, GURL("foo"), &web_app_info);
   EXPECT_TRUE(web_app_info.manifest_url.is_empty());
@@ -765,14 +764,14 @@
   };
 
   // Add more than |kMaxFileHandlers| file handlers.
-  std::vector<blink::Manifest::FileHandler> manifest_file_handlers;
+  std::vector<blink::mojom::ManifestFileHandlerPtr> manifest_file_handlers;
   for (unsigned i = 0; i <= 2 * kMaxFileHandlers; ++i) {
-    const std::u16string name = base::UTF8ToUTF16(base::StringPrintf("n%u", i));
-    std::map<std::u16string, std::vector<std::u16string>> accept;
-    accept[base::UTF8ToUTF16(mime_type(i))] = {base::UTF8ToUTF16(extension(i))};
-    manifest_file_handlers.push_back(
-        {action_url(i), u"unused name",
-         std::vector<blink::Manifest::ImageResource>(), std::move(accept)});
+    auto file_handler = blink::mojom::ManifestFileHandler::New();
+    file_handler->action = action_url(i);
+    file_handler->name = base::UTF8ToUTF16(base::StringPrintf("n%u", i));
+    file_handler->accept[base::UTF8ToUTF16(mime_type(i))] = {
+        base::UTF8ToUTF16(extension(i))};
+    manifest_file_handlers.push_back(std::move(file_handler));
   }
 
   apps::FileHandlers file_handlers =
diff --git a/chrome/browser/web_applications/components/web_app_utils.h b/chrome/browser/web_applications/components/web_app_utils.h
index 58c5a63..d062373 100644
--- a/chrome/browser/web_applications/components/web_app_utils.h
+++ b/chrome/browser/web_applications/components/web_app_utils.h
@@ -10,7 +10,6 @@
 
 #include "chrome/browser/web_applications/components/web_app_id.h"
 #include "components/services/app_service/public/cpp/file_handler.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
 
 class GURL;
 class Profile;
diff --git a/chrome/browser/web_applications/components/web_application_info.h b/chrome/browser/web_applications/components/web_application_info.h
index c994f84..3d0f815 100644
--- a/chrome/browser/web_applications/components/web_application_info.h
+++ b/chrome/browser/web_applications/components/web_application_info.h
@@ -21,12 +21,17 @@
 #include "components/webapps/common/web_page_metadata.mojom-forward.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
-#include "third_party/blink/public/mojom/manifest/manifest.mojom-shared.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/size.h"
 #include "url/gurl.h"
 
+class SkBitmap;
+
+// A map of icon urls to the bitmaps provided by that url.
+using IconsMap = std::map<GURL, std::vector<SkBitmap>>;
+
 using SquareSizePx = int;
 // Iterates in ascending order (checked in SortedSizesPxIsAscending test).
 using SortedSizesPx = base::flat_set<SquareSizePx, std::less<>>;
@@ -214,6 +219,12 @@
   // Icon bitmaps, keyed by their square size.
   IconBitmaps icon_bitmaps;
 
+  // A collection of unprocessed icons keyed by their download URL. The usage
+  // and purpose of these icons is tracked elsewhere, such as in
+  // `file_handlers`. Currently, this is only used for file handling icons, but
+  // other icons may be added here in the future.
+  IconsMap other_icon_bitmaps;
+
   // Represents whether the icons for the web app are generated by Chrome due to
   // no suitable icons being available.
   bool is_generated_icon = false;
diff --git a/chrome/browser/web_applications/docs/manifest_representations.md b/chrome/browser/web_applications/docs/manifest_representations.md
index fb174bb..4851478 100644
--- a/chrome/browser/web_applications/docs/manifest_representations.md
+++ b/chrome/browser/web_applications/docs/manifest_representations.md
@@ -8,7 +8,7 @@
    Output of the [Blink manifest parser](../../../../third_party/blink/renderer/modules/manifest/manifest_parser.cc).
 
  - [blink::Manifest](../../../../third_party/blink/public/common/manifest/manifest.h)\
-   Pre blink.mojom.Manifest representation that should be cleaned up.
+   Pre blink.mojom.Manifest representation that is getting cleaned up: https://crbug.com/1233362
 
  - [WebApplicationInfo](../components/web_application_info.h)\
    Used for installation and updates.
diff --git a/chrome/browser/web_applications/extensions/externally_managed_app_install_task_unittest.cc b/chrome/browser/web_applications/extensions/externally_managed_app_install_task_unittest.cc
index ceabf2b..b7b97b0 100644
--- a/chrome/browser/web_applications/extensions/externally_managed_app_install_task_unittest.cc
+++ b/chrome/browser/web_applications/extensions/externally_managed_app_install_task_unittest.cc
@@ -46,7 +46,7 @@
 #include "content/public/test/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "url/gurl.h"
 
 namespace web_app {
@@ -260,6 +260,11 @@
     ++num_reparent_tab_calls_;
   }
 
+  void SetRemoveSourceCallbackForTesting(
+      base::RepeatingCallback<void(const AppId&)>) override {
+    NOTIMPLEMENTED();
+  }
+
  private:
   WebAppRegistrarMutable* registrar_ = nullptr;
 
@@ -361,7 +366,7 @@
 
     install_manager_->SetDataRetrieverFactoryForTesting(
         GetFactoryForRetriever(std::move(data_retriever)));
-    auto manifest = std::make_unique<blink::Manifest>();
+    auto manifest = blink::mojom::Manifest::New();
     manifest->start_url = options.install_url;
     manifest->name = u"Manifest Name";
 
diff --git a/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc b/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc
index 892f61b..38b924c 100644
--- a/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc
+++ b/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc
@@ -15,11 +15,11 @@
 #include "base/values.h"
 #include "chrome/browser/prefs/browser_prefs.h"
 #include "chrome/browser/web_applications/components/external_install_options.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/components/policy/web_app_policy_constants.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_install_utils.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/policy/web_app_policy_manager_observer.h"
 #include "chrome/browser/web_applications/test/test_app_registry_controller.h"
 #include "chrome/browser/web_applications/test/test_externally_managed_app_manager.h"
diff --git a/chrome/browser/web_applications/externally_managed_app_install_task.h b/chrome/browser/web_applications/externally_managed_app_install_task.h
index 64946b44..0ba2bb1 100644
--- a/chrome/browser/web_applications/externally_managed_app_install_task.h
+++ b/chrome/browser/web_applications/externally_managed_app_install_task.h
@@ -10,11 +10,11 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/web_applications/components/external_install_options.h"
 #include "chrome/browser/web_applications/components/externally_installed_web_app_prefs.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/components/web_app_id.h"
 #include "chrome/browser/web_applications/components/web_app_install_utils.h"
 #include "chrome/browser/web_applications/components/web_app_url_loader.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/os_integration_manager.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
diff --git a/chrome/browser/web_applications/externally_managed_app_manager.cc b/chrome/browser/web_applications/externally_managed_app_manager.cc
index 3abf0f1..8c03b5c 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager.cc
+++ b/chrome/browser/web_applications/externally_managed_app_manager.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 
 #include <algorithm>
 #include <map>
diff --git a/chrome/browser/web_applications/components/externally_managed_app_manager.h b/chrome/browser/web_applications/externally_managed_app_manager.h
similarity index 96%
rename from chrome/browser/web_applications/components/externally_managed_app_manager.h
rename to chrome/browser/web_applications/externally_managed_app_manager.h
index 2e119a1..2e273bd 100644
--- a/chrome/browser/web_applications/components/externally_managed_app_manager.h
+++ b/chrome/browser/web_applications/externally_managed_app_manager.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_EXTERNALLY_MANAGED_APP_MANAGER_H_
-#define CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_EXTERNALLY_MANAGED_APP_MANAGER_H_
+#ifndef CHROME_BROWSER_WEB_APPLICATIONS_EXTERNALLY_MANAGED_APP_MANAGER_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_EXTERNALLY_MANAGED_APP_MANAGER_H_
 
 #include <map>
 #include <memory>
@@ -200,4 +200,4 @@
 
 }  // namespace web_app
 
-#endif  // CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_EXTERNALLY_MANAGED_APP_MANAGER_H_
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_EXTERNALLY_MANAGED_APP_MANAGER_H_
diff --git a/chrome/browser/web_applications/externally_managed_app_manager_impl.h b/chrome/browser/web_applications/externally_managed_app_manager_impl.h
index 8af2ee5f..993c671 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager_impl.h
+++ b/chrome/browser/web_applications/externally_managed_app_manager_impl.h
@@ -13,9 +13,9 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/web_applications/components/external_install_options.h"
 #include "chrome/browser/web_applications/components/externally_installed_web_app_prefs.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/components/web_app_url_loader.h"
 #include "chrome/browser/web_applications/externally_managed_app_install_task.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 class GURL;
diff --git a/chrome/browser/web_applications/externally_managed_app_manager_impl_unittest.cc b/chrome/browser/web_applications/externally_managed_app_manager_impl_unittest.cc
index 5483ce7..3d92d69 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager_impl_unittest.cc
+++ b/chrome/browser/web_applications/externally_managed_app_manager_impl_unittest.cc
@@ -18,11 +18,11 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
 #include "base/timer/mock_timer.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/components/install_finalizer.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
 #include "chrome/browser/web_applications/externally_managed_app_install_task.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/externally_managed_app_registration_task.h"
 #include "chrome/browser/web_applications/test/test_install_finalizer.h"
 #include "chrome/browser/web_applications/test/test_web_app_registry_controller.h"
diff --git a/chrome/browser/web_applications/components/externally_managed_app_manager_unittest.cc b/chrome/browser/web_applications/externally_managed_app_manager_unittest.cc
similarity index 98%
rename from chrome/browser/web_applications/components/externally_managed_app_manager_unittest.cc
rename to chrome/browser/web_applications/externally_managed_app_manager_unittest.cc
index a67c687..a8351074 100644
--- a/chrome/browser/web_applications/components/externally_managed_app_manager_unittest.cc
+++ b/chrome/browser/web_applications/externally_managed_app_manager_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 
 #include <algorithm>
 #include <sstream>
diff --git a/chrome/browser/web_applications/externally_managed_app_registration_task.cc b/chrome/browser/web_applications/externally_managed_app_registration_task.cc
index 7a0f07d..c456ba8 100644
--- a/chrome/browser/web_applications/externally_managed_app_registration_task.cc
+++ b/chrome/browser/web_applications/externally_managed_app_registration_task.cc
@@ -6,8 +6,8 @@
 
 #include "base/callback_helpers.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/components/web_app_url_loader.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/service_worker_context.h"
 #include "content/public/browser/storage_partition.h"
diff --git a/chrome/browser/web_applications/manifest_update_manager.cc b/chrome/browser/web_applications/manifest_update_manager.cc
index 8eb1e8b5..ac224cc 100644
--- a/chrome/browser/web_applications/manifest_update_manager.cc
+++ b/chrome/browser/web_applications/manifest_update_manager.cc
@@ -28,7 +28,7 @@
 
 void ManifestUpdateManager::SetSubsystems(
     WebAppRegistrar* registrar,
-    AppIconManager* icon_manager,
+    WebAppIconManager* icon_manager,
     WebAppUiManager* ui_manager,
     WebAppInstallManager* install_manager,
     SystemWebAppManager* system_web_app_manager,
diff --git a/chrome/browser/web_applications/manifest_update_manager.h b/chrome/browser/web_applications/manifest_update_manager.h
index 2573285..7f00215 100644
--- a/chrome/browser/web_applications/manifest_update_manager.h
+++ b/chrome/browser/web_applications/manifest_update_manager.h
@@ -45,7 +45,7 @@
   ~ManifestUpdateManager() override;
 
   void SetSubsystems(WebAppRegistrar* registrar,
-                     AppIconManager* icon_manager,
+                     WebAppIconManager* icon_manager,
                      WebAppUiManager* ui_manager,
                      WebAppInstallManager* install_manager,
                      SystemWebAppManager* system_web_app_manager,
@@ -86,7 +86,7 @@
                     ManifestUpdateResult result);
 
   WebAppRegistrar* registrar_ = nullptr;
-  AppIconManager* icon_manager_ = nullptr;
+  WebAppIconManager* icon_manager_ = nullptr;
   WebAppUiManager* ui_manager_ = nullptr;
   WebAppInstallManager* install_manager_ = nullptr;
   SystemWebAppManager* system_web_app_manager_ = nullptr;
diff --git a/chrome/browser/web_applications/manifest_update_manager_browsertest.cc b/chrome/browser/web_applications/manifest_update_manager_browsertest.cc
index a9791f1..c2b4f2e2 100644
--- a/chrome/browser/web_applications/manifest_update_manager_browsertest.cc
+++ b/chrome/browser/web_applications/manifest_update_manager_browsertest.cc
@@ -26,11 +26,10 @@
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
-#include "chrome/browser/web_applications/components/app_shortcut_manager.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/components/install_finalizer.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/components/web_app_utils.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/os_integration_manager.h"
 #include "chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h"
 #include "chrome/browser/web_applications/test/web_app_install_observer.h"
@@ -40,6 +39,7 @@
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/browser/web_applications/web_app_registry_update.h"
+#include "chrome/browser/web_applications/web_app_shortcut_manager.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
diff --git a/chrome/browser/web_applications/manifest_update_task.cc b/chrome/browser/web_applications/manifest_update_task.cc
index bb59e17..eff1219 100644
--- a/chrome/browser/web_applications/manifest_update_task.cc
+++ b/chrome/browser/web_applications/manifest_update_task.cc
@@ -13,7 +13,6 @@
 #include "base/feature_list.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/browser_features.h"
-#include "chrome/browser/web_applications/components/app_icon_manager.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_install_utils.h"
@@ -21,12 +20,14 @@
 #include "chrome/browser/web_applications/components/web_application_info.h"
 #include "chrome/browser/web_applications/os_integration_manager.h"
 #include "chrome/browser/web_applications/web_app.h"
+#include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "chrome/browser/web_applications/web_app_install_manager.h"
 #include "chrome/browser/web_applications/web_app_installation_utils.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/common/chrome_features.h"
 #include "components/webapps/browser/installable/installable_manager.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/manifest/manifest_util.h"
 #include "ui/gfx/skia_util.h"
 
 namespace web_app {
@@ -121,7 +122,7 @@
     StoppedCallback stopped_callback,
     bool hang_for_testing,
     const WebAppRegistrar& registrar,
-    const AppIconManager& icon_manager,
+    const WebAppIconManager& icon_manager,
     WebAppUiManager* ui_manager,
     WebAppInstallManager* install_manager,
     OsIntegrationManager& os_integration_manager)
diff --git a/chrome/browser/web_applications/manifest_update_task.h b/chrome/browser/web_applications/manifest_update_task.h
index ec9dfd5..ebc4d54 100644
--- a/chrome/browser/web_applications/manifest_update_task.h
+++ b/chrome/browser/web_applications/manifest_update_task.h
@@ -5,20 +5,17 @@
 #ifndef CHROME_BROWSER_WEB_APPLICATIONS_MANIFEST_UPDATE_TASK_H_
 #define CHROME_BROWSER_WEB_APPLICATIONS_MANIFEST_UPDATE_TASK_H_
 
-#include <map>
-
 #include "base/check_op.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/web_applications/components/app_icon_manager.h"
 #include "chrome/browser/web_applications/components/web_app_icon_downloader.h"
 #include "chrome/browser/web_applications/components/web_app_id.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
+#include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/services/app_service/public/cpp/file_handler.h"
 #include "components/services/app_service/public/cpp/protocol_handler_info.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
 
 struct WebApplicationInfo;
 
@@ -30,7 +27,7 @@
 enum class AppIdentityUpdate;
 struct IconDiff;
 
-class AppIconManager;
+class WebAppIconManager;
 class WebAppRegistrar;
 class WebAppUiManager;
 class WebAppInstallManager;
@@ -85,7 +82,7 @@
                      StoppedCallback stopped_callback,
                      bool hang_for_testing,
                      const WebAppRegistrar& registrar,
-                     const AppIconManager& icon_manager,
+                     const WebAppIconManager& icon_manager,
                      WebAppUiManager* ui_manager,
                      WebAppInstallManager* install_manager,
                      OsIntegrationManager& os_integration_manager);
@@ -141,7 +138,7 @@
   void DestroySelf(ManifestUpdateResult result);
 
   const WebAppRegistrar& registrar_;
-  const AppIconManager& icon_manager_;
+  const WebAppIconManager& icon_manager_;
   WebAppUiManager& ui_manager_;
   WebAppInstallManager& install_manager_;
   OsIntegrationManager& os_integration_manager_;
diff --git a/chrome/browser/web_applications/manifest_update_task_unittest.cc b/chrome/browser/web_applications/manifest_update_task_unittest.cc
index 79c6f994..ddc0cd4 100644
--- a/chrome/browser/web_applications/manifest_update_task_unittest.cc
+++ b/chrome/browser/web_applications/manifest_update_task_unittest.cc
@@ -6,7 +6,7 @@
 
 #include "base/strings/utf_string_conversions.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 
 namespace web_app {
 
@@ -24,13 +24,16 @@
 }
 
 // Note: Keep in sync with GetDefaultAppsFileHandlers() above.
-std::vector<blink::Manifest::FileHandler> GetDefaultManifestFileHandlers() {
-  blink::Manifest::FileHandler handler;
-  handler.action = GURL("http://foo.com/?plaintext");
-  handler.name = u"Text";
+std::vector<blink::mojom::ManifestFileHandlerPtr>
+GetDefaultManifestFileHandlers() {
+  std::vector<blink::mojom::ManifestFileHandlerPtr> handlers;
+  auto handler = blink::mojom::ManifestFileHandler::New();
+  handler->action = GURL("http://foo.com/?plaintext");
+  handler->name = u"Text";
   std::vector<std::u16string> extensions = {u".txt", u".md"};
-  handler.accept.emplace(u"text/plain", extensions);
-  return {handler};
+  handler->accept.emplace(u"text/plain", extensions);
+  handlers.push_back(std::move(handler));
+  return handlers;
 }
 
 }  // anonymous namespace
@@ -56,14 +59,14 @@
 
 TEST_F(ManifestUpdateTaskTest, TestSecondFileHandlerAdded) {
   apps::FileHandlers old_handlers = GetDefaultAppsFileHandlers();
-  std::vector<blink::Manifest::FileHandler> manifest_handlers =
+  std::vector<blink::mojom::ManifestFileHandlerPtr> manifest_handlers =
       GetDefaultManifestFileHandlers();
-  blink::Manifest::FileHandler second_handler;
-  second_handler.action = GURL("http://foo.com/?csv");
-  second_handler.name = u"Comma-Separated Value";
+  auto second_handler = blink::mojom::ManifestFileHandler::New();
+  second_handler->action = GURL("http://foo.com/?csv");
+  second_handler->name = u"Comma-Separated Value";
   std::vector<std::u16string> extensions = {u".csv"};
-  second_handler.accept.emplace(u"text/csv", extensions);
-  manifest_handlers.push_back(second_handler);
+  second_handler->accept.emplace(u"text/csv", extensions);
+  manifest_handlers.push_back(std::move(second_handler));
 
   apps::FileHandlers new_handlers =
       CreateFileHandlersFromManifest(manifest_handlers, GURL("http://foo.com"));
@@ -73,9 +76,9 @@
 // Ignore name changes, because the registrar doesn't store the name.
 TEST_F(ManifestUpdateTaskTest, TestFileHandlerChangedName) {
   apps::FileHandlers old_handlers = GetDefaultAppsFileHandlers();
-  std::vector<blink::Manifest::FileHandler> manifest_handlers =
+  std::vector<blink::mojom::ManifestFileHandlerPtr> manifest_handlers =
       GetDefaultManifestFileHandlers();
-  manifest_handlers[0].name = u"Comma-Separated Values";
+  manifest_handlers[0]->name = u"Comma-Separated Values";
 
   apps::FileHandlers new_handlers =
       CreateFileHandlersFromManifest(manifest_handlers, GURL("http://foo.com"));
@@ -84,9 +87,9 @@
 
 TEST_F(ManifestUpdateTaskTest, TestFileHandlerChangedAction) {
   apps::FileHandlers old_handlers = GetDefaultAppsFileHandlers();
-  std::vector<blink::Manifest::FileHandler> manifest_handlers =
+  std::vector<blink::mojom::ManifestFileHandlerPtr> manifest_handlers =
       GetDefaultManifestFileHandlers();
-  manifest_handlers[0].action = GURL("/?csvtext");
+  manifest_handlers[0]->action = GURL("/?csvtext");
 
   apps::FileHandlers new_handlers =
       CreateFileHandlersFromManifest(manifest_handlers, GURL("http://foo.com"));
@@ -95,10 +98,10 @@
 
 TEST_F(ManifestUpdateTaskTest, TestFileHandlerExtraAccept) {
   apps::FileHandlers old_handlers = GetDefaultAppsFileHandlers();
-  std::vector<blink::Manifest::FileHandler> manifest_handlers =
+  std::vector<blink::mojom::ManifestFileHandlerPtr> manifest_handlers =
       GetDefaultManifestFileHandlers();
   std::vector<std::u16string> csv_extensions = {u".csv"};
-  manifest_handlers[0].accept.emplace(u"text/csv", csv_extensions);
+  manifest_handlers[0]->accept.emplace(u"text/csv", csv_extensions);
 
   apps::FileHandlers new_handlers =
       CreateFileHandlersFromManifest(manifest_handlers, GURL("http://foo.com"));
diff --git a/chrome/browser/web_applications/os_integration_manager.cc b/chrome/browser/web_applications/os_integration_manager.cc
index ebba8cc..9fc42609 100644
--- a/chrome/browser/web_applications/os_integration_manager.cc
+++ b/chrome/browser/web_applications/os_integration_manager.cc
@@ -93,7 +93,7 @@
 
 OsIntegrationManager::OsIntegrationManager(
     Profile* profile,
-    std::unique_ptr<AppShortcutManager> shortcut_manager,
+    std::unique_ptr<WebAppShortcutManager> shortcut_manager,
     std::unique_ptr<FileHandlerManager> file_handler_manager,
     std::unique_ptr<ProtocolHandlerManager> protocol_handler_manager,
     std::unique_ptr<UrlHandlerManager> url_handler_manager)
@@ -107,7 +107,7 @@
 
 void OsIntegrationManager::SetSubsystems(WebAppRegistrar* registrar,
                                          WebAppUiManager* ui_manager,
-                                         AppIconManager* icon_manager) {
+                                         WebAppIconManager* icon_manager) {
   registrar_ = registrar;
   ui_manager_ = ui_manager;
   file_handler_manager_->SetSubsystems(registrar);
@@ -265,7 +265,7 @@
 
 void OsIntegrationManager::GetShortcutInfoForApp(
     const AppId& app_id,
-    AppShortcutManager::GetShortcutInfoCallback callback) {
+    WebAppShortcutManager::GetShortcutInfoCallback callback) {
   DCHECK(shortcut_manager_);
   return shortcut_manager_->GetShortcutInfoForApp(app_id, std::move(callback));
 }
diff --git a/chrome/browser/web_applications/os_integration_manager.h b/chrome/browser/web_applications/os_integration_manager.h
index 3e97835b..3ce484b 100644
--- a/chrome/browser/web_applications/os_integration_manager.h
+++ b/chrome/browser/web_applications/os_integration_manager.h
@@ -13,7 +13,6 @@
 #include "base/callback_forward.h"
 #include "base/memory/weak_ptr.h"
 #include "base/strings/string_piece_forward.h"
-#include "chrome/browser/web_applications/components/app_shortcut_manager.h"
 #include "chrome/browser/web_applications/components/file_handler_manager.h"
 #include "chrome/browser/web_applications/components/protocol_handler_manager.h"
 #include "chrome/browser/web_applications/components/url_handler_manager.h"
@@ -21,6 +20,7 @@
 #include "chrome/browser/web_applications/components/web_app_id.h"
 #include "chrome/browser/web_applications/components/web_app_run_on_os_login.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
+#include "chrome/browser/web_applications/web_app_shortcut_manager.h"
 #include "components/services/app_service/public/cpp/file_handler.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -33,7 +33,7 @@
 namespace web_app {
 
 class WebAppRegistrar;
-class AppIconManager;
+class WebAppIconManager;
 class TestOsIntegrationManager;
 class WebAppUiManager;
 
@@ -75,7 +75,7 @@
  public:
   explicit OsIntegrationManager(
       Profile* profile,
-      std::unique_ptr<AppShortcutManager> shortcut_manager,
+      std::unique_ptr<WebAppShortcutManager> shortcut_manager,
       std::unique_ptr<FileHandlerManager> file_handler_manager,
       std::unique_ptr<ProtocolHandlerManager> protocol_handler_manager,
       std::unique_ptr<UrlHandlerManager> url_handler_manager);
@@ -83,7 +83,7 @@
 
   void SetSubsystems(WebAppRegistrar* registrar,
                      WebAppUiManager* ui_manager,
-                     AppIconManager* icon_manager);
+                     WebAppIconManager* icon_manager);
 
   void Start();
 
@@ -119,16 +119,16 @@
       FileHandlerUpdateAction file_handlers_need_os_update,
       const WebApplicationInfo& web_app_info);
 
-  // Proxy calls for AppShortcutManager.
+  // Proxy calls for WebAppShortcutManager.
   // virtual for testing
   virtual void GetAppExistingShortCutLocation(
       ShortcutLocationCallback callback,
       std::unique_ptr<ShortcutInfo> shortcut_info);
 
-  // Proxy calls for AppShortcutManager.
+  // Proxy calls for WebAppShortcutManager.
   void GetShortcutInfoForApp(
       const AppId& app_id,
-      AppShortcutManager::GetShortcutInfoCallback callback);
+      WebAppShortcutManager::GetShortcutInfoCallback callback);
 
   // Proxy calls for FileHandlerManager.
   bool IsFileHandlingAPIAvailable(const AppId& app_id);
@@ -175,7 +175,7 @@
       FileHandlerUpdateAction file_handlers_need_os_update);
 
  protected:
-  AppShortcutManager* shortcut_manager() { return shortcut_manager_.get(); }
+  WebAppShortcutManager* shortcut_manager() { return shortcut_manager_.get(); }
   FileHandlerManager* file_handler_manager() {
     return file_handler_manager_.get();
   }
@@ -186,7 +186,7 @@
     return url_handler_manager_.get();
   }
   void set_shortcut_manager(
-      std::unique_ptr<AppShortcutManager> shortcut_manager) {
+      std::unique_ptr<WebAppShortcutManager> shortcut_manager) {
     shortcut_manager_ = std::move(shortcut_manager);
   }
   void set_file_handler_manager(
@@ -276,7 +276,7 @@
   WebAppRegistrar* registrar_ = nullptr;
   WebAppUiManager* ui_manager_ = nullptr;
 
-  std::unique_ptr<AppShortcutManager> shortcut_manager_;
+  std::unique_ptr<WebAppShortcutManager> shortcut_manager_;
   std::unique_ptr<FileHandlerManager> file_handler_manager_;
   std::unique_ptr<ProtocolHandlerManager> protocol_handler_manager_;
   std::unique_ptr<UrlHandlerManager> url_handler_manager_;
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.h b/chrome/browser/web_applications/policy/web_app_policy_manager.h
index 901dec6..2db1e17 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.h
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.h
@@ -10,7 +10,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/values.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/policy/web_app_policy_manager_observer.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
diff --git a/chrome/browser/web_applications/preinstalled_web_app_manager.cc b/chrome/browser/web_applications/preinstalled_web_app_manager.cc
index 1f797a1..cce3c80c 100644
--- a/chrome/browser/web_applications/preinstalled_web_app_manager.cc
+++ b/chrome/browser/web_applications/preinstalled_web_app_manager.cc
@@ -33,11 +33,11 @@
 #include "chrome/browser/apps/user_type_filter.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/components/externally_installed_web_app_prefs.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/components/preinstalled_app_install_features.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/components/web_app_install_utils.h"
 #include "chrome/browser/web_applications/extension_status_utils.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/preinstalled_web_app_utils.h"
 #include "chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.h"
 #include "chrome/browser/web_applications/web_app.h"
diff --git a/chrome/browser/web_applications/preinstalled_web_app_manager.h b/chrome/browser/web_applications/preinstalled_web_app_manager.h
index 05087942..817acadb 100644
--- a/chrome/browser/web_applications/preinstalled_web_app_manager.h
+++ b/chrome/browser/web_applications/preinstalled_web_app_manager.h
@@ -13,7 +13,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/values.h"
 #include "chrome/browser/web_applications/components/external_install_options.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/file_utils_wrapper.h"
 #include "url/gurl.h"
 
diff --git a/chrome/browser/web_applications/system_web_apps/system_web_app_manager.h b/chrome/browser/web_applications/system_web_apps/system_web_app_manager.h
index 2dfb075..16334f41 100644
--- a/chrome/browser/web_applications/system_web_apps/system_web_app_manager.h
+++ b/chrome/browser/web_applications/system_web_apps/system_web_app_manager.h
@@ -15,9 +15,9 @@
 #include "base/containers/flat_map.h"
 #include "base/memory/weak_ptr.h"
 #include "base/one_shot_event.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/components/web_app_url_loader.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/system_web_apps/system_web_app_background_task.h"
 #include "chrome/browser/web_applications/system_web_apps/system_web_app_delegate.h"
 #include "chrome/browser/web_applications/system_web_apps/system_web_app_types.h"
diff --git a/chrome/browser/web_applications/test/test_data_retriever.cc b/chrome/browser/web_applications/test/test_data_retriever.cc
index d9eee12f..79409b0 100644
--- a/chrome/browser/web_applications/test/test_data_retriever.cc
+++ b/chrome/browser/web_applications/test/test_data_retriever.cc
@@ -11,7 +11,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 
 namespace web_app {
 
@@ -36,12 +36,8 @@
     content::WebContents* web_contents,
     bool bypass_service_worker_check,
     CheckInstallabilityCallback callback) {
-  absl::optional<blink::Manifest> opt_manifest;
-  if (manifest_ && !manifest_->IsEmpty())
-    opt_manifest = *manifest_;
-
   completion_callback_ =
-      base::BindOnce(std::move(callback), opt_manifest, manifest_url_,
+      base::BindOnce(std::move(callback), manifest_.Clone(), manifest_url_,
                      /*valid_manifest_for_web_app=*/true, is_installable_);
   ScheduleCompletionCallback();
 }
@@ -72,7 +68,7 @@
   SetRendererWebApplicationInfo(std::make_unique<WebApplicationInfo>());
 }
 
-void TestDataRetriever::SetManifest(std::unique_ptr<blink::Manifest> manifest,
+void TestDataRetriever::SetManifest(blink::mojom::ManifestPtr manifest,
                                     bool is_installable,
                                     GURL manifest_url) {
   manifest_ = std::move(manifest);
@@ -99,7 +95,7 @@
                                                    const GURL& scope) {
   SetEmptyRendererWebApplicationInfo();
 
-  auto manifest = std::make_unique<blink::Manifest>();
+  auto manifest = blink::mojom::Manifest::New();
   manifest->start_url = url;
   manifest->scope = scope;
   manifest->display = DisplayMode::kStandalone;
diff --git a/chrome/browser/web_applications/test/test_data_retriever.h b/chrome/browser/web_applications/test/test_data_retriever.h
index 44c10bd3..20d6a0c 100644
--- a/chrome/browser/web_applications/test/test_data_retriever.h
+++ b/chrome/browser/web_applications/test/test_data_retriever.h
@@ -11,6 +11,7 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/web_applications/components/web_app_data_retriever.h"
 #include "chrome/browser/web_applications/components/web_app_install_utils.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 #include "url/gurl.h"
 
 struct WebApplicationInfo;
@@ -44,7 +45,7 @@
       std::unique_ptr<WebApplicationInfo> web_app_info);
   void SetEmptyRendererWebApplicationInfo();
   // Set arguments to respond on |CheckInstallabilityAndRetrieveManifest|.
-  void SetManifest(std::unique_ptr<blink::Manifest> manifest,
+  void SetManifest(blink::mojom::ManifestPtr manifest,
                    bool is_installable,
                    GURL manifest_url = GURL());
   // Set icons to respond on |GetIcons|.
@@ -71,7 +72,7 @@
   base::OnceClosure completion_callback_;
   std::unique_ptr<WebApplicationInfo> web_app_info_;
 
-  std::unique_ptr<blink::Manifest> manifest_;
+  blink::mojom::ManifestPtr manifest_;
   GURL manifest_url_;
   bool is_installable_;
 
diff --git a/chrome/browser/web_applications/test/test_install_finalizer.cc b/chrome/browser/web_applications/test/test_install_finalizer.cc
index 8e1e7db22..5afe2ee 100644
--- a/chrome/browser/web_applications/test/test_install_finalizer.cc
+++ b/chrome/browser/web_applications/test/test_install_finalizer.cc
@@ -110,6 +110,10 @@
                                        content::WebContents* web_contents) {
   ++num_reparent_tab_calls_;
 }
+void TestInstallFinalizer::SetRemoveSourceCallbackForTesting(
+    base::RepeatingCallback<void(const AppId&)>) {
+  NOTIMPLEMENTED();
+}
 
 void TestInstallFinalizer::SetNextFinalizeInstallResult(
     const AppId& app_id,
diff --git a/chrome/browser/web_applications/test/test_install_finalizer.h b/chrome/browser/web_applications/test/test_install_finalizer.h
index e1302c2..a302470 100644
--- a/chrome/browser/web_applications/test/test_install_finalizer.h
+++ b/chrome/browser/web_applications/test/test_install_finalizer.h
@@ -56,6 +56,8 @@
   void ReparentTab(const AppId& app_id,
                    bool shortcut_created,
                    content::WebContents* web_contents) override;
+  void SetRemoveSourceCallbackForTesting(
+      base::RepeatingCallback<void(const AppId&)>) override;
 
   void SetNextFinalizeInstallResult(const AppId& app_id,
                                     InstallResultCode code);
diff --git a/chrome/browser/web_applications/test/test_os_integration_manager.cc b/chrome/browser/web_applications/test/test_os_integration_manager.cc
index c527c03..534700fc9 100644
--- a/chrome/browser/web_applications/test/test_os_integration_manager.cc
+++ b/chrome/browser/web_applications/test/test_os_integration_manager.cc
@@ -6,7 +6,6 @@
 
 #include "base/containers/contains.h"
 #include "base/threading/sequenced_task_runner_handle.h"
-#include "chrome/browser/web_applications/components/app_shortcut_manager.h"
 #include "chrome/browser/web_applications/components/file_handler_manager.h"
 #include "chrome/browser/web_applications/components/protocol_handler_manager.h"
 #include "chrome/browser/web_applications/components/url_handler_manager.h"
@@ -15,11 +14,12 @@
 #include "chrome/browser/web_applications/test/fake_protocol_handler_manager.h"
 #include "chrome/browser/web_applications/test/fake_url_handler_manager.h"
 #include "chrome/browser/web_applications/test/test_file_handler_manager.h"
+#include "chrome/browser/web_applications/web_app_shortcut_manager.h"
 
 namespace web_app {
 TestOsIntegrationManager::TestOsIntegrationManager(
     Profile* profile,
-    std::unique_ptr<AppShortcutManager> shortcut_manager,
+    std::unique_ptr<WebAppShortcutManager> shortcut_manager,
     std::unique_ptr<FileHandlerManager> file_handler_manager,
     std::unique_ptr<ProtocolHandlerManager> protocol_handler_manager,
     std::unique_ptr<UrlHandlerManager> url_handler_manager)
@@ -142,7 +142,7 @@
 }
 
 TestShortcutManager::TestShortcutManager(Profile* profile)
-    : AppShortcutManager(profile) {}
+    : WebAppShortcutManager(profile, nullptr, nullptr, nullptr) {}
 
 TestShortcutManager::~TestShortcutManager() = default;
 
diff --git a/chrome/browser/web_applications/test/test_os_integration_manager.h b/chrome/browser/web_applications/test/test_os_integration_manager.h
index 6da3a56..bed349c1 100644
--- a/chrome/browser/web_applications/test/test_os_integration_manager.h
+++ b/chrome/browser/web_applications/test/test_os_integration_manager.h
@@ -13,7 +13,7 @@
 
 namespace web_app {
 
-class AppShortcutManager;
+class WebAppShortcutManager;
 class FileHandlerManager;
 class ProtocolHandlerManager;
 class UrlHandlerManager;
@@ -22,7 +22,7 @@
  public:
   TestOsIntegrationManager(
       Profile* profile,
-      std::unique_ptr<AppShortcutManager> shortcut_manager,
+      std::unique_ptr<WebAppShortcutManager> shortcut_manager,
       std::unique_ptr<FileHandlerManager> file_handler_manager,
       std::unique_ptr<ProtocolHandlerManager> protocol_handler_manager,
       std::unique_ptr<UrlHandlerManager> url_handler_manager);
@@ -107,7 +107,7 @@
 };
 
 // Stub test shortcut manager.
-class TestShortcutManager : public AppShortcutManager {
+class TestShortcutManager : public WebAppShortcutManager {
  public:
   explicit TestShortcutManager(Profile* profile);
   ~TestShortcutManager() override;
diff --git a/chrome/browser/web_applications/test/test_web_app_provider.cc b/chrome/browser/web_applications/test/test_web_app_provider.cc
index 7d37bc5..92513ec 100644
--- a/chrome/browser/web_applications/test/test_web_app_provider.cc
+++ b/chrome/browser/web_applications/test/test_web_app_provider.cc
@@ -8,10 +8,10 @@
 
 #include "base/bind.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/components/install_finalizer.h"
 #include "chrome/browser/web_applications/components/web_app_ui_manager.h"
 #include "chrome/browser/web_applications/components/web_app_utils.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/os_integration_manager.h"
 #include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
 #include "chrome/browser/web_applications/system_web_apps/system_web_app_manager.h"
diff --git a/chrome/browser/web_applications/test/web_app_icon_test_utils.cc b/chrome/browser/web_applications/test/web_app_icon_test_utils.cc
index 35a1ac00..1f3a604 100644
--- a/chrome/browser/web_applications/test/web_app_icon_test_utils.cc
+++ b/chrome/browser/web_applications/test/web_app_icon_test_utils.cc
@@ -92,6 +92,14 @@
   return icons_dir;
 }
 
+base::FilePath GetOtherIconsDir(Profile* profile, const AppId& app_id) {
+  base::FilePath web_apps_root_directory = GetWebAppsRootDirectory(profile);
+  base::FilePath app_dir =
+      GetManifestResourcesDirectoryForApp(web_apps_root_directory, app_id);
+  base::FilePath icons_dir = app_dir.AppendASCII("Image Cache");
+  return icons_dir;
+}
+
 bool ReadBitmap(FileUtilsWrapper* utils,
                 const base::FilePath& file_path,
                 SkBitmap* bitmap) {
@@ -209,7 +217,7 @@
   }
 
   base::RunLoop run_loop;
-  icon_manager.WriteData(app_id, std::move(icon_bitmaps), {},
+  icon_manager.WriteData(app_id, std::move(icon_bitmaps), {}, {},
                          base::BindLambdaForTesting([&](bool success) {
                            DCHECK(success);
                            run_loop.Quit();
diff --git a/chrome/browser/web_applications/test/web_app_icon_test_utils.h b/chrome/browser/web_applications/test/web_app_icon_test_utils.h
index 3d39a3f..ab5a403 100644
--- a/chrome/browser/web_applications/test/web_app_icon_test_utils.h
+++ b/chrome/browser/web_applications/test/web_app_icon_test_utils.h
@@ -48,6 +48,8 @@
 
 base::FilePath GetAppIconsMaskableDir(Profile* profile, const AppId& app_id);
 
+base::FilePath GetOtherIconsDir(Profile* profile, const AppId& app_id);
+
 // Performs blocking IO and decompression.
 bool ReadBitmap(FileUtilsWrapper* utils,
                 const base::FilePath& file_path,
diff --git a/chrome/browser/web_applications/test/web_app_registration_waiter.h b/chrome/browser/web_applications/test/web_app_registration_waiter.h
index bbd70ff..77a3297 100644
--- a/chrome/browser/web_applications/test/web_app_registration_waiter.h
+++ b/chrome/browser/web_applications/test/web_app_registration_waiter.h
@@ -6,7 +6,7 @@
 #define CHROME_BROWSER_WEB_APPLICATIONS_TEST_WEB_APP_REGISTRATION_WAITER_H_
 
 #include "base/run_loop.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 
diff --git a/chrome/browser/web_applications/web_app_helpers.cc b/chrome/browser/web_applications/web_app_helpers.cc
index 1f0836ac..08f68af 100644
--- a/chrome/browser/web_applications/web_app_helpers.cc
+++ b/chrome/browser/web_applications/web_app_helpers.cc
@@ -13,6 +13,7 @@
 #include "components/crx_file/id_util.h"
 #include "crypto/sha2.h"
 #include "extensions/common/constants.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "url/gurl.h"
 #include "url/url_constants.h"
 
@@ -67,7 +68,7 @@
       crypto::SHA256HashString(GenerateAppIdUnhashed(manifest_id, start_url)));
 }
 
-AppId GenerateAppIdFromManifest(const blink::Manifest& manifest) {
+AppId GenerateAppIdFromManifest(const blink::mojom::Manifest& manifest) {
   return GenerateAppId(
       manifest.id.has_value()
           ? absl::optional<std::string>(base::UTF16ToUTF8(manifest.id.value()))
diff --git a/chrome/browser/web_applications/web_app_icon_manager.cc b/chrome/browser/web_applications/web_app_icon_manager.cc
index c68f29c1c..ab8c68d 100644
--- a/chrome/browser/web_applications/web_app_icon_manager.cc
+++ b/chrome/browser/web_applications/web_app_icon_manager.cc
@@ -87,33 +87,21 @@
   SquareSizePx size;
 };
 
-// Returns false if directory doesn't exist or it is not writable.
-Result<bool> CreateDirectoryIfNotExists(FileUtilsWrapper* utils,
-                                        const base::FilePath& path) {
-  if (utils->PathExists(path)) {
-    if (!utils->DirectoryExists(path)) {
-      return {.error_log = {
-                  CreateError({"Not a directory: ", path.AsUTF8Unsafe()})}};
-    }
-    if (!utils->PathIsWritable(path)) {
-      return {.error_log = {
-                  CreateError({"Can't write to path: ", path.AsUTF8Unsafe()})}};
-    }
-    // This is a directory we can write to.
-    return {.value = true};
-  }
-
-  // Directory doesn't exist, so create it.
+Result<bool> CreateDirectory(FileUtilsWrapper* utils,
+                             const base::FilePath& path) {
   if (!utils->CreateDirectory(path)) {
     return {.error_log = {CreateError(
                 {"Could not create directory: ", path.AsUTF8Unsafe()})}};
   }
+
   return {.value = true};
 }
 
 // This is a private implementation detail of WebAppIconManager, where and how
 // to store icon files.
-base::FilePath GetAppIconsDirectory(
+// `app_manifest_resources_directory` is the path to the app-specific
+// subdirectory of the profile's manifest resources directory.
+base::FilePath GetProductIconsDirectory(
     const base::FilePath& app_manifest_resources_directory,
     IconPurpose purpose) {
   constexpr base::FilePath::CharType kIconsAnyDirectoryName[] =
@@ -138,9 +126,7 @@
 // to store shortcuts menu icons files.
 // All of the other shortcut icon directories appear under the directory for
 // |ANY|.
-base::FilePath GetAppShortcutsMenuIconsDirectory(
-    const base::FilePath& app_manifest_resources_directory,
-    IconPurpose purpose) {
+base::FilePath GetAppShortcutsMenuIconsRelativeDirectory(IconPurpose purpose) {
   constexpr base::FilePath::CharType kShortcutsMenuIconsDirectoryName[] =
       FILE_PATH_LITERAL("Shortcuts Menu Icons");
 
@@ -151,8 +137,7 @@
       kShortcutsMenuIconsMaskableDirectoryName[] =
           FILE_PATH_LITERAL("Maskable");
 
-  base::FilePath shortcuts_icons_directory =
-      app_manifest_resources_directory.Append(kShortcutsMenuIconsDirectoryName);
+  base::FilePath shortcuts_icons_directory(kShortcutsMenuIconsDirectoryName);
 
   switch (purpose) {
     case IconPurpose::ANY:
@@ -166,6 +151,16 @@
   }
 }
 
+base::FilePath GetOtherIconsRelativeDirectory() {
+  return base::FilePath(FILE_PATH_LITERAL("Image Cache"));
+}
+
+// Returns a string suitable for use as a directory for the given URL. This name
+// is a hash of the URL.
+std::string GetDirectoryNameForUrl(const GURL& url) {
+  return base::NumberToString(base::PersistentHash(url.spec()));
+}
+
 Result<bool> WriteIcon(FileUtilsWrapper* utils,
                        const base::FilePath& icons_dir,
                        const SkBitmap& bitmap) {
@@ -192,19 +187,23 @@
   return {.value = true};
 }
 
-Result<bool> WriteIcons(FileUtilsWrapper* utils,
-                        const base::FilePath& icons_dir,
-                        const std::map<SquareSizePx, SkBitmap>& icon_bitmaps) {
-  if (!utils->CreateDirectory(icons_dir)) {
-    return {.error_log = {CreateError({"Could not create icons directory: ",
-                                       icons_dir.AsUTF8Unsafe()})}};
-  }
+Result<bool> WriteProductIcons(FileUtilsWrapper* utils,
+                               const IconBitmaps& icon_bitmaps,
+                               const base::FilePath& base_dir) {
+  for (IconPurpose purpose : kIconPurposes) {
+    base::FilePath icons_dir = GetProductIconsDirectory(base_dir, purpose);
 
-  for (const std::pair<const SquareSizePx, SkBitmap>& icon_bitmap :
-       icon_bitmaps) {
-    Result<bool> write_result = WriteIcon(utils, icons_dir, icon_bitmap.second);
-    if (write_result.HasErrors())
-      return write_result;
+    auto create_result = CreateDirectory(utils, icons_dir);
+    if (create_result.HasErrors())
+      return create_result;
+
+    for (const std::pair<const SquareSizePx, SkBitmap>& icon_bitmap :
+         icon_bitmaps.GetBitmapsForPurpose(purpose)) {
+      Result<bool> write_result =
+          WriteIcon(utils, icons_dir, icon_bitmap.second);
+      if (write_result.HasErrors())
+        return write_result;
+    }
   }
 
   return {.value = true};
@@ -214,17 +213,15 @@
 // new directory per shortcut item using its index in the vector.
 Result<bool> WriteShortcutsMenuIcons(
     FileUtilsWrapper* utils,
-    const base::FilePath& app_manifest_resources_directory,
-    const ShortcutsMenuIconBitmaps& shortcuts_menu_icon_bitmaps) {
+    const ShortcutsMenuIconBitmaps& shortcuts_menu_icon_bitmaps,
+    const base::FilePath& app_manifest_resources_directory) {
   for (IconPurpose purpose : kIconPurposes) {
     const base::FilePath shortcuts_menu_icons_dir =
-        GetAppShortcutsMenuIconsDirectory(app_manifest_resources_directory,
-                                          purpose);
-    if (!utils->CreateDirectory(shortcuts_menu_icons_dir)) {
-      return {.error_log = {
-                  CreateError({"Could not create directory: ",
-                               shortcuts_menu_icons_dir.AsUTF8Unsafe()})}};
-    }
+        app_manifest_resources_directory.Append(
+            GetAppShortcutsMenuIconsRelativeDirectory(purpose));
+    auto create_result = CreateDirectory(utils, shortcuts_menu_icons_dir);
+    if (create_result.HasErrors())
+      return create_result;
 
     int shortcut_index = -1;
     for (const IconBitmaps& icon_bitmaps : shortcuts_menu_icon_bitmaps) {
@@ -237,11 +234,9 @@
       const base::FilePath shortcuts_menu_icon_dir =
           shortcuts_menu_icons_dir.AppendASCII(
               base::NumberToString(shortcut_index));
-      if (!utils->CreateDirectory(shortcuts_menu_icon_dir)) {
-        return {.error_log = {
-                    CreateError({"Could not create directory: ",
-                                 shortcuts_menu_icon_dir.AsUTF8Unsafe()})}};
-      }
+      create_result = CreateDirectory(utils, shortcuts_menu_icon_dir);
+      if (create_result.HasErrors())
+        return create_result;
 
       for (const std::pair<const SquareSizePx, SkBitmap>& icon_bitmap :
            bitmaps) {
@@ -255,74 +250,54 @@
   return {.value = true};
 }
 
-// Performs blocking I/O. May be called on another thread.
-// Returns true if no errors occurred.
-Result<bool> WriteAppIconDataBlocking(
-    const std::unique_ptr<FileUtilsWrapper>& utils,
-    const base::FilePath& web_apps_directory,
-    const AppId& app_id,
-    const IconBitmaps& icon_bitmaps) {
-  // Create the temp directory under the web apps root.
-  // This guarantees it is on the same file system as the WebApp's eventual
-  // install target.
-  base::FilePath temp_dir = GetWebAppsTempDirectory(web_apps_directory);
-  Result<bool> create_result =
-      CreateDirectoryIfNotExists(utils.get(), temp_dir);
+Result<bool> WriteOtherIcons(
+    FileUtilsWrapper* utils,
+    const IconsMap& other_icons,
+    const base::FilePath& app_manifest_resources_directory) {
+  const base::FilePath general_icons_dir =
+      app_manifest_resources_directory.Append(GetOtherIconsRelativeDirectory());
+  auto create_result = CreateDirectory(utils, general_icons_dir);
   if (create_result.HasErrors())
     return create_result;
 
-  base::ScopedTempDir app_temp_dir;
-  if (!app_temp_dir.CreateUniqueTempDirUnderPath(temp_dir)) {
-    return {.error_log = {
-                CreateError({"Could not create temporary WebApp directory in: ",
-                             app_temp_dir.GetPath().AsUTF8Unsafe()})}};
+  for (const std::pair<const GURL, std::vector<SkBitmap>>& entry :
+       other_icons) {
+    const base::FilePath subdir =
+        general_icons_dir.AppendASCII(GetDirectoryNameForUrl(entry.first));
+    create_result = CreateDirectory(utils, subdir);
+    if (create_result.HasErrors())
+      return create_result;
+
+    const std::vector<SkBitmap>& icon_bitmaps = entry.second;
+    for (const SkBitmap& icon_bitmap : icon_bitmaps) {
+      Result<bool> write_result = WriteIcon(utils, subdir, icon_bitmap);
+      if (write_result.HasErrors())
+        return write_result;
+    }
   }
-
-  for (IconPurpose purpose : kIconPurposes) {
-    Result<bool> write_result = WriteIcons(
-        utils.get(), GetAppIconsDirectory(app_temp_dir.GetPath(), purpose),
-        icon_bitmaps.GetBitmapsForPurpose(purpose));
-    if (write_result.HasErrors())
-      return write_result;
-  }
-
-  base::FilePath manifest_resources_directory =
-      GetManifestResourcesDirectory(web_apps_directory);
-  create_result =
-      CreateDirectoryIfNotExists(utils.get(), manifest_resources_directory);
-  if (create_result.HasErrors())
-    return create_result;
-
-  base::FilePath app_dir =
-      GetManifestResourcesDirectoryForApp(web_apps_directory, app_id);
-
-  // Try to delete the destination. Needed for update. Ignore the result.
-  utils->DeleteFileRecursively(app_dir);
-
-  // Commit: move whole app data dir to final destination in one mv operation.
-  if (!utils->Move(app_temp_dir.GetPath(), app_dir)) {
-    return {.error_log = {CreateError({"Could not move temp WebApp directory:",
-                                       app_temp_dir.GetPath().AsUTF8Unsafe(),
-                                       " to: ", app_dir.AsUTF8Unsafe()})}};
-  }
-
-  app_temp_dir.Take();
   return {.value = true};
 }
 
-// Performs blocking I/O. May be called on another thread.
-// Returns true if no errors occurred.
-Result<bool> WriteShortcutsMenuIconsDataBlocking(
+// Performs blocking I/O. Returns true if no errors occurred. This is used for
+// several kinds of icon data. The passed callbacks allow for varying the
+// implementation based on data type. `write_icons_callback` writes the icons
+// data under the passed base directory. `subdir_for_icons` is a relative
+// FilePath representing a directory which holds all the data written by
+// `write_icons_callback`. The path is relative to the app's manifest resources
+// directory.
+Result<bool> WriteIconsDataBlocking(
     const std::unique_ptr<FileUtilsWrapper>& utils,
     const base::FilePath& web_apps_directory,
     const AppId& app_id,
-    const ShortcutsMenuIconBitmaps& shortcuts_menu_icon_bitmaps) {
+    const base::RepeatingCallback<Result<bool>(const base::FilePath& path)>&
+        write_icons_callback,
+    const base::FilePath& subdir_for_icons) {
+  DCHECK(!subdir_for_icons.IsAbsolute());
   // Create the temp directory under the web apps root.
   // This guarantees it is on the same file system as the WebApp's eventual
   // install target.
   base::FilePath temp_dir = GetWebAppsTempDirectory(web_apps_directory);
-  Result<bool> create_result =
-      CreateDirectoryIfNotExists(utils.get(), temp_dir);
+  Result<bool> create_result = CreateDirectory(utils.get(), temp_dir);
   if (create_result.HasErrors())
     return create_result;
 
@@ -335,65 +310,103 @@
 
   base::FilePath manifest_resources_directory =
       GetManifestResourcesDirectory(web_apps_directory);
-  create_result =
-      CreateDirectoryIfNotExists(utils.get(), manifest_resources_directory);
+  create_result = CreateDirectory(utils.get(), manifest_resources_directory);
   if (create_result.HasErrors())
     return create_result;
 
-  base::FilePath app_dir =
-      GetManifestResourcesDirectoryForApp(web_apps_directory, app_id);
-
-  // Create app_dir if it doesn't already exist. We'll need this for
-  // WriteShortcutsMenuIconsData unittests.
-  create_result = CreateDirectoryIfNotExists(utils.get(), app_dir);
-  if (create_result.HasErrors())
-    return create_result;
-
-  Result<bool> write_result = WriteShortcutsMenuIcons(
-      utils.get(), app_temp_dir.GetPath(), shortcuts_menu_icon_bitmaps);
+  Result<bool> write_result = write_icons_callback.Run(app_temp_dir.GetPath());
   if (write_result.HasErrors())
     return write_result;
 
-  {
-    base::FilePath shortcuts_menu_icons_dir =
-        GetAppShortcutsMenuIconsDirectory(app_dir, IconPurpose::ANY);
+  base::FilePath app_dir =
+      GetManifestResourcesDirectoryForApp(web_apps_directory, app_id);
+  base::FilePath final_icons_dir = app_dir.Append(subdir_for_icons);
+  // Create app_dir if it doesn't already exist. We'll need this for
+  // WriteShortcutsMenuIconsData unittests.
+  if (final_icons_dir != app_dir) {
+    create_result = CreateDirectory(utils.get(), app_dir);
+    if (create_result.HasErrors())
+      return create_result;
+  }
 
-    // Delete the destination. Needed for update. Return if destination isn't
-    // clear.
-    if (!utils->DeleteFileRecursively(shortcuts_menu_icons_dir)) {
-      return {.error_log = {
-                  CreateError({"Could not delete: ",
-                               shortcuts_menu_icons_dir.AsUTF8Unsafe()})}};
-    }
+  // Delete the destination. Needed for update. Ignore the result.
+  utils->DeleteFileRecursively(final_icons_dir);
 
-    // Commit: move whole shortcuts menu icons data dir to final destination in
-    // one mv operation.
-    if (!utils->Move(GetAppShortcutsMenuIconsDirectory(app_temp_dir.GetPath(),
-                                                       IconPurpose::ANY),
-                     shortcuts_menu_icons_dir)) {
-      return {.error_log = {CreateError(
-                  {"Could not move: ", app_temp_dir.GetPath().AsUTF8Unsafe(),
-                   " to: ", shortcuts_menu_icons_dir.AsUTF8Unsafe()})}};
-    }
+  base::FilePath temp_icons_dir =
+      app_temp_dir.GetPath().Append(subdir_for_icons);
+  // Commit: move whole icons data dir to final destination in one mv
+  // operation.
+  if (!utils->Move(temp_icons_dir, final_icons_dir)) {
+    return {.error_log = {
+                CreateError({"Could not move: ", temp_icons_dir.AsUTF8Unsafe(),
+                             " to: ", final_icons_dir.AsUTF8Unsafe()})}};
   }
 
   return {.value = true};
 }
 
+Result<bool> WriteProductIconsDataBlocking(
+    const std::unique_ptr<FileUtilsWrapper>& utils,
+    const base::FilePath& web_apps_directory,
+    const AppId& app_id,
+    const IconBitmaps& icon_bitmaps) {
+  auto write_icons_callback =
+      base::BindRepeating(&WriteProductIcons, utils.get(), icon_bitmaps);
+  // Place product icons directly in the app's directory.
+  base::FilePath subdir_for_icons;
+  return WriteIconsDataBlocking(utils, web_apps_directory, app_id,
+                                write_icons_callback, subdir_for_icons);
+}
+
+Result<bool> WriteShortcutsMenuIconsDataBlocking(
+    const std::unique_ptr<FileUtilsWrapper>& utils,
+    const base::FilePath& web_apps_directory,
+    const AppId& app_id,
+    const ShortcutsMenuIconBitmaps& shortcuts_menu_icon_bitmaps) {
+  auto write_icons_callback = base::BindRepeating(
+      &WriteShortcutsMenuIcons, utils.get(), shortcuts_menu_icon_bitmaps);
+  base::FilePath subdir_for_icons =
+      GetAppShortcutsMenuIconsRelativeDirectory(IconPurpose::ANY);
+
+  return WriteIconsDataBlocking(utils, web_apps_directory, app_id,
+                                write_icons_callback, subdir_for_icons);
+}
+
+Result<bool> WriteOtherIconsDataBlocking(
+    const std::unique_ptr<FileUtilsWrapper>& utils,
+    const base::FilePath& web_apps_directory,
+    const AppId& app_id,
+    const IconsMap& other_icons) {
+  auto write_icons_callback =
+      base::BindRepeating(&WriteOtherIcons, utils.get(), other_icons);
+  base::FilePath subdir_for_icons = GetOtherIconsRelativeDirectory();
+
+  return WriteIconsDataBlocking(utils, web_apps_directory, app_id,
+                                write_icons_callback, subdir_for_icons);
+}
+
 Result<bool> WriteDataBlocking(
     const std::unique_ptr<FileUtilsWrapper>& utils,
     const base::FilePath& web_apps_directory,
     const AppId& app_id,
     const IconBitmaps& icon_bitmaps,
-    const ShortcutsMenuIconBitmaps& shortcuts_menu_icon_bitmaps) {
-  auto result =
-      WriteAppIconDataBlocking(utils, web_apps_directory, app_id, icon_bitmaps);
+    const ShortcutsMenuIconBitmaps& shortcuts_menu_icon_bitmaps,
+    const IconsMap& other_icons) {
+  auto result = WriteProductIconsDataBlocking(utils, web_apps_directory, app_id,
+                                              icon_bitmaps);
   if (result.HasErrors())
     return result;
 
   if (!shortcuts_menu_icon_bitmaps.empty()) {
     result = WriteShortcutsMenuIconsDataBlocking(
         utils, web_apps_directory, app_id, shortcuts_menu_icon_bitmaps);
+    if (result.HasErrors())
+      return result;
+  }
+
+  if (!other_icons.empty()) {
+    result = WriteOtherIconsDataBlocking(utils, web_apps_directory, app_id,
+                                         other_icons);
   }
 
   return result;
@@ -410,25 +423,28 @@
   return utils->DeleteFileRecursively(app_dir);
 }
 
+// `web_apps_directory` is the path to the directory where all web app data is
+// stored for the relevant profile.
 base::FilePath GetIconFileName(const base::FilePath& web_apps_directory,
                                const IconId& icon_id) {
   base::FilePath app_dir =
       GetManifestResourcesDirectoryForApp(web_apps_directory, icon_id.app_id);
-  base::FilePath icons_dir = GetAppIconsDirectory(app_dir, icon_id.purpose);
+  base::FilePath icons_dir = GetProductIconsDirectory(app_dir, icon_id.purpose);
 
   return icons_dir.AppendASCII(base::StringPrintf("%i.png", icon_id.size));
 }
 
+// `web_apps_directory` is the path to the directory where all web app data is
+// stored for the relevant profile.
 base::FilePath GetManifestResourcesShortcutsMenuIconFileName(
     const base::FilePath& web_apps_directory,
     const AppId& app_id,
     IconPurpose purpose,
     int index,
     int icon_size_px) {
-  const base::FilePath manifest_app_dir =
-      GetManifestResourcesDirectoryForApp(web_apps_directory, app_id);
   const base::FilePath manifest_shortcuts_menu_icons_dir =
-      GetAppShortcutsMenuIconsDirectory(manifest_app_dir, purpose);
+      GetManifestResourcesDirectoryForApp(web_apps_directory, app_id)
+          .Append(GetAppShortcutsMenuIconsRelativeDirectory(purpose));
   const base::FilePath manifest_shortcuts_menu_icon_dir =
       manifest_shortcuts_menu_icons_dir.AppendASCII(
           base::NumberToString(index));
@@ -619,7 +635,7 @@
 }
 
 void WrapReadCompressedIconWithPurposeCallback(
-    AppIconManager::ReadCompressedIconWithPurposeCallback callback,
+    WebAppIconManager::ReadCompressedIconWithPurposeCallback callback,
     IconPurpose purpose,
     std::vector<uint8_t> data) {
   std::move(callback).Run(purpose, std::move(data));
@@ -662,6 +678,19 @@
     base::MayBlock(), base::TaskPriority::USER_VISIBLE,
     base::TaskShutdownBehavior::BLOCK_SHUTDOWN};
 
+void WrapReadIconCallback(WebAppIconManager::ReadIconCallback callback,
+                          IconPurpose ignored,
+                          SkBitmap bitmap) {
+  std::move(callback).Run(std::move(bitmap));
+}
+
+void WrapReadCompressedIconCallback(
+    WebAppIconManager::ReadCompressedIconCallback callback,
+    IconPurpose ignored,
+    std::vector<uint8_t> data) {
+  std::move(callback).Run(std::move(data));
+}
+
 }  // namespace
 
 WebAppIconManager::WebAppIconManager(Profile* profile,
@@ -679,6 +708,7 @@
     AppId app_id,
     IconBitmaps icon_bitmaps,
     ShortcutsMenuIconBitmaps shortcuts_menu_icon_bitmaps,
+    IconsMap other_icons_map,
     WriteDataCallback callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
@@ -686,7 +716,8 @@
       FROM_HERE, kTaskTraits,
       base::BindOnce(WriteDataBlocking, utils_->Clone(), web_apps_directory_,
                      std::move(app_id), std::move(icon_bitmaps),
-                     std::move(shortcuts_menu_icon_bitmaps)),
+                     std::move(shortcuts_menu_icon_bitmaps),
+                     std::move(other_icons_map)),
       base::BindOnce(&LogErrorsCallCallback<bool>,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
@@ -731,7 +762,7 @@
                                 icon_sizes);
 }
 
-absl::optional<AppIconManager::IconSizeAndPurpose>
+absl::optional<WebAppIconManager::IconSizeAndPurpose>
 WebAppIconManager::FindIconMatchBigger(const AppId& app_id,
                                        const std::vector<IconPurpose>& purposes,
                                        SquareSizePx min_size) const {
@@ -868,6 +899,25 @@
                      weak_ptr_factory_.GetWeakPtr(), std::move(wrapped)));
 }
 
+void WebAppIconManager::ReadSmallestIconAny(const AppId& app_id,
+                                            SquareSizePx min_icon_size,
+                                            ReadIconCallback callback) const {
+  ReadIconWithPurposeCallback wrapped =
+      base::BindOnce(WrapReadIconCallback, std::move(callback));
+  ReadSmallestIcon(app_id, {IconPurpose::ANY}, min_icon_size,
+                   std::move(wrapped));
+}
+
+void WebAppIconManager::ReadSmallestCompressedIconAny(
+    const AppId& app_id,
+    SquareSizePx min_icon_size,
+    ReadCompressedIconCallback callback) const {
+  ReadCompressedIconWithPurposeCallback wrapped =
+      base::BindOnce(WrapReadCompressedIconCallback, std::move(callback));
+  ReadSmallestCompressedIcon(app_id, {IconPurpose::ANY}, min_icon_size,
+                             std::move(wrapped));
+}
+
 SkBitmap WebAppIconManager::GetFavicon(const AppId& app_id) const {
   auto iter = favicon_cache_.find(app_id);
   if (iter == favicon_cache_.end())
@@ -977,7 +1027,15 @@
   favicon_monochrome_read_callback_ = std::move(callback);
 }
 
-absl::optional<AppIconManager::IconSizeAndPurpose>
+// static
+void WebAppIconManager::WrapReadIconWithPurposeCallback(
+    ReadIconWithPurposeCallback callback,
+    IconPurpose purpose,
+    SkBitmap bitmap) {
+  std::move(callback).Run(purpose, std::move(bitmap));
+}
+
+absl::optional<WebAppIconManager::IconSizeAndPurpose>
 WebAppIconManager::FindIconMatchSmaller(
     const AppId& app_id,
     const std::vector<IconPurpose>& purposes,
diff --git a/chrome/browser/web_applications/web_app_icon_manager.h b/chrome/browser/web_applications/web_app_icon_manager.h
index b43b28b..cc9f6fc3 100644
--- a/chrome/browser/web_applications/web_app_icon_manager.h
+++ b/chrome/browser/web_applications/web_app_icon_manager.h
@@ -13,7 +13,6 @@
 #include "base/files/file_path.h"
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
-#include "chrome/browser/web_applications/components/app_icon_manager.h"
 #include "chrome/browser/web_applications/components/app_registrar_observer.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
@@ -31,7 +30,7 @@
 using SquareSizeDip = int;
 
 // Exclusively used from the UI thread.
-class WebAppIconManager : public AppIconManager, public AppRegistrarObserver {
+class WebAppIconManager : public AppRegistrarObserver {
  public:
   using FaviconReadCallback =
       base::RepeatingCallback<void(const AppId& app_id)>;
@@ -51,42 +50,100 @@
   void WriteData(AppId app_id,
                  IconBitmaps icon_bitmaps,
                  ShortcutsMenuIconBitmaps shortcuts_menu_icons,
+                 IconsMap other_icons_map,
                  WriteDataCallback callback);
   void DeleteData(AppId app_id, WriteDataCallback callback);
 
-  // AppIconManager:
-  WebAppIconManager* AsWebAppIconManager() override;
-  void Start() override;
-  void Shutdown() override;
+  void Start();
+  void Shutdown();
+
+  WebAppIconManager* AsWebAppIconManager();
+
+  // Returns false if any icon in |icon_sizes_in_px| is missing from downloaded
+  // icons for a given app and |purpose|.
   bool HasIcons(const AppId& app_id,
                 IconPurpose purpose,
-                const SortedSizesPx& icon_sizes) const override;
+                const SortedSizesPx& icon_sizes) const;
+  struct IconSizeAndPurpose {
+    SquareSizePx size_px = 0;
+    IconPurpose purpose = IconPurpose::ANY;
+  };
+  // For each of |purposes|, in the given order, looks for an icon with size at
+  // least |min_icon_size|. Returns information on the first icon found.
   absl::optional<IconSizeAndPurpose> FindIconMatchBigger(
       const AppId& app_id,
       const std::vector<IconPurpose>& purposes,
-      SquareSizePx min_size) const override;
+      SquareSizePx min_size) const;
+  // Returns whether there is a downloaded icon of at least |min_size| for any
+  // of the given |purposes|.
   bool HasSmallestIcon(const AppId& app_id,
                        const std::vector<IconPurpose>& purposes,
-                       SquareSizePx min_size) const override;
+                       SquareSizePx min_size) const;
+
+  using ReadIconsCallback =
+      base::OnceCallback<void(std::map<SquareSizePx, SkBitmap> icon_bitmaps)>;
+  // Reads specified icon bitmaps for an app and |purpose|. Returns empty map in
+  // |callback| if IO error.
   void ReadIcons(const AppId& app_id,
                  IconPurpose purpose,
                  const SortedSizesPx& icon_sizes,
-                 ReadIconsCallback callback) const override;
+                 ReadIconsCallback callback) const;
+
+  // TODO (crbug.com/1102701): Callback with const ref instead of value.
+  using ReadIconBitmapsCallback =
+      base::OnceCallback<void(IconBitmaps icon_bitmaps)>;
+  // Reads all icon bitmaps for an app. Returns empty |icon_bitmaps| in
+  // |callback| if IO error.
   void ReadAllIcons(const AppId& app_id,
-                    ReadIconBitmapsCallback callback) const override;
-  void ReadAllShortcutsMenuIcons(
-      const AppId& app_id,
-      ReadShortcutsMenuIconsCallback callback) const override;
+                    ReadIconBitmapsCallback callback) const;
+
+  using ReadShortcutsMenuIconsCallback = base::OnceCallback<void(
+      ShortcutsMenuIconBitmaps shortcuts_menu_icon_bitmaps)>;
+  // Reads bitmaps for all shortcuts menu icons for an app. Returns a vector of
+  // map<SquareSizePx, SkBitmap>. The index of a map in the vector is the same
+  // as that of its corresponding shortcut in the manifest's shortcuts vector.
+  // Returns empty vector in |callback| if we hit any error.
+  void ReadAllShortcutsMenuIcons(const AppId& app_id,
+                                 ReadShortcutsMenuIconsCallback callback) const;
+
+  using ReadIconWithPurposeCallback =
+      base::OnceCallback<void(IconPurpose, SkBitmap)>;
+  // For each of |purposes|, in the given order, looks for an icon with size at
+  // least |min_icon_size|. Returns the first icon found, as a bitmap. Returns
+  // an empty SkBitmap in |callback| if IO error.
   void ReadSmallestIcon(const AppId& app_id,
                         const std::vector<IconPurpose>& purposes,
                         SquareSizePx min_size_in_px,
-                        ReadIconWithPurposeCallback callback) const override;
+                        ReadIconWithPurposeCallback callback) const;
+
+  using ReadCompressedIconWithPurposeCallback =
+      base::OnceCallback<void(IconPurpose, std::vector<uint8_t> data)>;
+  // For each of |purposes|, in the given order, looks for an icon with size at
+  // least |min_icon_size|. Returns the first icon found, compressed as PNG.
+  // Returns empty |data| in |callback| if IO error.
   void ReadSmallestCompressedIcon(
       const AppId& app_id,
       const std::vector<IconPurpose>& purposes,
       SquareSizePx min_size_in_px,
-      ReadCompressedIconWithPurposeCallback callback) const override;
-  SkBitmap GetFavicon(const AppId& app_id) const override;
+      ReadCompressedIconWithPurposeCallback callback) const;
+
+  using ReadIconCallback = base::OnceCallback<void(SkBitmap)>;
+  // Convenience method for |ReadSmallestIcon| with IconPurpose::ANY only.
+  void ReadSmallestIconAny(const AppId& app_id,
+                           SquareSizePx min_icon_size,
+                           ReadIconCallback callback) const;
+
+  using ReadCompressedIconCallback =
+      base::OnceCallback<void(std::vector<uint8_t> data)>;
+  // Convenience method for |ReadSmallestCompressedIcon| with IconPurpose::ANY
+  // only.
+  void ReadSmallestCompressedIconAny(const AppId& app_id,
+                                     SquareSizePx min_icon_size,
+                                     ReadCompressedIconCallback callback) const;
+
+  // Returns a square icon of gfx::kFaviconSize px, or an empty bitmap if not
+  // found.
+  SkBitmap GetFavicon(const AppId& app_id) const;
 
   gfx::ImageSkia GetFaviconImageSkia(const AppId& app_id) const;
   gfx::ImageSkia GetMonochromeFavicon(const AppId& app_id) const;
@@ -122,6 +179,11 @@
   std::vector<std::string>* error_log() { return error_log_.get(); }
 
  private:
+  static void WrapReadIconWithPurposeCallback(
+      ReadIconWithPurposeCallback callback,
+      IconPurpose purpose,
+      SkBitmap bitmap);
+
   absl::optional<IconSizeAndPurpose> FindIconMatchSmaller(
       const AppId& app_id,
       const std::vector<IconPurpose>& purposes,
diff --git a/chrome/browser/web_applications/web_app_icon_manager_unittest.cc b/chrome/browser/web_applications/web_app_icon_manager_unittest.cc
index afdc1257..840d995 100644
--- a/chrome/browser/web_applications/web_app_icon_manager_unittest.cc
+++ b/chrome/browser/web_applications/web_app_icon_manager_unittest.cc
@@ -46,7 +46,7 @@
 
 namespace {
 
-using IconSizeAndPurpose = AppIconManager::IconSizeAndPurpose;
+using IconSizeAndPurpose = WebAppIconManager::IconSizeAndPurpose;
 
 }  // namespace
 
@@ -94,7 +94,7 @@
     }
 
     base::RunLoop run_loop;
-    icon_manager_->WriteData(app_id, {}, std::move(shortcuts_menu_icons),
+    icon_manager_->WriteData(app_id, {}, std::move(shortcuts_menu_icons), {},
                              base::BindLambdaForTesting([&](bool success) {
                                EXPECT_TRUE(success);
                                run_loop.Quit();
@@ -541,7 +541,7 @@
     base::RunLoop run_loop;
 
     // Overwrite red icons with green and blue ones.
-    icon_manager().WriteData(app_id, std::move(icon_bitmaps), {},
+    icon_manager().WriteData(app_id, std::move(icon_bitmaps), {}, {},
                              base::BindLambdaForTesting([&](bool success) {
                                EXPECT_TRUE(success);
                                run_loop.Quit();
@@ -770,7 +770,7 @@
   }
 }
 
-TEST_F(WebAppIconManagerTest, WriteShortcutsMenuIconsEmptyMap) {
+TEST_F(WebAppIconManagerTest, WriteNonProductIconsEmptyMaps) {
   auto web_app = test::CreateMinimalWebApp();
   const AppId app_id = web_app->app_id();
 
@@ -778,9 +778,8 @@
 
   controller().RegisterApp(std::move(web_app));
 
-  ShortcutsMenuIconBitmaps shortcuts_menu_icons;
   base::RunLoop run_loop;
-  icon_manager().WriteData(app_id, {}, std::move(shortcuts_menu_icons),
+  icon_manager().WriteData(app_id, {}, {}, {},
                            base::BindLambdaForTesting([&](bool success) {
                              EXPECT_TRUE(success);
                              run_loop.Quit();
@@ -791,6 +790,34 @@
   ShortcutsMenuIconBitmaps shortcuts_menu_icons_map =
       ReadAllShortcutsMenuIcons(app_id);
   EXPECT_EQ(0u, shortcuts_menu_icons_map.size());
+
+  EXPECT_FALSE(file_utils().PathExists(GetOtherIconsDir(profile(), app_id)));
+  // TODO(estade): check that WebAppIconManager returns no data when other icons
+  // are read. (When there is a read function.)
+}
+
+TEST_F(WebAppIconManagerTest, WriteOtherIconsToDisk) {
+  auto web_app = test::CreateMinimalWebApp();
+  const AppId app_id = web_app->app_id();
+
+  controller().RegisterApp(std::move(web_app));
+
+  IconsMap other_icons;
+  const GURL example_gurl("https://example.com/image.png");
+  AddIconToIconsMap(example_gurl, 48, SK_ColorBLUE, &other_icons);
+  base::RunLoop run_loop;
+  icon_manager().WriteData(app_id, {}, {}, other_icons,
+                           base::BindLambdaForTesting([&](bool success) {
+                             EXPECT_TRUE(success);
+                             run_loop.Quit();
+                           }));
+  run_loop.Run();
+
+  base::FilePath other_icons_dir = GetOtherIconsDir(profile(), app_id);
+  EXPECT_TRUE(file_utils().DirectoryExists(other_icons_dir));
+  EXPECT_FALSE(file_utils().IsDirectoryEmpty(other_icons_dir));
+  // TODO(estade): check that WebAppIconManager returns correct data when other
+  // icons are read. (When there is a read function.)
 }
 
 TEST_F(WebAppIconManagerTest, ReadIconsFailed) {
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.cc b/chrome/browser/web_applications/web_app_install_finalizer.cc
index 0d4e3daf..a06860e 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/web_app_install_finalizer.cc
@@ -388,6 +388,11 @@
   started_ = false;
 }
 
+void WebAppInstallFinalizer::SetRemoveSourceCallbackForTesting(
+    base::RepeatingCallback<void(const AppId&)> callback) {
+  install_source_removed_callback_for_testing_ = std::move(callback);
+}
+
 void WebAppInstallFinalizer::UninstallWebAppInternal(
     const AppId& app_id,
     webapps::WebappUninstallSource uninstall_source,
@@ -488,6 +493,8 @@
     ScopedRegistryUpdate update(registry_controller().AsWebAppSyncBridge());
     WebApp* app_to_update = update->UpdateApp(app_id);
     app_to_update->RemoveSource(source);
+    if (install_source_removed_callback_for_testing_)
+      install_source_removed_callback_for_testing_.Run(app_id);
 
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback),
@@ -505,7 +512,7 @@
 
   icon_manager_->WriteData(
       std::move(app_id), web_app_info.icon_bitmaps,
-      web_app_info.shortcuts_menu_icon_bitmaps,
+      web_app_info.shortcuts_menu_icon_bitmaps, web_app_info.other_icon_bitmaps,
       base::BindOnce(&WebAppInstallFinalizer::OnIconsDataWritten,
                      weak_ptr_factory_.GetWeakPtr(), std::move(commit_callback),
                      std::move(web_app)));
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.h b/chrome/browser/web_applications/web_app_install_finalizer.h
index 688bd6b..a494ceb 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.h
+++ b/chrome/browser/web_applications/web_app_install_finalizer.h
@@ -9,6 +9,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/callback_forward.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/web_applications/components/install_finalizer.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
@@ -67,6 +68,9 @@
   void Start() override;
   void Shutdown() override;
 
+  void SetRemoveSourceCallbackForTesting(
+      base::RepeatingCallback<void(const AppId&)>) override;
+
   Profile* profile() { return profile_; }
 
   WebAppRegistrar& GetWebAppRegistrar() const;
@@ -141,6 +145,9 @@
   base::flat_map<AppId, std::unique_ptr<SyncUninstallState>>
       pending_sync_uninstalls_;
 
+  base::RepeatingCallback<void(const AppId& app_id)>
+      install_source_removed_callback_for_testing_;
+
   std::unique_ptr<FileHandlersPermissionHelper> file_handlers_helper_;
 
   base::WeakPtrFactory<WebAppInstallFinalizer> weak_ptr_factory_{this};
diff --git a/chrome/browser/web_applications/web_app_install_manager_unittest.cc b/chrome/browser/web_applications/web_app_install_manager_unittest.cc
index e5bf048..65166ea6b 100644
--- a/chrome/browser/web_applications/web_app_install_manager_unittest.cc
+++ b/chrome/browser/web_applications/web_app_install_manager_unittest.cc
@@ -45,6 +45,7 @@
 #include "components/webapps/browser/installable/installable_metrics.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/manifest/display_mode.mojom-shared.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "url/gurl.h"
 
@@ -88,13 +89,16 @@
   return icons;
 }
 
-std::unique_ptr<blink::Manifest> ConvertWebAppToManifest(const WebApp& app) {
-  auto manifest = std::make_unique<blink::Manifest>();
+blink::mojom::ManifestPtr ConvertWebAppToManifest(const WebApp& app) {
+  auto manifest = blink::mojom::Manifest::New();
   manifest->start_url = app.start_url();
   manifest->scope = app.start_url();
   manifest->short_name = u"Short Name to be overriden.";
   manifest->name = base::UTF8ToUTF16(app.name());
-  manifest->theme_color = app.theme_color();
+  if (app.theme_color()) {
+    manifest->has_theme_color = true;
+    manifest->theme_color = *app.theme_color();
+  }
   manifest->display = app.display_mode();
   manifest->icons = ConvertWebAppIconsToImageResources(app);
   return manifest;
diff --git a/chrome/browser/web_applications/web_app_install_task.cc b/chrome/browser/web_applications/web_app_install_task.cc
index 1f25243..15a84c4 100644
--- a/chrome/browser/web_applications/web_app_install_task.cc
+++ b/chrome/browser/web_applications/web_app_install_task.cc
@@ -35,7 +35,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "url/gurl.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -443,7 +443,7 @@
 }
 
 void WebAppInstallTask::OnWebAppInstallabilityChecked(
-    absl::optional<blink::Manifest> manifest,
+    blink::mojom::ManifestPtr opt_manifest,
     const GURL& manifest_url,
     bool valid_manifest_for_web_app,
     bool is_installable) {
@@ -451,8 +451,8 @@
     return;
 
   if (is_installable) {
-    DCHECK(manifest);
-    CallInstallCallback(GenerateAppIdFromManifest(manifest.value()),
+    DCHECK(opt_manifest);
+    CallInstallCallback(GenerateAppIdFromManifest(*opt_manifest),
                         InstallResultCode::kSuccessNewInstall);
   } else {
     CallInstallCallback(AppId(), InstallResultCode::kNotInstallable);
@@ -520,7 +520,7 @@
 void WebAppInstallTask::OnDidPerformInstallableCheck(
     std::unique_ptr<WebApplicationInfo> web_app_info,
     bool force_shortcut_app,
-    absl::optional<blink::Manifest> manifest,
+    blink::mojom::ManifestPtr opt_manifest,
     const GURL& manifest_url,
     bool valid_manifest_for_web_app,
     bool is_installable) {
@@ -528,7 +528,6 @@
     return;
 
   DCHECK(web_app_info);
-  DCHECK(!manifest || !manifest->IsEmpty());
 
   if (install_params_ && install_params_->require_manifest &&
       !valid_manifest_for_web_app) {
@@ -542,8 +541,9 @@
                                         ? ForInstallableSite::kYes
                                         : ForInstallableSite::kNo;
 
-  if (manifest)
-    UpdateWebAppInfoFromManifest(*manifest, manifest_url, web_app_info.get());
+  if (opt_manifest)
+    UpdateWebAppInfoFromManifest(*opt_manifest, manifest_url,
+                                 web_app_info.get());
 
   AppId app_id =
       GenerateAppId(web_app_info->manifest_id, web_app_info->start_url);
@@ -559,20 +559,20 @@
 
   // A system app should always have a manifest icon.
   if (install_source_ == webapps::WebappInstallSource::SYSTEM_DEFAULT) {
-    DCHECK(manifest);
-    DCHECK(!manifest->icons.empty());
+    DCHECK(opt_manifest);
+    DCHECK(!opt_manifest->icons.empty());
   }
 
   // If the manifest specified icons, don't use the page icons.
-  const bool skip_page_favicons = manifest && !manifest->icons.empty();
+  const bool skip_page_favicons = opt_manifest && !opt_manifest->icons.empty();
 
-  CheckForPlayStoreIntentOrGetIcons(manifest, std::move(web_app_info),
-                                    std::move(icon_urls), for_installable_site,
-                                    skip_page_favicons);
+  CheckForPlayStoreIntentOrGetIcons(
+      std::move(opt_manifest), std::move(web_app_info), std::move(icon_urls),
+      for_installable_site, skip_page_favicons);
 }
 
 void WebAppInstallTask::CheckForPlayStoreIntentOrGetIcons(
-    absl::optional<blink::Manifest> manifest,
+    blink::mojom::ManifestPtr opt_manifest,
     std::unique_ptr<WebApplicationInfo> web_app_info,
     std::vector<GURL> icon_urls,
     ForInstallableSite for_installable_site,
@@ -582,8 +582,8 @@
   // cannot be sent to the store.
   if (base::FeatureList::IsEnabled(features::kApkWebAppInstalls) &&
       for_installable_site == ForInstallableSite::kYes &&
-      !background_installation_ && manifest) {
-    for (const auto& application : manifest->related_applications) {
+      !background_installation_ && opt_manifest) {
+    for (const auto& application : opt_manifest->related_applications) {
       std::string id =
           base::UTF16ToUTF8(application.id.value_or(std::u16string()));
       if (!base::EqualsASCII(application.platform.value_or(std::u16string()),
diff --git a/chrome/browser/web_applications/web_app_install_task.h b/chrome/browser/web_applications/web_app_install_task.h
index 57d9981..a472761 100644
--- a/chrome/browser/web_applications/web_app_install_task.h
+++ b/chrome/browser/web_applications/web_app_install_task.h
@@ -21,15 +21,12 @@
 #include "components/webapps/browser/installable/installable_metrics.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 
 class GURL;
 class Profile;
 struct WebApplicationInfo;
 
-namespace blink {
-struct Manifest;
-}
-
 namespace content {
 class WebContents;
 }
@@ -183,11 +180,10 @@
   void OnWebAppUrlLoadedCheckAndRetrieveManifest(
       content::WebContents* web_contents,
       WebAppUrlLoader::Result result);
-  void OnWebAppInstallabilityChecked(
-      absl::optional<blink::Manifest> opt_manifest,
-      const GURL& manifest_url,
-      bool valid_manifest_for_web_app,
-      bool is_installable);
+  void OnWebAppInstallabilityChecked(blink::mojom::ManifestPtr opt_manifest,
+                                     const GURL& manifest_url,
+                                     bool valid_manifest_for_web_app,
+                                     bool is_installable);
 
   void OnGetWebApplicationInfo(
       bool force_shortcut_app,
@@ -202,7 +198,7 @@
   void OnDidPerformInstallableCheck(
       std::unique_ptr<WebApplicationInfo> web_app_info,
       bool force_shortcut_app,
-      absl::optional<blink::Manifest> opt_manifest,
+      blink::mojom::ManifestPtr opt_manifest,
       const GURL& manifest_url,
       bool valid_manifest_for_web_app,
       bool is_installable);
@@ -212,7 +208,7 @@
   // synchronously calls OnDidCheckForIntentToPlayStore() implicitly failing the
   // check if it cannot be made.
   void CheckForPlayStoreIntentOrGetIcons(
-      absl::optional<blink::Manifest> opt_manifest,
+      blink::mojom::ManifestPtr opt_manifest,
       std::unique_ptr<WebApplicationInfo> web_app_info,
       std::vector<GURL> icon_urls,
       ForInstallableSite for_installable_site,
diff --git a/chrome/browser/web_applications/web_app_install_task_unittest.cc b/chrome/browser/web_applications/web_app_install_task_unittest.cc
index 528fda1..93677dbb 100644
--- a/chrome/browser/web_applications/web_app_install_task_unittest.cc
+++ b/chrome/browser/web_applications/web_app_install_task_unittest.cc
@@ -56,7 +56,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/features.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "url/gurl.h"
@@ -218,7 +218,7 @@
     data_retriever_->SetRendererWebApplicationInfo(
         std::move(renderer_web_app_info));
 
-    auto manifest = std::make_unique<blink::Manifest>();
+    auto manifest = blink::mojom::Manifest::New();
     manifest->start_url = url;
     manifest->short_name = u"Manifest Name";
     data_retriever_->SetManifest(std::move(manifest), /*is_installable=*/true);
@@ -409,7 +409,7 @@
                         theme_color,
                         /*open_as_window*/ true);
   {
-    auto manifest = std::make_unique<blink::Manifest>();
+    auto manifest = blink::mojom::Manifest::New();
     manifest->start_url = url;
     manifest->scope = scope;
     manifest->short_name = base::ASCIIToUTF16(manifest_name);
@@ -462,7 +462,7 @@
   // Force reinstall:
   CreateRendererAppInfo(url, "Renderer Name2", "Renderer Description2");
   {
-    auto manifest = std::make_unique<blink::Manifest>();
+    auto manifest = blink::mojom::Manifest::New();
     manifest->start_url = url;
     manifest->scope = url;
     manifest->short_name = u"Manifest Name2";
@@ -557,16 +557,17 @@
       GenerateAppId(/*manifest_id=*/absl::nullopt, manifest_start_url);
   const std::string manifest_name = "Name from Manifest";
   const GURL manifest_scope = GURL("https://example.com/scope");
-  const absl::optional<SkColor> manifest_theme_color = 0xAABBCCDD;
+  const SkColor manifest_theme_color = 0xAABBCCDD;
   const absl::optional<SkColor> expected_theme_color = 0xFFBBCCDD;  // Opaque.
   const auto display_mode = DisplayMode::kMinimalUi;
 
   {
-    auto manifest = std::make_unique<blink::Manifest>();
+    auto manifest = blink::mojom::Manifest::New();
     manifest->short_name = u"Short Name from Manifest";
     manifest->name = base::ASCIIToUTF16(manifest_name);
     manifest->start_url = manifest_start_url;
     manifest->scope = manifest_scope;
+    manifest->has_theme_color = true;
     manifest->theme_color = manifest_theme_color;
     manifest->display = display_mode;
 
@@ -696,7 +697,7 @@
 
   // Prepare all the data to be fetched or downloaded.
   {
-    auto manifest = std::make_unique<blink::Manifest>();
+    auto manifest = blink::mojom::Manifest::New();
     manifest->start_url = url;
     manifest->short_name = u"Manifest Name";
 
@@ -893,7 +894,7 @@
   const GURL url = GURL("https://example.com/path");
   const AppId app_id = GenerateAppId(/*manifest_id=*/absl::nullopt, url);
 
-  auto manifest = std::make_unique<blink::Manifest>();
+  auto manifest = blink::mojom::Manifest::New();
   manifest->start_url = url;
   manifest->short_name = u"Server Name";
 
@@ -1034,7 +1035,7 @@
   CreateRendererAppInfo(url, name, description, /*scope*/ GURL{}, theme_color,
                         /*open_as_window*/ true);
   {
-    auto manifest = std::make_unique<blink::Manifest>();
+    auto manifest = blink::mojom::Manifest::New();
     manifest->start_url = url;
     manifest->scope = scope;
     blink::Manifest::RelatedApplication related_app;
@@ -1270,7 +1271,7 @@
   const AppId app_id =
       GenerateAppId(/*manifest_id=*/absl::nullopt, manifest_start_url);
 
-  auto manifest = std::make_unique<blink::Manifest>();
+  auto manifest = blink::mojom::Manifest::New();
   manifest->short_name = u"Short Name from Manifest";
   manifest->name = u"Name from Manifest";
   manifest->start_url = GURL("https://example.com/start");
@@ -1420,8 +1421,9 @@
       SquareSizePx icon_size,
       GURL icon_src) {
     InstallResult result;
-    auto manifest = std::make_unique<blink::Manifest>();
+    auto manifest = blink::mojom::Manifest::New();
     manifest->start_url = start_url;
+    manifest->has_theme_color = true;
     manifest->theme_color = theme_color;
     manifest->name = u"Manifest Name";
 
@@ -1682,8 +1684,8 @@
                                        ContentSetting::CONTENT_SETTING_ALLOW);
   }
 
-  std::unique_ptr<blink::Manifest> CreateManifest(const GURL& url) {
-    auto manifest = std::make_unique<blink::Manifest>();
+  blink::mojom::ManifestPtr CreateManifest(const GURL& url) {
+    auto manifest = blink::mojom::Manifest::New();
     manifest->start_url = url;
     manifest->name = u"Manifest Name";
     return manifest;
@@ -1699,17 +1701,16 @@
   }
 
   void AddFileHandler(
-      std::vector<blink::Manifest::FileHandler>* file_handlers) {
-    blink::Manifest::FileHandler file_handler;
-    file_handler.action = GURL("https://example.com/action");
-    file_handler.name = u"Test handler";
-    file_handler.accept[u"application/pdf"].emplace_back(u".pdf");
-    file_handlers->emplace_back(file_handler);
+      std::vector<blink::mojom::ManifestFileHandlerPtr>* file_handlers) {
+    auto file_handler = blink::mojom::ManifestFileHandler::New();
+    file_handler->action = GURL("https://example.com/action");
+    file_handler->name = u"Test handler";
+    file_handler->accept[u"application/pdf"].emplace_back(u".pdf");
+    file_handlers->push_back(std::move(file_handler));
   }
 
-  InstallResult InstallWebAppFromManifest(
-      std::unique_ptr<blink::Manifest> manifest,
-      webapps::WebappInstallSource source) {
+  InstallResult InstallWebAppFromManifest(blink::mojom::ManifestPtr manifest,
+                                          webapps::WebappInstallSource source) {
     data_retriever_->SetManifest(std::move(manifest), /*is_installable=*/true);
 
     base::RunLoop run_loop;
@@ -1812,7 +1813,7 @@
 
   // Update the app, adding a file handler.
   auto app_info = CreateWebApplicationInfo(url);
-  std::vector<blink::Manifest::FileHandler> file_handlers;
+  std::vector<blink::mojom::ManifestFileHandlerPtr> file_handlers;
   AddFileHandler(&file_handlers);
   app_info->file_handlers = CreateFileHandlersFromManifest(file_handlers, url);
 
@@ -1844,7 +1845,7 @@
 
   // Update the app, adding a file handler.
   auto app_info = CreateWebApplicationInfo(url);
-  std::vector<blink::Manifest::FileHandler> file_handlers;
+  std::vector<blink::mojom::ManifestFileHandlerPtr> file_handlers;
   AddFileHandler(&file_handlers);
   app_info->file_handlers = CreateFileHandlersFromManifest(file_handlers, url);
 
diff --git a/chrome/browser/web_applications/web_app_installation_utils.cc b/chrome/browser/web_applications/web_app_installation_utils.cc
index 2e3c7eb..dd74e22 100644
--- a/chrome/browser/web_applications/web_app_installation_utils.cc
+++ b/chrome/browser/web_applications/web_app_installation_utils.cc
@@ -24,7 +24,6 @@
 #include "components/services/app_service/public/cpp/share_target.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/features.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkColor.h"
 
diff --git a/chrome/browser/web_applications/web_app_installation_utils_unittest.cc b/chrome/browser/web_applications/web_app_installation_utils_unittest.cc
index 58c65577..80420c9 100644
--- a/chrome/browser/web_applications/web_app_installation_utils_unittest.cc
+++ b/chrome/browser/web_applications/web_app_installation_utils_unittest.cc
@@ -17,7 +17,6 @@
 #include "components/services/app_service/public/cpp/share_target.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
 #include "url/gurl.h"
 
 using base::UTF8ToUTF16;
diff --git a/chrome/browser/web_applications/web_app_notifications_interactive_uitest.cc b/chrome/browser/web_applications/web_app_notifications_interactive_uitest.cc
index f2e05aa..61f99393f 100644
--- a/chrome/browser/web_applications/web_app_notifications_interactive_uitest.cc
+++ b/chrome/browser/web_applications/web_app_notifications_interactive_uitest.cc
@@ -131,20 +131,50 @@
   EXPECT_TRUE(RequestAndAcceptPermission());
 
   EXPECT_TRUE(AwaitScript("awaitServiceWorkerActivation()").ExtractBool());
-  EXPECT_TRUE(AwaitScript("displayPersistentNotification()").ExtractBool());
 
-  std::vector<message_center::Notification> notifications =
-      GetDisplayedNotifications(/*is_persistent=*/true);
-  ASSERT_EQ(1u, notifications.size());
+  {
+    EXPECT_TRUE(AwaitScript("displayPersistentNotification()").ExtractBool());
 
-  const message_center::Notification& notification = notifications[0];
+    std::vector<message_center::Notification> notifications =
+        GetDisplayedNotifications(/*is_persistent=*/true);
+    ASSERT_EQ(1u, notifications.size());
 
-  EXPECT_EQ(u"Notification Title", notification.title());
+    const message_center::Notification& notification = notifications[0];
 
-  ASSERT_FALSE(notification.notifier_id().title.has_value());
-  EXPECT_TRUE(notification.small_image().IsEmpty());
+    EXPECT_EQ(u"Notification Title", notification.title());
 
-  EXPECT_TRUE(AwaitScript("closeAllPersistentNotifications()").ExtractBool());
+    ASSERT_FALSE(notification.notifier_id().title.has_value());
+    EXPECT_TRUE(notification.small_image().IsEmpty());
+
+    EXPECT_TRUE(AwaitScript("closeAllPersistentNotifications()").ExtractBool());
+  }
+
+  {
+    EXPECT_TRUE(
+        AwaitScript("displayPersistentNotificationWithBadge()").ExtractBool());
+
+    std::vector<message_center::Notification> notifications =
+        GetDisplayedNotifications(/*is_persistent=*/true);
+    ASSERT_EQ(1u, notifications.size());
+
+    const message_center::Notification& notification = notifications[0];
+
+    EXPECT_EQ(u"Notification With Badge", notification.title());
+
+    EXPECT_FALSE(notification.notifier_id().title.has_value());
+
+    // small_image() here is chrome/test/data/web_app_notifications/blue-32.png.
+    ASSERT_FALSE(notification.small_image().IsEmpty());
+    const SkBitmap badge_from_js = *notification.small_image().ToSkBitmap();
+
+    EXPECT_EQ(32, badge_from_js.width());
+    EXPECT_EQ(32, badge_from_js.height());
+
+    EXPECT_EQ(color_utils::SkColorToRgbaString(SK_ColorBLUE),
+              color_utils::SkColorToRgbaString(badge_from_js.getColor(8, 8)));
+
+    EXPECT_TRUE(AwaitScript("closeAllPersistentNotifications()").ExtractBool());
+  }
 }
 
 IN_PROC_BROWSER_TEST_F(WebAppNotificationsBrowserTest_IconAndTitleEnabled,
@@ -159,35 +189,69 @@
   EXPECT_TRUE(RequestAndAcceptPermission());
 
   EXPECT_TRUE(AwaitScript("awaitServiceWorkerActivation()").ExtractBool());
-  EXPECT_TRUE(AwaitScript("displayPersistentNotification()").ExtractBool());
 
-  std::vector<message_center::Notification> notifications =
-      GetDisplayedNotifications(/*is_persistent=*/true);
-  ASSERT_EQ(1u, notifications.size());
+  {
+    EXPECT_TRUE(AwaitScript("displayPersistentNotification()").ExtractBool());
 
-  const message_center::Notification& notification = notifications[0];
+    std::vector<message_center::Notification> notifications =
+        GetDisplayedNotifications(/*is_persistent=*/true);
+    ASSERT_EQ(1u, notifications.size());
 
-  EXPECT_EQ(u"Notification Title", notification.title());
+    const message_center::Notification& notification = notifications[0];
 
-  ASSERT_TRUE(notification.notifier_id().title.has_value());
-  EXPECT_EQ(u"Web App Notifications Test",
-            notification.notifier_id().title.value());
+    EXPECT_EQ(u"Notification Title", notification.title());
 
-  ASSERT_FALSE(notification.small_image().IsEmpty());
-  const SkBitmap monochrome_badge = *notification.small_image().ToSkBitmap();
+    ASSERT_TRUE(notification.notifier_id().title.has_value());
+    EXPECT_EQ(u"Web App Notifications Test",
+              notification.notifier_id().title.value());
 
-  EXPECT_EQ(16, monochrome_badge.width());
-  EXPECT_EQ(16, monochrome_badge.height());
+    ASSERT_FALSE(notification.small_image().IsEmpty());
+    const SkBitmap monochrome_badge = *notification.small_image().ToSkBitmap();
 
-  // the center of web_app_notifications/monochrome-32.png is transparent.
-  EXPECT_EQ(color_utils::SkColorToRgbaString(SK_ColorTRANSPARENT),
-            color_utils::SkColorToRgbaString(monochrome_badge.getColor(8, 8)));
+    EXPECT_EQ(16, monochrome_badge.width());
+    EXPECT_EQ(16, monochrome_badge.height());
 
-  // theme_color in web_app_notifications/manifest.json is red.
-  EXPECT_EQ(color_utils::SkColorToRgbaString(SK_ColorRED),
-            color_utils::SkColorToRgbaString(monochrome_badge.getColor(0, 0)));
+    // the center of web_app_notifications/monochrome-32.png is transparent.
+    EXPECT_EQ(
+        color_utils::SkColorToRgbaString(SK_ColorTRANSPARENT),
+        color_utils::SkColorToRgbaString(monochrome_badge.getColor(8, 8)));
 
-  EXPECT_TRUE(AwaitScript("closeAllPersistentNotifications()").ExtractBool());
+    // theme_color in web_app_notifications/manifest.json is red.
+    EXPECT_EQ(
+        color_utils::SkColorToRgbaString(SK_ColorRED),
+        color_utils::SkColorToRgbaString(monochrome_badge.getColor(0, 0)));
+
+    EXPECT_TRUE(AwaitScript("closeAllPersistentNotifications()").ExtractBool());
+  }
+
+  {
+    EXPECT_TRUE(
+        AwaitScript("displayPersistentNotificationWithBadge()").ExtractBool());
+
+    std::vector<message_center::Notification> notifications =
+        GetDisplayedNotifications(/*is_persistent=*/true);
+    ASSERT_EQ(1u, notifications.size());
+
+    const message_center::Notification& notification = notifications[0];
+
+    EXPECT_EQ(u"Notification With Badge", notification.title());
+
+    ASSERT_TRUE(notification.notifier_id().title.has_value());
+    EXPECT_EQ(u"Web App Notifications Test",
+              notification.notifier_id().title.value());
+
+    // small_image() here is chrome/test/data/web_app_notifications/blue-32.png.
+    ASSERT_FALSE(notification.small_image().IsEmpty());
+    const SkBitmap badge_from_js = *notification.small_image().ToSkBitmap();
+
+    EXPECT_EQ(32, badge_from_js.width());
+    EXPECT_EQ(32, badge_from_js.height());
+
+    EXPECT_EQ(color_utils::SkColorToRgbaString(SK_ColorBLUE),
+              color_utils::SkColorToRgbaString(badge_from_js.getColor(8, 8)));
+
+    EXPECT_TRUE(AwaitScript("closeAllPersistentNotifications()").ExtractBool());
+  }
 }
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/web_app_provider.cc b/chrome/browser/web_applications/web_app_provider.cc
index 8e927ae..8969389 100644
--- a/chrome/browser/web_applications/web_app_provider.cc
+++ b/chrome/browser/web_applications/web_app_provider.cc
@@ -174,7 +174,7 @@
   return *audio_focus_id_map_;
 }
 
-AppIconManager& WebAppProvider::icon_manager() {
+WebAppIconManager& WebAppProvider::icon_manager() {
   CheckIsConnected();
   return *icon_manager_;
 }
diff --git a/chrome/browser/web_applications/web_app_provider.h b/chrome/browser/web_applications/web_app_provider.h
index e238834..abb84df6 100644
--- a/chrome/browser/web_applications/web_app_provider.h
+++ b/chrome/browser/web_applications/web_app_provider.h
@@ -10,8 +10,8 @@
 
 #include "base/memory/weak_ptr.h"
 #include "base/one_shot_event.h"
-#include "chrome/browser/web_applications/components/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/components/web_app_id.h"
+#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "components/keyed_service/core/keyed_service.h"
 
@@ -29,7 +29,7 @@
 
 // Forward declarations of generalized interfaces.
 class AppRegistryController;
-class AppIconManager;
+class WebAppIconManager;
 class PreinstalledWebAppManager;
 class InstallFinalizer;
 class ManifestUpdateManager;
@@ -114,7 +114,7 @@
   WebAppAudioFocusIdMap& audio_focus_id_map();
 
   // Implements fetching of app icons.
-  AppIconManager& icon_manager();
+  WebAppIconManager& icon_manager();
 
   SystemWebAppManager& system_web_app_manager();
 
@@ -162,7 +162,7 @@
   std::unique_ptr<WebAppRegistrar> registrar_;
   std::unique_ptr<AppRegistryController> registry_controller_;
   std::unique_ptr<PreinstalledWebAppManager> preinstalled_web_app_manager_;
-  std::unique_ptr<AppIconManager> icon_manager_;
+  std::unique_ptr<WebAppIconManager> icon_manager_;
   std::unique_ptr<InstallFinalizer> install_finalizer_;
   std::unique_ptr<ManifestUpdateManager> manifest_update_manager_;
   std::unique_ptr<ExternallyManagedAppManager> externally_managed_app_manager_;
diff --git a/chrome/browser/web_applications/web_app_registrar.h b/chrome/browser/web_applications/web_app_registrar.h
index 7fdff06f..0002d748 100644
--- a/chrome/browser/web_applications/web_app_registrar.h
+++ b/chrome/browser/web_applications/web_app_registrar.h
@@ -142,7 +142,7 @@
   base::Time GetAppLastLaunchTime(const AppId& app_id) const;
   base::Time GetAppInstallTime(const AppId& app_id) const;
 
-  // Returns the "icons" field from the app manifest, use |AppIconManager| to
+  // Returns the "icons" field from the app manifest, use |WebAppIconManager| to
   // load icon bitmap data.
   std::vector<WebApplicationIconInfo> GetAppIconInfos(
       const AppId& app_id) const;
@@ -150,8 +150,8 @@
   // Represents which icon sizes we successfully downloaded from the IconInfos.
   SortedSizesPx GetAppDownloadedIconSizesAny(const AppId& app_id) const;
 
-  // Returns the "shortcuts" field from the app manifest, use |AppIconManager|
-  // to load shortcuts menu icons bitmaps data.
+  // Returns the "shortcuts" field from the app manifest, use
+  // |WebAppIconManager| to load shortcuts menu icons bitmaps data.
   std::vector<WebApplicationShortcutsMenuItemInfo> GetAppShortcutsMenuItemInfos(
       const AppId& app_id) const;
 
diff --git a/chrome/browser/web_applications/web_app_shortcut_manager.cc b/chrome/browser/web_applications/web_app_shortcut_manager.cc
index d07c925..edae550c 100644
--- a/chrome/browser/web_applications/web_app_shortcut_manager.cc
+++ b/chrome/browser/web_applications/web_app_shortcut_manager.cc
@@ -9,6 +9,8 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/files/file_path.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/no_destructor.h"
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/profiles/profile.h"
@@ -21,23 +23,251 @@
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/services/app_service/public/cpp/file_handler.h"
+#include "content/public/browser/browser_thread.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/image/image_skia_rep_default.h"
 
 namespace web_app {
 
+namespace {
+
+// UMA metric name for shortcuts creation result.
+constexpr const char* kCreationResultMetric =
+    "WebApp.Shortcuts.Creation.Result";
+
+// Result of shortcuts creation process.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class CreationResult {
+  kSuccess = 0,
+  kFailToCreateShortcut = 1,
+  kMaxValue = kFailToCreateShortcut
+};
+
+WebAppShortcutManager::ShortcutCallback& GetShortcutUpdateCallbackForTesting() {
+  static base::NoDestructor<WebAppShortcutManager::ShortcutCallback> callback;
+  return *callback;
+}
+
+}  // namespace
+
 WebAppShortcutManager::WebAppShortcutManager(
     Profile* profile,
     WebAppIconManager* icon_manager,
     FileHandlerManager* file_handler_manager,
     ProtocolHandlerManager* protocol_handler_manager)
-    : AppShortcutManager(profile),
+    : profile_(profile),
       icon_manager_(icon_manager),
       file_handler_manager_(file_handler_manager),
       protocol_handler_manager_(protocol_handler_manager) {}
 
 WebAppShortcutManager::~WebAppShortcutManager() = default;
 
+void WebAppShortcutManager::SetSubsystems(WebAppIconManager* icon_manager,
+                                          WebAppRegistrar* registrar) {
+  icon_manager_ = icon_manager;
+  registrar_ = registrar;
+}
+
+void WebAppShortcutManager::UpdateShortcuts(const AppId& app_id,
+                                            base::StringPiece old_name) {
+  if (!CanCreateShortcuts())
+    return;
+
+  GetShortcutInfoForApp(
+      app_id,
+      base::BindOnce(
+          &WebAppShortcutManager::OnShortcutInfoRetrievedUpdateShortcuts,
+          weak_ptr_factory_.GetWeakPtr(), base::UTF8ToUTF16(old_name)));
+}
+
+void WebAppShortcutManager::GetAppExistingShortCutLocation(
+    ShortcutLocationCallback callback,
+    std::unique_ptr<ShortcutInfo> shortcut_info) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  // Ownership of |shortcut_info| moves to the Reply, which is guaranteed to
+  // outlive the const reference.
+  const ShortcutInfo& shortcut_info_ref = *shortcut_info;
+  internals::GetShortcutIOTaskRunner()->PostTaskAndReplyWithResult(
+      FROM_HERE,
+      base::BindOnce(&internals::GetAppExistingShortCutLocationImpl,
+                     std::cref(shortcut_info_ref)),
+      base::BindOnce(
+          [](std::unique_ptr<ShortcutInfo> shortcut_info,
+             ShortcutLocationCallback callback, ShortcutLocations locations) {
+            DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+            shortcut_info.reset();
+            std::move(callback).Run(locations);
+          },
+          std::move(shortcut_info), std::move(callback)));
+}
+
+void WebAppShortcutManager::SetShortcutUpdateCallbackForTesting(
+    base::OnceCallback<void(const ShortcutInfo*)> callback) {
+  GetShortcutUpdateCallbackForTesting() = std::move(callback);  // IN-TEST
+}
+
+bool WebAppShortcutManager::CanCreateShortcuts() const {
+#if defined(OS_CHROMEOS)
+  return false;
+#else
+  return true;
+#endif
+}
+
+void WebAppShortcutManager::SuppressShortcutsForTesting() {
+  suppress_shortcuts_for_testing_ = true;
+}
+
+void WebAppShortcutManager::CreateShortcuts(const AppId& app_id,
+                                            bool add_to_desktop,
+                                            CreateShortcutsCallback callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK(CanCreateShortcuts());
+
+  GetShortcutInfoForApp(
+      app_id,
+      base::BindOnce(
+          &WebAppShortcutManager::OnShortcutInfoRetrievedCreateShortcuts,
+          weak_ptr_factory_.GetWeakPtr(), add_to_desktop,
+          base::BindOnce(&WebAppShortcutManager::OnShortcutsCreated,
+                         weak_ptr_factory_.GetWeakPtr(), app_id,
+                         std::move(callback))));
+}
+
+void WebAppShortcutManager::DeleteShortcuts(
+    const AppId& app_id,
+    const base::FilePath& shortcuts_data_dir,
+    std::unique_ptr<ShortcutInfo> shortcut_info,
+    DeleteShortcutsCallback callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK(CanCreateShortcuts());
+
+  internals::ScheduleDeletePlatformShortcuts(
+      shortcuts_data_dir, std::move(shortcut_info),
+      base::BindOnce(&WebAppShortcutManager::OnShortcutsDeleted,
+                     weak_ptr_factory_.GetWeakPtr(), app_id,
+                     std::move(callback)));
+}
+
+void WebAppShortcutManager::ReadAllShortcutsMenuIconsAndRegisterShortcutsMenu(
+    const AppId& app_id,
+    RegisterShortcutsMenuCallback callback) {
+  icon_manager_->ReadAllShortcutsMenuIcons(
+      app_id,
+      base::BindOnce(
+          &WebAppShortcutManager::OnShortcutsMenuIconsReadRegisterShortcutsMenu,
+          weak_ptr_factory_.GetWeakPtr(), app_id, std::move(callback)));
+}
+
+void WebAppShortcutManager::RegisterShortcutsMenuWithOs(
+    const AppId& app_id,
+    const std::vector<WebApplicationShortcutsMenuItemInfo>&
+        shortcuts_menu_item_infos,
+    const ShortcutsMenuIconBitmaps& shortcuts_menu_icon_bitmaps) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  if (!web_app::ShouldRegisterShortcutsMenuWithOs() ||
+      suppress_shortcuts_for_testing_) {
+    return;
+  }
+
+  std::unique_ptr<ShortcutInfo> shortcut_info = BuildShortcutInfo(app_id);
+  if (!shortcut_info)
+    return;
+
+  // |shortcut_data_dir| is located in per-app OS integration resources
+  // directory. See GetOsIntegrationResourcesDirectoryForApp function for more
+  // info.
+  base::FilePath shortcut_data_dir =
+      internals::GetShortcutDataDir(*shortcut_info);
+  web_app::RegisterShortcutsMenuWithOs(
+      shortcut_info->extension_id, shortcut_info->profile_path,
+      shortcut_data_dir, shortcuts_menu_item_infos,
+      shortcuts_menu_icon_bitmaps);
+}
+
+void WebAppShortcutManager::UnregisterShortcutsMenuWithOs(const AppId& app_id) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  if (!web_app::ShouldRegisterShortcutsMenuWithOs())
+    return;
+
+  web_app::UnregisterShortcutsMenuWithOs(app_id, profile_->GetPath());
+}
+
+void WebAppShortcutManager::OnShortcutsCreated(const AppId& app_id,
+                                               CreateShortcutsCallback callback,
+                                               bool success) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  UMA_HISTOGRAM_ENUMERATION(kCreationResultMetric,
+                            success ? CreationResult::kSuccess
+                                    : CreationResult::kFailToCreateShortcut);
+  std::move(callback).Run(success);
+}
+
+void WebAppShortcutManager::OnShortcutsDeleted(const AppId& app_id,
+                                               DeleteShortcutsCallback callback,
+                                               bool success) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  std::move(callback).Run(success);
+}
+
+void WebAppShortcutManager::OnShortcutInfoRetrievedCreateShortcuts(
+    bool add_to_desktop,
+    CreateShortcutsCallback callback,
+    std::unique_ptr<ShortcutInfo> info) {
+  if (suppress_shortcuts_for_testing_) {
+    std::move(callback).Run(/*shortcut_created=*/true);
+    return;
+  }
+
+  if (info == nullptr) {
+    std::move(callback).Run(/*shortcut_created=*/false);
+    return;
+  }
+
+  base::FilePath shortcut_data_dir = internals::GetShortcutDataDir(*info);
+
+  ShortcutLocations locations;
+  locations.on_desktop = add_to_desktop;
+  locations.applications_menu_location = APP_MENU_LOCATION_SUBDIR_CHROMEAPPS;
+
+  internals::ScheduleCreatePlatformShortcuts(
+      std::move(shortcut_data_dir), locations, SHORTCUT_CREATION_BY_USER,
+      std::move(info), std::move(callback));
+}
+
+void WebAppShortcutManager::OnShortcutsMenuIconsReadRegisterShortcutsMenu(
+    const AppId& app_id,
+    RegisterShortcutsMenuCallback callback,
+    ShortcutsMenuIconBitmaps shortcuts_menu_icon_bitmaps) {
+  std::vector<WebApplicationShortcutsMenuItemInfo> shortcuts_menu_item_infos =
+      registrar_->GetAppShortcutsMenuItemInfos(app_id);
+  if (!shortcuts_menu_item_infos.empty()) {
+    RegisterShortcutsMenuWithOs(app_id, shortcuts_menu_item_infos,
+                                shortcuts_menu_icon_bitmaps);
+  }
+
+  std::move(callback).Run(/*shortcuts_menu_registered=*/true);
+}
+
+void WebAppShortcutManager::OnShortcutInfoRetrievedUpdateShortcuts(
+    std::u16string old_name,
+    std::unique_ptr<ShortcutInfo> shortcut_info) {
+  if (GetShortcutUpdateCallbackForTesting())
+    std::move(GetShortcutUpdateCallbackForTesting()).Run(shortcut_info.get());
+
+  if (suppress_shortcuts_for_testing_ || !shortcut_info)
+    return;
+
+  base::FilePath shortcut_data_dir =
+      internals::GetShortcutDataDir(*shortcut_info);
+  internals::PostShortcutIOTask(
+      base::BindOnce(&internals::UpdatePlatformShortcuts,
+                     std::move(shortcut_data_dir), std::move(old_name)),
+      std::move(shortcut_info));
+}
+
 std::unique_ptr<ShortcutInfo> WebAppShortcutManager::BuildShortcutInfo(
     const AppId& app_id) {
   const WebApp* app = GetWebAppRegistrar().GetAppById(app_id);
@@ -119,9 +349,9 @@
   shortcut_info->url = app->start_url();
   shortcut_info->title = base::UTF8ToUTF16(app->name());
   shortcut_info->description = base::UTF8ToUTF16(app->description());
-  shortcut_info->profile_path = profile()->GetPath();
+  shortcut_info->profile_path = profile_->GetPath();
   shortcut_info->profile_name =
-      profile()->GetPrefs()->GetString(prefs::kProfileName);
+      profile_->GetPrefs()->GetString(prefs::kProfileName);
   shortcut_info->is_multi_profile = true;
 
   if (const apps::FileHandlers* file_handlers =
@@ -144,7 +374,7 @@
 }
 
 WebAppRegistrar& WebAppShortcutManager::GetWebAppRegistrar() {
-  WebAppRegistrar* web_app_registrar = registrar()->AsWebAppRegistrar();
+  WebAppRegistrar* web_app_registrar = registrar_->AsWebAppRegistrar();
   DCHECK(web_app_registrar);
   return *web_app_registrar;
 }
diff --git a/chrome/browser/web_applications/web_app_shortcut_manager.h b/chrome/browser/web_applications/web_app_shortcut_manager.h
index 2f26aaa..2e8cc4c4 100644
--- a/chrome/browser/web_applications/web_app_shortcut_manager.h
+++ b/chrome/browser/web_applications/web_app_shortcut_manager.h
@@ -9,7 +9,8 @@
 #include <memory>
 
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/web_applications/components/app_shortcut_manager.h"
+#include "chrome/browser/web_applications/components/web_app_shortcut.h"
+#include "chrome/browser/web_applications/components/web_app_shortcuts_menu.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
 #include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "third_party/skia/include/core/SkBitmap.h"
@@ -25,7 +26,16 @@
 class WebAppRegistrar;
 struct ShortcutInfo;
 
-class WebAppShortcutManager : public AppShortcutManager {
+using ShortcutLocationCallback =
+    base::OnceCallback<void(ShortcutLocations shortcut_locations)>;
+
+// This class manages creation/update/deletion of OS shortcuts for web
+// applications.
+//
+// TODO(crbug.com/860581): Migrate functions from
+// web_app_extension_shortcut.(h|cc) and
+// platform_apps/shortcut_manager.(h|cc) to WebAppShortcutManager.
+class WebAppShortcutManager {
  public:
   WebAppShortcutManager(Profile* profile,
                         WebAppIconManager* icon_manager,
@@ -33,25 +43,118 @@
                         ProtocolHandlerManager* protocol_handler_manager);
   WebAppShortcutManager(const WebAppShortcutManager&) = delete;
   WebAppShortcutManager& operator=(const WebAppShortcutManager&) = delete;
-  ~WebAppShortcutManager() override;
+  virtual ~WebAppShortcutManager();
 
-  // AppShortcutManager:
-  std::unique_ptr<ShortcutInfo> BuildShortcutInfo(const AppId& app_id) override;
-  void GetShortcutInfoForApp(const AppId& app_id,
-                             GetShortcutInfoCallback callback) override;
+  void SetSubsystems(WebAppIconManager* icon_manager,
+                     WebAppRegistrar* registrar);
+
+  void Start();
+  void Shutdown();
+
+  // Tells the WebAppShortcutManager that no shortcuts should actually be
+  // written to the disk.
+  void SuppressShortcutsForTesting();
+
+  bool CanCreateShortcuts() const;
+  void CreateShortcuts(const AppId& app_id,
+                       bool add_to_desktop,
+                       CreateShortcutsCallback callback);
+  // Fetch already-updated shortcut data and deploy to OS integration.
+  void UpdateShortcuts(const AppId& app_id, base::StringPiece old_name);
+  void DeleteShortcuts(const AppId& app_id,
+                       const base::FilePath& shortcuts_data_dir,
+                       std::unique_ptr<ShortcutInfo> shortcut_info,
+                       DeleteShortcutsCallback callback);
+
+  // Posts a task on the IO thread to gather existing shortcut locations
+  // according to |shortcut_info|. The result will be passed into |callback|.
+  // virtual for testing.
+  virtual void GetAppExistingShortCutLocation(
+      ShortcutLocationCallback callback,
+      std::unique_ptr<ShortcutInfo> shortcut_info);
+
+  // TODO(crbug.com/1098471): Move this into web_app_shortcuts_menu_win.cc when
+  // a callback is integrated into the Shortcuts Menu registration flow.
+  using RegisterShortcutsMenuCallback = base::OnceCallback<void(bool success)>;
+  // Registers a shortcuts menu for a web app after reading its shortcuts menu
+  // icons from disk.
+  //
+  // TODO(crbug.com/1098471): Consider unifying this method and
+  // RegisterShortcutsMenuWithOs() below.
+  void ReadAllShortcutsMenuIconsAndRegisterShortcutsMenu(
+      const AppId& app_id,
+      RegisterShortcutsMenuCallback callback);
+
+  // Registers a shortcuts menu for the web app's icon with the OS.
+  //
+  // TODO(crbug.com/1098471): Add a callback as part of the Shortcuts Menu
+  // registration flow.
+  void RegisterShortcutsMenuWithOs(
+      const AppId& app_id,
+      const std::vector<WebApplicationShortcutsMenuItemInfo>&
+          shortcuts_menu_item_infos,
+      const ShortcutsMenuIconBitmaps& shortcuts_menu_icon_bitmaps);
+
+  void UnregisterShortcutsMenuWithOs(const AppId& app_id);
+
+  // Builds initial ShortcutInfo without |ShortcutInfo::favicon| being read.
+  // virtual for testing.
+  //
+  // TODO(crbug.com/1225132): Get rid of |BuildShortcutInfo| method: inline it
+  // or make it private.
+  virtual std::unique_ptr<ShortcutInfo> BuildShortcutInfo(const AppId& app_id);
+
+  // The result of a call to GetShortcutInfo.
+  using GetShortcutInfoCallback =
+      base::OnceCallback<void(std::unique_ptr<ShortcutInfo>)>;
+  // Asynchronously gets the information required to create a shortcut for
+  // |app_id| including all the icon bitmaps. Returns nullptr if app_id is
+  // uninstalled or becomes uninstalled during the asynchronous read of icons.
+  // virtual for testing.
+  virtual void GetShortcutInfoForApp(const AppId& app_id,
+                                     GetShortcutInfoCallback callback);
+
+  using ShortcutCallback = base::OnceCallback<void(const ShortcutInfo*)>;
+  static void SetShortcutUpdateCallbackForTesting(ShortcutCallback callback);
 
  private:
   void OnIconsRead(const AppId& app_id,
                    GetShortcutInfoCallback callback,
                    std::map<SquareSizePx, SkBitmap> icon_bitmaps);
 
+  void OnShortcutsCreated(const AppId& app_id,
+                          CreateShortcutsCallback callback,
+                          bool success);
+  void OnShortcutsDeleted(const AppId& app_id,
+                          DeleteShortcutsCallback callback,
+                          bool success);
+
+  void OnShortcutInfoRetrievedCreateShortcuts(
+      bool add_to_desktop,
+      CreateShortcutsCallback callback,
+      std::unique_ptr<ShortcutInfo> info);
+
+  void OnShortcutInfoRetrievedUpdateShortcuts(
+      std::u16string old_name,
+      std::unique_ptr<ShortcutInfo> info);
+
+  void OnShortcutsMenuIconsReadRegisterShortcutsMenu(
+      const AppId& app_id,
+      RegisterShortcutsMenuCallback callback,
+      ShortcutsMenuIconBitmaps shortcuts_menu_icon_bitmaps);
+
   std::unique_ptr<ShortcutInfo> BuildShortcutInfoForWebApp(const WebApp* app);
 
   WebAppRegistrar& GetWebAppRegistrar();
 
-  WebAppIconManager* icon_manager_;
-  FileHandlerManager* file_handler_manager_;
-  ProtocolHandlerManager* protocol_handler_manager_;
+  bool suppress_shortcuts_for_testing_ = false;
+
+  Profile* const profile_;
+
+  WebAppRegistrar* registrar_ = nullptr;
+  WebAppIconManager* icon_manager_ = nullptr;
+  FileHandlerManager* file_handler_manager_ = nullptr;
+  ProtocolHandlerManager* protocol_handler_manager_ = nullptr;
 
   base::WeakPtrFactory<WebAppShortcutManager> weak_ptr_factory_{this};
 };
diff --git a/chrome/common/extensions/api/autotest_private.idl b/chrome/common/extensions/api/autotest_private.idl
index 58d30d1..91bd2cd 100644
--- a/chrome/common/extensions/api/autotest_private.idl
+++ b/chrome/common/extensions/api/autotest_private.idl
@@ -973,9 +973,12 @@
     // Send WM event to change the app window's window state.
     // |id|: the id of the window
     // |change|: WM event type to send to the app window.
-    // |callback|: called when the window state is changed.
+    // |wait|: whether the method should wait for the window state to change before returning.
+    // |callback|: called when the window state is changed if |wait| is true.
+    //             Otherwise, called right after the WM event is sent.
     static void setAppWindowState(long id,
                                   WindowStateChangeDict change,
+                                  optional boolean wait,
                                   WindowStateCallback callback);
 
     // Activate app window given by "id".
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index abc645cc..724da0e 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -465,6 +465,13 @@
 // The list of extensions allowed to use the platformKeys API for remote
 // attestation.
 const char kAttestationExtensionAllowlist[] = "attestation.extension_allowlist";
+
+// The list of extensions allowed to skip print job confirmation dialog when
+// they use the chrome.printing.submitJob() function. Note that this used to be
+// `kPrintingAPIExtensionsWhitelist`, hence the difference between the variable
+// name and the string value.
+const char kPrintingAPIExtensionsAllowlist[] =
+    "printing.printing_api_extensions_whitelist";
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -1428,13 +1435,6 @@
 const char kPrintJobHistoryExpirationPeriod[] =
     "printing.print_job_history_expiration_period";
 
-// The list of extensions allowed to skip print job confirmation dialog when
-// they use the chrome.printing.submitJob() function. Note that this used to be
-// `kPrintingAPIExtensionsWhitelist`, hence the difference between the variable
-// name and the string value.
-const char kPrintingAPIExtensionsAllowlist[] =
-    "printing.printing_api_extensions_whitelist";
-
 // Boolean flag which represents whether the user's print job history can be
 // deleted.
 const char kDeletePrintJobHistoryAllowed[] =
@@ -2008,7 +2008,7 @@
 #if defined(OS_WIN)
 // Mapping of origin to their origin id (UnguessableToken). Origin IDs are only
 // stored for origins using MediaFoundation-based CDMs.
-const char kMediaCdmOrigin[] = "media.cdm.origins";
+const char kMediaCdmOriginData[] = "media.cdm.origin_data";
 #endif  // defined(OS_WIN)
 
 // The last used printer and its settings.
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 758749c..e57f2c4 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -208,6 +208,7 @@
 extern const char kHideWebStoreIcon[];
 #if defined(OS_CHROMEOS)
 extern const char kAttestationExtensionAllowlist[];
+extern const char kPrintingAPIExtensionsAllowlist[];
 #endif
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 extern const char kAccountManagerNumTimesMigrationRanSuccessfully[];
@@ -465,7 +466,6 @@
 extern const char kPrintingSendUsernameAndFilenameEnabled[];
 extern const char kPrintingMaxSheetsAllowed[];
 extern const char kPrintJobHistoryExpirationPeriod[];
-extern const char kPrintingAPIExtensionsAllowlist[];
 extern const char kDeletePrintJobHistoryAllowed[];
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
@@ -682,7 +682,7 @@
 extern const char kMediaDeviceIdSalt[];
 extern const char kMediaStorageIdSalt[];
 #if defined(OS_WIN)
-extern const char kMediaCdmOrigin[];
+extern const char kMediaCdmOriginData[];
 #endif  // defined(OS_WIN)
 
 extern const char kPrintPreviewStickySettings[];
diff --git a/chrome/services/speech/audio_source_fetcher_impl.cc b/chrome/services/speech/audio_source_fetcher_impl.cc
index 295c12a..ab53027 100644
--- a/chrome/services/speech/audio_source_fetcher_impl.cc
+++ b/chrome/services/speech/audio_source_fetcher_impl.cc
@@ -58,9 +58,12 @@
 
   device_id_ = device_id;
   audio_parameters_ = audio_parameters;
-  audio_capturer_source_ =
-      audio::CreateInputDevice(std::move(stream_factory), device_id_,
-                               audio::DeadStreamDetection::kEnabled);
+  auto audio_log_remote = VLOG_IS_ON(1)
+                              ? audio_log_receiver_.BindNewPipeAndPassRemote()
+                              : mojo::NullRemote();
+  audio_capturer_source_ = audio::CreateInputDevice(
+      std::move(stream_factory), device_id_,
+      audio::DeadStreamDetection::kEnabled, std::move(audio_log_remote));
   DCHECK(audio_capturer_source_);
 
   // TODO(crbug.com/1185978): Check implementation / sandbox policy on Mac and
@@ -120,4 +123,33 @@
                                           : audio_capturer_source_.get();
 }
 
+void AudioSourceFetcherImpl::OnCreated(const media::AudioParameters& params,
+                                       const std::string& device_id) {
+  VLOG(1) << "Created fetcher for device " << device_id << " with params "
+          << params.AsHumanReadableString();
+}
+
+void AudioSourceFetcherImpl::OnStarted() {
+  VLOG(1) << "OnStarted for " << device_id_;
+}
+void AudioSourceFetcherImpl::OnStopped() {
+  VLOG(1) << "OnStopped for " << device_id_;
+}
+void AudioSourceFetcherImpl::OnClosed() {
+  VLOG(1) << "OnClosed for " << device_id_;
+}
+void AudioSourceFetcherImpl::OnError() {
+  VLOG(1) << "OnError for " << device_id_;
+}
+void AudioSourceFetcherImpl::OnSetVolume(double volume) {
+  VLOG(1) << "Set volume for " << device_id_ << " to " << volume;
+}
+void AudioSourceFetcherImpl::OnLogMessage(const std::string& message) {
+  VLOG(1) << "Log Messages for " << device_id_ << ": " << message;
+}
+void AudioSourceFetcherImpl::OnProcessingStateChanged(
+    const std::string& message) {
+  VLOG(1) << "Processing State Changed for " << device_id_ << ": " << message;
+}
+
 }  // namespace speech
diff --git a/chrome/services/speech/audio_source_fetcher_impl.h b/chrome/services/speech/audio_source_fetcher_impl.h
index 406cd98..5690c87 100644
--- a/chrome/services/speech/audio_source_fetcher_impl.h
+++ b/chrome/services/speech/audio_source_fetcher_impl.h
@@ -8,6 +8,7 @@
 #include "base/memory/weak_ptr.h"
 #include "media/base/audio_capturer_source.h"
 #include "media/mojo/common/audio_data_s16_converter.h"
+#include "media/mojo/mojom/audio_logging.mojom.h"
 #include "media/mojo/mojom/speech_recognition_service.mojom.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -16,14 +17,15 @@
 
 class SpeechRecognitionRecognizerImpl;
 
-// Class to get microphone audio data and send it to a
+// Class to get device audio data and send it to a
 // SpeechRecognitionRecognizerImpl for transcription. Runs on the IO thread in
 // the Browser process in Chrome OS and in the Speech Recognition Service
 // utility process on Chrome or web speech fallback.
 class AudioSourceFetcherImpl
     : public media::mojom::AudioSourceFetcher,
       public media::AudioCapturerSource::CaptureCallback,
-      public media::AudioDataS16Converter {
+      public media::AudioDataS16Converter,
+      public media::mojom::AudioLog {
  public:
   AudioSourceFetcherImpl(
       std::unique_ptr<SpeechRecognitionRecognizerImpl> recognition_recognizer);
@@ -51,6 +53,16 @@
   void OnCaptureError(media::AudioCapturerSource::ErrorCode code,
                       const std::string& message) final;
   void OnCaptureMuted(bool is_muted) final {}
+  // media::mojom::AudioLog
+  void OnCreated(const media::AudioParameters& params,
+                 const std::string& device_id) override;
+  void OnStarted() override;
+  void OnStopped() override;
+  void OnClosed() override;
+  void OnError() override;
+  void OnSetVolume(double volume) override;
+  void OnLogMessage(const std::string& message) override;
+  void OnProcessingStateChanged(const std::string& message) override;
 
   void set_audio_capturer_source_for_tests(
       media::AudioCapturerSource* audio_capturer_source_for_tests) {
@@ -87,6 +99,8 @@
   // Whether audio capture is started.
   bool is_started_;
 
+  mojo::Receiver<media::mojom::AudioLog> audio_log_receiver_{this};
+
   base::WeakPtrFactory<AudioSourceFetcherImpl> weak_factory_{this};
 };
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 57f64f8..66dd1b6e 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -342,6 +342,10 @@
     ]
   }
 
+  if (is_chromeos_ash && also_build_lacros_chrome) {
+    data_deps += [ "//chrome:chrome(//build/toolchain/linux:lacros_clang_x64)" ]
+  }
+
   if (is_android) {
     public_deps += [
       ":test_support_ui_android",
@@ -3142,12 +3146,12 @@
         "../browser/ash/policy/handlers/device_system_wide_tracing_enabled_browsertest.cc",
         "../browser/ash/policy/handlers/minimum_version_policy_handler_browsertest.cc",
         "../browser/ash/policy/handlers/power_policy_browsertest.cc",
-        "../browser/ash/policy/handlers/restore_on_startup_browsertest_chromeos.cc",
+        "../browser/ash/policy/handlers/restore_on_startup_browsertest.cc",
         "../browser/ash/policy/handlers/site_isolation_flag_handling_browsertest.cc",
         "../browser/ash/policy/handlers/variations_service_policy_browsertest.cc",
         "../browser/ash/policy/login/blocking_login_browsertest.cc",
         "../browser/ash/policy/login/device_login_screen_policy_browsertest.cc",
-        "../browser/ash/policy/login/force_maximize_on_first_run_chromeos_browsertest.cc",
+        "../browser/ash/policy/login/force_maximize_on_first_run_browsertest.cc",
         "../browser/ash/policy/login/login_policy_test_base.cc",
         "../browser/ash/policy/login/login_policy_test_base.h",
         "../browser/ash/policy/login/login_screen_accessibility_policy_browsertest.cc",
@@ -5629,6 +5633,7 @@
       "../browser/profile_resetter/reset_report_uploader_unittest.cc",
       "../browser/renderer_context_menu/render_view_context_menu_unittest.cc",
       "../browser/search/background/ntp_background_service_unittest.cc",
+      "../browser/search/background/ntp_custom_background_service_unittest.cc",
       "../browser/search/chrome_colors/chrome_colors_service_unittest.cc",
       "../browser/search/instant_service_unittest.cc",
       "../browser/search/instant_unittest_base.cc",
@@ -8767,8 +8772,11 @@
       deps += [ "//ui/views" ]
     }
 
-    # This target should not require the Chrome executable to run.
-    assert_no_deps = [ "//chrome" ]
+    # This target should not require the Chrome executable to run
+    # except it wants to build lacros as a secondary toolchain.
+    if (!(is_chromeos_ash && also_build_lacros_chrome)) {
+      assert_no_deps = [ "//chrome" ]
+    }
   }
 
   # Executable to measure time to load libraries.
@@ -9462,13 +9470,20 @@
 }
 
 if (is_linux) {
-  group("variations_smoke_tests") {
-    testonly = true
+  script_test("variations_smoke_tests") {
+    run_under_python2 = true
+
+    script = "//testing/xvfb.py"
+    args = [ "@WrappedPath(" +
+             rebase_path("//testing/scripts/run_variations_smoke_tests.py",
+                         root_build_dir) + ")" ]
+
     data_deps = [
       "//chrome",
       "//chrome/test/chromedriver",
       "//testing:test_scripts_shared",
     ]
+
     data = [
       "//testing/scripts/run_variations_smoke_tests.py",
       "//testing/scripts/variations_smoke_test_data/",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java
index 2628f9e..8e4f2dd 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/ChromeActivityTestRule.java
@@ -481,6 +481,18 @@
      */
     @SuppressWarnings("unchecked")
     public static <T extends ChromeActivity> T waitFor(final Class<T> expectedClass) {
+        return waitFor(expectedClass, CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL);
+    }
+
+    /**
+     * Waits for an Activity of the given class to be started.
+     * @param expectedClass The class of the Activity being waited on.
+     * @param maxTimeToPoll Maximum time in milliseconds to poll.
+     * @return The Activity.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T extends ChromeActivity> T waitFor(
+            final Class<T> expectedClass, long maxTimeToPoll) {
         final Activity[] holder = new Activity[1];
         CriteriaHelper.pollUiThread(() -> {
             holder[0] = ApplicationStatus.getLastTrackedFocusedActivity();
@@ -488,7 +500,7 @@
             Criteria.checkThat(holder[0].getClass(), Matchers.typeCompatibleWith(expectedClass));
             Criteria.checkThat(
                     ((ChromeActivity) holder[0]).getActivityTab(), Matchers.notNullValue());
-        });
+        }, maxTimeToPoll, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
         return (T) holder[0];
     }
 
diff --git a/chrome/test/data/autofill/captured_sites/testcases.json b/chrome/test/data/autofill/captured_sites/testcases.json
index f339fe0..2347b735 100644
--- a/chrome/test/data/autofill/captured_sites/testcases.json
+++ b/chrome/test/data/autofill/captured_sites/testcases.json
@@ -180,7 +180,7 @@
     { "site_name": "sweet_water" },
     { "site_name": "talbots" },
     { "site_name": "tennis_warehouse" },
-    { "site_name": "therealreal" },
+    { "site_name": "therealreal", "bug_number":1236799 },
     { "site_name": "threadless" },
     { "site_name": "ticket_master" },
     { "site_name": "ticketmaster_uk" },
@@ -211,7 +211,7 @@
     { "site_name": "williams_sonoma" },
     { "site_name": "worldmarket", "disabled":true, "bug_number":1173040 },
     { "site_name": "zappos", "bug_number":1223946 },
-    { "site_name": "zenni_optical" },
+    { "site_name": "zenni_optical", "bug_number":1236798 },
     { "site_name": "zulily" }
   ]
 }
diff --git a/chrome/test/data/extensions/api_test/autotest_private/test.js b/chrome/test/data/extensions/api_test/autotest_private/test.js
index a061e5b9..04bd394e 100644
--- a/chrome/test/data/extensions/api_test/autotest_private/test.js
+++ b/chrome/test/data/extensions/api_test/autotest_private/test.js
@@ -733,6 +733,7 @@
         chrome.autotestPrivate.setAppWindowState(
             window.id,
             change,
+            true /* wait */,
             function(state) {
               chrome.test.assertEq(state, 'Fullscreen');
               chrome.autotestPrivate.getAppWindowList(async function(list) {
@@ -755,7 +756,8 @@
                   var revert_change = new Object();
                   revert_change.eventType = 'WMEventNormal';
                   chrome.autotestPrivate.setAppWindowState(
-                      window.id, revert_change, function(state) {
+                      window.id, revert_change, true /* wait */,
+                      function(state) {
                         chrome.test.assertEq(state, 'Normal');
                         chrome.test.assertNoLastError();
                         chrome.test.succeed();
@@ -783,24 +785,35 @@
         var change = new Object();
         change.eventType = 'WMEventFullscreen';
         chrome.autotestPrivate.setAppWindowState(
-            window.id, change, function(state) {
+            window.id, change, true /* wait */, function(state) {
               chrome.test.assertEq(state, 'Fullscreen');
 
-              chrome.autotestPrivate.setTabletModeEnabled(
-                  false, function(isEnabled) {
-                    chrome.test.assertFalse(isEnabled);
+              // Just send the rejectable request (normal state request in
+              // tablet mode) but without waiting for the state change.
+              const rejectable_change = {
+                eventType: 'WMEventNormal'
+              };
+              chrome.autotestPrivate.setAppWindowState(
+                  window.id, rejectable_change, false /* wait */,
+                  function(state) {
+                    chrome.autotestPrivate.setTabletModeEnabled(
+                        false, function(isEnabled) {
+                          chrome.test.assertFalse(isEnabled);
 
-                    // Revert window state back to normal and exit tablet mode
-                    // for the next test.
-                    var revert_change = new Object();
-                    revert_change.eventType = 'WMEventNormal';
-                    chrome.autotestPrivate.setAppWindowState(
-                        window.id, revert_change, function(state) {
-                          chrome.test.assertEq(state, 'Normal');
-                          chrome.test.assertNoLastError();
-                          chrome.test.succeed();
+                          // Revert window state back to normal and exit tablet
+                          // mode for the next test.
+                          const revert_change = {
+                            eventType: 'WMEventNormal'
+                          };
+                          chrome.autotestPrivate.setAppWindowState(
+                              window.id, revert_change, true /* wait */,
+                              function(state) {
+                                chrome.test.assertEq(state, 'Normal');
+                                chrome.test.assertNoLastError();
+                                chrome.test.succeed();
+                              });
                         });
-                  });
+                      });
             });
       });
     });
diff --git a/chrome/test/data/hats/hats_next_mock.html b/chrome/test/data/hats/hats_next_mock.html
index 7fdfed6..c04dd75 100644
--- a/chrome/test/data/hats/hats_next_mock.html
+++ b/chrome/test/data/hats/hats_next_mock.html
@@ -15,14 +15,17 @@
       // Watch for the testing trigger_id for HaTS Next, defined on the browser
       // side in hats_service.cc.
       if (params.get('trigger_id') == 'zishSVViB0kPN8UwQ150VGjBKuBP') {
-        // Check that the provided product specific data matches the values
-        // defined in hats_browsertest.cc, if it does provide a loaded response
-        // before closing.
+        // Check that the provided product specific data, and the provided
+        // preferred language, match the values defined in hats_browsertest.cc,
+        // if they do, provide a loaded response before closing.
         const productSpecificData = JSON.parse(
                   decodeURIComponent(params.get('product_specific_data')));
+        const languages =
+            JSON.parse(decodeURIComponent(params.get('languages')));
         if (productSpecificData['Test Field 1'] === 'true' &&
             productSpecificData['Test Field 2'] === 'false' &&
-            productSpecificData['Test Field 3'] === 'true') {
+            productSpecificData['Test Field 3'] === 'true' &&
+            languages.length == 1 && languages[0] == 'lt') {
           history.pushState('', '', '#loaded');
         }
         history.pushState('', '', '#close');
diff --git a/chrome/test/data/prefers-contrast.html b/chrome/test/data/prefers-contrast.html
index e677209..a234de48 100644
--- a/chrome/test/data/prefers-contrast.html
+++ b/chrome/test/data/prefers-contrast.html
@@ -7,5 +7,7 @@
     document.title = "less";
   } else if (window.matchMedia("(prefers-contrast: no-preference)").matches) {
     document.title = "no-preference";
+  } else if (window.matchMedia("(prefers-contrast: custom)").matches) {
+    document.title = "custom";
   }
 </script>
diff --git a/chrome/test/data/web_app_notifications/scripted_test_commands.js b/chrome/test/data/web_app_notifications/scripted_test_commands.js
index 6275294a..0429b59f 100644
--- a/chrome/test/data/web_app_notifications/scripted_test_commands.js
+++ b/chrome/test/data/web_app_notifications/scripted_test_commands.js
@@ -12,6 +12,14 @@
       {type: 'showNotification', useBadge: false, title: 'Notification Title'});
 }
 
+async function displayPersistentNotificationWithBadge() {
+  return await postRequestAwaitResponse({
+    type: 'showNotification',
+    useBadge: true,
+    title: 'Notification With Badge'
+  });
+}
+
 async function closeAllPersistentNotifications() {
   return await postRequestAwaitResponse({type: 'closeAllNotifications'});
 }
diff --git a/chrome/test/data/web_app_notifications/service_worker.js b/chrome/test/data/web_app_notifications/service_worker.js
index 262348f..bb3787a1 100644
--- a/chrome/test/data/web_app_notifications/service_worker.js
+++ b/chrome/test/data/web_app_notifications/service_worker.js
@@ -16,7 +16,7 @@
   event.respondWith(fetch(event.request));
 });
 
-function displayPersistentNotification(message) {
+async function displayPersistentNotification(message) {
   var options = {
     body: 'Notification Body',
     vibrate: [100, 50, 100],
@@ -27,25 +27,23 @@
     options.badge = 'blue-32.png';
   }
 
-  self.registration.showNotification(message.title, options);
+  return self.registration.showNotification(message.title, options);
 }
 
-self.addEventListener('message', event => {
+self.addEventListener('message', async (event) => {
   let message = event.data;
   let responsePort = event.ports[0];
 
   if (message.type === 'showNotification') {
-    displayPersistentNotification(message);
+    await displayPersistentNotification(message);
     responsePort.postMessage({result: true});
   }
 
   if (message.type === 'closeAllNotifications') {
-    self.registration.getNotifications().then(function(notifications) {
-      notifications.forEach(function(notification) {
-        notification.close();
-      });
-
-      responsePort.postMessage({result: true});
-    });
+    let notifications = await self.registration.getNotifications();
+    for (notification of notifications) {
+      notification.close();
+    }
+    responsePort.postMessage({result: true});
   }
 });
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index bb71d61..19b1413 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -111,6 +111,7 @@
       "tab_search/tab_search_browsertest.js",
       "text_defaults_browsertest.js",
       "webui_resource_async_browsertest.js",
+      "whats_new/whats_new_browsertest.js",
     ]
 
     if (is_chrome_branded) {
diff --git a/chrome/test/data/webui/chromeos/fake_network_config_mojom.js b/chrome/test/data/webui/chromeos/fake_network_config_mojom.js
index 3f9b36e..8d6ca59 100644
--- a/chrome/test/data/webui/chromeos/fake_network_config_mojom.js
+++ b/chrome/test/data/webui/chromeos/fake_network_config_mojom.js
@@ -110,6 +110,7 @@
 
     this.globalPolicy_ =
         /** @type {!chromeos.networkConfig.mojom.GlobalPolicy} */ ({
+          allow_only_policy_cellular_networks: false,
           allow_only_policy_networks_to_autoconnect: false,
           allow_only_policy_networks_to_connect: false,
           allow_only_policy_networks_to_connect_if_available: false,
diff --git a/chrome/test/data/webui/chromeos/personalization_app/local_images_element_test.js b/chrome/test/data/webui/chromeos/personalization_app/local_images_element_test.js
index b5ba472..ea80d7f 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/local_images_element_test.js
+++ b/chrome/test/data/webui/chromeos/personalization_app/local_images_element_test.js
@@ -159,7 +159,7 @@
         assertTrue(Array.from(images).every(
             image => image.getAttribute('aria-selected') === 'false'));
 
-        personalizationStore.data.selected = {key: 'LocalImage1'};
+        personalizationStore.data.currentSelected = {key: 'LocalImage1'};
         personalizationStore.notifyObservers();
 
         assertEquals(2, images.length);
diff --git a/chrome/test/data/webui/chromeos/personalization_app/wallpaper_selected_element_test.js b/chrome/test/data/webui/chromeos/personalization_app/wallpaper_selected_element_test.js
index 58c4a67..985d441 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/wallpaper_selected_element_test.js
+++ b/chrome/test/data/webui/chromeos/personalization_app/wallpaper_selected_element_test.js
@@ -91,7 +91,8 @@
   });
 
   test('shows wallpaper image and attribution when loaded', async () => {
-    personalizationStore.data.selected = wallpaperProvider.currentWallpaper;
+    personalizationStore.data.currentSelected =
+        wallpaperProvider.currentWallpaper;
 
     wallpaperSelectedElement = initElement(WallpaperSelected.is);
     await waitAfterNextRender(wallpaperSelectedElement);
@@ -123,7 +124,7 @@
   });
 
   test('shows unknown for empty attribution', async () => {
-    personalizationStore.data.selected = {
+    personalizationStore.data.currentSelected = {
       url: {url: 'data:image/png;base64,abc='},
       attribution: [],
       assetId: BigInt(100),
@@ -140,7 +141,7 @@
   });
 
   test('removes high resolution suffix from image url', async () => {
-    personalizationStore.data.selected = {
+    personalizationStore.data.currentSelected = {
       url: {url: 'https://images.googleusercontent.com/abc12=w456'},
       attribution: [],
       assetId: BigInt(100),
@@ -155,7 +156,8 @@
   });
 
   test('updates image when store is updated', async () => {
-    personalizationStore.data.selected = wallpaperProvider.currentWallpaper;
+    personalizationStore.data.currentSelected =
+        wallpaperProvider.currentWallpaper;
     personalizationStore.data.loading.selected = false;
 
     wallpaperSelectedElement = initElement(WallpaperSelected.is);
@@ -166,7 +168,7 @@
         `chrome://image/?${wallpaperProvider.currentWallpaper.url.url}`,
         img.src);
 
-    personalizationStore.data.selected = {
+    personalizationStore.data.currentSelected = {
       url: {url: 'https://testing'},
       attribution: ['New attribution'],
       assetId: BigInt(100),
@@ -183,7 +185,7 @@
 
     // Still loading.
     personalizationStore.data.loading.selected = true;
-    personalizationStore.data.selected = null;
+    personalizationStore.data.currentSelected = null;
     personalizationStore.notifyObservers();
     await waitAfterNextRender(wallpaperSelectedElement);
 
@@ -224,11 +226,12 @@
     assertEquals(0, personalizationStore.data.loading.selected);
     // Shallow equals - they should be the same object.
     assertEquals(
-        wallpaperProvider.currentWallpaper, personalizationStore.data.selected);
+        wallpaperProvider.currentWallpaper,
+        personalizationStore.data.currentSelected);
   });
 
   test('shows image url with data scheme', async () => {
-    personalizationStore.data.selected = {
+    personalizationStore.data.currentSelected = {
       url: {url: 'data:image/png;base64,abc='},
       attribution: [],
       assetId: BigInt(100),
@@ -242,7 +245,7 @@
   });
 
   test('shows daily refresh option on the collection view', async () => {
-    personalizationStore.data.selected = {
+    personalizationStore.data.currentSelected = {
       url: {url: 'data:image/png;base64,abc='},
       attribution: [],
       assetId: BigInt(100),
@@ -265,7 +268,7 @@
   test(
       'shows refresh button only on collection with daily refresh enabled',
       async () => {
-        personalizationStore.data.selected = {
+        personalizationStore.data.currentSelected = {
           url: {url: 'data:image/png;base64,abc='},
           attribution: [],
           assetId: BigInt(100),
diff --git a/chrome/test/data/webui/extensions/runtime_host_permissions_test.js b/chrome/test/data/webui/extensions/runtime_host_permissions_test.js
index de75495..559be05 100644
--- a/chrome/test/data/webui/extensions/runtime_host_permissions_test.js
+++ b/chrome/test/data/webui/extensions/runtime_host_permissions_test.js
@@ -53,14 +53,14 @@
     expectTrue(testIsVisible('#host-access'));
 
     const selectHostAccess = element.shadowRoot.querySelector('#host-access');
-    expectEquals(HostAccess.ON_CLICK, selectHostAccess.selected);
+    expectEquals(HostAccess.ON_CLICK, selectHostAccess.value);
     // For on-click mode, there should be no runtime hosts listed.
     expectFalse(testIsVisible('#hosts'));
 
     // Changing the data's access should change the UI appropriately.
     element.set('permissions.hostAccess', HostAccess.ON_ALL_SITES);
     flush();
-    expectEquals(HostAccess.ON_ALL_SITES, selectHostAccess.selected);
+    expectEquals(HostAccess.ON_ALL_SITES, selectHostAccess.value);
     expectFalse(testIsVisible('#hosts'));
 
     // Setting the mode to on specific sites should display the runtime hosts
@@ -71,7 +71,7 @@
       {host: 'https://chromium.org', granted: true}
     ]);
     flush();
-    expectEquals(HostAccess.ON_SPECIFIC_SITES, selectHostAccess.selected);
+    expectEquals(HostAccess.ON_SPECIFIC_SITES, selectHostAccess.value);
     expectTrue(testIsVisible('#hosts'));
     // Expect three entries in the list: the two hosts + the add-host button.
     expectEquals(
@@ -99,7 +99,8 @@
     // event, then verifies that the delegate was called with the correct
     // value.
     function expectDelegateCallOnAccessChange(newValue) {
-      selectHostAccess.selected = newValue;
+      selectHostAccess.value = newValue;
+      selectHostAccess.dispatchEvent(new CustomEvent('change'));
       return delegate.whenCalled('setItemHostAccess').then((args) => {
         expectEquals(ITEM_ID, args[0] /* id */);
         expectEquals(newValue, args[1] /* access */);
@@ -134,7 +135,8 @@
     const selectHostAccess = element.shadowRoot.querySelector('#host-access');
     assertTrue(!!selectHostAccess);
 
-    selectHostAccess.selected = HostAccess.ON_SPECIFIC_SITES;
+    selectHostAccess.value = HostAccess.ON_SPECIFIC_SITES;
+    selectHostAccess.dispatchEvent(new CustomEvent('change'));
 
     flush();
     const dialog =
@@ -156,7 +158,7 @@
     await whenClosed;
 
     flush();
-    expectEquals(HostAccess.ON_CLICK, selectHostAccess.selected);
+    expectEquals(HostAccess.ON_CLICK, selectHostAccess.value);
     expectEquals(
         getUserActionCount('Extensions.Settings.Hosts.AddHostDialogCanceled'),
         1);
@@ -166,7 +168,8 @@
         getUserActionCount('Extensions.Settings.Hosts.OnClickSelected'), 0);
     // Changing to a different option after this should still log a user action
     // as expected.
-    selectHostAccess.selected = HostAccess.ON_ALL_SITES;
+    selectHostAccess.value = HostAccess.ON_ALL_SITES;
+    selectHostAccess.dispatchEvent(new CustomEvent('change'));
     flush();
     expectEquals(
         getUserActionCount('Extensions.Settings.Hosts.OnAllSitesSelected'), 1);
@@ -185,7 +188,8 @@
     const selectHostAccess = element.shadowRoot.querySelector('#host-access');
     assertTrue(!!selectHostAccess);
 
-    selectHostAccess.selected = HostAccess.ON_SPECIFIC_SITES;
+    selectHostAccess.value = HostAccess.ON_SPECIFIC_SITES;
+    selectHostAccess.dispatchEvent(new CustomEvent('change'));
     expectEquals(
         getUserActionCount('Extensions.Settings.Hosts.OnSpecificSitesSelected'),
         1);
@@ -209,7 +213,7 @@
     dialog.shadowRoot.querySelector('.action-button').click();
     return whenClosed.then(() => {
       flush();
-      expectEquals(HostAccess.ON_SPECIFIC_SITES, selectHostAccess.selected);
+      expectEquals(HostAccess.ON_SPECIFIC_SITES, selectHostAccess.value);
       expectEquals(
           getUserActionCount(
               'Extensions.Settings.Hosts.AddHostDialogSubmitted'),
diff --git a/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_tests.js b/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_tests.js
index 21023043..91f2441 100644
--- a/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/os_paired_bluetooth_list_tests.js
@@ -8,7 +8,8 @@
 // #import 'chrome://os-settings/strings.m.js';
 
 // #import {flush, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-// #import {assertTrue} from '../../../chai_assert.js';
+// #import {assertTrue, assertEquals} from '../../../chai_assert.js';
+// #import {waitAfterNextRender} from 'chrome://test/test_util.m.js';
 // clang-format on
 
 suite('OsPairedBluetoothListTest', function() {
@@ -22,8 +23,31 @@
     Polymer.dom.flush();
   });
 
+  function flushAsync() {
+    Polymer.dom.flush();
+    return new Promise(resolve => setTimeout(resolve));
+  }
+
   test('Base Test', function() {
     const list = pairedBluetoothList.shadowRoot.querySelector('iron-list');
     assertTrue(!!list);
   });
+
+  test('Device list change renders items correctly', async function() {
+    // TODO(crbug.com/1010321): Use real Device objects.
+    pairedBluetoothList.devices = [{}, {}, {}];
+    await flushAsync();
+
+    const getListItems = () => {
+      return pairedBluetoothList.shadowRoot.querySelectorAll(
+          'os-settings-paired-bluetooth-list-item');
+    };
+    assertEquals(getListItems().length, 3);
+
+    pairedBluetoothList.devices = [{}, {}, {}, {}, {}];
+    await waitAfterNextRender(pairedBluetoothList);
+    Polymer.dom.flush();
+
+    assertEquals(getListItems().length, 5);
+  });
 });
\ No newline at end of file
diff --git a/chrome/test/data/webui/whats_new/test.html b/chrome/test/data/webui/whats_new/test.html
new file mode 100644
index 0000000..d37fedce
--- /dev/null
+++ b/chrome/test/data/webui/whats_new/test.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+  <div>Test page for What's New iframe</div>
+</body>
+</html>
diff --git a/chrome/test/data/webui/whats_new/whats_new_app_test.js b/chrome/test/data/webui/whats_new/whats_new_app_test.js
new file mode 100644
index 0000000..4f82b7b
--- /dev/null
+++ b/chrome/test/data/webui/whats_new/whats_new_app_test.js
@@ -0,0 +1,95 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {WhatsNewAppElement} from 'chrome://whats-new/whats_new_app.js';
+import {WhatsNewProxyImpl} from 'chrome://whats-new/whats_new_proxy.js';
+
+import {assertFalse, assertTrue} from '../chai_assert.js';
+import {TestBrowserProxy} from '../test_browser_proxy.m.js';
+import {flushTasks} from '../test_util.m.js';
+
+class TestWhatsNewProxy extends TestBrowserProxy {
+  /**
+   * @param {boolean=} shouldLoad Whether initialize() should load the test
+   *     URL. Defaults to true.
+   */
+  constructor(shouldLoad = true) {
+    super([
+      'initialize',
+    ]);
+
+    /** @private {boolean} */
+    this.shouldLoad_ = shouldLoad;
+  }
+
+  /** @override */
+  initialize(isAuto) {
+    this.methodCalled('initialize', isAuto);
+    return this.shouldLoad_ ?
+        Promise.resolve('chrome://test/whats_new/test.html') :
+        Promise.resolve();
+  }
+}
+
+suite('WhatsNewAppTest', function() {
+  setup(function() {
+    document.body.innerHTML = '';
+  });
+
+  test('with query parameter', async () => {
+    const proxy = new TestWhatsNewProxy();
+    WhatsNewProxyImpl.setInstance(proxy);
+    window.history.replaceState({}, '', '?auto=true');
+    const whatsNewApp = document.createElement('whats-new-app');
+    document.body.appendChild(whatsNewApp);
+    const isAuto = await proxy.whenCalled('initialize');
+    assertTrue(isAuto);
+    await flushTasks();
+
+    const iframe = whatsNewApp.shadowRoot.querySelector('#content');
+    assertTrue(!!iframe);
+    // iframe has latest=true URL query parameter.
+    assertEquals('chrome://test/whats_new/test.html?latest=true', iframe.src);
+    const errorPage =
+        whatsNewApp.shadowRoot.querySelector('whats-new-error-page');
+    assertFalse(!!errorPage);
+  });
+
+  test('no query parameter', async () => {
+    const proxy = new TestWhatsNewProxy();
+    WhatsNewProxyImpl.setInstance(proxy);
+    window.history.replaceState({}, '', '/');
+    const whatsNewApp = document.createElement('whats-new-app');
+    document.body.appendChild(whatsNewApp);
+    const isAuto = await proxy.whenCalled('initialize');
+    assertFalse(isAuto);
+    await flushTasks();
+
+    const iframe = whatsNewApp.shadowRoot.querySelector('#content');
+    assertTrue(!!iframe);
+    assertEquals('chrome://test/whats_new/test.html', iframe.src);
+    const errorPage =
+        whatsNewApp.shadowRoot.querySelector('whats-new-error-page');
+    assertFalse(!!errorPage);
+  });
+
+  test('no query parameter failure', async () => {
+    // Simulate a failure to load the remote content.
+    const proxy = new TestWhatsNewProxy(false);
+    WhatsNewProxyImpl.setInstance(proxy);
+    window.history.replaceState({}, '', '/');
+    const whatsNewApp = document.createElement('whats-new-app');
+    document.body.appendChild(whatsNewApp);
+    const isAuto = await proxy.whenCalled('initialize');
+    assertFalse(isAuto);
+    await flushTasks();
+
+    // Shows the error page in place of the iframe.
+    const iframe = whatsNewApp.shadowRoot.querySelector('#content');
+    assertFalse(!!iframe);
+    const errorPage =
+        whatsNewApp.shadowRoot.querySelector('whats-new-error-page');
+    assertTrue(!!errorPage);
+  });
+});
diff --git a/chrome/test/data/webui/whats_new/whats_new_browsertest.js b/chrome/test/data/webui/whats_new/whats_new_browsertest.js
new file mode 100644
index 0000000..af298ebb
--- /dev/null
+++ b/chrome/test/data/webui/whats_new/whats_new_browsertest.js
@@ -0,0 +1,42 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/** @fileoverview Runs the Polymer what's new tests on what's new UI. */
+
+// Polymer BrowserTest fixture.
+GEN_INCLUDE(['//chrome/test/data/webui/polymer_browser_test_base.js']);
+
+GEN('#include "chrome/browser/ui/ui_features.h"');
+GEN('#include "content/public/test/browser_test.h"');
+
+/** Test fixture for Polymer What's New elements. */
+const WhatsNewBrowserTest = class extends PolymerTest {
+  /** @override */
+  get browsePreload() {
+    throw 'this is abstract and should be overridden by subclasses';
+  }
+
+  /** @override */
+  get webuiHost() {
+    return 'whats-new';
+  }
+
+  /** @override */
+  get featureList() {
+    return {enabled: ['features::kChromeWhatsNewUI']};
+  }
+};
+
+// eslint-disable-next-line no-var
+var WhatsNewAppTest = class extends WhatsNewBrowserTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://whats-new/test_loader.html?module=whats_new/whats_new_app_test.js';
+  }
+};
+
+// TODO(https://crbug.com/1236879): Re-enable once flakiness is fixed.
+TEST_F('WhatsNewAppTest', 'DISABLED_All', function() {
+  mocha.run();
+});
diff --git a/chrome/test/delayload/delayloads_unittest.cc b/chrome/test/delayload/delayloads_unittest.cc
index aa76b540..788d74b 100644
--- a/chrome/test/delayload/delayloads_unittest.cc
+++ b/chrome/test/delayload/delayloads_unittest.cc
@@ -284,12 +284,16 @@
          "target was built, instead of delayloads_unittests.exe";
 
   static const char* const kValidFilePatterns[] = {
-      "KERNEL32.dll",
-      "chrome_elf.dll",
-      // On 64 bit the Version API's like VerQueryValue come from VERSION.dll.
-      // It depends on kernel32, advapi32 and api-ms-win-crt*.dll. This should
-      // be ok.
-      "VERSION.dll",
+    "KERNEL32.dll",
+    "chrome_elf.dll",
+    // On 64 bit the Version API's like VerQueryValue come from VERSION.dll.
+    // It depends on kernel32, advapi32 and api-ms-win-crt*.dll. This should
+    // be ok.
+    "VERSION.dll",
+#if defined(ADDRESS_SANITIZER)
+    // The ASan runtime uses the synchapi (see crbug.com/1236586).
+    "api-ms-win-core-synch-l1-2-0.dll",
+#endif
   };
 
   // Make sure all of chrome.exe's imports are in the valid imports list.
diff --git a/chromecast/cast_core/BUILD.gn b/chromecast/cast_core/BUILD.gn
index 61cae5a..192c96c3 100644
--- a/chromecast/cast_core/BUILD.gn
+++ b/chromecast/cast_core/BUILD.gn
@@ -225,6 +225,8 @@
   deps = [
     "//base",
     "//chromecast:chromecast_buildflags",
+    "//chromecast/browser:browser_base",
+    "//chromecast/metrics:cast_event_builder_simple",
     "//chromecast/service",
   ]
 
@@ -293,17 +295,30 @@
   deps = [ "//third_party/grpc:grpc++" ]
 }
 
+cast_source_set("cast_core_test_utils") {
+  testonly = true
+
+  sources = [
+    "cast_runtime_metrics_test_helpers.cc",
+    "cast_runtime_metrics_test_helpers.h",
+  ]
+
+  deps = [
+    "//third_party/metrics_proto",
+    "//third_party/openscreen/src/cast/cast_core/api:metrics_recorder_proto",
+  ]
+}
+
 # TODO(b/194439829): Upstream additional metrics unit tests.
 test("cast_cast_core_unittests") {
   sources = [
     "cast_runtime_histogram_flattener_unittest.cc",
-    "cast_runtime_metrics_test_helpers.cc",
-    "cast_runtime_metrics_test_helpers.h",
     "streaming_receiver_session_client_unittest.cc",
     "url_rewrite_rules_adapter_unittests.cc",
   ]
 
   deps = [
+    ":cast_core_test_utils",
     ":metrics_recorder",
     ":streaming_receiver_session_client",
     ":url_rewrite",
diff --git a/chromeos/LACROS_OWNERS b/chromeos/LACROS_OWNERS
index 823eb65b..762e7ea 100644
--- a/chromeos/LACROS_OWNERS
+++ b/chromeos/LACROS_OWNERS
@@ -2,3 +2,4 @@
 erikchen@chromium.org
 hidehiko@chromium.org
 rbock@google.com
+skuhne@chromium.org
diff --git a/chromeos/OWNERS b/chromeos/OWNERS
index c255edb..f0aae42b 100644
--- a/chromeos/OWNERS
+++ b/chromeos/OWNERS
@@ -14,6 +14,9 @@
 # Tast test control
 per-file tast_control.gni=*
 
+per-file LACROS_OWNERS=file://chromeos/LACROS_OWNERS
+per-file LACROS_OWNERS=set noparent
+
 # PST
 achuith@chromium.org
 alemate@chromium.org
diff --git a/chromeos/components/camera_app_ui/camera_app_helper_impl.cc b/chromeos/components/camera_app_ui/camera_app_helper_impl.cc
index f8c2e91..4663ef6 100644
--- a/chromeos/components/camera_app_ui/camera_app_helper_impl.cc
+++ b/chromeos/components/camera_app_ui/camera_app_helper_impl.cc
@@ -298,6 +298,7 @@
   if (!memory.IsValid()) {
     LOG(ERROR) << "Failed to map memory";
     std::move(callback).Run({});
+    return;
   }
   memcpy(memory.mapping.memory(), jpeg_data.data(), jpeg_data.size());
 
@@ -319,6 +320,7 @@
   if (!IsValidCorners(corners)) {
     LOG(ERROR) << "Failed to convert to document due to invalid corners";
     std::move(callback).Run({});
+    return;
   }
 
   base::MappedReadOnlyRegion memory =
@@ -326,6 +328,7 @@
   if (!memory.IsValid()) {
     LOG(ERROR) << "Failed to map memory";
     std::move(callback).Run({});
+    return;
   }
   memcpy(memory.mapping.memory(), jpeg_data.data(), jpeg_data.size());
 
diff --git a/chromeos/components/camera_app_ui/resources/.eslintrc.js b/chromeos/components/camera_app_ui/resources/.eslintrc.js
index 98f4117..fc1a430 100644
--- a/chromeos/components/camera_app_ui/resources/.eslintrc.js
+++ b/chromeos/components/camera_app_ui/resources/.eslintrc.js
@@ -401,6 +401,7 @@
     'CSSScale': 'readable',
     'CSSTransformValue': 'readable',
     'CSSTranslate': 'readable',
+    'CSSUnitValue': 'readable',
   },
   // Generally, the rules should be compatible to both bundled and the newest
   // stable eslint, so it's easier to upgrade and develop without the full
diff --git a/chromeos/components/camera_app_ui/resources/js/externs/w3c_api.js b/chromeos/components/camera_app_ui/resources/js/externs/w3c_api.js
index b35cf6c..01beda8 100644
--- a/chromeos/components/camera_app_ui/resources/js/externs/w3c_api.js
+++ b/chromeos/components/camera_app_ui/resources/js/externs/w3c_api.js
@@ -107,15 +107,8 @@
 /** @type {string} */
 OverconstrainedError.prototype.message;
 
-/**
- * @constructor
- */
-function CSSStyleValue() {}
 
-/**
- * @type {number}
- */
-CSSStyleValue.prototype.value;
+// CSS Typed OM Level 1: https://drafts.css-houdini.org/css-typed-om/
 
 /**
  * @constructor
@@ -139,6 +132,10 @@
  */
 Element.prototype.computedStyleMap = function() {};
 
+// The base StylePropertyMap is defined in
+// third_party/closure_compiler/externs/pending.js, but missing extend for
+// StylePropertyMapReadOnly.
+
 /**
  * @param {string} property
  * @return {?CSSStyleValue}
@@ -153,61 +150,8 @@
 
 /**
  * @constructor
- * @extends {CSSStyleValue}
- * @param {!CSSStyleValue} x
- * @param {!CSSStyleValue} y
  */
-function CSSTranslate(x, y) {}
-
-/**
- * @constructor
- * @extends {CSSStyleValue}
- * @param {!CSSStyleValue} x
- */
-function CSSRotate(x) {}
-
-/**
- * @type {!CSSStyleValue}
- */
-CSSRotate.prototype.angle;
-
-/**
- * @constructor
- * @extends {CSSStyleValue}
- * @param {!CSSStyleValue} x
- * @param {!CSSStyleValue} y
- */
-function CSSScale(x, y) {}
-
-/**
- * @typedef {(CSSTranslate|CSSRotate|CSSScale)}
- */
-let CSSTransformComponent;
-
-/**
- * @constructor
- * @implements {Iterable<!CSSTransformComponent>}
- * @param {!Array<!CSSTransformComponent>} transforms
- */
-function CSSTransformValue(transforms) {}
-
-/**
- * @param {number} px
- * @return {!CSSStyleValue}
- */
-CSS.px;
-
-/**
- * @param {number} rad
- * @return {!CSSStyleValue}
- */
-CSS.rad;
-
-/**
- * @param {number} number
- * @return {!CSSStyleValue}
- */
-CSS.number;
+function CSSStyleValue() {}
 
 /**
  * @constructor
@@ -216,7 +160,90 @@
 function CSSNumericValue() {}
 
 /**
- * @param {string} str
- * @return {!CSSStyleValue}
+ * @param {string} cssText
+ * @return {!CSSNumericValue}
  */
 CSSNumericValue.parse;
+
+/**
+ * @param {string} unit
+ * @return {!CSSUnitValue}
+ */
+CSSNumericValue.prototype.to;
+
+/**
+ * @typedef {number|CSSNumericValue}
+ */
+let CSSNumberish;
+
+/**
+ * @typedef {Object}
+ */
+let CSSTransformComponent;
+
+/**
+ * @constructor
+ * @extends {CSSTransformComponent}
+ * @param {!CSSNumericValue} x
+ * @param {!CSSNumericValue} y
+ */
+function CSSTranslate(x, y) {}
+
+/**
+ * @constructor
+ * @extends {CSSTransformComponent}
+ * @param {!CSSNumericValue} angle
+ */
+function CSSRotate(angle) {}
+
+/**
+ * @type {!CSSNumericValue}
+ */
+CSSRotate.prototype.angle;
+
+/**
+ * @constructor
+ * @extends {CSSTransformComponent}
+ * @param {!CSSNumberish} x
+ * @param {!CSSNumberish} y
+ */
+function CSSScale(x, y) {}
+
+/**
+ * @constructor
+ * @extends {CSSStyleValue}
+ * @implements {Iterable<!CSSTransformComponent>}
+ * @param {!Array<!CSSTransformComponent>} transforms
+ */
+function CSSTransformValue(transforms) {}
+
+/**
+ * @constructor
+ * @extends {CSSNumericValue}
+ * @param {number} value
+ * @param {string} unit
+ */
+function CSSUnitValue(value, unit) {}
+
+/**
+ * @type {number}
+ */
+CSSUnitValue.prototype.value;
+
+/**
+ * @param {number} px
+ * @return {!CSSUnitValue}
+ */
+CSS.px;
+
+/**
+ * @param {number} rad
+ * @return {!CSSUnitValue}
+ */
+CSS.rad;
+
+/**
+ * @param {number} number
+ * @return {!CSSUnitValue}
+ */
+CSS.number;
diff --git a/chromeos/components/camera_app_ui/resources/js/focus_ring.js b/chromeos/components/camera_app_ui/resources/js/focus_ring.js
index 8fabc2a..b2dcf57 100644
--- a/chromeos/components/camera_app_ui/resources/js/focus_ring.js
+++ b/chromeos/components/camera_app_ui/resources/js/focus_ring.js
@@ -8,6 +8,7 @@
 } from './chrome_util.js';
 import {cssStyle} from './css.js';
 import * as dom from './dom.js';
+import {getStyleValueInPx} from './util.js';
 
 /**
  * Focus ring element.
@@ -52,7 +53,7 @@
 function onFocus({target}) {
   const el = assertInstanceof(target, HTMLElement);
   const style = el.computedStyleMap();
-  const size = style.get('--focus-ring-size').value;
+  const size = getStyleValueInPx(style, '--focus-ring-size');
   const ringStyleValue = `${style.get('--focus-ring-style')}`;
   for (const v of ringStyleValues) {
     ring.classList.toggle(v, ringStyleValue.includes(v));
diff --git a/chromeos/components/camera_app_ui/resources/js/mojo/device_operator.js b/chromeos/components/camera_app_ui/resources/js/mojo/device_operator.js
index e3da1e5..f4ef3fd4 100644
--- a/chromeos/components/camera_app_ui/resources/js/mojo/device_operator.js
+++ b/chromeos/components/camera_app_ui/resources/js/mojo/device_operator.js
@@ -156,7 +156,7 @@
     const {device, status} =
         await this.deviceProvider_.getCameraAppDevice(deviceId);
     if (status === cros.mojom.GetCameraAppDeviceStatus.ERROR_INVALID_ID) {
-      throw new Error(`Invalid device id: ${deviceId}`);
+      throw new Error(`Invalid device id`);
     }
     if (device === null) {
       throw new Error('Unknown error');
diff --git a/chromeos/components/camera_app_ui/resources/js/new_feature_toast.js b/chromeos/components/camera_app_ui/resources/js/new_feature_toast.js
index 410c9e5..f94bba2 100644
--- a/chromeos/components/camera_app_ui/resources/js/new_feature_toast.js
+++ b/chromeos/components/camera_app_ui/resources/js/new_feature_toast.js
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import * as animation from './animation.js';
+import {assertInstanceof} from './chrome_util.js';
 import {cssStyle} from './css.js';
 import * as dom from './dom.js';
 import {I18nString} from './i18n_string.js';
@@ -39,22 +40,20 @@
     this.el_ = el;
 
     const style = this.el_.computedStyleMap();
-    const getValue = (name) =>
-        CSSNumericValue.parse(style.get(name).toString()).value;
 
     /**
      * Initial width of ripple in px.
      * @const {!number}
      * @private
      */
-    this.width_ = getValue('--ripple-start-width');
+    this.width_ = util.getStyleValueInPx(style, '--ripple-start-width');
 
     /**
      * Initial height of ripple in px.
      * @const {!number}
      * @private
      */
-    this.height_ = getValue('--ripple-start-height');
+    this.height_ = util.getStyleValueInPx(style, '--ripple-start-height');
 
     /**
      * @const {number}
@@ -65,8 +64,10 @@
     }, RIPPLE_DURATION_MS / RIPPLE_COUNT);
 
     RIPPLE_STYLE.setProperty('--duration', `${RIPPLE_DURATION_MS}ms`);
-    const scaleX = getValue('--ripple-scale-x');
-    const scaleY = getValue('--ripple-scale-y');
+    const scaleX =
+        assertInstanceof(style.get('--ripple-scale-x'), CSSUnitValue).value;
+    const scaleY =
+        assertInstanceof(style.get('--ripple-scale-y'), CSSUnitValue).value;
     RIPPLE_STYLE.setProperty('--scale', `scale(${scaleX}, ${scaleY})`);
 
     this.addRipple_();
@@ -158,8 +159,7 @@
           if (!style.has(cssName)) {
             continue;
           }
-          const offset =
-              CSSNumericValue.parse(style.get(cssName).toString()).value;
+          const offset = util.getStyleValueInPx(style, cssName);
           properties.push({elProperty, toastProperty, offset});
         }
       }
diff --git a/chromeos/components/camera_app_ui/resources/js/util.js b/chromeos/components/camera_app_ui/resources/js/util.js
index f0a8f20..d22b63b1 100644
--- a/chromeos/components/camera_app_ui/resources/js/util.js
+++ b/chromeos/components/camera_app_ui/resources/js/util.js
@@ -241,3 +241,13 @@
 export function sleep(ms) {
   return new Promise((resolve) => setTimeout(resolve, ms));
 }
+
+/**
+ * Gets value in px of a property in a StylePropertyMapReadOnly
+ * @param {!StylePropertyMapReadOnly} style
+ * @param {string} prop
+ * @return {number}
+ */
+export function getStyleValueInPx(style, prop) {
+  return assertInstanceof(style.get(prop), CSSNumericValue).to('px').value;
+}
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera/document_corner_overlay.js b/chromeos/components/camera_app_ui/resources/js/views/camera/document_corner_overlay.js
index f465d5c..ab76f59 100644
--- a/chromeos/components/camera_app_ui/resources/js/views/camera/document_corner_overlay.js
+++ b/chromeos/components/camera_app_ui/resources/js/views/camera/document_corner_overlay.js
@@ -79,7 +79,7 @@
     }
     for (const transform of transforms) {
       if (transform instanceof CSSRotate) {
-        return transform.angle.value;
+        return transform.angle.to('rad').value;
       }
     }
     return null;
diff --git a/chromeos/components/personalization_app/resources/common/constants.js b/chromeos/components/personalization_app/resources/common/constants.js
index 70499ce9..5000c14 100644
--- a/chromeos/components/personalization_app/resources/common/constants.js
+++ b/chromeos/components/personalization_app/resources/common/constants.js
@@ -21,7 +21,8 @@
   SEND_IMAGES: 'send_images',
   SEND_LOCAL_IMAGE_DATA: 'send_local_image_data',
   SEND_LOCAL_IMAGES: 'send_local_images',
-  SEND_SELECTED_WALLPAPER_ASSET_ID: 'send_selected_wallpaper_asset_id',
+  SEND_CURRENT_WALLPAPER_ASSET_ID: 'send_current_wallpaper_asset_id',
+  SEND_PENDING_WALLPAPER_ASSET_ID: 'send_pending_wallpaper_asset_id',
   SELECT_IMAGE: 'select_image',
   SELECT_LOCAL_IMAGE: 'select_local_image',
   SEND_VISIBLE: 'send_visible',
@@ -82,7 +83,15 @@
  *   assetId: ?bigint,
  * }}
  */
-export let SendSelectedWallpaperAssetIdEvent;
+export let SendCurrentWallpaperAssetIdEvent;
+
+/**
+ * @typedef {{
+ *  type: EventType,
+ *  assetId: ?bigint,
+ * }}
+ */
+export let SendPendingWallpaperAssetIdEvent;
 
 /**
  * @typedef {{ type: EventType, assetId: bigint }}
diff --git a/chromeos/components/personalization_app/resources/common/iframe_api.js b/chromeos/components/personalization_app/resources/common/iframe_api.js
index 96db9c80..986ffb2 100644
--- a/chromeos/components/personalization_app/resources/common/iframe_api.js
+++ b/chromeos/components/personalization_app/resources/common/iframe_api.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import {assert, assertNotReached} from '/assert.m.js';
-import {EventType, SelectCollectionEvent, SelectImageEvent, SelectLocalCollectionEvent, SendCollectionsEvent, SendImageCountsEvent, SendImagesEvent, SendLocalImageDataEvent, SendLocalImagesEvent, SendSelectedWallpaperAssetIdEvent, SendVisibleEvent, trustedOrigin, untrustedOrigin} from './constants.js';
+import {EventType, SelectCollectionEvent, SelectImageEvent, SelectLocalCollectionEvent, SendCollectionsEvent, SendCurrentWallpaperAssetIdEvent, SendImageCountsEvent, SendImagesEvent, SendLocalImageDataEvent, SendLocalImagesEvent, SendPendingWallpaperAssetIdEvent, SendVisibleEvent, trustedOrigin, untrustedOrigin} from './constants.js';
 import {isNonEmptyArray} from './utils.js';
 
 /**
@@ -92,9 +92,21 @@
  * @param {!Window} target
  * @param {?bigint} assetId
  */
-export function sendSelectedWallpaperAssetId(target, assetId) {
-  /** @type {!SendSelectedWallpaperAssetIdEvent} */
-  const event = {type: EventType.SEND_SELECTED_WALLPAPER_ASSET_ID, assetId};
+export function sendCurrentWallpaperAssetId(target, assetId) {
+  /** @type {!SendCurrentWallpaperAssetIdEvent} */
+  const event = {type: EventType.SEND_CURRENT_WALLPAPER_ASSET_ID, assetId};
+  target.postMessage(event, untrustedOrigin);
+}
+
+/**
+ * Send the |assetId| to the |target| iframe when the user clicks on online
+ * wallpaper image.
+ * @param {!Window} target
+ * @param {?bigint} assetId
+ */
+export function sendPendingWallpaperAssetId(target, assetId) {
+  /** @type {!SendPendingWallpaperAssetIdEvent} */
+  const event = {type: EventType.SEND_PENDING_WALLPAPER_ASSET_ID, assetId};
   target.postMessage(event, untrustedOrigin);
 }
 
@@ -182,7 +194,8 @@
    * @type {
    *   SendCollectionsEvent|
    *   SendImagesEvent|
-   *   SendSelectedWallpaperAssetIdEvent|
+   *   SendCurrentWallpaperAssetIdEvent|
+   *   SendPendingWallpaperAssetIdEvent|
    *   SendLocalImagesEvent|
    *   SendLocalImageDataEvent|
    *   SendVisibleEvent
@@ -201,7 +214,8 @@
       // Images array may be empty.
       assert(Array.isArray(data.images), 'Expected images array');
       return data.images;
-    case EventType.SEND_SELECTED_WALLPAPER_ASSET_ID:
+    case EventType.SEND_CURRENT_WALLPAPER_ASSET_ID:
+    case EventType.SEND_PENDING_WALLPAPER_ASSET_ID:
       assert(data.assetId === null || typeof data.assetId === 'bigint');
       return data.assetId;
     case EventType.SEND_VISIBLE:
diff --git a/chromeos/components/personalization_app/resources/common/styles.js b/chromeos/components/personalization_app/resources/common/styles.js
index 6dfd9621..23962677 100644
--- a/chromeos/components/personalization_app/resources/common/styles.js
+++ b/chromeos/components/personalization_app/resources/common/styles.js
@@ -125,12 +125,23 @@
       min-width: initial;
       margin: 12px;
     }
+    @keyframes scale-up {
+      from {
+        transform: scale(0);
+      }
+      to {
+        transform: scale(1);
+      }
+    }
     .photo-container iron-icon[icon='personalization:checkmark'] {
       --iron-icon-height: 20px;
       --iron-icon-width: 20px;
       left: 8px;
       position: absolute;
       top: 8px;
+      animation-name: scale-up;
+      animation-duration: 200ms;
+      animation-timing-functino: cubic-bezier(0.40, 0.00, 0.20, 1.00);
     }
     .photo-inner-container:not([aria-selected='true'])
     iron-icon[icon='personalization:checkmark'] {
@@ -141,9 +152,17 @@
           var(--personalization-app-second-tone-opacity));
       border-radius: 16px;
     }
+    @keyframes resize {
+      100% {
+        height: calc(100% - 8px);
+        width: calc(100% - 8px);
+      }
+    }
     .photo-inner-container[aria-selected='true'] .photo-images-container {
-      height: calc(100% - 8px);
-      width: calc(100% - 8px);
+      animation-name: resize;
+      animation-duration: 200ms;
+      animation-fill-mode: forwards;
+      animation-timing-function: cubic-bezier(0.40, 0.00, 0.20, 1.00);
     }
     .photo-inner-container:focus-visible:not([aria-selected='true'])
     .photo-images-container {
diff --git a/chromeos/components/personalization_app/resources/trusted/BUILD.gn b/chromeos/components/personalization_app/resources/trusted/BUILD.gn
index 82645f4..8c5ee72 100644
--- a/chromeos/components/personalization_app/resources/trusted/BUILD.gn
+++ b/chromeos/components/personalization_app/resources/trusted/BUILD.gn
@@ -31,6 +31,7 @@
 js_library("local_images_element") {
   deps = [
     ":personalization_controller",
+    ":personalization_reducers",
     ":styles",
     "../common:constants",
     "../common:icons",
@@ -151,6 +152,7 @@
 js_library("wallpaper_images_element") {
   deps = [
     ":personalization_controller",
+    ":personalization_reducers",
     ":styles",
     "../common:constants",
     "//third_party/polymer/v3_0/components-chromium/paper-spinner:paper-spinner-lite",
diff --git a/chromeos/components/personalization_app/resources/trusted/local_images_element.html b/chromeos/components/personalization_app/resources/trusted/local_images_element.html
index c4bda905..08eae83 100644
--- a/chromeos/components/personalization_app/resources/trusted/local_images_element.html
+++ b/chromeos/components/personalization_app/resources/trusted/local_images_element.html
@@ -30,7 +30,7 @@
               tabindex$="[[tabIndex]]" data-id$="[[getImageKey_(item)]]"
               on-click="onImageSelected_" on-keypress="onImageSelected_"
               aria-posinset$="[[getAriaIndex_(index)]]"
-              aria-selected$="[[getAriaSelected_(item, selected_)]]">
+              aria-selected$="[[getAriaSelected_(item, currentSelected_, pendingSelected_)]]">
             <div class="photo-images-container">
               <img src="[[getImageData_(item, imageData_)]]"
                   alt="[[item.name]]">
diff --git a/chromeos/components/personalization_app/resources/trusted/local_images_element.js b/chromeos/components/personalization_app/resources/trusted/local_images_element.js
index 42628b9..6c16038e 100644
--- a/chromeos/components/personalization_app/resources/trusted/local_images_element.js
+++ b/chromeos/components/personalization_app/resources/trusted/local_images_element.js
@@ -21,6 +21,7 @@
 import {isSelectionEvent, stringToUnguessableToken, unguessableTokensEqual, unguessableTokenToString} from '../common/utils.js';
 import {getWallpaperProvider} from './mojo_interface_provider.js';
 import {selectWallpaper} from './personalization_controller.js';
+import {DisplayableImage} from './personalization_reducers.js';
 import {WithPersonalizationStore} from './personalization_store.js';
 
 /** @polymer */
@@ -73,7 +74,16 @@
        * @type {?chromeos.personalizationApp.mojom.CurrentWallpaper}
        * @private
        */
-      selected_: {
+      currentSelected_: {
+        type: Object,
+      },
+
+      /**
+       * The pending selected image.
+       * @type {?DisplayableImage}
+       * @private
+       */
+      pendingSelected_: {
         type: Object,
       },
 
@@ -98,7 +108,8 @@
     this.watch('images_', state => state.local.images);
     this.watch('imageData_', state => state.local.data);
     this.watch('imageDataLoading_', state => state.loading.local.data);
-    this.watch('selected_', state => state.selected);
+    this.watch('currentSelected_', state => state.currentSelected);
+    this.watch('pendingSelected_', state => state.pendingSelected);
     this.updateFromStore();
   }
 
@@ -158,15 +169,19 @@
 
   /**
    * @param {!chromeos.personalizationApp.mojom.LocalImage} image
-   * @param {?chromeos.personalizationApp.mojom.CurrentWallpaper} selected
+   * @param {?chromeos.personalizationApp.mojom.CurrentWallpaper}
+   *     currentSelected
+   * @param {?DisplayableImage} pendingSelected
    * @return {string}
    * @private
    */
-  getAriaSelected_(image, selected) {
-    if (!image || !selected) {
+  getAriaSelected_(image, currentSelected, pendingSelected) {
+    if (!image || (!currentSelected && !pendingSelected)) {
       return 'false';
     }
-    return (!!selected && selected.key === image.name).toString();
+    return (!!pendingSelected && image.id === pendingSelected.id ||
+            !!currentSelected && currentSelected.key === image.name)
+        .toString();
   }
 
   /**
@@ -247,7 +262,6 @@
         getWallpaperProvider(), this.getStore());
   }
 
-
   /**
    * @param {number} i
    * @return {number}
diff --git a/chromeos/components/personalization_app/resources/trusted/personalization_reducers.js b/chromeos/components/personalization_app/resources/trusted/personalization_reducers.js
index 06baa3f..b5e1f55b 100644
--- a/chromeos/components/personalization_app/resources/trusted/personalization_reducers.js
+++ b/chromeos/components/personalization_app/resources/trusted/personalization_reducers.js
@@ -94,7 +94,8 @@
  *   backdrop: !BackdropState,
  *   loading: !LoadingState,
  *   local: !LocalState,
- *   selected: ?DisplayableImage,
+ *   currentSelected: ?DisplayableImage,
+ *   pendingSelected: ?DisplayableImage,
  *   dailyRefresh: !DailyRefreshState,
  * }}
  */
@@ -116,7 +117,8 @@
       setImage: 0,
     },
     local: {images: null, data: {}},
-    selected: null,
+    currentSelected: null,
+    pendingSelected: null,
     dailyRefresh: {collectionId: null},
   };
 }
@@ -279,7 +281,7 @@
  * @param {!Action} action
  * @return {?DisplayableImage}
  */
-function selectedReducer(state, action) {
+function currentSelectedReducer(state, action) {
   switch (action.name) {
     case ActionName.SET_SELECTED_IMAGE:
       return action.image;
@@ -289,6 +291,20 @@
 }
 
 /**
+ * @param {?DisplayableImage} state
+ * @param {!Action} action
+ * @return {?DisplayableImage}
+ */
+function pendingSelectedReducer(state, action) {
+  switch (action.name) {
+    case ActionName.BEGIN_SELECT_IMAGE:
+      return action.image;
+    default:
+      return state;
+  }
+}
+
+/**
  * @param {!DailyRefreshState} state
  * @param {!Action} action
  * @returns {!DailyRefreshState}
@@ -309,7 +325,8 @@
   backdrop: backdropReducer,
   loading: loadingReducer,
   local: localReducer,
-  selected: selectedReducer,
+  currentSelected: currentSelectedReducer,
+  pendingSelected: pendingSelectedReducer,
   dailyRefresh: dailyRefreshReducer,
 });
 
diff --git a/chromeos/components/personalization_app/resources/trusted/wallpaper_images_element.js b/chromeos/components/personalization_app/resources/trusted/wallpaper_images_element.js
index ba5613a..ce27043 100644
--- a/chromeos/components/personalization_app/resources/trusted/wallpaper_images_element.js
+++ b/chromeos/components/personalization_app/resources/trusted/wallpaper_images_element.js
@@ -12,9 +12,9 @@
 import 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
 import './styles.js';
 import {afterNextRender, html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {sendImages, sendSelectedWallpaperAssetId, sendVisible} from '../common/iframe_api.js';
+import {sendCurrentWallpaperAssetId, sendImages, sendPendingWallpaperAssetId, sendVisible} from '../common/iframe_api.js';
 import {isNonEmptyArray, promisifyOnload} from '../common/utils.js';
-import {WallpaperType} from './personalization_reducers.js';
+import {DisplayableImage, WallpaperType} from './personalization_reducers.js';
 import {WithPersonalizationStore} from './personalization_store.js';
 
 let sendImagesFunction = sendImages;
@@ -27,17 +27,17 @@
 }
 
 /**
- * If |selected| is set and is an online wallpaper, return the assetId of that
+ * If |current| is set and is an online wallpaper, return the assetId of that
  * image. Otherwise returns null.
- * @param {?chromeos.personalizationApp.mojom.CurrentWallpaper} selected
+ * @param {?chromeos.personalizationApp.mojom.CurrentWallpaper} current
  * @return {?bigint}
  */
-function getAssetId(selected) {
-  if (selected?.type !== WallpaperType.kOnline) {
+function getAssetId(current) {
+  if (current?.type !== WallpaperType.kOnline) {
     return null;
   }
   try {
-    return BigInt(selected.key);
+    return BigInt(current.key);
   } catch (e) {
     console.warn('Required a BigInt value here', e);
     return null;
@@ -100,7 +100,16 @@
       /**
        * @type {?chromeos.personalizationApp.mojom.CurrentWallpaper}
        */
-      selected_: {
+      currentSelected_: {
+        type: Object,
+      },
+
+      /**
+       * The pending selected image.
+       * @type {?DisplayableImage}
+       * @private
+       */
+      pendingSelected_: {
         type: Object,
       },
 
@@ -123,7 +132,8 @@
   static get observers() {
     return [
       'onShouldSendImages_(showImages_, collectionId)',
-      'onShouldSendSelectedAssetId_(showImages_, selected_)',
+      'onShouldSendCurrentAssetId_(showImages_, currentSelected_)',
+      'onShouldSendPendingAssetId_(pendingSelected_)',
     ]
   }
 
@@ -139,7 +149,8 @@
     this.watch('images_', state => state.backdrop.images);
     this.watch('imagesLoading_', state => state.loading.images);
     this.watch('collections_', state => state.backdrop.collections);
-    this.watch('selected_', state => state.selected);
+    this.watch('currentSelected_', state => state.currentSelected);
+    this.watch('pendingSelected_', state => state.pendingSelected);
     this.updateFromStore();
   }
 
@@ -206,18 +217,30 @@
 
   /**
    * @param {boolean} showImages
-   * @param {?chromeos.personalizationApp.mojom.CurrentWallpaper} selected
+   * @param {?chromeos.personalizationApp.mojom.CurrentWallpaper}
+   *     currentSelected
    */
-  async onShouldSendSelectedAssetId_(showImages, selected) {
+  async onShouldSendCurrentAssetId_(showImages, currentSelected) {
     if (showImages) {
-      const assetId = getAssetId(selected);
+      const assetId = getAssetId(currentSelected);
       const iframe = await this.iframePromise_;
-      sendSelectedWallpaperAssetId(
+      sendCurrentWallpaperAssetId(
           /** @type {!Window} */ (iframe.contentWindow), assetId);
     }
   }
 
   /**
+   * @param {?DisplayableImage} pendingSelected
+   */
+  async onShouldSendPendingAssetId_(pendingSelected) {
+    if (!pendingSelected || !pendingSelected.assetId)
+      return;
+    const iframe = await this.iframePromise_;
+    sendPendingWallpaperAssetId(
+        /** @type {!Window} */ (iframe.contentWindow), pendingSelected.assetId);
+  }
+
+  /**
    * @private
    * @param {string} collectionId
    * @param {?Array<!chromeos.personalizationApp.mojom.WallpaperCollection>}
diff --git a/chromeos/components/personalization_app/resources/trusted/wallpaper_selected_element.js b/chromeos/components/personalization_app/resources/trusted/wallpaper_selected_element.js
index 4bf6893..3466f01f 100644
--- a/chromeos/components/personalization_app/resources/trusted/wallpaper_selected_element.js
+++ b/chromeos/components/personalization_app/resources/trusted/wallpaper_selected_element.js
@@ -180,7 +180,7 @@
   /** @override */
   connectedCallback() {
     super.connectedCallback();
-    this.watch('image_', state => state.selected);
+    this.watch('image_', state => state.currentSelected);
     this.watch(
         'isLoading_',
         state => Math.max(state.loading.selected, state.loading.setImage) > 0 ||
diff --git a/chromeos/components/personalization_app/resources/untrusted/images_grid.js b/chromeos/components/personalization_app/resources/untrusted/images_grid.js
index 7104b1e..78927e6 100644
--- a/chromeos/components/personalization_app/resources/untrusted/images_grid.js
+++ b/chromeos/components/personalization_app/resources/untrusted/images_grid.js
@@ -79,9 +79,13 @@
           this.images_ = [];
         }
         return;
-      case EventType.SEND_SELECTED_WALLPAPER_ASSET_ID:
+      case EventType.SEND_CURRENT_WALLPAPER_ASSET_ID:
         this.selectedAssetId_ = validateReceivedData(
-            message, EventType.SEND_SELECTED_WALLPAPER_ASSET_ID);
+            message, EventType.SEND_CURRENT_WALLPAPER_ASSET_ID);
+        return;
+      case EventType.SEND_PENDING_WALLPAPER_ASSET_ID:
+        this.selectedAssetId_ = validateReceivedData(
+          message, EventType.SEND_PENDING_WALLPAPER_ASSET_ID);
         return;
       case EventType.SEND_VISIBLE:
         const visible = validateReceivedData(message, EventType.SEND_VISIBLE);
diff --git a/chromeos/crosapi/mojom/BUILD.gn b/chromeos/crosapi/mojom/BUILD.gn
index 0ea9a11..1d4bdb3 100644
--- a/chromeos/crosapi/mojom/BUILD.gn
+++ b/chromeos/crosapi/mojom/BUILD.gn
@@ -104,6 +104,10 @@
           mojom = "crosapi.mojom.WindowMode"
           cpp = "::apps::mojom::WindowMode"
         },
+        {
+          mojom = "crosapi.mojom.AppType"
+          cpp = "::apps::mojom::AppType"
+        },
       ]
       traits_headers = [
         "//chromeos/crosapi/mojom/app_service_types_mojom_traits.h",
diff --git a/chromeos/crosapi/mojom/app_service.mojom b/chromeos/crosapi/mojom/app_service.mojom
index 37b652b..9ffc247 100644
--- a/chromeos/crosapi/mojom/app_service.mojom
+++ b/chromeos/crosapi/mojom/app_service.mojom
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 //
-// Next MinVersion: 9
+// Next MinVersion: 10
 
 module crosapi.mojom;
 
@@ -119,4 +119,11 @@
 // the lacros-chrome so that the browser can access the app service info
 // in the same process.
 [Stable, Uuid="d77d3b7e-ef90-4615-b81b-7e43482a4d5e"]
-interface AppServiceSubscriber {};
+interface AppServiceSubscriber {
+  // Receives a stream of apps from ash-chrome from a publisher, and save to
+  // AppRegistryCache.
+  // If |should_notify_initialized| is true, notifies observers that |app_type|
+  // has finished initiating apps.
+  [MinVersion=9]
+  OnApps@0(array<App> deltas, AppType app_type, bool should_notify_initialized);
+};
diff --git a/chromeos/dbus/concierge/fake_concierge_client.cc b/chromeos/dbus/concierge/fake_concierge_client.cc
index 52f7b13..1e30b65 100644
--- a/chromeos/dbus/concierge/fake_concierge_client.cc
+++ b/chromeos/dbus/concierge/fake_concierge_client.cc
@@ -207,6 +207,15 @@
                      weak_ptr_factory_.GetWeakPtr(),
                      std::move(tremplin_started_signal)),
       send_tremplin_started_signal_delay_);
+
+  // Trigger VmStartedSignal
+  vm_tools::concierge::VmStartedSignal vm_started_signal;
+  vm_started_signal.set_name(request.name());
+  vm_started_signal.set_owner_id(request.owner_id());
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&FakeConciergeClient::NotifyVmStarted,
+                                weak_ptr_factory_.GetWeakPtr(),
+                                std::move(vm_started_signal)));
 }
 
 void FakeConciergeClient::NotifyTremplinStarted(
@@ -356,12 +365,16 @@
 
 void FakeConciergeClient::NotifyVmStarted(
     const vm_tools::concierge::VmStartedSignal& signal) {
+  // Now GetVmInfo can return success.
+  get_vm_info_response_->set_success(true);
   for (auto& observer : vm_observer_list_)
     observer.OnVmStarted(signal);
 }
 
 void FakeConciergeClient::NotifyVmStopped(
     const vm_tools::concierge::VmStoppedSignal& signal) {
+  // Now GetVmInfo can return success.
+  get_vm_info_response_->set_success(false);
   for (auto& observer : vm_observer_list_)
     observer.OnVmStopped(signal);
 }
@@ -402,7 +415,7 @@
   resume_vm_response_->set_success(true);
 
   get_vm_info_response_.emplace();
-  get_vm_info_response_->set_success(true);
+  get_vm_info_response_->set_success(false);
   get_vm_info_response_->mutable_vm_info()->set_seneschal_server_handle(1);
 
   get_vm_enterprise_reporting_info_response_.emplace();
diff --git a/chromeos/network/auto_connect_handler.cc b/chromeos/network/auto_connect_handler.cc
index c3225ca..7af030d 100644
--- a/chromeos/network/auto_connect_handler.cc
+++ b/chromeos/network/auto_connect_handler.cc
@@ -208,12 +208,12 @@
   std::set<std::string> hidden_hex_ssids_at_scan_start;
   std::swap(hidden_hex_ssids_at_scan_start_, hidden_hex_ssids_at_scan_start);
 
-  // Enforce AllowOnlyPolicyNetworksToConnectIfAvailable policy if enabled.
+  // Enforce AllowOnlyPolicyWiFiToConnectIfAvailable policy if enabled.
   const NetworkState* managed_network =
       network_state_handler_->GetAvailableManagedWifiNetwork();
   if (device_policy_applied_ && user_policy_applied_ && managed_network &&
       managed_configuration_handler_
-          ->AllowOnlyPolicyNetworksToConnectIfAvailable()) {
+          ->AllowOnlyPolicyWiFiToConnectIfAvailable()) {
     const NetworkState* connected_network =
         network_state_handler_->ConnectedNetworkByType(
             NetworkTypePattern::WiFi());
@@ -348,12 +348,12 @@
   std::vector<std::string> blocked_hex_ssids =
       managed_configuration_handler_->GetBlockedHexSSIDs();
   bool only_managed =
-      managed_configuration_handler_->AllowOnlyPolicyNetworksToConnect();
+      managed_configuration_handler_->AllowOnlyPolicyWiFiToConnect();
   bool only_managed_autoconnect =
       managed_configuration_handler_->AllowOnlyPolicyNetworksToAutoconnect();
   bool available_only =
       managed_configuration_handler_
-          ->AllowOnlyPolicyNetworksToConnectIfAvailable() &&
+          ->AllowOnlyPolicyWiFiToConnectIfAvailable() &&
       network_state_handler_->GetAvailableManagedWifiNetwork();
 
   // Enforce the autoconnect-policy only once.
diff --git a/chromeos/network/auto_connect_handler_unittest.cc b/chromeos/network/auto_connect_handler_unittest.cc
index 3c64503..1cb85492 100644
--- a/chromeos/network/auto_connect_handler_unittest.cc
+++ b/chromeos/network/auto_connect_handler_unittest.cc
@@ -757,7 +757,7 @@
 }
 
 TEST_F(AutoConnectHandlerTest,
-       DisconnectOnPolicyLoadingAllowOnlyPolicyNetworksToConnect) {
+       DisconnectOnPolicyLoadingAllowOnlyPolicyWiFiToConnect) {
   std::string wifi0_service_path =
       ConfigureService(kConfigWifi0UnmanagedSharedConnected);
   ASSERT_FALSE(wifi0_service_path.empty());
@@ -774,7 +774,7 @@
 
   base::DictionaryValue global_config;
   global_config.SetKey(
-      ::onc::global_network_config::kAllowOnlyPolicyNetworksToConnect,
+      ::onc::global_network_config::kAllowOnlyPolicyWiFiToConnect,
       base::Value(true));
 
   // Applying the policy which restricts connections should disconnect from the
@@ -901,7 +901,7 @@
   EXPECT_EQ(0, test_observer_->num_auto_connect_events());
 }
 
-TEST_F(AutoConnectHandlerTest, AllowOnlyPolicyNetworksToConnectIfAvailable) {
+TEST_F(AutoConnectHandlerTest, AllowOnlyPolicyWiFiToConnectIfAvailable) {
   std::string wifi0_service_path =
       ConfigureService(kConfigWifi0UnmanagedSharedConnected);
   ASSERT_FALSE(wifi0_service_path.empty());
@@ -915,12 +915,12 @@
   EXPECT_EQ(shill::kStateIdle, GetServiceState(wifi1_service_path));
   EXPECT_TRUE(helper().profile_test()->HasService(wifi0_service_path));
 
-  // Apply 'AllowOnlyPolicyNetworksToConnectIfAvailable' policy as a device
+  // Apply 'AllowOnlyPolicyWiFiToConnectIfAvailable' policy as a device
   // policy and provide a network configuration for wifi1 to make it managed.
   base::DictionaryValue global_config;
-  global_config.SetKey(::onc::global_network_config::
-                           kAllowOnlyPolicyNetworksToConnectIfAvailable,
-                       base::Value(true));
+  global_config.SetKey(
+      ::onc::global_network_config::kAllowOnlyPolicyWiFiToConnectIfAvailable,
+      base::Value(true));
   SetupPolicy(kPolicy, global_config, false /* load as device policy */);
   EXPECT_EQ(shill::kStateOnline, GetServiceState(wifi0_service_path));
   EXPECT_EQ(shill::kStateIdle, GetServiceState(wifi1_service_path));
diff --git a/chromeos/network/managed_network_configuration_handler.h b/chromeos/network/managed_network_configuration_handler.h
index dce53b3c..8cb532a 100644
--- a/chromeos/network/managed_network_configuration_handler.h
+++ b/chromeos/network/managed_network_configuration_handler.h
@@ -19,7 +19,6 @@
 
 namespace base {
 class DictionaryValue;
-class ListValue;
 }  // namespace base
 
 namespace chromeos {
@@ -127,7 +126,7 @@
   virtual void SetPolicy(
       ::onc::ONCSource onc_source,
       const std::string& userhash,
-      const base::ListValue& network_configs_onc,
+      const base::Value& network_configs_onc,
       const base::DictionaryValue& global_network_config) = 0;
 
   // Returns true if any policy application is currently running or pending.
@@ -170,12 +169,15 @@
       const std::string& guid,
       const std::string& profile_path) const = 0;
 
-  // Return true if the AllowOnlyPolicyNetworksToConnect policy is enabled.
-  virtual bool AllowOnlyPolicyNetworksToConnect() const = 0;
+  // Return true if AllowOnlyPolicyCellularNetworks policy is enabled.
+  virtual bool AllowOnlyPolicyCellularNetworks() const = 0;
 
-  // Return true if the AllowOnlyPolicyNetworksToConnectIfAvailable policy is
+  // Return true if the AllowOnlyPolicyWiFiToConnect policy is enabled.
+  virtual bool AllowOnlyPolicyWiFiToConnect() const = 0;
+
+  // Return true if the AllowOnlyPolicyWiFiToConnectIfAvailable policy is
   // enabled.
-  virtual bool AllowOnlyPolicyNetworksToConnectIfAvailable() const = 0;
+  virtual bool AllowOnlyPolicyWiFiToConnectIfAvailable() const = 0;
 
   // Return true if the AllowOnlyPolicyNetworksToAutoconnect policy is enabled.
   virtual bool AllowOnlyPolicyNetworksToAutoconnect() const = 0;
diff --git a/chromeos/network/managed_network_configuration_handler_impl.cc b/chromeos/network/managed_network_configuration_handler_impl.cc
index b08754d..54a3974 100644
--- a/chromeos/network/managed_network_configuration_handler_impl.cc
+++ b/chromeos/network/managed_network_configuration_handler_impl.cc
@@ -480,7 +480,7 @@
 void ManagedNetworkConfigurationHandlerImpl::SetPolicy(
     ::onc::ONCSource onc_source,
     const std::string& userhash,
-    const base::ListValue& network_configs_onc,
+    const base::Value& network_configs_onc,
     const base::DictionaryValue& global_network_config) {
   VLOG(1) << "Setting policies from: " << ToDebugString(onc_source, userhash);
 
@@ -691,8 +691,8 @@
 
     if (device_policy_applied_ && user_policy_applied_) {
       network_state_handler_->UpdateBlockedWifiNetworks(
-          AllowOnlyPolicyNetworksToConnect(),
-          AllowOnlyPolicyNetworksToConnectIfAvailable(), GetBlockedHexSSIDs());
+          AllowOnlyPolicyWiFiToConnect(),
+          AllowOnlyPolicyWiFiToConnectIfAvailable(), GetBlockedHexSSIDs());
     }
 
     for (auto& observer : observers_)
@@ -794,7 +794,7 @@
   return !IsNetworkConfiguredByPolicy(guid, profile_path);
 }
 
-bool ManagedNetworkConfigurationHandlerImpl::AllowOnlyPolicyNetworksToConnect()
+bool ManagedNetworkConfigurationHandlerImpl::AllowOnlyPolicyCellularNetworks()
     const {
   const base::DictionaryValue* global_network_config =
       GetGlobalConfigFromPolicy(
@@ -803,13 +803,27 @@
     return false;
 
   const base::Value* managed_only_value = global_network_config->FindKeyOfType(
-      ::onc::global_network_config::kAllowOnlyPolicyNetworksToConnect,
+      ::onc::global_network_config::kAllowOnlyPolicyCellularNetworks,
+      base::Value::Type::BOOLEAN);
+  return managed_only_value && managed_only_value->GetBool();
+}
+
+bool ManagedNetworkConfigurationHandlerImpl::AllowOnlyPolicyWiFiToConnect()
+    const {
+  const base::DictionaryValue* global_network_config =
+      GetGlobalConfigFromPolicy(
+          std::string() /* no username hash, device policy */);
+  if (!global_network_config)
+    return false;
+
+  const base::Value* managed_only_value = global_network_config->FindKeyOfType(
+      ::onc::global_network_config::kAllowOnlyPolicyWiFiToConnect,
       base::Value::Type::BOOLEAN);
   return managed_only_value && managed_only_value->GetBool();
 }
 
 bool ManagedNetworkConfigurationHandlerImpl::
-    AllowOnlyPolicyNetworksToConnectIfAvailable() const {
+    AllowOnlyPolicyWiFiToConnectIfAvailable() const {
   const base::DictionaryValue* global_network_config =
       GetGlobalConfigFromPolicy(
           std::string() /* no username hash, device policy */);
@@ -820,7 +834,7 @@
   const base::Value* available_only_value =
       global_network_config->FindKeyOfType(
           ::onc::global_network_config::
-              kAllowOnlyPolicyNetworksToConnectIfAvailable,
+              kAllowOnlyPolicyWiFiToConnectIfAvailable,
           base::Value::Type::BOOLEAN);
   return available_only_value && available_only_value->GetBool();
 }
diff --git a/chromeos/network/managed_network_configuration_handler_impl.h b/chromeos/network/managed_network_configuration_handler_impl.h
index 7c28a41..ddd65921 100644
--- a/chromeos/network/managed_network_configuration_handler_impl.h
+++ b/chromeos/network/managed_network_configuration_handler_impl.h
@@ -75,7 +75,7 @@
 
   void SetPolicy(::onc::ONCSource onc_source,
                  const std::string& userhash,
-                 const base::ListValue& network_configs_onc,
+                 const base::Value& network_configs_onc,
                  const base::DictionaryValue& global_network_config) override;
 
   bool IsAnyPolicyApplicationRunning() const override;
@@ -103,8 +103,9 @@
   bool CanRemoveNetworkConfig(const std::string& guid,
                               const std::string& profile_path) const override;
 
-  bool AllowOnlyPolicyNetworksToConnect() const override;
-  bool AllowOnlyPolicyNetworksToConnectIfAvailable() const override;
+  bool AllowOnlyPolicyCellularNetworks() const override;
+  bool AllowOnlyPolicyWiFiToConnect() const override;
+  bool AllowOnlyPolicyWiFiToConnectIfAvailable() const override;
   bool AllowOnlyPolicyNetworksToAutoconnect() const override;
   std::vector<std::string> GetBlockedHexSSIDs() const override;
 
diff --git a/chromeos/network/managed_network_configuration_handler_unittest.cc b/chromeos/network/managed_network_configuration_handler_unittest.cc
index 115ef91..3510b03d 100644
--- a/chromeos/network/managed_network_configuration_handler_unittest.cc
+++ b/chromeos/network/managed_network_configuration_handler_unittest.cc
@@ -805,8 +805,7 @@
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(ManagedNetworkConfigurationHandlerTest,
-       AllowOnlyPolicyNetworksToConnect) {
+TEST_F(ManagedNetworkConfigurationHandlerTest, AllowOnlyPolicyWiFiToConnect) {
   InitializeStandardProfiles();
 
   // Check transfer to NetworkStateHandler
@@ -815,22 +814,23 @@
       UpdateBlockedWifiNetworks(true, false, std::vector<std::string>()))
       .Times(1);
 
-  // Set 'AllowOnlyPolicyNetworksToConnect' policy and a random user policy.
+  // Set 'AllowOnlyPolicyWiFiToConnect' policy and another arbitrary user
+  // policy.
   SetPolicy(::onc::ONC_SOURCE_DEVICE_POLICY, std::string(),
             "policy/policy_allow_only_policy_networks_to_connect.onc");
   SetPolicy(::onc::ONC_SOURCE_USER_POLICY, kUser1, "policy/policy_wifi1.onc");
   base::RunLoop().RunUntilIdle();
 
   // Check ManagedNetworkConfigurationHandler policy accessors.
-  EXPECT_TRUE(managed_handler()->AllowOnlyPolicyNetworksToConnect());
-  EXPECT_FALSE(
-      managed_handler()->AllowOnlyPolicyNetworksToConnectIfAvailable());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyCellularNetworks());
+  EXPECT_TRUE(managed_handler()->AllowOnlyPolicyWiFiToConnect());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyWiFiToConnectIfAvailable());
   EXPECT_FALSE(managed_handler()->AllowOnlyPolicyNetworksToAutoconnect());
   EXPECT_TRUE(managed_handler()->GetBlockedHexSSIDs().empty());
 }
 
 TEST_F(ManagedNetworkConfigurationHandlerTest,
-       AllowOnlyPolicyNetworksToConnectIfAvailable) {
+       AllowOnlyPolicyWiFiToConnectIfAvailable) {
   InitializeStandardProfiles();
 
   // Check transfer to NetworkStateHandler
@@ -839,7 +839,8 @@
       UpdateBlockedWifiNetworks(false, true, std::vector<std::string>()))
       .Times(1);
 
-  // Set 'AllowOnlyPolicyNetworksToConnect' policy and a random user policy.
+  // Set 'AllowOnlyPolicyWiFiToConnectIfAvailable' policy and another arbitrary
+  // user policy.
   SetPolicy(
       ::onc::ONC_SOURCE_DEVICE_POLICY, std::string(),
       "policy/policy_allow_only_policy_networks_to_connect_if_available.onc");
@@ -847,8 +848,9 @@
   base::RunLoop().RunUntilIdle();
 
   // Check ManagedNetworkConfigurationHandler policy accessors.
-  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyNetworksToConnect());
-  EXPECT_TRUE(managed_handler()->AllowOnlyPolicyNetworksToConnectIfAvailable());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyCellularNetworks());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyWiFiToConnect());
+  EXPECT_TRUE(managed_handler()->AllowOnlyPolicyWiFiToConnectIfAvailable());
   EXPECT_FALSE(managed_handler()->AllowOnlyPolicyNetworksToAutoconnect());
   EXPECT_TRUE(managed_handler()->GetBlockedHexSSIDs().empty());
 }
@@ -863,20 +865,46 @@
       UpdateBlockedWifiNetworks(false, false, std::vector<std::string>()))
       .Times(1);
 
-  // Set 'AllowOnlyPolicyNetworksToAutoconnect' policy and a random user policy.
+  // Set 'AllowOnlyPolicyNetworksToAutoconnect' policy and another arbitrary
+  // user policy.
   SetPolicy(::onc::ONC_SOURCE_DEVICE_POLICY, std::string(),
             "policy/policy_allow_only_policy_networks_to_autoconnect.onc");
   SetPolicy(::onc::ONC_SOURCE_USER_POLICY, kUser1, "policy/policy_wifi1.onc");
   base::RunLoop().RunUntilIdle();
 
   // Check ManagedNetworkConfigurationHandler policy accessors.
-  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyNetworksToConnect());
-  EXPECT_FALSE(
-      managed_handler()->AllowOnlyPolicyNetworksToConnectIfAvailable());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyCellularNetworks());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyWiFiToConnect());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyWiFiToConnectIfAvailable());
   EXPECT_TRUE(managed_handler()->AllowOnlyPolicyNetworksToAutoconnect());
   EXPECT_TRUE(managed_handler()->GetBlockedHexSSIDs().empty());
 }
 
+TEST_F(ManagedNetworkConfigurationHandlerTest,
+       AllowOnlyPolicyCellularNetworks) {
+  InitializeStandardProfiles();
+
+  // Check transfer to NetworkStateHandler
+  EXPECT_CALL(
+      *network_state_handler_,
+      UpdateBlockedWifiNetworks(false, false, std::vector<std::string>()))
+      .Times(1);
+
+  // Set 'AllowOnlyPolicyCellularNetworks' policy and another arbitrary user
+  // policy.
+  SetPolicy(::onc::ONC_SOURCE_DEVICE_POLICY, std::string(),
+            "policy/policy_allow_only_policy_cellular_networks.onc");
+  SetPolicy(::onc::ONC_SOURCE_USER_POLICY, kUser1, "policy/policy_wifi1.onc");
+  base::RunLoop().RunUntilIdle();
+
+  // Check ManagedNetworkConfigurationHandler policy accessors.
+  EXPECT_TRUE(managed_handler()->AllowOnlyPolicyCellularNetworks());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyWiFiToConnect());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyWiFiToConnectIfAvailable());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyNetworksToAutoconnect());
+  EXPECT_TRUE(managed_handler()->GetBlockedHexSSIDs().empty());
+}
+
 // Test deprecated BlacklistedHexSSIDs property.
 TEST_F(ManagedNetworkConfigurationHandlerTest, GetBlacklistedHexSSIDs) {
   InitializeStandardProfiles();
@@ -887,16 +915,16 @@
               UpdateBlockedWifiNetworks(false, false, blocked))
       .Times(1);
 
-  // Set 'BlacklistedHexSSIDs' policy and a random user policy.
+  // Set 'BlacklistedHexSSIDs' policy and another arbitrary user policy.
   SetPolicy(::onc::ONC_SOURCE_DEVICE_POLICY, std::string(),
             "policy/policy_deprecated_blacklisted_hex_ssids.onc");
   SetPolicy(::onc::ONC_SOURCE_USER_POLICY, kUser1, "policy/policy_wifi1.onc");
   base::RunLoop().RunUntilIdle();
 
   // Check ManagedNetworkConfigurationHandler policy accessors.
-  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyNetworksToConnect());
-  EXPECT_FALSE(
-      managed_handler()->AllowOnlyPolicyNetworksToConnectIfAvailable());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyCellularNetworks());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyWiFiToConnect());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyWiFiToConnectIfAvailable());
   EXPECT_FALSE(managed_handler()->AllowOnlyPolicyNetworksToAutoconnect());
   EXPECT_EQ(blocked, managed_handler()->GetBlockedHexSSIDs());
 }
@@ -910,16 +938,16 @@
               UpdateBlockedWifiNetworks(false, false, blocked))
       .Times(1);
 
-  // Set 'BlockedHexSSIDs' policy and a random user policy.
+  // Set 'BlockedHexSSIDs' policy and another arbitrary user policy.
   SetPolicy(::onc::ONC_SOURCE_DEVICE_POLICY, std::string(),
             "policy/policy_blocked_hex_ssids.onc");
   SetPolicy(::onc::ONC_SOURCE_USER_POLICY, kUser1, "policy/policy_wifi1.onc");
   base::RunLoop().RunUntilIdle();
 
   // Check ManagedNetworkConfigurationHandler policy accessors.
-  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyNetworksToConnect());
-  EXPECT_FALSE(
-      managed_handler()->AllowOnlyPolicyNetworksToConnectIfAvailable());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyCellularNetworks());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyWiFiToConnect());
+  EXPECT_FALSE(managed_handler()->AllowOnlyPolicyWiFiToConnectIfAvailable());
   EXPECT_FALSE(managed_handler()->AllowOnlyPolicyNetworksToAutoconnect());
   EXPECT_EQ(blocked, managed_handler()->GetBlockedHexSSIDs());
 }
diff --git a/chromeos/network/mock_managed_network_configuration_handler.h b/chromeos/network/mock_managed_network_configuration_handler.h
index a885ea5..cdb5fe62 100644
--- a/chromeos/network/mock_managed_network_configuration_handler.h
+++ b/chromeos/network/mock_managed_network_configuration_handler.h
@@ -53,7 +53,7 @@
   MOCK_METHOD4(SetPolicy,
                void(::onc::ONCSource onc_source,
                     const std::string& userhash,
-                    const base::ListValue& network_configs_onc,
+                    const base::Value& network_configs_onc,
                     const base::DictionaryValue& global_network_config));
   MOCK_CONST_METHOD0(IsAnyPolicyApplicationRunning, bool());
   MOCK_CONST_METHOD3(
@@ -76,8 +76,9 @@
   MOCK_CONST_METHOD2(CanRemoveNetworkConfig,
                      bool(const std::string& guid,
                           const std::string& profile_path));
-  MOCK_CONST_METHOD0(AllowOnlyPolicyNetworksToConnect, bool());
-  MOCK_CONST_METHOD0(AllowOnlyPolicyNetworksToConnectIfAvailable, bool());
+  MOCK_CONST_METHOD0(AllowOnlyPolicyCellularNetworks, bool());
+  MOCK_CONST_METHOD0(AllowOnlyPolicyWiFiToConnect, bool());
+  MOCK_CONST_METHOD0(AllowOnlyPolicyWiFiToConnectIfAvailable, bool());
   MOCK_CONST_METHOD0(AllowOnlyPolicyNetworksToAutoconnect, bool());
   MOCK_CONST_METHOD0(GetBlockedHexSSIDs, std::vector<std::string>());
 
diff --git a/chromeos/network/network_connection_handler_impl_unittest.cc b/chromeos/network/network_connection_handler_impl_unittest.cc
index e4fbaaa5..f00be40 100644
--- a/chromeos/network/network_connection_handler_impl_unittest.cc
+++ b/chromeos/network/network_connection_handler_impl_unittest.cc
@@ -587,7 +587,7 @@
   ASSERT_FALSE(wifi0_service_path.empty());
   base::DictionaryValue global_config;
   global_config.SetKey(
-      ::onc::global_network_config::kAllowOnlyPolicyNetworksToConnect,
+      ::onc::global_network_config::kAllowOnlyPolicyWiFiToConnect,
       base::Value(true));
   SetupPolicy("[]", global_config, false /* load as device policy */);
   SetupPolicy("[]", base::DictionaryValue(), true /* load as user policy */);
diff --git a/chromeos/network/onc/onc_signature.cc b/chromeos/network/onc/onc_signature.cc
index e25ac2d..f1e403e 100644
--- a/chromeos/network/onc/onc_signature.cc
+++ b/chromeos/network/onc/onc_signature.cc
@@ -339,6 +339,7 @@
     {::onc::cellular::kSignalStrength, &kIntegerSignature},
     {::onc::cellular::kSIMLockStatus, &kSIMLockStatusSignature},
     {::onc::cellular::kSIMPresent, &kBoolSignature},
+    {::onc::cellular::kSMDPAddress, &kStringSignature},
     {::onc::cellular::kSupportNetworkScan, &kBoolSignature},
     {nullptr}};
 
@@ -379,11 +380,13 @@
     {nullptr}};
 
 const OncFieldSignature global_network_configuration_fields[] = {
+    {::onc::global_network_config::kAllowOnlyPolicyCellularNetworks,
+     &kBoolSignature},
     {::onc::global_network_config::kAllowOnlyPolicyNetworksToAutoconnect,
      &kBoolSignature},
-    {::onc::global_network_config::kAllowOnlyPolicyNetworksToConnect,
+    {::onc::global_network_config::kAllowOnlyPolicyWiFiToConnect,
      &kBoolSignature},
-    {::onc::global_network_config::kAllowOnlyPolicyNetworksToConnectIfAvailable,
+    {::onc::global_network_config::kAllowOnlyPolicyWiFiToConnectIfAvailable,
      &kBoolSignature},
     {/* Deprecated */ ::onc::global_network_config::kBlacklistedHexSSIDs,
      &kStringListSignature},
diff --git a/chromeos/network/onc/onc_validator.cc b/chromeos/network/onc/onc_validator.cc
index d831005..d7ec1f0 100644
--- a/chromeos/network/onc/onc_validator.cc
+++ b/chromeos/network/onc/onc_validator.cc
@@ -1061,16 +1061,19 @@
     }
   }
 
-  // Validate that kDisableNetworkTypes, kAllowOnlyPolicyNetworksToConnect and
-  // kBlockedHexSSIDs are only allowed in device policy.
+  // Validate that kDisableNetworkTypes, kAllowOnlyPolicyWiFiToConnect,
+  // kAllowOnlyPolicyCellularNetworks and kBlockedHexSSIDs are only allowed in
+  // device policy.
   if (!IsInDevicePolicy(result,
                         ::onc::global_network_config::kDisableNetworkTypes) ||
       !IsInDevicePolicy(
           result,
-          ::onc::global_network_config::kAllowOnlyPolicyNetworksToConnect) ||
-      !IsInDevicePolicy(result,
-                        ::onc::global_network_config::
-                            kAllowOnlyPolicyNetworksToConnectIfAvailable) ||
+          ::onc::global_network_config::kAllowOnlyPolicyCellularNetworks) ||
+      !IsInDevicePolicy(
+          result,
+          ::onc::global_network_config::kAllowOnlyPolicyWiFiToConnect) ||
+      !IsInDevicePolicy(result, ::onc::global_network_config::
+                                    kAllowOnlyPolicyWiFiToConnectIfAvailable) ||
       !IsInDevicePolicy(result,
                         ::onc::global_network_config::kBlockedHexSSIDs)) {
     return false;
diff --git a/chromeos/network/onc/onc_validator_unittest.cc b/chromeos/network/onc/onc_validator_unittest.cc
index 79fb606..4519725 100644
--- a/chromeos/network/onc/onc_validator_unittest.cc
+++ b/chromeos/network/onc/onc_validator_unittest.cc
@@ -166,6 +166,11 @@
                   &kToplevelConfigurationSignature,
                   true,
                   ::onc::ONC_SOURCE_DEVICE_POLICY),
+        // AllowOnlyPolicyCellularNetworks is only allowed for device policies.
+        OncParams("managed_toplevel_with_only_managed_cellular.onc",
+                  &kToplevelConfigurationSignature,
+                  true,
+                  ::onc::ONC_SOURCE_DEVICE_POLICY),
         OncParams("managed_toplevel_l2tpipsec.onc",
                   &kToplevelConfigurationSignature,
                   true),
diff --git a/chromeos/services/cros_healthd/public/cpp/service_connection_unittest.cc b/chromeos/services/cros_healthd/public/cpp/service_connection_unittest.cc
index cdaa337..03eb121 100644
--- a/chromeos/services/cros_healthd/public/cpp/service_connection_unittest.cc
+++ b/chromeos/services/cros_healthd/public/cpp/service_connection_unittest.cc
@@ -326,6 +326,9 @@
                NetworkDiagnosticsRoutines::RunVideoConferencingCallback),
               (override));
   MOCK_METHOD(void,
+              RunArcHttp,
+              (NetworkDiagnosticsRoutines::RunArcHttpCallback));
+  MOCK_METHOD(void,
               GetResult,
               (const network_diagnostics::mojom::RoutineType type,
                NetworkDiagnosticsRoutines::GetResultCallback),
diff --git a/chromeos/services/nearby/public/mojom/BUILD.gn b/chromeos/services/nearby/public/mojom/BUILD.gn
index 1bf13da..5a07371 100644
--- a/chromeos/services/nearby/public/mojom/BUILD.gn
+++ b/chromeos/services/nearby/public/mojom/BUILD.gn
@@ -22,6 +22,7 @@
     ":nearby_share_target_types",
     "//device/bluetooth/public/mojom:deprecated_experimental_interfaces",
     "//mojo/public/mojom/base",
+    "//sandbox/policy/mojom",
     "//services/network/public/mojom",
     "//url/mojom:url_mojom_gurl",
   ]
diff --git a/chromeos/services/nearby/public/mojom/sharing.mojom b/chromeos/services/nearby/public/mojom/sharing.mojom
index 4e2c833..3213d61e 100644
--- a/chromeos/services/nearby/public/mojom/sharing.mojom
+++ b/chromeos/services/nearby/public/mojom/sharing.mojom
@@ -6,11 +6,13 @@
 
 import "chromeos/services/nearby/public/mojom/nearby_connections.mojom";
 import "chromeos/services/nearby/public/mojom/nearby_decoder.mojom";
+import "sandbox/policy/mojom/sandbox.mojom";
 import "services/network/public/mojom/mdns_responder.mojom";
 import "services/network/public/mojom/p2p.mojom";
 
 // Interface for sharing related services. Lives in a sandboxed utility process
 // and is used by the browser process to offload unsafe protocol parsing.
+[ServiceSandbox=sandbox.mojom.Sandbox.kService]
 interface Sharing {
   // Connects to Nearby functionality: Nearby Connections and the Nearby Share
   // decoder. All dependencies should be provided via the |dependencies| struct.
diff --git a/chromeos/services/network_config/cros_network_config.cc b/chromeos/services/network_config/cros_network_config.cc
index 6148f0d..f293c2f 100644
--- a/chromeos/services/network_config/cros_network_config.cc
+++ b/chromeos/services/network_config/cros_network_config.cc
@@ -2655,15 +2655,18 @@
       network_configuration_handler_->GetGlobalConfigFromPolicy(
           /*userhash=*/std::string());
   if (global_policy_dict) {
+    result->allow_only_policy_cellular_networks = GetBoolean(
+        global_policy_dict,
+        ::onc::global_network_config::kAllowOnlyPolicyCellularNetworks);
     result->allow_only_policy_networks_to_autoconnect = GetBoolean(
         global_policy_dict,
         ::onc::global_network_config::kAllowOnlyPolicyNetworksToAutoconnect);
-    result->allow_only_policy_networks_to_connect = GetBoolean(
-        global_policy_dict,
-        ::onc::global_network_config::kAllowOnlyPolicyNetworksToConnect);
+    result->allow_only_policy_networks_to_connect =
+        GetBoolean(global_policy_dict,
+                   ::onc::global_network_config::kAllowOnlyPolicyWiFiToConnect);
     result->allow_only_policy_networks_to_connect_if_available = GetBoolean(
-        global_policy_dict, ::onc::global_network_config::
-                                kAllowOnlyPolicyNetworksToConnectIfAvailable);
+        global_policy_dict,
+        ::onc::global_network_config::kAllowOnlyPolicyWiFiToConnectIfAvailable);
     absl::optional<std::vector<std::string>> blocked_hex_ssids = GetStringList(
         global_policy_dict, ::onc::global_network_config::kBlockedHexSSIDs);
     if (blocked_hex_ssids)
diff --git a/chromeos/services/network_config/cros_network_config_unittest.cc b/chromeos/services/network_config/cros_network_config_unittest.cc
index 880514e..be63b4d 100644
--- a/chromeos/services/network_config/cros_network_config_unittest.cc
+++ b/chromeos/services/network_config/cros_network_config_unittest.cc
@@ -1630,7 +1630,7 @@
       ::onc::global_network_config::kAllowOnlyPolicyNetworksToAutoconnect,
       true);
   global_config.SetBoolKey(
-      ::onc::global_network_config::kAllowOnlyPolicyNetworksToConnect, false);
+      ::onc::global_network_config::kAllowOnlyPolicyWiFiToConnect, false);
   base::Value blocked(base::Value::Type::LIST);
   blocked.Append(base::Value("blocked_ssid1"));
   blocked.Append(base::Value("blocked_ssid2"));
@@ -1642,6 +1642,7 @@
   base::RunLoop().RunUntilIdle();
   mojom::GlobalPolicyPtr policy = GetGlobalPolicy();
   ASSERT_TRUE(policy);
+  EXPECT_EQ(false, policy->allow_only_policy_cellular_networks);
   EXPECT_EQ(true, policy->allow_only_policy_networks_to_autoconnect);
   EXPECT_EQ(false, policy->allow_only_policy_networks_to_connect);
   EXPECT_EQ(false, policy->allow_only_policy_networks_to_connect_if_available);
diff --git a/chromeos/services/network_config/public/mojom/cros_network_config.mojom b/chromeos/services/network_config/public/mojom/cros_network_config.mojom
index 9e57a05..d09ae88 100644
--- a/chromeos/services/network_config/public/mojom/cros_network_config.mojom
+++ b/chromeos/services/network_config/public/mojom/cros_network_config.mojom
@@ -845,6 +845,10 @@
 // Properties provided by GetGlobalPolicy. These properties affect network
 // configuration as a whole instead of individual network configurations.
 struct GlobalPolicy {
+  // If true, only cellular networks present in policy may be connected to and
+  // no new networks may be added or configured.
+  bool allow_only_policy_cellular_networks = false;
+
   // If true, only policy networks may auto connect.
   bool allow_only_policy_networks_to_autoconnect = false;
 
diff --git a/chromeos/services/network_health/public/mojom/network_diagnostics.mojom b/chromeos/services/network_health/public/mojom/network_diagnostics.mojom
index 2906548e..a03f335 100644
--- a/chromeos/services/network_health/public/mojom/network_diagnostics.mojom
+++ b/chromeos/services/network_health/public/mojom/network_diagnostics.mojom
@@ -25,6 +25,7 @@
   kHttpsFirewall,
   kHttpsLatency,
   kVideoConferencing,
+  kArcHttp,
 };
 
 // Each routine can result in one of the possible verdicts.
@@ -164,6 +165,21 @@
   kMediaFailure,
 };
 
+// Problems related to the ArcHttp routine.
+[Extensible]
+enum ArcHttpProblem {
+  // Failed to get the ARC Service Manager.
+  kFailedToGetArcServiceManager,
+  // Failed to get instance of the NetInstance service.
+  kFailedToGetNetInstanceForHttpTest,
+  // HTTPS request latency is high.
+  kHighLatency,
+  // HTTPS request latency is very high.
+  kVeryHighLatency,
+  // One or more HTTP requests resulted in a failure.
+  kFailedHttpRequests,
+};
+
 // Union of all possible routine problems. See each problem enum definition for
 // more details.
 union RoutineProblems {
@@ -179,6 +195,7 @@
   array<HttpsFirewallProblem> https_firewall_problems;
   array<HttpsLatencyProblem> https_latency_problems;
   array<VideoConferencingProblem> video_conferencing_problems;
+  array<ArcHttpProblem> arc_http_problems;
 };
 
 // A single routine's result.
@@ -293,6 +310,10 @@
   // (3) Reach common media endpoints.
   RunVideoConferencing(string? stun_server_hostname) => (RoutineResult result);
 
+  // Tests whether ARC can make successful HTTP GET requests to the resources
+  // needed during the ARC Provisioning step.
+  RunArcHttp() => (RoutineResult result);
+
   // Returns the most recent result for the specified routine type, if it has
   // been run.
   GetResult(RoutineType routine) => (RoutineResult? result);
diff --git a/chromeos/test/data/network/managed_toplevel_with_only_managed_cellular.onc b/chromeos/test/data/network/managed_toplevel_with_only_managed_cellular.onc
new file mode 100644
index 0000000..01257d0
--- /dev/null
+++ b/chromeos/test/data/network/managed_toplevel_with_only_managed_cellular.onc
@@ -0,0 +1,15 @@
+{
+  "GlobalNetworkConfiguration":{
+    "AllowOnlyPolicyCellularNetworks": true,
+  },
+  "NetworkConfigurations":[
+    {
+      "Ethernet":{
+        "Authentication":"None"
+      },
+      "GUID":"guid",
+      "Name":"name",
+      "Type":"Ethernet"
+    }
+  ]
+}
diff --git a/chromeos/test/data/network/policy/policy_allow_only_policy_cellular_networks.onc b/chromeos/test/data/network/policy/policy_allow_only_policy_cellular_networks.onc
new file mode 100644
index 0000000..2a95100
--- /dev/null
+++ b/chromeos/test/data/network/policy/policy_allow_only_policy_cellular_networks.onc
@@ -0,0 +1,8 @@
+{
+  "NetworkConfigurations": [
+  ],
+  "GlobalNetworkConfiguration": {
+    "AllowOnlyPolicyCellularNetworks": true
+  },
+  "Type": "UnencryptedConfiguration"
+}
\ No newline at end of file
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 363d32b2..54b9a347 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -48,10 +48,17 @@
 test("components_unittests") {
   use_xvfb = use_xvfb_in_this_config
 
-  if (is_android || is_linux || is_chromeos || is_mac || is_win) {
+  if (is_android || is_linux || is_chromeos || is_mac || is_win || is_fuchsia) {
     data = [ "test/data/" ]
   }
 
+  if (is_fuchsia) {
+    additional_manifest_fragments = [
+      # TODO(crbug.com/1185811): Figure out why jit_capabilities is needed.
+      "//build/config/fuchsia/test/jit_capabilities.test-cmx",
+    ]
+  }
+
   # Add only ":unit_tests" dependencies here.If your tests have dependencies
   #(this would at least include the component itself), they should be on the
   # test source set and not here.
diff --git a/components/arc/BUILD.gn b/components/arc/BUILD.gn
index 8f4dbfe..9660577 100644
--- a/components/arc/BUILD.gn
+++ b/components/arc/BUILD.gn
@@ -410,6 +410,8 @@
     "test/fake_memory_instance.h",
     "test/fake_nearby_share_instance.cc",
     "test/fake_nearby_share_instance.h",
+    "test/fake_net_instance.cc",
+    "test/fake_net_instance.h",
     "test/fake_pip_instance.cc",
     "test/fake_pip_instance.h",
     "test/fake_policy_instance.cc",
diff --git a/components/arc/ime/arc_ime_bridge_impl.cc b/components/arc/ime/arc_ime_bridge_impl.cc
index 4778168..a251f0e1 100644
--- a/components/arc/ime/arc_ime_bridge_impl.cc
+++ b/components/arc/ime/arc_ime_bridge_impl.cc
@@ -20,6 +20,17 @@
 namespace arc {
 namespace {
 
+mojom::SegmentStyle GetSegmentStyle(const ui::ImeTextSpan& ime_text_span) {
+  if (ime_text_span.thickness == ui::ImeTextSpan::Thickness::kNone ||
+      ime_text_span.underline_style == ui::ImeTextSpan::UnderlineStyle::kNone) {
+    return mojom::SegmentStyle::NONE;
+  }
+  if (ime_text_span.thickness == ui::ImeTextSpan::Thickness::kThick) {
+    return mojom::SegmentStyle::EMPHASIZED;
+  }
+  return mojom::SegmentStyle::DEFAULT;
+}
+
 std::vector<mojom::CompositionSegmentPtr> ConvertSegments(
     const ui::CompositionText& composition) {
   std::vector<mojom::CompositionSegmentPtr> segments;
@@ -31,6 +42,7 @@
         (ime_text_span.thickness == ui::ImeTextSpan::Thickness::kThick ||
          (composition.selection.start() == ime_text_span.start_offset &&
           composition.selection.end() == ime_text_span.end_offset));
+    segment->style = GetSegmentStyle(ime_text_span);
     segments.push_back(std::move(segment));
   }
   return segments;
diff --git a/components/arc/mojom/ime.mojom b/components/arc/mojom/ime.mojom
index 0e91a86..d8dbc52 100644
--- a/components/arc/mojom/ime.mojom
+++ b/components/arc/mojom/ime.mojom
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// Next MinVersion: 18
+// Next MinVersion: 19
 
 module arc.mojom;
 
@@ -33,6 +33,13 @@
 const int32 TEXT_INPUT_FLAG_AUTOCAPITALIZE_WORDS = 256;       // 1 << 8
 const int32 TEXT_INPUT_FLAG_AUTOCAPITALIZE_SENTENCES = 512;   // 1 << 9
 
+[Extensible]
+enum SegmentStyle {
+  DEFAULT,
+  EMPHASIZED,
+  NONE,
+};
+
 // Represents a single segment of text currently composed by IME.
 struct CompositionSegment {
   // Start offset of the segment in UTF-16 index.
@@ -40,7 +47,10 @@
   // End offset of the segment in UTF-16 index.
   uint32 end_offset;
   // Indicates that this segment should be emphasized.
+  // This field is deprecated, use |style| instead.
   bool emphasized;
+  // Visual style of this segment.
+  [MinVersion=18] SegmentStyle style;
 };
 
 // Represents the information of a key event.
diff --git a/components/arc/mojom/nearby_share.mojom b/components/arc/mojom/nearby_share.mojom
index e3635cf4..98e0dd1 100644
--- a/components/arc/mojom/nearby_share.mojom
+++ b/components/arc/mojom/nearby_share.mojom
@@ -51,7 +51,7 @@
 interface NearbyShareHost {
   // Used to create a NearbyShare session.
   StartNearbyShare@0(
-      int32 task_id,
+      uint32 task_id,
       ShareIntentInfo info,
       pending_remote<NearbyShareSessionInstance> instance)
       => (pending_remote<NearbyShareSessionHost> host);
diff --git a/components/arc/test/fake_net_instance.cc b/components/arc/test/fake_net_instance.cc
new file mode 100644
index 0000000..82b5005
--- /dev/null
+++ b/components/arc/test/fake_net_instance.cc
@@ -0,0 +1,48 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/arc/test/fake_net_instance.h"
+
+namespace arc {
+
+FakeNetInstance::FakeNetInstance() {}
+
+FakeNetInstance::~FakeNetInstance() = default;
+
+void FakeNetInstance::InitDeprecated(
+    mojo::PendingRemote<mojom::NetHost> host_remote) {}
+
+void FakeNetInstance::Init(::mojo::PendingRemote<mojom::NetHost> host_remote,
+                           InitCallback callback) {}
+
+void FakeNetInstance::ScanCompleted() {}
+
+void FakeNetInstance::WifiEnabledStateChanged(bool is_enabled) {}
+
+void FakeNetInstance::DisconnectAndroidVpn() {}
+
+void FakeNetInstance::ConfigureAndroidVpn() {}
+
+void FakeNetInstance::ActiveNetworksChanged(
+    std::vector<mojom::NetworkConfigurationPtr> network) {}
+
+void FakeNetInstance::DnsResolutionTest(const std::string& transport_name,
+                                        const std::string& host_name,
+                                        DnsResolutionTestCallback callback) {}
+
+void FakeNetInstance::HttpTest(const std::string& transport_name,
+                               const ::GURL& url,
+                               HttpTestCallback callback) {
+  mojom::ArcHttpTestResultPtr result_ptr = mojom::ArcHttpTestResult::New();
+  result_ptr->is_successful = http_test_result_.is_successful;
+  result_ptr->status_code = http_test_result_.status_code;
+  result_ptr->duration_ms = http_test_result_.duration_ms;
+  std::move(callback).Run(std::move(result_ptr));
+}
+
+void FakeNetInstance::PingTest(const std::string& transport_name,
+                               const std::string& ip_address,
+                               PingTestCallback callback) {}
+
+}  // namespace arc
diff --git a/components/arc/test/fake_net_instance.h b/components/arc/test/fake_net_instance.h
new file mode 100644
index 0000000..0b9e8840
--- /dev/null
+++ b/components/arc/test/fake_net_instance.h
@@ -0,0 +1,58 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_ARC_TEST_FAKE_NET_INSTANCE_H_
+#define COMPONENTS_ARC_TEST_FAKE_NET_INSTANCE_H_
+
+#include "components/arc/mojom/net.mojom.h"
+
+namespace arc {
+
+class FakeNetInstance : public mojom::NetInstance {
+ public:
+  FakeNetInstance();
+  ~FakeNetInstance() override;
+
+  FakeNetInstance(const FakeNetInstance&) = delete;
+  FakeNetInstance& operator=(const FakeNetInstance&) = delete;
+
+  void InitDeprecated(mojo::PendingRemote<mojom::NetHost> host_remote) override;
+
+  void Init(::mojo::PendingRemote<mojom::NetHost> host_remote,
+            InitCallback callback) override;
+
+  void ScanCompleted() override;
+
+  void WifiEnabledStateChanged(bool is_enabled) override;
+
+  void DisconnectAndroidVpn() override;
+
+  void ConfigureAndroidVpn() override;
+
+  void ActiveNetworksChanged(
+      std::vector<mojom::NetworkConfigurationPtr> network) override;
+
+  void DnsResolutionTest(const std::string& transport_name,
+                         const std::string& host_name,
+                         DnsResolutionTestCallback callback) override;
+
+  void HttpTest(const std::string& transport_name,
+                const ::GURL& url,
+                HttpTestCallback callback) override;
+
+  void PingTest(const std::string& transport_name,
+                const std::string& ip_address,
+                PingTestCallback callback) override;
+
+  void set_http_test_result(mojom::ArcHttpTestResult http_test_result) {
+    http_test_result_ = http_test_result;
+  }
+
+ private:
+  mojom::ArcHttpTestResult http_test_result_;
+};
+
+}  // namespace arc
+
+#endif  // COMPONENTS_ARC_TEST_FAKE_NET_INSTANCE_H_
diff --git a/components/autofill/core/browser/autofill_download_manager.cc b/components/autofill/core/browser/autofill_download_manager.cc
index 2a20824..606ca7d 100644
--- a/components/autofill/core/browser/autofill_download_manager.cc
+++ b/components/autofill/core/browser/autofill_download_manager.cc
@@ -420,6 +420,24 @@
   if (upload.has_has_form_tag())
     out << Tr{} << "has_form_tag:" << upload.has_form_tag();
 
+  if (upload.has_single_username_data()) {
+    LogBuffer single_username_data;
+    single_username_data << Tag{"span"} << "[";
+    single_username_data
+        << Tr{} << "username_form_signature:"
+        << upload.single_username_data().username_form_signature();
+    single_username_data
+        << Tr{} << "username_field_signature:"
+        << upload.single_username_data().username_field_signature();
+    single_username_data << Tr{} << "value_type:"
+                         << static_cast<int>(
+                                upload.single_username_data().value_type());
+    single_username_data << Tr{} << "prompt_edit:"
+                         << static_cast<int>(
+                                upload.single_username_data().prompt_edit());
+    out << Tr{} << "single_username_data" << std::move(single_username_data);
+  }
+
   out << Tr{} << "form_signature:" << upload.form_signature();
   for (const auto& field : upload.field()) {
     out << Tr{} << Attrib{"style", "font-weight: bold"}
diff --git a/components/autofill/core/browser/autofill_regex_constants.cc b/components/autofill/core/browser/autofill_regex_constants.cc
index b6e8edf..08271ba 100644
--- a/components/autofill/core/browser/autofill_regex_constants.cc
+++ b/components/autofill/core/browser/autofill_regex_constants.cc
@@ -606,4 +606,12 @@
 const char16_t kMerchantPromoCodeRe[] =
     u"\\bpromo code\\b|\\bcoupon code\\b|\\bgift code\\b";
 
+/////////////////////////////////////////////////////////////////////////////
+// votes_uploader.cc
+/////////////////////////////////////////////////////////////////////////////
+const char16_t kEmailValueRe[] =
+    u"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$";
+const char16_t kPhoneValueRe[] = u"^[0-9()+-]{6,25}$";
+const char16_t kUsernameLikeValueRe[] = u"[A-Za-z0-9_\\-.]{7,30}";
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/autofill_regex_constants.h b/components/autofill/core/browser/autofill_regex_constants.h
index 4434193..c606c849 100644
--- a/components/autofill/core/browser/autofill_regex_constants.h
+++ b/components/autofill/core/browser/autofill_regex_constants.h
@@ -77,6 +77,9 @@
 extern const char16_t kOneTimePwdRe[];
 extern const char16_t kHiddenValueRe[];
 extern const char16_t kMerchantPromoCodeRe[];
+extern const char16_t kEmailValueRe[];
+extern const char16_t kPhoneValueRe[];
+extern const char16_t kUsernameLikeValueRe[];
 
 // Used to match field data that might be a UPI Virtual Payment Address.
 // See:
diff --git a/components/autofill/core/browser/form_structure.cc b/components/autofill/core/browser/form_structure.cc
index 614b542..ee001d17 100644
--- a/components/autofill/core/browser/form_structure.cc
+++ b/components/autofill/core/browser/form_structure.cc
@@ -722,6 +722,8 @@
   if (!current_page_language_->empty() && randomized_encoder_ != nullptr) {
     upload->set_language(current_page_language_.value());
   }
+  if (single_username_data_)
+    upload->mutable_single_username_data()->CopyFrom(*single_username_data_);
 
   auto triggering_event = (submission_event_ != SubmissionIndicatorEvent::NONE)
                               ? submission_event_
diff --git a/components/autofill/core/browser/form_structure.h b/components/autofill/core/browser/form_structure.h
index 6084cf6..fb4fce4 100644
--- a/components/autofill/core/browser/form_structure.h
+++ b/components/autofill/core/browser/form_structure.h
@@ -417,6 +417,15 @@
                          form_interactions_ukm_logger, nullptr);
   }
 
+  void set_single_username_data(
+      AutofillUploadContents::SingleUsernameData single_username_data) {
+    single_username_data_ = single_username_data;
+  }
+  absl::optional<AutofillUploadContents::SingleUsernameData>
+  single_username_data() const {
+    return single_username_data_;
+  }
+
  private:
   friend class AutofillMergeTest;
   friend class FormStructureTestImpl;
@@ -674,6 +683,10 @@
   // frame.
   FormRendererId unique_renderer_id_;
 
+  // Single username details, if applicable.
+  absl::optional<AutofillUploadContents::SingleUsernameData>
+      single_username_data_;
+
   DISALLOW_COPY_AND_ASSIGN(FormStructure);
 };
 
diff --git a/components/autofill/core/browser/form_structure_unittest.cc b/components/autofill/core/browser/form_structure_unittest.cc
index a2adce3..3f881369 100644
--- a/components/autofill/core/browser/form_structure_unittest.cc
+++ b/components/autofill/core/browser/form_structure_unittest.cc
@@ -5154,7 +5154,6 @@
   form.fields.push_back(field);
 
   FormStructure form_structure(form);
-  form_structure.set_passwords_were_revealed(true);
   form_structure.field(0)->set_single_username_vote_type(
       AutofillUploadContents::Field::STRONG);
 
@@ -5168,6 +5167,39 @@
             upload.field(0).single_username_vote_type());
 }
 
+TEST_F(FormStructureTestImpl, EncodeUploadRequest_WithSingleUsernameData) {
+  FormData form;
+  form.url = GURL("http://www.foo.com/");
+  FormFieldData field;
+  field.name = u"text field";
+  field.unique_renderer_id = MakeFieldRendererId();
+  form.fields.push_back(field);
+
+  FormStructure form_structure(form);
+
+  AutofillUploadContents::SingleUsernameData single_username_data;
+  single_username_data.set_username_form_signature(12345);
+  single_username_data.set_username_field_signature(678910);
+  single_username_data.set_value_type(AutofillUploadContents::EMAIL);
+  single_username_data.set_prompt_edit(AutofillUploadContents::EDITED_POSITIVE);
+  form_structure.set_single_username_data(single_username_data);
+
+  AutofillUploadContents upload;
+  std::vector<FormSignature> signatures;
+  EXPECT_TRUE(form_structure.EncodeUploadRequest(
+      {{}} /* available_field_types */, false /* form_was_autofilled */,
+      std::string() /* login_form_signature */, true /* observed_submission */,
+      false /* is_raw_metadata_uploading_enabled */, &upload, &signatures));
+  EXPECT_EQ(form_structure.single_username_data()->username_form_signature(),
+            upload.single_username_data().username_form_signature());
+  EXPECT_EQ(form_structure.single_username_data()->username_field_signature(),
+            upload.single_username_data().username_field_signature());
+  EXPECT_EQ(form_structure.single_username_data()->value_type(),
+            upload.single_username_data().value_type());
+  EXPECT_EQ(form_structure.single_username_data()->prompt_edit(),
+            upload.single_username_data().prompt_edit());
+}
+
 // Test that server predictions get precedence over htmll types if they are
 // overrides.
 TEST_F(FormStructureTestImpl, ParseQueryResponse_ServerPredictionIsOverride) {
diff --git a/components/autofill/core/browser/proto/server.proto b/components/autofill/core/browser/proto/server.proto
index 6ecd17f..96ecfa7 100644
--- a/components/autofill/core/browser/proto/server.proto
+++ b/components/autofill/core/browser/proto/server.proto
@@ -454,6 +454,39 @@
 
   // Captures whether or not this upload was a candidate for throttling.
   optional bool was_throttleable = 38;
+
+  // Type of the value seen in the potential single username field.
+  enum ValueType {
+    TYPE_UNSPECIFIED = 0;
+    NO_VALUE_TYPE = 1;
+    STORED_FOR_CURRENT_DOMAIN = 2;
+    // TODO(crbug.com/959776) Not used yet. Check against all domains to be
+    // implemented.
+    STORED_FOR_ANOTHER_DOMAIN = 3;
+    EMAIL = 4;
+    PHONE = 5;
+    USERNAME_LIKE = 6;
+    VALUE_WITH_NO_WHITESPACE = 7;
+    VALUE_WITH_WHITESPACE = 8;
+  }
+  // Information received from user interaction with the prompt.
+  enum SingleUsernamePromptEdit {
+    EDIT_UNSPECIFIED = 0;
+    NOT_EDITED_POSITIVE = 1;
+    NOT_EDITED_NEGATIVE = 2;
+    EDITED_POSITIVE = 3;
+    EDITED_NEGATIVE = 4;
+  }
+  message SingleUsernameData {
+    optional fixed64 username_form_signature = 1;
+    optional fixed32 username_field_signature = 2;
+    optional ValueType value_type = 3;
+    optional SingleUsernamePromptEdit prompt_edit = 4;
+  }
+  // This message is recorded on forms that do not have a username field.
+  // The message contains data on a preceding form with potential username
+  // field.
+  optional SingleUsernameData single_username_data = 42;
 }
 
 // This proto contains information about the validity of each field in an
diff --git a/components/component_updater/android/component_loader_policy.h b/components/component_updater/android/component_loader_policy.h
index 47b13f1..0d0f3b8 100644
--- a/components/component_updater/android/component_loader_policy.h
+++ b/components/component_updater/android/component_loader_policy.h
@@ -93,7 +93,7 @@
   // Returns a Human readable string that can be used as a suffix for recorded
   // UMA metrics. New suffixes should be added to
   // "ComponentUpdater.AndroidComponentLoader.ComponentName" in
-  // tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml.
+  // tools/metrics/histograms/metadata/histogram_suffixes_list.xml.
   virtual std::string GetMetricsSuffix() const = 0;
 };
 
diff --git a/components/continuous_search/common/public/mojom/continuous_search.mojom b/components/continuous_search/common/public/mojom/continuous_search.mojom
index 11c793cf..74bf2c3 100644
--- a/components/continuous_search/common/public/mojom/continuous_search.mojom
+++ b/components/continuous_search/common/public/mojom/continuous_search.mojom
@@ -27,6 +27,9 @@
 
   // Ads.
   kAds,
+
+  // Related searches.
+  kRelatedSearches
 };
 
 // A logically related group of search results with a label. E.g. ads, organic,
diff --git a/components/continuous_search/renderer/search_result_extractor_browsertest.cc b/components/continuous_search/renderer/search_result_extractor_browsertest.cc
index e71d475..849dca7 100644
--- a/components/continuous_search/renderer/search_result_extractor_browsertest.cc
+++ b/components/continuous_search/renderer/search_result_extractor_browsertest.cc
@@ -262,11 +262,130 @@
       std::move(expected_results));
 }
 
+TEST_F(SearchResultExtractorImplRenderViewTest, TestExtractRelatedSearches) {
+  auto result1 = mojom::SearchResult::New();
+  result1->link = GURL("https://www.example1.com/");
+  result1->title = u"Related 1";
+
+  auto result2 = mojom::SearchResult::New();
+  result2->link = GURL("https://www.example2.com/");
+  result2->title = u"Related 2";
+
+  auto related_searches_group = mojom::ResultGroup::New();
+  related_searches_group->type = mojom::ResultType::kRelatedSearches;
+  related_searches_group->results.push_back(std::move(result1));
+  related_searches_group->results.push_back(std::move(result2));
+
+  auto expected_results = mojom::CategoryResults::New();
+  expected_results->category_type = mojom::Category::kOrganic;
+  expected_results->groups.push_back(std::move(related_searches_group));
+
+  LoadHtmlAndExpectExtractedOutput(
+      R"(<!doctype html>
+         <body>
+           <div id="w3bYAd">
+             <div class="foo">
+               <a href="https://www.example1.com/" class="k8XOCe bar">
+                 <div class="s75CSd bar">
+                   <span>Related 1</span>
+                 </div>
+               </a>
+               <a href="https://www.example2.com/" class="k8XOCe baz">
+                 <div class="s75CSd baz">
+                   <span>Related 2</span>
+                 </div>
+               </a>
+             </div>
+             <div class="mnr-c bar">
+               <a href="https://www.example1.com/" class="k8XOCe buz">
+                 <div>
+                   <span>Skipped</span>
+                 </div>
+               </a>
+             </div>
+           </div>
+         </body>)",
+      {mojom::ResultType::kRelatedSearches},
+      mojom::SearchResultExtractor::Status::kSuccess,
+      std::move(expected_results));
+}
+
 // The tests below this line are intended to test the branching of the
 // extractor. The goal is to ensure there are no scenarios where the extraction
 // might crash/fail if an almost correct result is presented.
 
-TEST_F(SearchResultExtractorImplRenderViewTest, TestNoRso) {
+TEST_F(SearchResultExtractorImplRenderViewTest,
+       TestExtractRelatedSearchesNoId) {
+  // No id="w3bYAd".
+  LoadHtmlAndExpectExtractedOutput(
+      R"(<!doctype html>
+         <body>
+           <div>
+             <a href="https://www.example1.com/" class="k8XOCe bar">
+               <div class="s75CSd bar">
+                 <span>Related 1</span>
+               </div>
+             </a>
+           </div>
+         </body>)",
+      {mojom::ResultType::kRelatedSearches},
+      mojom::SearchResultExtractor::Status::kNoResults,
+      mojom::CategoryResults::New());
+}
+
+TEST_F(SearchResultExtractorImplRenderViewTest,
+       TestExtractRelatedSearchesNoAnchors) {
+  // No anchors.
+  LoadHtmlAndExpectExtractedOutput(
+      R"(<!doctype html>
+         <body>
+           <div id="w3bYAd">
+           </div>
+         </body>)",
+      {mojom::ResultType::kRelatedSearches},
+      mojom::SearchResultExtractor::Status::kNoResults,
+      mojom::CategoryResults::New());
+}
+
+TEST_F(SearchResultExtractorImplRenderViewTest,
+       TestExtractRelatedSearchesNoAnchorClass) {
+  // No "k8XOCe" class on anchors.
+  LoadHtmlAndExpectExtractedOutput(
+      R"(<!doctype html>
+         <body>
+           <div id="w3bYAd">
+             <a href="https://www.example1.com/" class="bar">
+               <div class="s75CSd bar">
+                 <span>Related 1</span>
+               </div>
+             </a>
+           </div>
+         </body>)",
+      {mojom::ResultType::kRelatedSearches},
+      mojom::SearchResultExtractor::Status::kNoResults,
+      mojom::CategoryResults::New());
+}
+
+TEST_F(SearchResultExtractorImplRenderViewTest,
+       TestExtractRelatedSearchesNoTitleClass) {
+  // No "s75CSd" class on title div.
+  LoadHtmlAndExpectExtractedOutput(
+      R"(<!doctype html>
+         <body>
+           <div id="w3bYAd">
+             <a href="https://www.example1.com/" class="k8XOCe bar">
+               <div class="bar">
+                 <span>Related 1</span>
+               </div>
+             </a>
+           </div>
+         </body>)",
+      {mojom::ResultType::kRelatedSearches},
+      mojom::SearchResultExtractor::Status::kNoResults,
+      mojom::CategoryResults::New());
+}
+
+TEST_F(SearchResultExtractorImplRenderViewTest, TestExtractResultsNoRso) {
   // No class="rso".
   LoadHtmlAndExpectExtractedOutput(
       R"(<!doctype html>
@@ -284,7 +403,7 @@
       mojom::CategoryResults::New());
 }
 
-TEST_F(SearchResultExtractorImplRenderViewTest, TestExtractNoDivs) {
+TEST_F(SearchResultExtractorImplRenderViewTest, TestExtractResultsNoDivs) {
   // No divs inside "rso".
   LoadHtmlAndExpectExtractedOutput(
       R"(<!doctype html>
@@ -300,7 +419,8 @@
       mojom::CategoryResults::New());
 }
 
-TEST_F(SearchResultExtractorImplRenderViewTest, TestExtractNoMnrCardNoClass) {
+TEST_F(SearchResultExtractorImplRenderViewTest,
+       TestExtractResultsNoMnrCardNoClass) {
   // No class attribute on inner divs.
   LoadHtmlAndExpectExtractedOutput(
       R"(<!doctype html>
@@ -337,7 +457,8 @@
       mojom::CategoryResults::New());
 }
 
-TEST_F(SearchResultExtractorImplRenderViewTest, TestExtractNoLinkNoAnchor) {
+TEST_F(SearchResultExtractorImplRenderViewTest,
+       TestExtractResultsNoLinkNoAnchor) {
   // No anchor inside mnr-c.
   LoadHtmlAndExpectExtractedOutput(
       R"(<!doctype html>
@@ -355,7 +476,8 @@
       mojom::CategoryResults::New());
 }
 
-TEST_F(SearchResultExtractorImplRenderViewTest, TestExtractNoLinkNoHref) {
+TEST_F(SearchResultExtractorImplRenderViewTest,
+       TestExtractResultsNoLinkNoHref) {
   // No href for the anchor.
   LoadHtmlAndExpectExtractedOutput(
       R"(<!doctype html>
@@ -373,7 +495,8 @@
       mojom::CategoryResults::New());
 }
 
-TEST_F(SearchResultExtractorImplRenderViewTest, TestExtractNoLinkEmptyHref) {
+TEST_F(SearchResultExtractorImplRenderViewTest,
+       TestExtractResultsNoLinkEmptyHref) {
   // Empty href.
   LoadHtmlAndExpectExtractedOutput(
       R"(<!doctype html>
@@ -391,7 +514,8 @@
       mojom::CategoryResults::New());
 }
 
-TEST_F(SearchResultExtractorImplRenderViewTest, TestExtractNoLinkWrongScheme) {
+TEST_F(SearchResultExtractorImplRenderViewTest,
+       TestExtractResultsNoLinkWrongScheme) {
   // href is not http/https.
   LoadHtmlAndExpectExtractedOutput(
       R"(<!doctype html>
@@ -409,7 +533,8 @@
       mojom::CategoryResults::New());
 }
 
-TEST_F(SearchResultExtractorImplRenderViewTest, TestExtractNoTitleNoDiv) {
+TEST_F(SearchResultExtractorImplRenderViewTest,
+       TestExtractResultsNoTitleNoDiv) {
   // No inner div.
   LoadHtmlAndExpectExtractedOutput(
       R"(<!doctype html>
@@ -427,7 +552,8 @@
       mojom::CategoryResults::New());
 }
 
-TEST_F(SearchResultExtractorImplRenderViewTest, TestExtractNoTitleNoRole) {
+TEST_F(SearchResultExtractorImplRenderViewTest,
+       TestExtractResultsNoTitleNoRole) {
   // Inner div has no role.
   LoadHtmlAndExpectExtractedOutput(
       R"(<!doctype html>
@@ -446,7 +572,7 @@
 }
 
 TEST_F(SearchResultExtractorImplRenderViewTest,
-       TestExtractNoTitleNotDivHeading) {
+       TestExtractResultsNoTitleNotDivHeading) {
   // Not a div, but role="heading".
   LoadHtmlAndExpectExtractedOutput(
       R"(<!doctype html>
@@ -464,7 +590,8 @@
       mojom::CategoryResults::New());
 }
 
-TEST_F(SearchResultExtractorImplRenderViewTest, TestExtractNoTitleNoText) {
+TEST_F(SearchResultExtractorImplRenderViewTest,
+       TestExtractResultsNoTitleNoText) {
   // No text.
   LoadHtmlAndExpectExtractedOutput(
       R"(<!doctype html>
diff --git a/components/continuous_search/renderer/search_result_extractor_impl.cc b/components/continuous_search/renderer/search_result_extractor_impl.cc
index 6328777..5d3a8ea 100644
--- a/components/continuous_search/renderer/search_result_extractor_impl.cc
+++ b/components/continuous_search/renderer/search_result_extractor_impl.cc
@@ -7,8 +7,8 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/containers/contains.h"
 #include "base/strings/string_util.h"
-#include "base/strings/utf_string_conversions.h"
 #include "components/continuous_search/common/title_validator.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
 #include "third_party/blink/public/platform/web_string.h"
@@ -132,6 +132,78 @@
   return true;
 }
 
+bool ExtractRelatedSearches(blink::WebDocument document,
+                            mojom::CategoryResultsPtr& results) {
+  auto group = mojom::ResultGroup::New();
+  group->type = mojom::ResultType::kRelatedSearches;
+
+  blink::WebElement container = document.GetElementById("w3bYAd");
+  if (container.IsNull()) {
+    return false;
+  }
+
+  blink::WebElementCollection anchors = container.GetElementsByHTMLTagName("a");
+  if (anchors.IsNull()) {
+    return false;
+  }
+
+  // Loop through the anchors inside id="w3bYAd" and extract urls and titles.
+  // This only works on Desktop SRP. Related Searches anchor elements use a
+  // different class name on Mobile. To enable this on Mobile, either check for
+  // the additional class name or remove the class name check for the anchors.
+  for (blink::WebElement anchor = anchors.FirstItem(); !anchor.IsNull();
+       anchor = anchors.NextItem()) {
+    if (!anchor.HasAttribute("class") ||
+        !base::Contains(anchor.GetAttribute("class").Utf8(), "k8XOCe")) {
+      continue;
+    }
+
+    blink::WebString string_url = anchor.GetAttribute("href");
+    if (string_url.IsNull() || string_url.IsEmpty()) {
+      continue;
+    }
+
+    GURL url = GURL(document.CompleteURL(string_url));
+    if (!url.is_valid() || url.is_empty() || !url.SchemeIsHTTPOrHTTPS()) {
+      continue;
+    }
+
+    std::u16string title;
+    blink::WebElementCollection inner_divs =
+        anchor.GetElementsByHTMLTagName("div");
+    if (inner_divs.IsNull()) {
+      continue;
+    }
+    for (blink::WebElement inner_div = inner_divs.FirstItem();
+         !inner_div.IsNull(); inner_div = inner_divs.NextItem()) {
+      if (!inner_div.HasAttribute("class") ||
+          !base::Contains(inner_div.GetAttribute("class").Utf8(), "s75CSd")) {
+        continue;
+      }
+      title = inner_div.TextContent().Utf16();
+      break;
+    }
+
+    title = base::CollapseWhitespace(title, true);
+    if (title.empty()) {
+      continue;
+    }
+
+    auto result = mojom::SearchResult::New();
+    result->link = url;
+    result->title = ValidateTitle(title);
+    group->results.push_back(std::move(result));
+  }
+
+  if (group->results.empty()) {
+    return false;
+  }
+
+  results->category_type = mojom::Category::kOrganic;
+  results->groups.push_back(std::move(group));
+  return true;
+}
+
 }  // namespace
 
 // static
@@ -173,6 +245,15 @@
           return;
         }
         break;
+      case mojom::ResultType::kRelatedSearches:
+        if (!ExtractRelatedSearches(document, category_result)) {
+          // Extracting related searches is a requirement, if requested.
+          std::move(callback).Run(
+              mojom::SearchResultExtractor::Status::kNoResults,
+              std::move(category_result));
+          return;
+        }
+        break;
     }
   }
 
diff --git a/components/favicon/DEPS b/components/favicon/DEPS
index 9c235365e..6f5be78c 100644
--- a/components/favicon/DEPS
+++ b/components/favicon/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+components/favicon_base",
+  "+third_party/blink/public/mojom/manifest",
   "+ui/base",
   "+ui/gfx",
 ]
diff --git a/components/favicon/content/content_favicon_driver.cc b/components/favicon/content/content_favicon_driver.cc
index c51caba4..c914b47 100644
--- a/components/favicon/content/content_favicon_driver.cc
+++ b/components/favicon/content/content_favicon_driver.cc
@@ -16,7 +16,7 @@
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/page.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "ui/gfx/image/image.h"
 
 namespace favicon {
@@ -75,7 +75,7 @@
 void ContentFaviconDriver::OnDidDownloadManifest(
     ManifestDownloadCallback callback,
     const GURL& manifest_url,
-    const blink::Manifest& manifest) {
+    blink::mojom::ManifestPtr manifest) {
   // ~WebContentsImpl triggers running any pending callbacks for manifests.
   // As we're about to be destroyed ignore the request. To do otherwise may
   // result in calling back to this and attempting to use the WebContents, which
@@ -84,9 +84,11 @@
     return;
 
   std::vector<FaviconURL> candidates;
-  for (const auto& icon : manifest.icons) {
-    candidates.emplace_back(icon.src, favicon_base::IconType::kWebManifestIcon,
-                            icon.sizes);
+  if (manifest) {
+    for (const auto& icon : manifest->icons) {
+      candidates.emplace_back(
+          icon.src, favicon_base::IconType::kWebManifestIcon, icon.sizes);
+    }
   }
   std::move(callback).Run(candidates);
 }
diff --git a/components/favicon/content/content_favicon_driver.h b/components/favicon/content/content_favicon_driver.h
index 92c54be..cac93989 100644
--- a/components/favicon/content/content_favicon_driver.h
+++ b/components/favicon/content/content_favicon_driver.h
@@ -15,12 +15,9 @@
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 #include "url/gurl.h"
 
-namespace blink {
-struct Manifest;
-}  // namespace blink
-
 namespace favicon {
 
 class CoreFaviconService;
@@ -71,7 +68,7 @@
   // Callback when a manifest is downloaded.
   void OnDidDownloadManifest(ManifestDownloadCallback callback,
                              const GURL& manifest_url,
-                             const blink::Manifest& manifest);
+                             blink::mojom::ManifestPtr manifest);
 
   // FaviconHandler::Delegate implementation.
   int DownloadImage(const GURL& url,
diff --git a/components/history/core/browser/BUILD.gn b/components/history/core/browser/BUILD.gn
index 1418217..beff231 100644
--- a/components/history/core/browser/BUILD.gn
+++ b/components/history/core/browser/BUILD.gn
@@ -174,6 +174,7 @@
     "//components/test/data/history/history.42.sql",
     "//components/test/data/history/history.43.sql",
     "//components/test/data/history/history.44.sql",
+    "//components/test/data/history/history.45.sql",
     "//components/test/data/history/thumbnail_wild/Favicons.corrupt_meta.disable",
     "//components/test/data/history/thumbnail_wild/Favicons.v2.init.sql",
     "//components/test/data/history/thumbnail_wild/Favicons.v3.init.sql",
@@ -220,6 +221,7 @@
     ":unit_tests_bundle_data",
     "//base",
     "//base/test:test_support",
+    "//components/enterprise/common:download_item_reroute_info",
     "//components/favicon/core:database",
     "//components/favicon_base",
     "//components/history/core/common",
diff --git a/components/history/core/browser/DEPS b/components/history/core/browser/DEPS
index 197d2ce..748dca2a 100644
--- a/components/history/core/browser/DEPS
+++ b/components/history/core/browser/DEPS
@@ -8,4 +8,5 @@
   "+components/optimization_guide",
   "+components/signin/public/identity_manager",
   "+components/variations",
+  "+components/enterprise/common/download_item_reroute_info.h"
 ]
diff --git a/components/history/core/browser/download_database.cc b/components/history/core/browser/download_database.cc
index 107257c..b043c70 100644
--- a/components/history/core/browser/download_database.cc
+++ b/components/history/core/browser/download_database.cc
@@ -33,6 +33,7 @@
 const char kDownloadsTable[] = "downloads";
 
 const char kDownloadsSlicesTable[] = "downloads_slices";
+const char kDownloadsRerouteInfoTable[] = "downloads_reroute_info";
 
 // Reason for dropping a particular record. Used for UMA.
 enum DroppedReason {
@@ -363,6 +364,13 @@
       "PRIMARY KEY (download_id, offset) )",
       kDownloadsSlicesTable);
 
+  const std::string kRerouteInfoSchema = base::StringPrintf(
+      "CREATE TABLE %s ("
+      "download_id INTEGER NOT NULL,"               // downloads.id.
+      "reroute_info_serialized  VARCHAR NOT NULL,"  // Reroute info.
+      "PRIMARY KEY (download_id) )",
+      kDownloadsRerouteInfoTable);
+
   bool ret;
   if (GetDB().DoesTableExist(kDownloadsTable)) {
     // If the "downloads" table exists, "downloads_url_chain" might not be there
@@ -379,10 +387,15 @@
           GetDB().Execute(kUrlChainSchema);
   }
 
-  // Making sure the "downloads_slices" table is created as it is introduced in
-  // version 33. This table doesn't require migration of existing tables.
-  return ret && (GetDB().DoesTableExist(kDownloadsSlicesTable) ||
-                 GetDB().Execute(kSlicesSchema.c_str()));
+  // Making sure these tables introduced in later versions are created. They
+  // don't require migration of existing tables. The "downloads_slices" table is
+  // introduced in version 33. The "downloads_reroute_info" table is introduced
+  // in version 46.
+  return ret &&
+         (GetDB().DoesTableExist(kDownloadsSlicesTable) ||
+          GetDB().Execute(kSlicesSchema.c_str())) &&
+         (GetDB().DoesTableExist(kDownloadsRerouteInfoTable) ||
+          GetDB().Execute(kRerouteInfoSchema.c_str()));
 }
 
 uint32_t DownloadDatabase::GetNextDownloadId() {
@@ -535,6 +548,7 @@
   }
 
   QueryDownloadSlices(&info_map);
+  QueryDownloadRerouteInfos(&info_map);
 
   for (auto it = info_map.begin(); it != info_map.end(); ++it) {
     DownloadRow* row = it->second;
@@ -612,6 +626,13 @@
     }
   }
 
+  if (data.reroute_info_serialized.empty()) {
+    RemoveDownloadRerouteInfo(data.id);
+  } else if (!CreateOrUpdateDownloadRerouteInfo(data.id,
+                                                data.reroute_info_serialized)) {
+    return false;
+  }
+
   return true;
 }
 
@@ -748,6 +769,11 @@
     }
   }
 
+  if (!CreateOrUpdateDownloadRerouteInfo(info.id,
+                                         info.reroute_info_serialized)) {
+    return false;
+  }
+
   return true;
 }
 
@@ -766,6 +792,7 @@
   }
   RemoveDownloadURLs(id);
   RemoveDownloadSlices(id);
+  RemoveDownloadRerouteInfo(id);
 }
 
 void DownloadDatabase::RemoveDownloadURLs(DownloadId id) {
@@ -835,8 +862,8 @@
     // Convert signed integer from sqlite to unsigned DownloadId.
     int64_t signed_id = statement_query.ColumnInt64(column++);
     bool success = ConvertIntToDownloadId(signed_id, &info.download_id);
-    DCHECK(success) << "Invalid download ID found in downloads_slices table "
-                    << signed_id;
+    DCHECK(success) << "Invalid download ID found in " << kDownloadsSlicesTable
+                    << " table " << signed_id;
     info.offset = statement_query.ColumnInt64(column++);
     info.received_bytes = statement_query.ColumnInt64(column++);
     info.finished = static_cast<bool>(statement_query.ColumnInt64(column++));
@@ -853,4 +880,69 @@
   }
 }
 
+bool DownloadDatabase::CreateOrUpdateDownloadRerouteInfo(
+    DownloadId id,
+    const std::string& reroute_info_serialized) {
+  // If `reroute_info` is empty, remove it from the table the table.
+  if (reroute_info_serialized.empty()) {
+    RemoveDownloadRerouteInfo(id);
+    return true;
+  }
+  sql::Statement statement_replace(GetDB().GetCachedStatement(
+      SQL_FROM_HERE,
+      base::StringPrintf("REPLACE INTO %s "
+                         "(download_id, reroute_info_serialized) "
+                         "VALUES (?, ?)",
+                         kDownloadsRerouteInfoTable)
+          .c_str()));
+  int column = 0;
+  statement_replace.BindInt64(column++, id);
+  statement_replace.BindString(column++, reroute_info_serialized);
+  return statement_replace.Run();
+}
+
+void DownloadDatabase::RemoveDownloadRerouteInfo(DownloadId id) {
+  sql::Statement statement_delete(GetDB().GetCachedStatement(
+      SQL_FROM_HERE, base::StringPrintf("DELETE FROM %s WHERE download_id=?",
+                                        kDownloadsRerouteInfoTable)
+                         .c_str()));
+  statement_delete.BindInt64(0, id);
+  statement_delete.Run();
+}
+
+void DownloadDatabase::QueryDownloadRerouteInfos(
+    DownloadRowMap* download_row_map) {
+  DCHECK(download_row_map);
+  sql::Statement statement_query(GetDB().GetCachedStatement(
+      SQL_FROM_HERE,
+      base::StringPrintf("SELECT download_id, reroute_info_serialized "
+                         "FROM %s "
+                         "ORDER BY download_id",
+                         kDownloadsRerouteInfoTable)
+          .c_str()));
+
+  while (statement_query.Step()) {
+    int column = 0;
+    // Convert signed integer from sqlite to unsigned DownloadId.
+    int64_t signed_id = statement_query.ColumnInt64(column++);
+    DownloadId download_id;
+    bool success = ConvertIntToDownloadId(signed_id, &download_id);
+    DCHECK(success) << "Invalid download ID found in "
+                    << kDownloadsRerouteInfoTable << " table " << signed_id;
+
+    std::string reroute_info_serialized =
+        statement_query.ColumnString(column++);
+
+    // Confirm the download_id has already been seen--if it hasn't, discard the
+    // record.
+    auto it = download_row_map->find(download_id);
+    bool found = (it != download_row_map->end());
+    if (!found) {
+      RemoveDownloadRerouteInfo(download_id);
+      continue;
+    }
+    it->second->reroute_info_serialized = reroute_info_serialized;
+  }
+}
+
 }  // namespace history
diff --git a/components/history/core/browser/download_database.h b/components/history/core/browser/download_database.h
index a7d428c..4039df1 100644
--- a/components/history/core/browser/download_database.h
+++ b/components/history/core/browser/download_database.h
@@ -137,11 +137,21 @@
   // Delete all the download slices associated with one DownloadRow.
   void RemoveDownloadSlices(DownloadId id);
 
-  // Helper method to query the download slices for all the records in
-  // `download_row_map`.
+  // Query the download slices for all the records in `download_row_map`.
   using DownloadRowMap = std::map<DownloadId, DownloadRow*>;
   void QueryDownloadSlices(DownloadRowMap* download_row_map);
 
+  // Creates a new reroute info if it doesn't exist, or updates an existing
+  // one. Returns true on success, or false otherwise.
+  bool CreateOrUpdateDownloadRerouteInfo(
+      DownloadId id,
+      const std::string& reroute_info_serialized);
+
+  // Delete the download reroute info associated with one DownloadRow.
+  void RemoveDownloadRerouteInfo(DownloadId id);
+  // Query the download reroute infos for all the records in `download_row_map`.
+  void QueryDownloadRerouteInfos(DownloadRowMap* download_row_map);
+
   bool owning_thread_set_;
   base::PlatformThreadId owning_thread_;
 
diff --git a/components/history/core/browser/download_row.cc b/components/history/core/browser/download_row.cc
index f84721c0..54564d1 100644
--- a/components/history/core/browser/download_row.cc
+++ b/components/history/core/browser/download_row.cc
@@ -32,7 +32,8 @@
          last_access_time == rhs.last_access_time &&
          transient == rhs.transient && by_ext_id == rhs.by_ext_id &&
          by_ext_name == rhs.by_ext_name &&
-         download_slice_info == rhs.download_slice_info;
+         download_slice_info == rhs.download_slice_info &&
+         reroute_info_serialized == rhs.reroute_info_serialized;
 }
 
 }  // namespace history
diff --git a/components/history/core/browser/download_row.h b/components/history/core/browser/download_row.h
index cf812e5..3cee2a07 100644
--- a/components/history/core/browser/download_row.h
+++ b/components/history/core/browser/download_row.h
@@ -125,6 +125,9 @@
   // Data slices that have been downloaded so far. The slices must be ordered
   // by their offset.
   std::vector<DownloadSliceInfo> download_slice_info;
+
+  // The serialized string of the download's |reroute_info| proto.
+  std::string reroute_info_serialized;
 };
 
 }  // namespace history
diff --git a/components/history/core/browser/history_backend_db_unittest.cc b/components/history/core/browser/history_backend_db_unittest.cc
index 286f567..2e29e1c 100644
--- a/components/history/core/browser/history_backend_db_unittest.cc
+++ b/components/history/core/browser/history_backend_db_unittest.cc
@@ -34,6 +34,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
+#include "components/enterprise/common/download_item_reroute_info.h"
 #include "components/history/core/browser/download_constants.h"
 #include "components/history/core/browser/download_row.h"
 #include "components/history/core/browser/history_constants.h"
@@ -99,6 +100,7 @@
   EXPECT_EQ("by_ext_name", downloads[0].by_ext_name);
   EXPECT_EQ("application/vnd.oasis.opendocument.text", downloads[0].mime_type);
   EXPECT_EQ("application/octet-stream", downloads[0].original_mime_type);
+  EXPECT_TRUE(downloads[0].reroute_info_serialized.empty());
 
   db_->QueryDownloads(&downloads);
   EXPECT_EQ(1U, downloads.size());
@@ -808,6 +810,67 @@
   }
 }
 
+// Tests that downloads_reroute_info table are automatically added when
+// migrating to version 46.
+TEST_F(HistoryBackendDBTest, MigrateDownloadsRerouteInfoTable) {
+  ASSERT_NO_FATAL_FAILURE(CreateDBVersion(45));
+  {
+    sql::Database db;
+    ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
+  }
+
+  // Re-open the db using the HistoryDatabase, which should migrate to the
+  // current version, creating the downloads_downloads_reroute_info table.
+  CreateBackendAndDatabase();
+  DeleteBackend();
+  {
+    // Re-open the db for manual manipulation.
+    sql::Database db;
+    ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
+    // The version should have been updated.
+    int cur_version = HistoryDatabase::GetCurrentVersion();
+    ASSERT_LE(45, cur_version);
+    {
+      sql::Statement s(db.GetUniqueStatement(
+          "SELECT value FROM meta WHERE key = 'version'"));
+      EXPECT_TRUE(s.Step());
+      EXPECT_EQ(cur_version, s.ColumnInt(0));
+    }
+    {
+      // The downloads_downloads_reroute_info table should be ready for use.
+      sql::Statement s1(
+          db.GetUniqueStatement("SELECT COUNT(*) from downloads_reroute_info"));
+      EXPECT_TRUE(s1.Step());
+      EXPECT_EQ(0, s1.ColumnInt(0));
+      sql::Statement statement(db.GetCachedStatement(
+          SQL_FROM_HERE,
+          "INSERT INTO downloads_reroute_info "
+          "(download_id, reroute_info_serialized) VALUES (?, ?)"));
+      int column = 0;
+      statement.BindInt64(column++, 1);       // download_id.
+      statement.BindString(column++, "abc");  // reroute_info_serialized.
+      ASSERT_TRUE(statement.Run());
+    }
+    {
+      // Verify that we can load it back from db.
+      sql::Statement s1(
+          db.GetUniqueStatement("SELECT COUNT(*) from downloads_reroute_info"));
+      EXPECT_TRUE(s1.Step());
+      EXPECT_EQ(1, s1.ColumnInt(0));
+      sql::Statement statement(
+          db.GetCachedStatement(SQL_FROM_HERE,
+                                "SELECT download_id, reroute_info_serialized "
+                                "FROM downloads_reroute_info "
+                                "ORDER BY download_id"));
+      int column = 0;
+      ASSERT_TRUE(statement.Step());
+      EXPECT_EQ(statement.ColumnInt64(column++), 1);  // download_id.
+      EXPECT_EQ(statement.ColumnString(column++),
+                "abc");  // reroute_info_serialized.
+    }
+  }
+}
+
 TEST_F(HistoryBackendDBTest, DownloadCreateAndQuery) {
   CreateBackendAndDatabase();
 
@@ -1330,6 +1393,186 @@
   EXPECT_EQ(0u, results[0].download_slice_info.size());
 }
 
+TEST_F(HistoryBackendDBTest, CreateAndUpdateDownloadRerouteInfoThenRemoveItem) {
+  CreateBackendAndDatabase();
+
+  DownloadRow download;
+  download.current_path = base::FilePath(FILE_PATH_LITERAL("/path/1"));
+  download.target_path = base::FilePath(FILE_PATH_LITERAL("/path/2"));
+  download.url_chain.emplace_back(GURL("http://example.com/a"));
+  download.referrer_url = GURL("http://example.com/referrer");
+  download.site_url = GURL("http://example.com");
+  download.tab_url = GURL("http://example.com/tab-url");
+  download.tab_referrer_url = GURL("http://example.com/tab-referrer");
+  download.http_method = "GET";
+  download.mime_type = "mime/type";
+  download.original_mime_type = "original/mime-type";
+  download.start_time = base::Time::Now();
+  download.end_time = download.start_time + base::TimeDelta::FromHours(1);
+  download.etag = "etag1";
+  download.last_modified = "last_modified_1";
+  download.received_bytes = 10;
+  download.total_bytes = 1500;
+  download.state = DownloadState::INTERRUPTED;
+  download.danger_type = DownloadDangerType::NOT_DANGEROUS;
+  download.interrupt_reason = kTestDownloadInterruptReasonCrash;
+  download.hash = "hash-value1";
+  download.id = 1;
+  download.guid = "FE672168-26EF-4275-A149-FEC25F6A75F9";
+  download.opened = false;
+  download.last_access_time =
+      download.start_time + base::TimeDelta::FromHours(5);
+  download.transient = false;
+  download.by_ext_id = "extension-id";
+  download.by_ext_name = "extension-name";
+
+  enterprise_connectors::DownloadItemRerouteInfo reroute_info;
+  reroute_info.set_service_provider(enterprise_connectors::BOX);
+  reroute_info.mutable_box()->set_folder_id("67890");
+  download.reroute_info_serialized = reroute_info.SerializeAsString();
+
+  ASSERT_TRUE(db_->CreateDownload(download));
+  std::vector<DownloadRow> results;
+  db_->QueryDownloads(&results);
+  ASSERT_EQ(1u, results.size());
+  EXPECT_EQ(download, results[0]);
+  enterprise_connectors::DownloadItemRerouteInfo reroute_info_loaded;
+  EXPECT_TRUE(
+      reroute_info_loaded.ParseFromString(results[0].reroute_info_serialized));
+  EXPECT_TRUE(RerouteInfosEqual(reroute_info, reroute_info_loaded))
+      << "Expected: " << reroute_info.DebugString()
+      << "\nActual:" << reroute_info_loaded.DebugString();
+
+  // Update reroute info and trigger an update.
+  reroute_info.mutable_box()->set_file_id("12345");
+  download.reroute_info_serialized = reroute_info.SerializeAsString();
+  ASSERT_TRUE(db_->UpdateDownload(download));
+  db_->QueryDownloads(&results);
+  ASSERT_EQ(1u, results.size());
+  EXPECT_EQ(download, results[0]);
+  EXPECT_TRUE(
+      reroute_info_loaded.ParseFromString(results[0].reroute_info_serialized));
+  EXPECT_TRUE(RerouteInfosEqual(reroute_info, reroute_info_loaded))
+      << "Expected: " << reroute_info.DebugString()
+      << "\nActual:" << reroute_info_loaded.DebugString();
+
+  // Unlock the db for manual manipulation.
+  DeleteBackend();
+  {
+    sql::Database db;
+    ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
+    // Verify that there is an entry in the downloads_reroute_info table.
+    sql::Statement statement_query(db.GetCachedStatement(
+        SQL_FROM_HERE,
+        "SELECT download_id, reroute_info_serialized FROM "
+        "downloads_reroute_info ORDER BY download_id"));
+    ASSERT_TRUE(statement_query.Step());
+  }
+  // Lock the db again for Query below.
+  CreateBackendAndDatabase();
+
+  // Remove download item to check that entry in reroute info table is also
+  // deleted.
+  db_->RemoveDownload(results[0].id);
+
+  // Unlock the db for manual manipulation.
+  DeleteBackend();
+  {
+    sql::Database db;
+    ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
+    // Verify that the entry has been deleted from the downloads_reroute_info
+    // table.
+    sql::Statement statement_query(db.GetCachedStatement(
+        SQL_FROM_HERE,
+        "SELECT download_id, reroute_info_serialized FROM "
+        "downloads_reroute_info ORDER BY download_id"));
+    ASSERT_FALSE(statement_query.Step());
+  }
+  // Lock the db again for Query below.
+  CreateBackendAndDatabase();
+
+  // Verify that the entry has been removed.
+  db_->QueryDownloads(&results);
+  ASSERT_EQ(0u, results.size());
+}
+
+TEST_F(HistoryBackendDBTest, DownloadRerouteInfoDeletedIfEmpty) {
+  CreateBackendAndDatabase();
+
+  DownloadRow download;
+  download.current_path = base::FilePath(FILE_PATH_LITERAL("/path/1"));
+  download.target_path = base::FilePath(FILE_PATH_LITERAL("/path/2"));
+  download.url_chain.emplace_back(GURL("http://example.com/a"));
+  download.referrer_url = GURL("http://example.com/referrer");
+  download.site_url = GURL("http://example.com");
+  download.tab_url = GURL("http://example.com/tab-url");
+  download.tab_referrer_url = GURL("http://example.com/tab-referrer");
+  download.http_method = "GET";
+  download.mime_type = "mime/type";
+  download.original_mime_type = "original/mime-type";
+  download.start_time = base::Time::Now();
+  download.end_time = download.start_time + base::TimeDelta::FromHours(1);
+  download.etag = "etag1";
+  download.last_modified = "last_modified_1";
+  download.received_bytes = 10;
+  download.total_bytes = 1500;
+  download.state = DownloadState::INTERRUPTED;
+  download.danger_type = DownloadDangerType::NOT_DANGEROUS;
+  download.interrupt_reason = kTestDownloadInterruptReasonCrash;
+  download.hash = "hash-value1";
+  download.id = 1;
+  download.guid = "FE672168-26EF-4275-A149-FEC25F6A75F9";
+  download.opened = false;
+  download.last_access_time =
+      download.start_time + base::TimeDelta::FromHours(5);
+  download.transient = true;
+  download.by_ext_id = "extension-id";
+  download.by_ext_name = "extension-name";
+
+  // Setup with a pre-existing reroute info.
+  enterprise_connectors::DownloadItemRerouteInfo reroute_info;
+  reroute_info.set_service_provider(enterprise_connectors::BOX);
+  reroute_info.mutable_box()->set_folder_id("67890");
+  download.reroute_info_serialized = reroute_info.SerializeAsString();
+  ASSERT_TRUE(db_->CreateDownload(download));
+  std::vector<DownloadRow> results;
+  db_->QueryDownloads(&results);
+  ASSERT_EQ(1u, results.size());
+  EXPECT_EQ(download, results[0]);
+  enterprise_connectors::DownloadItemRerouteInfo reroute_info_loaded;
+  EXPECT_TRUE(
+      reroute_info_loaded.ParseFromString(results[0].reroute_info_serialized));
+  EXPECT_TRUE(RerouteInfosEqual(reroute_info, reroute_info_loaded))
+      << "Expected: " << reroute_info.DebugString()
+      << "\nActual:" << reroute_info_loaded.DebugString();
+
+  // Update with empty reroute_info.
+  download.reroute_info_serialized = std::string();
+  ASSERT_TRUE(db_->UpdateDownload(download));
+
+  // Unlock the db for manual manipulation.
+  DeleteBackend();
+  {
+    sql::Database db;
+    ASSERT_TRUE(db.Open(history_dir_.Append(kHistoryFilename)));
+    // Verify that the entry has been deleted from the downloads_reroute_info
+    // table.
+    sql::Statement statement_query(db.GetCachedStatement(
+        SQL_FROM_HERE,
+        "SELECT download_id, reroute_info_serialized FROM "
+        "downloads_reroute_info ORDER BY download_id"));
+    ASSERT_FALSE(statement_query.Step());
+  }
+  // Lock the db again for Query below.
+  CreateBackendAndDatabase();
+
+  // Verify that the entry without reroute info get loaded properly.
+  db_->QueryDownloads(&results);
+  ASSERT_EQ(1u, results.size());
+  EXPECT_EQ(download, results[0]);
+  EXPECT_TRUE(results[0].reroute_info_serialized.empty());
+}
+
 TEST_F(HistoryBackendDBTest, MigratePresentations) {
   // Create the db we want. Use 22 since segments didn't change in that time
   // frame.
diff --git a/components/history/core/browser/history_database.cc b/components/history/core/browser/history_database.cc
index 033ee5d..f1a733e4 100644
--- a/components/history/core/browser/history_database.cc
+++ b/components/history/core/browser/history_database.cc
@@ -37,7 +37,7 @@
 // Current version number. We write databases at the "current" version number,
 // but any previous version that can read the "compatible" one can make do with
 // our database without *too* many bad effects.
-const int kCurrentVersionNumber = 45;
+const int kCurrentVersionNumber = 46;
 const int kCompatibleVersionNumber = 16;
 const char kEarlyExpirationThresholdKey[] = "early_expiration_threshold";
 
@@ -643,6 +643,12 @@
     meta_table_.SetVersionNumber(cur_version);
   }
 
+  if (cur_version == 45) {
+    // New download reroute infos table is introduced, no migration needed.
+    cur_version++;
+    meta_table_.SetVersionNumber(cur_version);
+  }
+
   // =========================       ^^ new migration code goes here ^^
   // ADDING NEW MIGRATION CODE
   // =========================
diff --git a/components/history/core/test/history_backend_db_base_test.cc b/components/history/core/test/history_backend_db_base_test.cc
index 4c05946..152d335 100644
--- a/components/history/core/test/history_backend_db_base_test.cc
+++ b/components/history/core/test/history_backend_db_base_test.cc
@@ -146,6 +146,7 @@
   download.transient = true;
   download.by_ext_id = "by_ext_id";
   download.by_ext_name = "by_ext_name";
+  download.reroute_info_serialized = "";
   return db_->CreateDownload(download);
 }
 
diff --git a/components/history/core/test/history_unittest_base.cc b/components/history/core/test/history_unittest_base.cc
index 707ccc8..ec63972b 100644
--- a/components/history/core/test/history_unittest_base.cc
+++ b/components/history/core/test/history_unittest_base.cc
@@ -22,7 +22,8 @@
 void HistoryUnitTestBase::ExecuteSQLScript(const base::FilePath& sql_path,
                                            const base::FilePath& db_path) {
   std::string sql;
-  ASSERT_TRUE(base::ReadFileToString(sql_path, &sql));
+  ASSERT_TRUE(base::ReadFileToString(sql_path, &sql))
+      << "Path: " << sql_path << "\nContent read before failure: " << sql;
 
   // Replace the 'last_visit_time', 'visit_time', 'time_slot' values in this
   // SQL with the current time.
diff --git a/components/history_clusters/core/BUILD.gn b/components/history_clusters/core/BUILD.gn
index f75c75c..abde240 100644
--- a/components/history_clusters/core/BUILD.gn
+++ b/components/history_clusters/core/BUILD.gn
@@ -38,6 +38,7 @@
     "//components/history_clusters/core/proto",
     "//components/keyed_service/core",
     "//components/query_parser",
+    "//components/url_formatter",
     "//net",
     "//services/network/public/cpp",
     "//services/network/public/mojom",
diff --git a/components/history_clusters/core/DEPS b/components/history_clusters/core/DEPS
index a8563a9..54415f7 100644
--- a/components/history_clusters/core/DEPS
+++ b/components/history_clusters/core/DEPS
@@ -2,6 +2,7 @@
   "+components/history/core",
   "+components/keyed_service/core",
   "+components/query_parser",
+  "+components/url_formatter",
   "+net",
   "+services/network/public/cpp",
   "+services/network/public/mojom",
diff --git a/components/history_clusters/core/history_clusters_service.cc b/components/history_clusters/core/history_clusters_service.cc
index 0bdef2b..88d54bd 100644
--- a/components/history_clusters/core/history_clusters_service.cc
+++ b/components/history_clusters/core/history_clusters_service.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 #include <iterator>
 #include <numeric>
+#include <string>
 #include <utility>
 
 #include "base/bind.h"
@@ -21,9 +22,11 @@
 #include "components/history/core/browser/history_backend.h"
 #include "components/history/core/browser/history_database.h"
 #include "components/history/core/browser/history_db_task.h"
+#include "components/history/core/browser/history_types.h"
 #include "components/history_clusters/core/history_clusters_buildflags.h"
 #include "components/history_clusters/core/memories_features.h"
 #include "components/history_clusters/core/remote_clustering_backend.h"
+#include "components/url_formatter/url_formatter.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/l10n/time_format.h"
 
@@ -185,6 +188,44 @@
   Callback callback_;
 };
 
+// Returns true if `find_nodes` matches `cluster`.
+// This is deliberately meant to closely mirror the History implementation..
+// TODO(tommycli): Merge with `URLDatabase::GetTextMatchesWithAlgorithm()`.
+bool DoesQueryMatchCluster(const query_parser::QueryNodeVector& find_nodes,
+                           const history::Cluster& cluster) {
+  query_parser::QueryWordVector find_in_words;
+
+  // All of the cluster's `keyword`s go into `find_in_words`.
+  // Each `keyword` may have multiple terms, so loop over them.
+  for (auto& keyword : cluster.keywords) {
+    query_parser::QueryParser::ExtractQueryWords(base::i18n::ToLower(keyword),
+                                                 &find_in_words);
+  }
+
+  // Also extract all of the visits' URLs and titles into `find_in_words`.
+  for (const auto& visit : cluster.scored_annotated_visits) {
+    GURL gurl = visit.annotated_visit.url_row.url();
+
+    std::u16string url_lower =
+        base::i18n::ToLower(base::UTF8ToUTF16(gurl.possibly_invalid_spec()));
+    query_parser::QueryParser::ExtractQueryWords(url_lower, &find_in_words);
+
+    if (gurl.is_valid()) {
+      // Decode punycode to match IDN.
+      std::u16string ascii = base::ASCIIToUTF16(gurl.host());
+      std::u16string utf = url_formatter::IDNToUnicode(gurl.host());
+      if (ascii != utf)
+        query_parser::QueryParser::ExtractQueryWords(utf, &find_in_words);
+    }
+
+    std::u16string title_lower =
+        base::i18n::ToLower(visit.annotated_visit.url_row.title());
+    query_parser::QueryParser::ExtractQueryWords(title_lower, &find_in_words);
+  }
+
+  return query_parser::QueryParser::DoesQueryMatch(find_in_words, find_nodes);
+}
+
 // Filter `clusters` matching `query`. There are additional filters (e.g.
 // `max_time`) used when requesting `QueryClusters()`, but this function is only
 // responsible for matching `query`.
@@ -195,26 +236,17 @@
     return clusters;
 
   // Extract query nodes from the query string.
-  query_parser::QueryNodeVector query_nodes;
+  query_parser::QueryNodeVector find_nodes;
   query_parser::QueryParser::ParseQueryNodes(
       base::UTF8ToUTF16(query),
-      query_parser::MatchingAlgorithm::ALWAYS_PREFIX_SEARCH, &query_nodes);
+      query_parser::MatchingAlgorithm::ALWAYS_PREFIX_SEARCH, &find_nodes);
 
   std::vector<history::Cluster> matching_clusters;
   base::ranges::copy_if(clusters, std::back_inserter(matching_clusters),
-                        [&](const auto& cluster) {
-                          query_parser::QueryWordVector find_in_words;
-                          for (auto& keyword : cluster.keywords) {
-                            // Each `keyword` may itself have multiple terms
-                            // that we need to extract and append to
-                            // `find_in_words`.
-                            query_parser::QueryParser::ExtractQueryWords(
-                                base::i18n::ToLower(keyword), &find_in_words);
-                          }
-
-                          return query_parser::QueryParser::DoesQueryMatch(
-                              find_in_words, query_nodes);
+                        [&find_nodes](const history::Cluster& cluster) {
+                          return DoesQueryMatchCluster(find_nodes, cluster);
                         });
+
   return matching_clusters;
 }
 
diff --git a/components/history_clusters/core/history_clusters_service_test_api.cc b/components/history_clusters/core/history_clusters_service_test_api.cc
index 38d09e3..70ed6f6 100644
--- a/components/history_clusters/core/history_clusters_service_test_api.cc
+++ b/components/history_clusters/core/history_clusters_service_test_api.cc
@@ -19,7 +19,7 @@
       history::AnnotatedVisit visit;
       visit.url_row.set_id(1);
       visit.url_row.set_url(GURL("https://google.com/"));
-      visit.url_row.set_title(u"Google title");
+      visit.url_row.set_title(u"Search Engine Title");
       visit.visit_row.visit_id = 1;
       // Choose a recent time, as otherwise History will discard the visit.
       visit.visit_row.visit_time =
@@ -34,7 +34,7 @@
       history::AnnotatedVisit visit;
       visit.url_row.set_id(2);
       visit.url_row.set_url(GURL("https://github.com/"));
-      visit.url_row.set_title(u"Github title");
+      visit.url_row.set_title(u"Code Storage Title");
       visit.visit_row.visit_id = 2;
       // Choose a recent time, as otherwise History will discard the visit.
       visit.visit_row.visit_time =
diff --git a/components/history_clusters/core/history_clusters_service_unittest.cc b/components/history_clusters/core/history_clusters_service_unittest.cc
index 11f0e27..b115a58 100644
--- a/components/history_clusters/core/history_clusters_service_unittest.cc
+++ b/components/history_clusters/core/history_clusters_service_unittest.cc
@@ -330,6 +330,13 @@
       {"red or", true, false},
       {"red ora", true, false},
       {"red oran", true, false},
+      // Verify that we can search by URL.
+      {"goog", true, false},
+      // Verify we can search by page title, even mismatching case.
+      {"code", true, true},
+      // Verify that we match if the input query spans cluster keywords,
+      // visit URLs, and visit titles.
+      {"goog code apples", true, false},
   };
 
   for (size_t i = 0; i < base::size(test_data); ++i) {
@@ -360,7 +367,7 @@
             EXPECT_EQ(visits[0].annotated_visit.visit_row.visit_time,
                       GetHardcodedTestVisits()[1].visit_row.visit_time);
             EXPECT_EQ(visits[0].annotated_visit.url_row.title(),
-                      u"Github title");
+                      u"Code Storage Title");
             EXPECT_FALSE(
                 visits[0].annotated_visit.context_annotations.is_new_bookmark);
             EXPECT_TRUE(visits[0]
@@ -373,7 +380,7 @@
             EXPECT_EQ(visits[1].annotated_visit.visit_row.visit_time,
                       GetHardcodedTestVisits()[0].visit_row.visit_time);
             EXPECT_EQ(visits[1].annotated_visit.url_row.title(),
-                      u"Google title");
+                      u"Search Engine Title");
             EXPECT_TRUE(
                 visits[1].annotated_visit.context_annotations.is_new_bookmark);
             EXPECT_FALSE(visits[1]
@@ -396,7 +403,7 @@
             EXPECT_EQ(visits[0].annotated_visit.visit_row.visit_time,
                       GetHardcodedTestVisits()[1].visit_row.visit_time);
             EXPECT_EQ(visits[0].annotated_visit.url_row.title(),
-                      u"Github title");
+                      u"Code Storage Title");
             EXPECT_TRUE(cluster.keywords.empty());
           }
 
diff --git a/components/media_message_center/media_notification_view_impl_unittest.cc b/components/media_message_center/media_notification_view_impl_unittest.cc
index 74d29d2..06e19d7 100644
--- a/components/media_message_center/media_notification_view_impl_unittest.cc
+++ b/components/media_message_center/media_notification_view_impl_unittest.cc
@@ -26,7 +26,6 @@
 #include "ui/events/base_event_utils.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/message_center_constants.h"
-#include "ui/message_center/views/message_view_factory.h"
 #include "ui/message_center/views/notification_control_buttons_view.h"
 #include "ui/message_center/views/notification_header_view.h"
 #include "ui/views/controls/image_view.h"
diff --git a/components/media_message_center/media_notification_view_modern_impl_unittest.cc b/components/media_message_center/media_notification_view_modern_impl_unittest.cc
index f37e743..5334c81 100644
--- a/components/media_message_center/media_notification_view_modern_impl_unittest.cc
+++ b/components/media_message_center/media_notification_view_modern_impl_unittest.cc
@@ -28,7 +28,6 @@
 #include "ui/events/base_event_utils.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/message_center_constants.h"
-#include "ui/message_center/views/message_view_factory.h"
 #include "ui/message_center/views/notification_control_buttons_view.h"
 #include "ui/message_center/views/notification_header_view.h"
 #include "ui/views/controls/image_view.h"
diff --git a/components/omnibox/browser/android/java/res/drawable-hdpi/omnibox_https_valid_arrow.png b/components/omnibox/browser/android/java/res/drawable-hdpi/omnibox_https_valid_arrow.png
index 9c569b2..28efd86b 100644
--- a/components/omnibox/browser/android/java/res/drawable-hdpi/omnibox_https_valid_arrow.png
+++ b/components/omnibox/browser/android/java/res/drawable-hdpi/omnibox_https_valid_arrow.png
Binary files differ
diff --git a/components/omnibox/browser/android/java/res/drawable-mdpi/omnibox_https_valid_arrow.png b/components/omnibox/browser/android/java/res/drawable-mdpi/omnibox_https_valid_arrow.png
index 59a97c50..60cb391 100644
--- a/components/omnibox/browser/android/java/res/drawable-mdpi/omnibox_https_valid_arrow.png
+++ b/components/omnibox/browser/android/java/res/drawable-mdpi/omnibox_https_valid_arrow.png
Binary files differ
diff --git a/components/omnibox/browser/android/java/res/drawable-xhdpi/omnibox_https_valid_arrow.png b/components/omnibox/browser/android/java/res/drawable-xhdpi/omnibox_https_valid_arrow.png
index c8c9f04..870f5d3d 100644
--- a/components/omnibox/browser/android/java/res/drawable-xhdpi/omnibox_https_valid_arrow.png
+++ b/components/omnibox/browser/android/java/res/drawable-xhdpi/omnibox_https_valid_arrow.png
Binary files differ
diff --git a/components/omnibox/browser/android/java/res/drawable-xxhdpi/omnibox_https_valid_arrow.png b/components/omnibox/browser/android/java/res/drawable-xxhdpi/omnibox_https_valid_arrow.png
index d4796dc..582c3f0 100644
--- a/components/omnibox/browser/android/java/res/drawable-xxhdpi/omnibox_https_valid_arrow.png
+++ b/components/omnibox/browser/android/java/res/drawable-xxhdpi/omnibox_https_valid_arrow.png
Binary files differ
diff --git a/components/omnibox/browser/android/java/res/drawable-xxxhdpi/omnibox_https_valid_arrow.png b/components/omnibox/browser/android/java/res/drawable-xxxhdpi/omnibox_https_valid_arrow.png
index fa8244b..7fdd036 100644
--- a/components/omnibox/browser/android/java/res/drawable-xxxhdpi/omnibox_https_valid_arrow.png
+++ b/components/omnibox/browser/android/java/res/drawable-xxxhdpi/omnibox_https_valid_arrow.png
Binary files differ
diff --git a/components/onc/OWNERS b/components/onc/OWNERS
index ae9ccd5..6eb8032c 100644
--- a/components/onc/OWNERS
+++ b/components/onc/OWNERS
@@ -1,4 +1,6 @@
 armansito@chromium.org
+azeemarshad@chromium.org
 gauravsh@chromium.org
 gspencer@chromium.org
+khorimoto@chromium.org
 stevenjb@chromium.org
diff --git a/components/onc/docs/onc_spec.md b/components/onc/docs/onc_spec.md
index 6f90ea9..c5fa7ad 100644
--- a/components/onc/docs/onc_spec.md
+++ b/components/onc/docs/onc_spec.md
@@ -158,6 +158,12 @@
 A [Help Center article](https://support.google.com/chrome/a/answer/6326250)
 warns admins of the implications of mis-using this policy for Chrome OS.
 
+* **AllowOnlyPolicyCellularNetworks**
+    * (optional, defaults to false) - **boolean**
+    * When this field is present and set to true, only cellular networks present
+      in policy may be connected to. No new cellular networks may be added or
+      configured. This allows admins to ensure that only policy configured
+      cellular networks are accessible.
 
 * **AllowOnlyPolicyNetworksToAutoconnect**
     * (optional, defaults to false) - **boolean**
@@ -1553,6 +1559,11 @@
         is *LTE*) - **boolean**
     * For GSM or LTE modems, indicates whether a SIM card is present or not.
 
+* **SMDPAddress**
+    * (optional, read-only) - **string**
+    * When set with the address of an SMDP+ server, indicates that eSIM profile
+      for this network should be downloaded and installed using this address.
+
 * **SupportNetworkScan**
     * (optional, read-only) - **boolean**
     * True if the cellular network supports scanning.
diff --git a/components/onc/onc_constants.cc b/components/onc/onc_constants.cc
index 7ca4bb8f..80b9684 100644
--- a/components/onc/onc_constants.cc
+++ b/components/onc/onc_constants.cc
@@ -134,6 +134,7 @@
 const char kSignalStrength[] = "SignalStrength";
 const char kSIMLockStatus[] = "SIMLockStatus";
 const char kSIMPresent[] = "SIMPresent";
+const char kSMDPAddress[] = "SMDPAddress";
 const char kSupportNetworkScan[] = "SupportNetworkScan";
 const char kTechnologyCdma1Xrtt[] = "CDMA1XRTT";
 const char kTechnologyEdge[] = "EDGE";
@@ -497,12 +498,22 @@
 }  // namespace substitutes
 
 namespace global_network_config {
+const char kAllowOnlyPolicyCellularNetworks[] =
+    "AllowOnlyPolicyCellularNetworks";
 const char kAllowOnlyPolicyNetworksToAutoconnect[] =
     "AllowOnlyPolicyNetworksToAutoconnect";
 const char kAllowOnlyPolicyNetworksToConnect[] =
     "AllowOnlyPolicyNetworksToConnect";
 const char kAllowOnlyPolicyNetworksToConnectIfAvailable[] =
     "AllowOnlyPolicyNetworksToConnectIfAvailable";
+// AllowOnlyPolicyNetworksToConnect and
+// AllowOnlyPolicyNetworksToConnectIfAvailable field are currently only applied
+// to WiFi networks. TODO(crbug.com/1234561): Fix this when ONC field is
+// updated.
+const char* kAllowOnlyPolicyWiFiToConnect = kAllowOnlyPolicyNetworksToConnect;
+const char* kAllowOnlyPolicyWiFiToConnectIfAvailable =
+    kAllowOnlyPolicyNetworksToConnectIfAvailable;
+
 const char kBlacklistedHexSSIDs[] = "BlacklistedHexSSIDs";  // Deprecated
 const char kBlockedHexSSIDs[] = "BlockedHexSSIDs";
 const char kDisableNetworkTypes[] = "DisableNetworkTypes";
diff --git a/components/onc/onc_constants.h b/components/onc/onc_constants.h
index b9c3f91a..0e8d41c 100644
--- a/components/onc/onc_constants.h
+++ b/components/onc/onc_constants.h
@@ -148,6 +148,7 @@
 ONC_EXPORT extern const char kSignalStrength[];
 ONC_EXPORT extern const char kSIMLockStatus[];
 ONC_EXPORT extern const char kSIMPresent[];
+ONC_EXPORT extern const char kSMDPAddress[];
 ONC_EXPORT extern const char kSupportNetworkScan[];
 ONC_EXPORT extern const char kTechnologyCdma1Xrtt[];
 ONC_EXPORT extern const char kTechnologyEdge[];
@@ -504,9 +505,10 @@
 }  // namespace proxy
 
 namespace global_network_config {
+ONC_EXPORT extern const char kAllowOnlyPolicyCellularNetworks[];
 ONC_EXPORT extern const char kAllowOnlyPolicyNetworksToAutoconnect[];
-ONC_EXPORT extern const char kAllowOnlyPolicyNetworksToConnect[];
-ONC_EXPORT extern const char kAllowOnlyPolicyNetworksToConnectIfAvailable[];
+ONC_EXPORT extern const char* kAllowOnlyPolicyWiFiToConnect;
+ONC_EXPORT extern const char* kAllowOnlyPolicyWiFiToConnectIfAvailable;
 ONC_EXPORT extern const char kBlacklistedHexSSIDs[];  // Deprecated
 ONC_EXPORT extern const char kBlockedHexSSIDs[];
 ONC_EXPORT extern const char kDisableNetworkTypes[];
diff --git a/components/page_load_metrics/browser/BUILD.gn b/components/page_load_metrics/browser/BUILD.gn
index 8053583..b8eab02 100644
--- a/components/page_load_metrics/browser/BUILD.gn
+++ b/components/page_load_metrics/browser/BUILD.gn
@@ -24,8 +24,6 @@
     "observers/early_hints_page_load_metrics_observer.h",
     "observers/layout_page_load_metrics_observer.cc",
     "observers/layout_page_load_metrics_observer.h",
-    "observers/prerender_features_page_load_metrics_observer.cc",
-    "observers/prerender_features_page_load_metrics_observer.h",
     "observers/prerender_page_load_metrics_observer.cc",
     "observers/prerender_page_load_metrics_observer.h",
     "observers/use_counter/ukm_features.cc",
@@ -107,7 +105,6 @@
     "observers/early_hints_page_load_metrics_observer_unittest.cc",
     "observers/page_load_metrics_observer_content_test_harness.cc",
     "observers/page_load_metrics_observer_content_test_harness.h",
-    "observers/prerender_features_page_load_metrics_observer_unittest.cc",
     "observers/use_counter_page_load_metrics_observer_unittest.cc",
     "page_load_metrics_memory_tracker_unittest.cc",
     "page_load_metrics_util_unittest.cc",
diff --git a/components/page_load_metrics/browser/observers/prerender_features_page_load_metrics_observer.cc b/components/page_load_metrics/browser/observers/prerender_features_page_load_metrics_observer.cc
deleted file mode 100644
index 1fa65a4..0000000
--- a/components/page_load_metrics/browser/observers/prerender_features_page_load_metrics_observer.cc
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/page_load_metrics/browser/observers/prerender_features_page_load_metrics_observer.h"
-
-#include "components/page_load_metrics/browser/metrics_web_contents_observer.h"
-#include "content/public/browser/web_contents.h"
-#include "third_party/blink/public/mojom/web_feature/web_feature.mojom.h"
-
-namespace {
-
-// The purpose of this observer is to estimate how many prerendering pages will
-// use certain features. The plan for prerendering is to delay loading of
-// cross-origin iframes, so we want to ignore feature uses inside cross-origin
-// iframes.  To do this, just check if the |url| is cross-origin to
-// |first_party_url|. This may not be an accurate count if a third-party
-// subframe embeds a first-party subframe, or if there is a way for a frame to
-// access cross-origin storage, but it's probably not significant.
-//
-// This check is somewhat heavy so it should be done after cheaper early
-// returns.
-bool IsFirstParty(const GURL& url, const GURL& first_party_url) {
-  return url::IsSameOriginWith(url, first_party_url);
-}
-
-}  // namespace
-
-void PrerenderFeaturesPageLoadMetricsObserver::OnFirstContentfulPaintInPage(
-    const page_load_metrics::mojom::PageLoadTiming& timing) {
-  did_fcp_ = true;
-}
-
-void PrerenderFeaturesPageLoadMetricsObserver::OnStorageAccessed(
-    const GURL& url,
-    const GURL& first_party_url,
-    bool blocked_by_policy,
-    page_load_metrics::StorageType access_type) {
-  // TODO(falken): Account for `blocked_by_policy`?
-
-  switch (access_type) {
-    case page_load_metrics::StorageType::kLocalStorage:
-      if (did_local_storage_)
-        return;
-      if (!IsFirstParty(url, first_party_url))
-        return;
-
-      did_local_storage_ = true;
-      RecordFeatureUse(
-          did_fcp_ ? blink::mojom::WebFeature::kLocalStorageFirstUsedAfterFcp
-                   : blink::mojom::WebFeature::kLocalStorageFirstUsedBeforeFcp);
-      break;
-    case page_load_metrics::StorageType::kSessionStorage:
-      if (did_session_storage_)
-        return;
-      if (!IsFirstParty(url, first_party_url))
-        return;
-
-      did_session_storage_ = true;
-      RecordFeatureUse(
-          did_fcp_
-              ? blink::mojom::WebFeature::kSessionStorageFirstUsedAfterFcp
-              : blink::mojom::WebFeature::kSessionStorageFirstUsedBeforeFcp);
-      break;
-    case page_load_metrics::StorageType::kFileSystem:
-    case page_load_metrics::StorageType::kIndexedDb:
-    case page_load_metrics::StorageType::kCacheStorage:
-      return;
-  }
-}
-
-void PrerenderFeaturesPageLoadMetricsObserver::RecordFeatureUse(
-    blink::mojom::WebFeature feature) {
-  page_load_metrics::MetricsWebContentsObserver::RecordFeatureUsage(
-      GetDelegate().GetWebContents()->GetMainFrame(), feature);
-}
diff --git a/components/page_load_metrics/browser/observers/prerender_features_page_load_metrics_observer.h b/components/page_load_metrics/browser/observers/prerender_features_page_load_metrics_observer.h
deleted file mode 100644
index 7fd6d08b..0000000
--- a/components/page_load_metrics/browser/observers/prerender_features_page_load_metrics_observer.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_PRERENDER_FEATURES_PAGE_LOAD_METRICS_OBSERVER_H_
-#define COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_PRERENDER_FEATURES_PAGE_LOAD_METRICS_OBSERVER_H_
-
-#include "components/page_load_metrics/browser/page_load_metrics_observer.h"
-
-namespace blink {
-namespace mojom {
-enum class WebFeature : int32_t;
-}  // namespace mojom
-}  // namespace blink
-
-// Records metrics relevant to prerendering. Currently it logs feature usage in
-// normal page loads which, when if used during prerendering, may result in
-// cancelling or freezing the prerender, to help estimate the effect on
-// coverage.
-class PrerenderFeaturesPageLoadMetricsObserver
-    : public page_load_metrics::PageLoadMetricsObserver {
- public:
-  PrerenderFeaturesPageLoadMetricsObserver() = default;
-
-  // page_load_metrics::PageLoadMetricsObserver implementation:
-  void OnFirstContentfulPaintInPage(
-      const page_load_metrics::mojom::PageLoadTiming& timing) override;
-  void OnStorageAccessed(const GURL& url,
-                         const GURL& first_party_url,
-                         bool blocked_by_policy,
-                         page_load_metrics::StorageType access_type) override;
-
- private:
-  void RecordFeatureUse(blink::mojom::WebFeature feature);
-
-  bool did_fcp_ = false;
-  bool did_local_storage_ = false;
-  bool did_session_storage_ = false;
-};
-
-#endif  // COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_PRERENDER_FEATURES_PAGE_LOAD_METRICS_OBSERVER_H_
diff --git a/components/page_load_metrics/browser/observers/prerender_features_page_load_metrics_observer_unittest.cc b/components/page_load_metrics/browser/observers/prerender_features_page_load_metrics_observer_unittest.cc
deleted file mode 100644
index 9767e49f..0000000
--- a/components/page_load_metrics/browser/observers/prerender_features_page_load_metrics_observer_unittest.cc
+++ /dev/null
@@ -1,221 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/page_load_metrics/browser/observers/prerender_features_page_load_metrics_observer.h"
-
-#include <memory>
-
-#include "components/page_load_metrics/browser/observers/page_load_metrics_observer_content_test_harness.h"
-#include "components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h"
-#include "components/page_load_metrics/browser/page_load_tracker.h"
-#include "components/page_load_metrics/common/test/page_load_metrics_test_util.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/test/navigation_simulator.h"
-
-namespace {
-
-const char kDefaultTestUrl[] = "https://a.test";
-const char kOtherOriginUrl[] = "https://b.test";
-const char kFeaturesHistogramName[] = "Blink.UseCounter.Features";
-
-class PrerenderFeaturesPageLoadMetricsObserverTest
-    : public page_load_metrics::PageLoadMetricsObserverContentTestHarness {
- protected:
-  void RegisterObservers(page_load_metrics::PageLoadTracker* tracker) override {
-    // PrerenderFeaturesPageLoadMetricsObserver requires
-    // UseCounterPageLoadMetricsObserver to log UseCounter to UMA.
-    tracker->AddObserver(std::make_unique<UseCounterPageLoadMetricsObserver>());
-
-    tracker->AddObserver(
-        std::make_unique<PrerenderFeaturesPageLoadMetricsObserver>());
-  }
-
-  void SimulateFirstContentfulPaint() {
-    page_load_metrics::mojom::PageLoadTiming timing;
-    page_load_metrics::InitPageLoadTimingForTest(&timing);
-    timing.navigation_start = base::Time::Now();
-    timing.parse_timing->parse_stop = base::TimeDelta::FromMilliseconds(50);
-    timing.paint_timing->first_contentful_paint =
-        base::TimeDelta::FromMilliseconds(100);
-    PopulateRequiredTimingFields(&timing);
-    tester()->SimulateTimingUpdate(timing);
-  }
-
-  int GetPageVisits() {
-    return tester()->histogram_tester().GetBucketCount(
-        kFeaturesHistogramName, static_cast<base::Histogram::Sample>(
-                                    blink::mojom::WebFeature::kPageVisits));
-  }
-
-  int GetLocalStorageBeforeFcpCount() {
-    return tester()->histogram_tester().GetBucketCount(
-        kFeaturesHistogramName,
-        static_cast<base::Histogram::Sample>(
-            blink::mojom::WebFeature::kLocalStorageFirstUsedBeforeFcp));
-  }
-
-  int GetLocalStorageAfterFcpCount() {
-    return tester()->histogram_tester().GetBucketCount(
-        kFeaturesHistogramName,
-        static_cast<base::Histogram::Sample>(
-            blink::mojom::WebFeature::kLocalStorageFirstUsedAfterFcp));
-  }
-
-  int GetSessionStorageBeforeFcpCount() {
-    return tester()->histogram_tester().GetBucketCount(
-        kFeaturesHistogramName,
-        static_cast<base::Histogram::Sample>(
-            blink::mojom::WebFeature::kSessionStorageFirstUsedBeforeFcp));
-  }
-
-  int GetSessionStorageAfterFcpCount() {
-    return tester()->histogram_tester().GetBucketCount(
-        kFeaturesHistogramName,
-        static_cast<base::Histogram::Sample>(
-            blink::mojom::WebFeature::kSessionStorageFirstUsedAfterFcp));
-  }
-};
-
-TEST_F(PrerenderFeaturesPageLoadMetricsObserverTest, NoLocalStorage) {
-  NavigateAndCommit(GURL(kDefaultTestUrl));
-
-  EXPECT_EQ(GetPageVisits(), 1);
-  EXPECT_EQ(GetLocalStorageBeforeFcpCount(), 0);
-  EXPECT_EQ(GetLocalStorageAfterFcpCount(), 0);
-}
-
-TEST_F(PrerenderFeaturesPageLoadMetricsObserverTest, LocalStorageBeforeFcp) {
-  NavigateAndCommit(GURL(kDefaultTestUrl));
-
-  // Access local storage.
-  tester()->SimulateStorageAccess(
-      GURL(kDefaultTestUrl), GURL(kDefaultTestUrl), false,
-      page_load_metrics::StorageType::kLocalStorage);
-
-  // Reach FCP.
-  SimulateFirstContentfulPaint();
-
-  // Access local storage again.
-  tester()->SimulateStorageAccess(
-      GURL(kDefaultTestUrl), GURL(kDefaultTestUrl), false,
-      page_load_metrics::StorageType::kLocalStorage);
-
-  EXPECT_EQ(GetPageVisits(), 1);
-  EXPECT_EQ(GetLocalStorageBeforeFcpCount(), 1);
-  // The UMA counts the first use, so AfterFcp is 0.
-  EXPECT_EQ(GetLocalStorageAfterFcpCount(), 0);
-}
-
-TEST_F(PrerenderFeaturesPageLoadMetricsObserverTest, LocalStorageAfterFcp) {
-  NavigateAndCommit(GURL(kDefaultTestUrl));
-
-  // Reach FCP.
-  SimulateFirstContentfulPaint();
-
-  // Access local storage.
-  tester()->SimulateStorageAccess(
-      GURL(kDefaultTestUrl), GURL(kDefaultTestUrl), false,
-      page_load_metrics::StorageType::kLocalStorage);
-
-  EXPECT_EQ(GetPageVisits(), 1);
-  EXPECT_EQ(GetLocalStorageBeforeFcpCount(), 0);
-  EXPECT_EQ(GetLocalStorageAfterFcpCount(), 1);
-}
-
-TEST_F(PrerenderFeaturesPageLoadMetricsObserverTest, ThirdPartyLocalStorage) {
-  NavigateAndCommit(GURL(kDefaultTestUrl));
-
-  tester()->SimulateStorageAccess(
-      GURL(kOtherOriginUrl), GURL(kDefaultTestUrl), false,
-      page_load_metrics::StorageType::kLocalStorage);
-
-  // Cross-origin storage is not logged.
-  EXPECT_EQ(GetPageVisits(), 1);
-  EXPECT_EQ(GetLocalStorageBeforeFcpCount(), 0);
-  EXPECT_EQ(GetLocalStorageAfterFcpCount(), 0);
-}
-
-TEST_F(PrerenderFeaturesPageLoadMetricsObserverTest, NoSessionStorage) {
-  NavigateAndCommit(GURL(kDefaultTestUrl));
-
-  EXPECT_EQ(GetPageVisits(), 1);
-  EXPECT_EQ(GetSessionStorageBeforeFcpCount(), 0);
-  EXPECT_EQ(GetSessionStorageAfterFcpCount(), 0);
-}
-
-TEST_F(PrerenderFeaturesPageLoadMetricsObserverTest, SessionStorageBeforeFcp) {
-  NavigateAndCommit(GURL(kDefaultTestUrl));
-
-  // Access session storage.
-  tester()->SimulateStorageAccess(
-      GURL(kDefaultTestUrl), GURL(kDefaultTestUrl), false,
-      page_load_metrics::StorageType::kSessionStorage);
-
-  // Reach FCP.
-  SimulateFirstContentfulPaint();
-
-  // Access session storage again.
-  tester()->SimulateStorageAccess(
-      GURL(kDefaultTestUrl), GURL(kDefaultTestUrl), false,
-      page_load_metrics::StorageType::kSessionStorage);
-
-  EXPECT_EQ(GetPageVisits(), 1);
-  EXPECT_EQ(GetSessionStorageBeforeFcpCount(), 1);
-  // The UMA counts the first use, so AfterFcp is 0.
-  EXPECT_EQ(GetSessionStorageAfterFcpCount(), 0);
-}
-
-TEST_F(PrerenderFeaturesPageLoadMetricsObserverTest, SessionStorageAfterFcp) {
-  NavigateAndCommit(GURL(kDefaultTestUrl));
-
-  // Reach FCP.
-  SimulateFirstContentfulPaint();
-
-  // Access session storage.
-  tester()->SimulateStorageAccess(
-      GURL(kDefaultTestUrl), GURL(kDefaultTestUrl), false,
-      page_load_metrics::StorageType::kSessionStorage);
-
-  EXPECT_EQ(GetPageVisits(), 1);
-  EXPECT_EQ(GetSessionStorageBeforeFcpCount(), 0);
-  EXPECT_EQ(GetSessionStorageAfterFcpCount(), 1);
-}
-
-TEST_F(PrerenderFeaturesPageLoadMetricsObserverTest, ThirdPartySessionStorage) {
-  NavigateAndCommit(GURL(kDefaultTestUrl));
-
-  tester()->SimulateStorageAccess(
-      GURL(kOtherOriginUrl), GURL(kDefaultTestUrl), false,
-      page_load_metrics::StorageType::kSessionStorage);
-
-  // Cross-origin storage is not logged.
-  EXPECT_EQ(GetPageVisits(), 1);
-  EXPECT_EQ(GetSessionStorageBeforeFcpCount(), 0);
-  EXPECT_EQ(GetSessionStorageAfterFcpCount(), 0);
-}
-
-TEST_F(PrerenderFeaturesPageLoadMetricsObserverTest, MultipleStorage) {
-  NavigateAndCommit(GURL(kDefaultTestUrl));
-
-  // Access local storage.
-  tester()->SimulateStorageAccess(
-      GURL(kDefaultTestUrl), GURL(kDefaultTestUrl), false,
-      page_load_metrics::StorageType::kLocalStorage);
-
-  // Reach FCP.
-  SimulateFirstContentfulPaint();
-
-  // Access session storage.
-  tester()->SimulateStorageAccess(
-      GURL(kDefaultTestUrl), GURL(kDefaultTestUrl), false,
-      page_load_metrics::StorageType::kSessionStorage);
-
-  EXPECT_EQ(GetPageVisits(), 1);
-  EXPECT_EQ(GetLocalStorageBeforeFcpCount(), 1);
-  EXPECT_EQ(GetLocalStorageAfterFcpCount(), 0);
-  EXPECT_EQ(GetSessionStorageBeforeFcpCount(), 0);
-  EXPECT_EQ(GetSessionStorageAfterFcpCount(), 1);
-}
-
-}  // namespace
diff --git a/components/page_load_metrics/browser/observers/prerender_page_load_metrics_observer.cc b/components/page_load_metrics/browser/observers/prerender_page_load_metrics_observer.cc
index 6158573..8c8af39 100644
--- a/components/page_load_metrics/browser/observers/prerender_page_load_metrics_observer.cc
+++ b/components/page_load_metrics/browser/observers/prerender_page_load_metrics_observer.cc
@@ -119,8 +119,12 @@
 
 void PrerenderPageLoadMetricsObserver::RecordSessionEndHistograms(
     const page_load_metrics::mojom::PageLoadTiming& main_frame_timing) {
-  if (!GetDelegate().WasPrerenderedThenActivatedInForeground())
+  if (!GetDelegate().WasPrerenderedThenActivatedInForeground() ||
+      !main_frame_timing.activation_start) {
+    // Even if the page was activated, activation_start may not yet been
+    // notified by the renderer. Ignore such page loads.
     return;
+  }
 
   const page_load_metrics::ContentfulPaintTimingInfo& largest_contentful_paint =
       GetDelegate()
diff --git a/components/page_load_metrics/browser/page_load_metrics_embedder_base.cc b/components/page_load_metrics/browser/page_load_metrics_embedder_base.cc
index c664a7f..d067aa0 100644
--- a/components/page_load_metrics/browser/page_load_metrics_embedder_base.cc
+++ b/components/page_load_metrics/browser/page_load_metrics_embedder_base.cc
@@ -10,7 +10,6 @@
 #include "components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.h"
 #include "components/page_load_metrics/browser/observers/early_hints_page_load_metrics_observer.h"
 #include "components/page_load_metrics/browser/observers/layout_page_load_metrics_observer.h"
-#include "components/page_load_metrics/browser/observers/prerender_features_page_load_metrics_observer.h"
 #include "components/page_load_metrics/browser/observers/prerender_page_load_metrics_observer.h"
 #include "components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h"
 #include "components/page_load_metrics/browser/page_load_tracker.h"
@@ -32,8 +31,6 @@
     tracker->AddObserver(std::make_unique<LayoutPageLoadMetricsObserver>());
     tracker->AddObserver(std::make_unique<UseCounterPageLoadMetricsObserver>());
     tracker->AddObserver(std::make_unique<EarlyHintsPageLoadMetricsObserver>());
-    tracker->AddObserver(
-        std::make_unique<PrerenderFeaturesPageLoadMetricsObserver>());
     tracker->AddObserver(std::make_unique<PrerenderPageLoadMetricsObserver>());
   }
   // Allow the embedder to register any embedder-specific observers
diff --git a/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h b/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h
index 82b9a66..c269ac7 100644
--- a/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h
+++ b/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h
@@ -152,7 +152,8 @@
   virtual const LargestContentfulPaintHandler&
   GetExperimentalLargestContentfulPaintHandler() const = 0;
 
-  // UKM source ID for the current page load.
+  // UKM source ID for the current page load. For prerendered page loads, this
+  // returns ukm::kInvalidSourceId until activation navigation.
   virtual ukm::SourceId GetPageUkmSourceId() const = 0;
 
   // Whether the associated navigation is the first navigation in its associated
diff --git a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc
index fbc1e4fa..c1577df60 100644
--- a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc
+++ b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.cc
@@ -270,6 +270,10 @@
     MergeBackForwardCacheTiming(navigation_start_offset,
                                 new_page_load_timing.back_forward_cache_timings,
                                 is_main_frame);
+    if (is_main_frame) {
+      MaybeUpdateTimeDelta(&target_->activation_start, navigation_start_offset,
+                           new_page_load_timing.activation_start);
+    }
   }
 
   // Whether we merged a new value.
@@ -444,7 +448,9 @@
       main_frame_metadata_(mojom::FrameMetadata::New()),
       subframe_metadata_(mojom::FrameMetadata::New()),
       page_input_timing_(mojom::InputTiming()),
-      mobile_friendliness_(blink::MobileFriendliness()) {}
+      mobile_friendliness_(blink::MobileFriendliness()),
+      is_prerendered_page_load_(navigation_handle->IsInPrerenderedMainFrame()) {
+}
 
 PageLoadMetricsUpdateDispatcher::~PageLoadMetricsUpdateDispatcher() {
   ShutDown();
@@ -841,6 +847,13 @@
       // ordering of these events in their callbacks.
       return;
     }
+    if (is_prerendered_page_load_ &&
+        !pending_merged_page_timing_->activation_start) {
+      // Similarly, in a prerendered page load we may receive a first paint in a
+      // child frame before we've received a notification for activation start
+      // in the main frame.
+      return;
+    }
   }
   if (current_merged_page_timing_->Equals(*pending_merged_page_timing_))
     return;
diff --git a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
index 77469cff..24d93ac 100644
--- a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
+++ b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
@@ -285,6 +285,9 @@
   // MobileFrienddliness data for current view.
   blink::MobileFriendliness mobile_friendliness_;
 
+  // True if this page load started in prerender.
+  const bool is_prerendered_page_load_;
+
   // In general, page_render_data_ contains combined data across all frames on
   // the page, while main_frame_render_data_ contains data specific to the main
   // frame.
diff --git a/components/page_load_metrics/browser/page_load_tracker.cc b/components/page_load_metrics/browser/page_load_tracker.cc
index bcc1415..84ccc9cb 100644
--- a/components/page_load_metrics/browser/page_load_tracker.cc
+++ b/components/page_load_metrics/browser/page_load_tracker.cc
@@ -242,8 +242,6 @@
       aborted_chain_size_same_url_(aborted_chain_size_same_url),
       embedder_interface_(embedder_interface),
       metrics_update_dispatcher_(this, navigation_handle, embedder_interface),
-      source_id_(ukm::ConvertToSourceId(navigation_handle->GetNavigationId(),
-                                        ukm::SourceIdType::NAVIGATION_ID)),
       web_contents_(navigation_handle->GetWebContents()),
       is_first_navigation_in_web_contents_(
           is_first_navigation_in_web_contents) {
@@ -257,6 +255,8 @@
         internal::kPageLoadPrerender2Event,
         internal::PageLoadPrerenderEvent::kNavigationInPrerenderedMainFrame);
   } else {
+    source_id_ = ukm::ConvertToSourceId(navigation_handle->GetNavigationId(),
+                                        ukm::SourceIdType::NAVIGATION_ID);
     INVOKE_AND_PRUNE_OBSERVERS(observers_, OnStart, navigation_handle,
                                currently_committed_url, started_in_foreground_);
   }
@@ -456,6 +456,9 @@
 
 void PageLoadTracker::DidActivatePrerenderedPage(
     content::NavigationHandle* navigation_handle) {
+  source_id_ = ukm::ConvertToSourceId(navigation_handle->GetNavigationId(),
+                                      ukm::SourceIdType::NAVIGATION_ID);
+
   if (GetWebContents()->GetVisibility() == content::Visibility::VISIBLE) {
     was_prerendered_then_activated_in_foreground_ = true;
     PageShown();
diff --git a/components/page_load_metrics/browser/page_load_tracker.h b/components/page_load_metrics/browser/page_load_tracker.h
index 20eb27c..1a91c9f 100644
--- a/components/page_load_metrics/browser/page_load_tracker.h
+++ b/components/page_load_metrics/browser/page_load_tracker.h
@@ -493,7 +493,7 @@
 
   PageLoadMetricsUpdateDispatcher metrics_update_dispatcher_;
 
-  const ukm::SourceId source_id_;
+  ukm::SourceId source_id_ = ukm::kInvalidSourceId;
 
   content::WebContents* const web_contents_;
 
diff --git a/components/password_manager/core/browser/password_form_manager.cc b/components/password_manager/core/browser/password_form_manager.cc
index 2b985e0..4e00030 100644
--- a/components/password_manager/core/browser/password_form_manager.cc
+++ b/components/password_manager/core/browser/password_form_manager.cc
@@ -709,25 +709,35 @@
   metrics_recorder_->set_possible_username_used(false);
   votes_uploader_.clear_single_username_vote_data();
 
+  // TODO(crbug.com/959776): Reset possible username after it's used.
   if (IsUsernameFirstFlowFeatureEnabled() &&
-      parsed_submitted_form_->username_value.empty() && possible_username &&
-      IsPossibleSingleUsernameAvailable(possible_username)) {
-    // Suggest the possible username value in a prompt if the server confirmed
-    // it is a single username field. Otherwise, |possible_username| is used
-    // only for voting.
-    if (possible_username->HasSingleUsernameServerPrediction()) {
-      parsed_submitted_form_->username_value = possible_username->value;
-      LogUsingPossibleUsername(client_, /*is_used*/ true,
-                               "Valid possible username, populated in prompt");
-    } else {
-      LogUsingPossibleUsername(
-          client_, /*is_used*/ true,
-          "Valid possible username, not populated in prompt");
+      parsed_submitted_form_->username_value.empty()) {
+    if (IsPossibleSingleUsernameAvailable(possible_username)) {
+      // Suggest the possible username value in a prompt if the server confirmed
+      // it is a single username field. Otherwise, |possible_username| is used
+      // only for voting.
+      if (possible_username->HasSingleUsernameServerPrediction()) {
+        parsed_submitted_form_->username_value = possible_username->value;
+        metrics_recorder_->set_possible_username_used(true);
+        LogUsingPossibleUsername(
+            client_, /*is_used*/ true,
+            "Valid possible username, populated in prompt");
+      } else {
+        LogUsingPossibleUsername(
+            client_, /*is_used*/ true,
+            "Valid possible username, not populated in prompt");
+      }
+      votes_uploader_.set_single_username_vote_data(
+          possible_username->renderer_id, possible_username->value,
+          possible_username->form_predictions.value_or(FormPredictions()),
+          form_fetcher_->GetBestMatches());
+    } else {  // !IsPossibleSingleUsernameAvailable(possible_username)
+      // If no single username typing preceded single password typing, set
+      // empty single username vote data for the fallback classifier.
+      votes_uploader_.set_single_username_vote_data(
+          FieldRendererId(), std::u16string(), FormPredictions(),
+          form_fetcher_->GetBestMatches());
     }
-    metrics_recorder_->set_possible_username_used(true);
-    votes_uploader_.set_single_username_vote_data(
-        possible_username->renderer_id, possible_username->value,
-        possible_username->form_predictions.value_or(FormPredictions()));
   }
   CreatePendingCredentials();
   return true;
diff --git a/components/password_manager/core/browser/password_form_manager_unittest.cc b/components/password_manager/core/browser/password_form_manager_unittest.cc
index c2a4533..c19905a 100644
--- a/components/password_manager/core/browser/password_form_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_form_manager_unittest.cc
@@ -246,13 +246,14 @@
 // Create simple SINGLE_USERNAME predictions.
 FormPredictions MakeSingleUsernamePredictions(
     autofill::FormSignature form_signature,
+    autofill::FieldSignature field_signature,
     autofill::FieldRendererId field_renderer_id) {
   FormPredictions predictions;
   predictions.form_signature = form_signature;
 
   PasswordFieldPrediction field_prediction;
   field_prediction.renderer_id = field_renderer_id;
-  field_prediction.signature.value() = 123;
+  field_prediction.signature = field_signature;
   field_prediction.type = autofill::SINGLE_USERNAME;
   predictions.fields.push_back(field_prediction);
 
@@ -2152,8 +2153,10 @@
 
   // Create form predictions and set them to |possible_username_data|.
   constexpr autofill::FormSignature kUsernameFormSignature(1000);
+  constexpr autofill::FieldSignature kUsernameFieldSignature(123);
   possible_username_data.form_predictions = MakeSingleUsernamePredictions(
-      kUsernameFormSignature, kUsernameFieldRendererId);
+      kUsernameFormSignature, kUsernameFieldSignature,
+      kUsernameFieldRendererId);
 
   ASSERT_TRUE(form_manager_->ProvisionallySave(submitted_form, &driver_,
                                                &possible_username_data));
@@ -2181,8 +2184,10 @@
       username_field_name, possible_username, base::Time::Now(),
       0 /* driver_id */);
   constexpr autofill::FormSignature kUsernameFormSignature(1000);
+  constexpr autofill::FieldSignature kUsernameFieldSignature(123);
   possible_username_data.form_predictions = MakeSingleUsernamePredictions(
-      kUsernameFormSignature, kUsernameFieldRendererId);
+      kUsernameFormSignature, kUsernameFieldSignature,
+      kUsernameFieldRendererId);
 
   FormData submitted_form = observed_form_only_password_fields_;
   submitted_form.fields[0].value = u"strongpassword";
@@ -2221,8 +2226,10 @@
         saved_match_.signon_realm, kUsernameFieldRendererId, field_name,
         possible_username, base::Time::Now(), 0 /* driver_id */);
     constexpr autofill::FormSignature kUsernameFormSignature(1000);
+    constexpr autofill::FieldSignature kUsernameFieldSignature(123);
     possible_username_data.form_predictions = MakeSingleUsernamePredictions(
-        kUsernameFormSignature, kUsernameFieldRendererId);
+        kUsernameFormSignature, kUsernameFieldSignature,
+        kUsernameFieldRendererId);
 
     MockFieldInfoManager mock_field_manager;
     ON_CALL(mock_field_manager, GetFieldType(_, _))
@@ -2234,6 +2241,8 @@
     // |possible_username_data| will be taken for setting username.
     FormData submitted_form = observed_form_only_password_fields_;
     submitted_form.fields[0].value = u"strongpassword";
+    if (is_password_update)
+      submitted_form.fields[0].autocomplete_attribute = "new-password";
 
     ASSERT_TRUE(form_manager_->ProvisionallySave(submitted_form, &driver_,
                                                  &possible_username_data));
@@ -2242,15 +2251,7 @@
     // Check that uploads for both username and password form happen.
     testing::InSequence in_sequence;
 
-    if (!is_password_update) {
-      // Upload for the password form.
-      EXPECT_CALL(mock_autofill_download_manager_,
-                  StartUploadRequest(
-                      SignatureIs(CalculateFormSignature(submitted_form)),
-                      false, _, _, true, nullptr));
-    }
-
-    // Upload for the username form.
+    // Upload username first flow votes on the username form.
 #if !defined(OS_ANDROID)
     EXPECT_CALL(mock_autofill_download_manager_,
                 StartUploadRequest(SignatureIs(kUsernameFormSignature), false,
@@ -2258,12 +2259,39 @@
                                    nullptr));
 #endif  // !defined(OS_ANDROID)
 
+    // Upload username first flow votes on the password form.
+    autofill::AutofillUploadContents::SingleUsernameData
+        expected_single_username_data;
+    expected_single_username_data.set_username_form_signature(
+        kUsernameFormSignature.value());
+    expected_single_username_data.set_username_field_signature(
+        kUsernameFieldSignature.value());
+    expected_single_username_data.set_value_type(
+        is_password_update
+            ? autofill::AutofillUploadContents::STORED_FOR_CURRENT_DOMAIN
+            : autofill::AutofillUploadContents::USERNAME_LIKE);
+// As Android does not allow username editing, |NO_INFORMATION| about prompt
+// edits is uploaded.
+#if !defined(OS_ANDROID)
+    expected_single_username_data.set_prompt_edit(
+        autofill::AutofillUploadContents::EDITED_POSITIVE);
+#else
+    expected_single_username_data.set_prompt_edit(
+        autofill::AutofillUploadContents::EDIT_UNSPECIFIED);
+#endif  // !defined(OS_ANDROID)
+    EXPECT_CALL(
+        mock_autofill_download_manager_,
+        StartUploadRequest(
+            AllOf(SignatureIs(CalculateFormSignature(submitted_form)),
+                  UploadedSingleUsernameDataIs(expected_single_username_data)),
+            _, _, _, _, _));
+
     if (is_password_update) {
-      // Upload for the password form.
-      EXPECT_CALL(mock_autofill_download_manager_,
-                  StartUploadRequest(
-                      SignatureIs(CalculateFormSignature(submitted_form)),
-                      false, _, _, true, nullptr));
+      // Expect another upload for first login votes. This upload is not related
+      // to UFF, so it should not contain single username data.
+      EXPECT_CALL(
+          mock_autofill_download_manager_,
+          StartUploadRequest(SingleUsernameDataNotUploaded(), _, _, _, _, _));
     }
 
     if (!is_password_update)
@@ -2275,8 +2303,15 @@
   }
 }
 
-// Tests that username is taken during username first flow.
+// Tests that a negative vote is sent when a single username candidate is
+// populated in a prompt, but then is removed by the user in the prompt.
 TEST_P(PasswordFormManagerTest, NegativeUsernameFirstFlowVotes) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kUsernameFirstFlow,
+                            features::kUsernameFirstFlowFallbackCrowdsourcing},
+      /*disabled_features=*/{});
+
   constexpr char16_t kPossibleUsername[] = u"possible_username";
 
   constexpr autofill::FieldRendererId kUsernameFieldRendererId(100);
@@ -2289,12 +2324,6 @@
   ON_CALL(client_, GetFieldInfoManager)
       .WillByDefault(Return(&mock_field_manager));
 
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatures(
-      /*enabled_features=*/{features::kUsernameFirstFlow,
-                            features::kUsernameFirstFlowFallbackCrowdsourcing},
-      /*disabled_features=*/{});
-
   CreateFormManager(observed_form_only_password_fields_);
   fetcher_->NotifyFetchCompleted();
 
@@ -2318,14 +2347,16 @@
   ASSERT_TRUE(form_manager_->ProvisionallySave(submitted_form, &driver_,
                                                &possible_username_data));
 
+  // Simulate showing the prompt and saving the suggested value.
+  // TODO(crbug/959776) Add a unittest for the case when this method is not
+  // called.
+  form_manager_->SaveSuggestedUsernameValueToVotesUploader();
+
   // Simulate the user modifying the username in the prompt.
   form_manager_->OnUpdateUsernameFromPrompt(u"different_username");
 
   // Check that uploads for both username and password form happen.
   testing::InSequence in_sequence;
-  // Upload for the password form.
-  EXPECT_CALL(mock_autofill_download_manager_,
-              StartUploadRequest(_, false, _, _, true, nullptr));
 
   // Upload for the username form. Ensure that we send `NOT_USERNAME` for the
   // username field.
@@ -2337,6 +2368,32 @@
 #else
   EXPECT_CALL(mock_autofill_download_manager_, StartUploadRequest).Times(0);
 #endif  // !defined(OS_ANDROID)
+
+  // Upload for the password form.
+  autofill::AutofillUploadContents::SingleUsernameData
+      expected_single_username_data;
+  expected_single_username_data.set_username_form_signature(
+      kUsernameFormSignature.value());
+  expected_single_username_data.set_username_field_signature(
+      kUsernameFieldSignature.value());
+  expected_single_username_data.set_value_type(
+      autofill::AutofillUploadContents::USERNAME_LIKE);
+// As Android does not allow username editing, |NO_INFORMATION| about prompt
+// edits is uploaded.
+#if !defined(OS_ANDROID)
+  expected_single_username_data.set_prompt_edit(
+      autofill::AutofillUploadContents::EDITED_NEGATIVE);
+#else
+  expected_single_username_data.set_prompt_edit(
+      autofill::AutofillUploadContents::EDIT_UNSPECIFIED);
+#endif  // !defined(OS_ANDROID)
+  EXPECT_CALL(
+      mock_autofill_download_manager_,
+      StartUploadRequest(
+          AllOf(SignatureIs(CalculateFormSignature(submitted_form)),
+                UploadedSingleUsernameDataIs(expected_single_username_data)),
+          _, _, _, _, _));
+
   form_manager_->Save();
 }
 
@@ -2360,8 +2417,10 @@
       saved_match_.signon_realm, kUsernameFieldRendererId, field_name,
       possible_username, base::Time::Now(), 0 /* driver_id */);
   constexpr autofill::FormSignature kUsernameFormSignature(1000);
+  constexpr autofill::FieldSignature kUsernameFieldSignature(123);
   possible_username_data.form_predictions = MakeSingleUsernamePredictions(
-      kUsernameFormSignature, kUsernameFieldRendererId);
+      kUsernameFormSignature, kUsernameFieldSignature,
+      kUsernameFieldRendererId);
 
   MockFieldInfoManager mock_field_manager;
   ON_CALL(mock_field_manager, GetFieldType(_, _))
@@ -2379,16 +2438,25 @@
 
   // Check that uploads for both username and password form happen.
   testing::InSequence in_sequence;
-  // Upload for the password form.
-  EXPECT_CALL(mock_autofill_download_manager_,
-              StartUploadRequest(_, false, _, _, true, nullptr));
 
-  // No upload for the username form.
+  // No single username upload for the username form with a nameless field.
   EXPECT_CALL(
       mock_autofill_download_manager_,
       StartUploadRequest(SignatureIs(kUsernameFormSignature), _, _, _, _, _))
       .Times(0);
 
+  // Upload single username data for the password form.
+  autofill::AutofillUploadContents::SingleUsernameData
+      expected_single_username_data;
+  expected_single_username_data.set_value_type(
+      autofill::AutofillUploadContents::NO_VALUE_TYPE);
+  EXPECT_CALL(
+      mock_autofill_download_manager_,
+      StartUploadRequest(
+          AllOf(SignatureIs(CalculateFormSignature(submitted_form)),
+                UploadedSingleUsernameDataIs(expected_single_username_data)),
+          _, _, _, _, _));
+
   form_manager_->Save();
 }
 
@@ -2996,8 +3064,10 @@
       saved_match_.signon_realm, kUsernameFieldRendererId, username_field_name,
       possible_username, base::Time::Now(), 0 /* driver_id */);
   constexpr autofill::FormSignature kUsernameFormSignature(1000);
+  constexpr autofill::FieldSignature kUsernameFieldSignature(123);
   possible_username_data.form_predictions = MakeSingleUsernamePredictions(
-      kUsernameFormSignature, kUsernameFieldRendererId);
+      kUsernameFormSignature, kUsernameFieldSignature,
+      kUsernameFieldRendererId);
 
   FormData submitted_form = observed_form_only_password_fields_;
   submitted_form.fields[0].value = u"strongpassword";
@@ -3026,8 +3096,10 @@
       username_field_name, possible_username, base::Time::Now(),
       0 /* driver_id */);
   constexpr autofill::FormSignature kUsernameFormSignature(1000);
+  constexpr autofill::FieldSignature kUsernameFieldSignature(123);
   possible_username_data.form_predictions = MakeSingleUsernamePredictions(
-      kUsernameFormSignature, kUsernameFieldRendererId);
+      kUsernameFormSignature, kUsernameFieldSignature,
+      kUsernameFieldRendererId);
 
   FormData submitted_form = observed_form_only_password_fields_;
   submitted_form.fields[0].value = u"strongpassword";
diff --git a/components/password_manager/core/browser/password_form_metrics_recorder.h b/components/password_manager/core/browser/password_form_metrics_recorder.h
index 7ed2dee..0c6630c 100644
--- a/components/password_manager/core/browser/password_form_metrics_recorder.h
+++ b/components/password_manager/core/browser/password_form_metrics_recorder.h
@@ -524,7 +524,9 @@
   absl::optional<metrics_util::PasswordAccountStorageUsageLevel>
       account_storage_usage_level_;
 
+  // Whether a single username candidate was populated in prompt.
   bool possible_username_used_ = false;
+
   bool username_updated_in_bubble_ = false;
 
   absl::optional<JsOnlyInput> js_only_input_;
diff --git a/components/password_manager/core/browser/password_save_manager_impl.cc b/components/password_manager/core/browser/password_save_manager_impl.cc
index b3bdd79..9080e3f 100644
--- a/components/password_manager/core/browser/password_save_manager_impl.cc
+++ b/components/password_manager/core/browser/password_save_manager_impl.cc
@@ -497,9 +497,13 @@
       parsed_submitted_form.submission_event);
   metrics_recorder_->SetSubmissionIndicatorEvent(
       parsed_submitted_form.submission_event);
-  // Remember the final username value which the user accepted the prompt
-  // with in order to produce votes based on username edits.
-  votes_uploader_->set_saved_username(pending_credentials_.username_value);
+// It's not possible to edit username in a save/update prompt on Android.
+// TODO(crbug.com/959776): Get rid of this method, by passing
+// |pending_credentials_| directly to MaybeSendSingleUsernameVote.
+#if !defined(OS_ANDROID)
+  votes_uploader_->CalculateUsernamePromptEditState(
+      /*saved_username=*/pending_credentials_.username_value);
+#endif  // !defined(OS_ANDROID)
 
   if (IsNewLogin()) {
     metrics_util::LogNewlySavedPasswordIsGenerated(
@@ -537,10 +541,10 @@
         *observed_form, parsed_submitted_form, &pending_credentials_);
   }
   if (IsPasswordUpdate()) {
+    votes_uploader_->MaybeSendSingleUsernameVote();
     votes_uploader_->UploadPasswordVote(
         parsed_submitted_form, parsed_submitted_form, autofill::NEW_PASSWORD,
         FormStructure(pending_credentials_.form_data).FormSignatureAsStr());
-    votes_uploader_->MaybeSendSingleUsernameVote();
   }
 
   if (pending_credentials_.times_used == 1) {
diff --git a/components/password_manager/core/browser/password_save_manager_impl_unittest.cc b/components/password_manager/core/browser/password_save_manager_impl_unittest.cc
index e17d13b4..0a47eb35 100644
--- a/components/password_manager/core/browser/password_save_manager_impl_unittest.cc
+++ b/components/password_manager/core/browser/password_save_manager_impl_unittest.cc
@@ -606,7 +606,6 @@
   EXPECT_EQ(submitted_form.url, saved_form.url);
   EXPECT_EQ(expected_signon_realm, saved_form.signon_realm);
   EXPECT_EQ(new_username, saved_form.username_value);
-  EXPECT_EQ(new_username, votes_uploader()->saved_username());
   EXPECT_EQ(new_password, saved_form.password_value);
 
   EXPECT_EQ(submitted_form.fields[kUsernameFieldIndex].name,
@@ -697,7 +696,6 @@
 
   EXPECT_TRUE(ArePasswordFormUniqueKeysEqual(saved_match_, updated_form));
   EXPECT_EQ(new_password, updated_form.password_value);
-  EXPECT_EQ(username, votes_uploader()->saved_username());
 }
 
 // Tests that when the user changes password on a change password form then the
@@ -738,7 +736,6 @@
 
   EXPECT_TRUE(ArePasswordFormUniqueKeysEqual(saved_match_, updated_form));
   EXPECT_EQ(new_password, updated_form.password_value);
-  EXPECT_EQ(saved_match_.username_value, votes_uploader()->saved_username());
 }
 
 TEST_P(PasswordSaveManagerImplTest, UpdateUsernameToAnotherFieldValue) {
@@ -775,7 +772,6 @@
 
   password_save_manager_impl()->Save(&observed_form_only_password_fields_,
                                      parsed_submitted_form);
-  EXPECT_EQ(user_chosen_username, votes_uploader()->saved_username());
 }
 
 TEST_P(PasswordSaveManagerImplTest, UpdateUsernameToAlreadyExisting) {
diff --git a/components/password_manager/core/browser/vote_uploads_test_matchers.h b/components/password_manager/core/browser/vote_uploads_test_matchers.h
index 739c97e..ccbed192 100644
--- a/components/password_manager/core/browser/vote_uploads_test_matchers.h
+++ b/components/password_manager/core/browser/vote_uploads_test_matchers.h
@@ -187,6 +187,55 @@
   return true;
 }
 
+MATCHER_P(UploadedSingleUsernameDataIs, expected_data, "") {
+  if (!arg.single_username_data()) {
+    *result_listener << "Single username data missing";
+    return false;
+  }
+  if (expected_data.username_form_signature() !=
+      arg.single_username_data()->username_form_signature()) {
+    // Wrong form signature.
+    *result_listener << "Expected form signature is "
+                     << expected_data.username_form_signature()
+                     << ", but found "
+                     << arg.single_username_data()->username_form_signature();
+    return false;
+  }
+  if (expected_data.username_field_signature() !=
+      arg.single_username_data()->username_field_signature()) {
+    // Wrong field signature.
+    *result_listener << "Expected field signature is "
+                     << expected_data.username_field_signature()
+                     << ", but found "
+                     << arg.single_username_data()->username_field_signature();
+    return false;
+  }
+  if (expected_data.value_type() != arg.single_username_data()->value_type()) {
+    // Wrong value type.
+    *result_listener << "Expected value type is " << expected_data.value_type()
+                     << ", but found "
+                     << arg.single_username_data()->value_type();
+    return false;
+  }
+  if (expected_data.prompt_edit() !=
+      arg.single_username_data()->prompt_edit()) {
+    // Wrong information about username edits in prompt.
+    *result_listener << "Expected prompt edit is "
+                     << expected_data.prompt_edit() << ", but found "
+                     << arg.single_username_data()->prompt_edit();
+    return false;
+  }
+  return true;
+}
+
+MATCHER(SingleUsernameDataNotUploaded, "") {
+  if (arg.single_username_data()) {
+    *result_listener << "Single username not expected to be uploaded";
+    return false;
+  }
+  return true;
+}
+
 MATCHER_P(PasswordsWereRevealed, passwords_were_revealed, "") {
   return passwords_were_revealed == arg.passwords_were_revealed();
 }
diff --git a/components/password_manager/core/browser/votes_uploader.cc b/components/password_manager/core/browser/votes_uploader.cc
index 39ca0da6..81255f03 100644
--- a/components/password_manager/core/browser/votes_uploader.cc
+++ b/components/password_manager/core/browser/votes_uploader.cc
@@ -18,6 +18,8 @@
 #include "build/build_config.h"
 #include "components/autofill/core/browser/autofill_download_manager.h"
 #include "components/autofill/core/browser/autofill_field.h"
+#include "components/autofill/core/browser/autofill_regex_constants.h"
+#include "components/autofill/core/browser/autofill_regexes.h"
 #include "components/autofill/core/browser/form_structure.h"
 #include "components/autofill/core/browser/randomized_encoder.h"
 #include "components/autofill/core/common/form_data.h"
@@ -27,14 +29,17 @@
 #include "components/password_manager/core/browser/field_info_manager.h"
 #include "components/password_manager/core/browser/password_manager_client.h"
 #include "components/password_manager/core/browser/password_manager_util.h"
+#include "components/password_manager/core/common/password_manager_features.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 using autofill::AutofillDownloadManager;
 using autofill::AutofillField;
 using autofill::AutofillUploadContents;
+using autofill::FieldRendererId;
 using autofill::FieldSignature;
 using autofill::FormData;
 using autofill::FormFieldData;
+using autofill::FormSignature;
 using autofill::FormStructure;
 using autofill::RandomizedEncoder;
 using autofill::ServerFieldType;
@@ -174,15 +179,59 @@
          kNumberOfLowEntropyHashValues;
 }
 
+FieldSignature GetUsernameFieldSignature(
+    const SingleUsernameVoteData& single_username_data) {
+  for (const auto& field : single_username_data.form_predictions.fields) {
+    if (field.renderer_id == single_username_data.renderer_id)
+      return field.signature;
+  }
+  return FieldSignature();
+}
+
+AutofillUploadContents::ValueType GetValueType(
+    const std::u16string& username_value,
+    const std::vector<const PasswordForm*>& stored_credentials) {
+  if (username_value.empty())
+    return AutofillUploadContents::NO_VALUE_TYPE;
+
+  // Check if |username_value| is an already stored username.
+  // TODO(crbug.com/959776) Implement checking against usenames stored for all
+  // domains and return STORED_FOR_ANOTHER_DOMAIN in that case.
+  auto credential_match = base::ranges::find_if(
+      stored_credentials, [&username_value](const PasswordForm* credential) {
+        return credential->username_value == username_value;
+      });
+  if (credential_match != stored_credentials.end())
+    return AutofillUploadContents::STORED_FOR_CURRENT_DOMAIN;
+
+  if (autofill::MatchesPattern(username_value, autofill::kEmailValueRe))
+    return AutofillUploadContents::EMAIL;
+
+  if (autofill::MatchesPattern(username_value, autofill::kPhoneValueRe))
+    return AutofillUploadContents::PHONE;
+
+  if (autofill::MatchesPattern(username_value, autofill::kUsernameLikeValueRe))
+    return AutofillUploadContents::USERNAME_LIKE;
+
+  if (username_value.find(' ') != std::u16string::npos)
+    return AutofillUploadContents::VALUE_WITH_WHITESPACE;
+
+  return AutofillUploadContents::VALUE_WITH_NO_WHITESPACE;
+}
+
 }  // namespace
 
 SingleUsernameVoteData::SingleUsernameVoteData(
-    autofill::FieldRendererId renderer_id,
-    const std::u16string& username_candidate_value,
-    const FormPredictions& form_predictions)
-    : renderer_id(renderer_id),
-      username_candidate_value(username_candidate_value),
-      form_predictions(form_predictions) {}
+    FieldRendererId renderer_id,
+    const std::u16string& username_value,
+    const FormPredictions& form_predictions,
+    const std::vector<const PasswordForm*>& stored_credentials)
+    : renderer_id(renderer_id), form_predictions(form_predictions) {
+  base::TrimWhitespace(username_value, base::TrimPositions::TRIM_ALL,
+                       &username_candidate_value);
+  value_type = GetValueType(username_candidate_value, stored_credentials);
+  prompt_edit = autofill::AutofillUploadContents::EDIT_UNSPECIFIED;
+}
 
 SingleUsernameVoteData::SingleUsernameVoteData(
     const SingleUsernameVoteData& other) = default;
@@ -215,6 +264,7 @@
   // Credentials that have been previously used (e.g., PSL matches) are checked
   // to see if they are valid account creation forms.
   if (pending_credentials->times_used == 0) {
+    MaybeSendSingleUsernameVote();
     UploadPasswordVote(*pending_credentials, submitted_form, autofill::PASSWORD,
                        std::string());
     if (username_correction_vote_) {
@@ -222,7 +272,6 @@
                          autofill::USERNAME,
                          FormStructure(observed).FormSignatureAsStr());
     }
-    MaybeSendSingleUsernameVote();
   } else {
     SendVoteOnCredentialsReuse(observed, submitted_form, pending_credentials);
   }
@@ -330,6 +379,12 @@
         form_structure.set_passwords_were_revealed(
             has_passwords_revealed_vote_);
       }
+      // If a user accepts a save or update prompt, send a single username vote.
+      if ((autofill_type == autofill::PASSWORD ||
+           autofill_type == autofill::NEW_PASSWORD) &&
+          single_username_vote_data_) {
+        SetSingleUsernameVoteOnPasswordForm(form_structure);
+      }
     }
     if (autofill_type != autofill::ACCOUNT_CREATION_PASSWORD) {
       if (generation_popup_was_shown_)
@@ -449,7 +504,7 @@
 }
 
 void VotesUploader::SetInitialHashValueOfUsernameField(
-    autofill::FieldRendererId username_element_renderer_id,
+    FieldRendererId username_element_renderer_id,
     FormStructure* form_structure) {
   auto it = initial_values_.find(username_element_renderer_id);
 
@@ -490,9 +545,7 @@
   ServerFieldTypeSet available_field_types;
   for (size_t i = 0; i < form_to_upload->field_count(); ++i) {
     AutofillField* field = form_to_upload->field(i);
-
-    autofill::FieldRendererId field_renderer_id =
-        predictions.fields[i].renderer_id;
+    FieldRendererId field_renderer_id = predictions.fields[i].renderer_id;
 
     if (field_renderer_id != single_username_vote_data_->renderer_id) {
       field->set_possible_types({autofill::UNKNOWN_TYPE});
@@ -502,24 +555,69 @@
                                          predictions.fields[i].signature) !=
         autofill::UNKNOWN_TYPE) {
       // The vote for this field has been already sent. Don't send again.
-      return;
+      break;
     }
-    if (!SetSingleUsernameVote(field, &available_field_types,
-                               form_to_upload->form_signature())) {
+    if (!SetSingleUsernameVoteOnUsernameForm(field, &available_field_types,
+                                             predictions.form_signature)) {
       // The single username field has no field type. Don't send vote.
       return;
     }
   }
 
-  if (password_manager_util::IsLoggingActive(client_)) {
-    BrowserSavePasswordProgressLogger logger(client_->GetLogManager());
-    logger.LogFormStructure(Logger::STRING_USERNAME_FIRST_FLOW_VOTE,
-                            *form_to_upload);
+  // Upload a vote on the username form if available.
+  if (!available_field_types.empty()) {
+    if (password_manager_util::IsLoggingActive(client_)) {
+      BrowserSavePasswordProgressLogger logger(client_->GetLogManager());
+      logger.LogFormStructure(Logger::STRING_USERNAME_FIRST_FLOW_VOTE,
+                              *form_to_upload);
+    }
+    StartUploadRequest(std::move(form_to_upload), available_field_types);
   }
-
-  StartUploadRequest(std::move(form_to_upload), available_field_types);
 }
 
+#if !defined(OS_ANDROID)
+void VotesUploader::CalculateUsernamePromptEditState(
+    const std::u16string& saved_username) {
+  if (!single_username_vote_data_ ||
+      single_username_vote_data_->username_candidate_value.empty()) {
+    return;
+  }
+  const auto& single_username_value =
+      single_username_vote_data_->username_candidate_value;
+
+  autofill::AutofillUploadContents::SingleUsernamePromptEdit prompt_edit =
+      autofill::AutofillUploadContents::EDIT_UNSPECIFIED;
+  if (saved_username != suggested_username_) {
+    // In this branch, the user edited the username in a prompt before accepting
+    // it.
+
+    // The user removed some suggested username and that username wasn't the
+    // possible single username (|single_username_value|) => this is neither
+    // negative nor positive vote. If the user removes |single_username_value|,
+    // then it is a negative signal and will be reported below.
+    if (saved_username.empty() &&
+        suggested_username_ != single_username_value) {
+      return;
+    }
+
+    if (saved_username == single_username_value)
+      prompt_edit = autofill::AutofillUploadContents::EDITED_POSITIVE;
+    else
+      prompt_edit = autofill::AutofillUploadContents::EDITED_NEGATIVE;
+
+  } else {  // saved_username == suggested_username
+    // In this branch the user did NOT edit the username in prompt and accepted
+    // it as it is.
+
+    if (saved_username == single_username_value)
+      prompt_edit = autofill::AutofillUploadContents::NOT_EDITED_POSITIVE;
+    else
+      prompt_edit = autofill::AutofillUploadContents::NOT_EDITED_NEGATIVE;
+  }
+  single_username_vote_data_->prompt_edit = prompt_edit;
+}
+#endif  // !defined(OS_ANDROID)
+
 void VotesUploader::AddGeneratedVote(FormStructure* form_structure) {
   DCHECK(form_structure);
   DCHECK(generation_popup_was_shown_);
@@ -706,8 +804,8 @@
       std::string(), true /* observed_submission */, nullptr /* prefs */);
 }
 
-void VotesUploader::SaveFieldVote(autofill::FormSignature form_signature,
-                                  autofill::FieldSignature field_signature,
+void VotesUploader::SaveFieldVote(FormSignature form_signature,
+                                  FieldSignature field_signature,
                                   autofill::ServerFieldType field_type) {
   FieldInfoManager* field_info_manager = client_->GetFieldInfoManager();
   if (!field_info_manager)
@@ -715,10 +813,10 @@
   field_info_manager->AddFieldType(form_signature, field_signature, field_type);
 }
 
-bool VotesUploader::SetSingleUsernameVote(
+bool VotesUploader::SetSingleUsernameVoteOnUsernameForm(
     AutofillField* field,
     ServerFieldTypeSet* available_field_types,
-    autofill::FormSignature form_signature) {
+    FormSignature form_signature) {
   ServerFieldType type = autofill::UNKNOWN_TYPE;
   autofill::AutofillUploadContents_Field_SingleUsernameVoteType vote_type =
       AutofillUploadContents::Field::DEFAULT;
@@ -733,27 +831,18 @@
 // It's not possible to edit username in the save prompt on Android, thus it's
 // not possible to rely on this heuristic.
 #if !defined(OS_ANDROID)
-    if (saved_username_ != suggested_username_) {
-      // The user edited the username in a prompt before accepting it.
-      // The user removed some suggested username and that username wasn't the
-      // possible single username (|single_username_value|) => this is neither
-      // negative or positive vote. If the user removes |single_username_value|,
-      // then it is a negative signal and will be reported below.
-      if (saved_username_.empty() &&
-          suggested_username_ != single_username_value) {
-        return false;
-      }
-      type = saved_username_ == single_username_value
-                 ? autofill::SINGLE_USERNAME
-                 : autofill::NOT_USERNAME;
-      vote_type = AutofillUploadContents::Field::STRONG;
-    } else {  // saved_username_ == suggested_username
-      // The user did NOT edit the username in prompt and accepted it as it is.
-      type = saved_username_ == single_username_value
-                 ? autofill::SINGLE_USERNAME
-                 : autofill::NOT_USERNAME;
-      vote_type = AutofillUploadContents::Field::WEAK;
-    }
+    const auto& prompt_edit = single_username_vote_data_->prompt_edit;
+    // There is no meaningful data on prompt edit, the vote should not be sent.
+    if (prompt_edit == AutofillUploadContents::EDIT_UNSPECIFIED)
+      return false;
+    type = (prompt_edit == AutofillUploadContents::EDITED_POSITIVE ||
+            prompt_edit == AutofillUploadContents::NOT_EDITED_POSITIVE)
+               ? autofill::SINGLE_USERNAME
+               : autofill::NOT_USERNAME;
+    vote_type = (prompt_edit == AutofillUploadContents::EDITED_POSITIVE ||
+                 prompt_edit == AutofillUploadContents::EDITED_NEGATIVE)
+                    ? AutofillUploadContents::Field::STRONG
+                    : AutofillUploadContents::Field::WEAK;
 #else
     return false;
 #endif  // !defined(OS_ANDROID)
@@ -765,4 +854,21 @@
   return true;
 }
 
+void VotesUploader::SetSingleUsernameVoteOnPasswordForm(
+    FormStructure& form_structure) {
+  if (!base::FeatureList::IsEnabled(
+          features::kUsernameFirstFlowFallbackCrowdsourcing)) {
+    return;
+  }
+  AutofillUploadContents::SingleUsernameData single_username_data;
+  single_username_data.set_username_form_signature(
+      single_username_vote_data_->form_predictions.form_signature.value());
+  single_username_data.set_username_field_signature(
+      GetUsernameFieldSignature(*single_username_vote_data_).value());
+  single_username_data.set_value_type(single_username_vote_data_->value_type);
+  single_username_data.set_prompt_edit(single_username_vote_data_->prompt_edit);
+
+  form_structure.set_single_username_data(single_username_data);
+}
+
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/votes_uploader.h b/components/password_manager/core/browser/votes_uploader.h
index fa6bba7..1d95c8cb 100644
--- a/components/password_manager/core/browser/votes_uploader.h
+++ b/components/password_manager/core/browser/votes_uploader.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "build/build_config.h"
 #include "components/autofill/core/browser/field_types.h"
 #include "components/autofill/core/browser/proto/server.pb.h"
 #include "components/autofill/core/common/signatures.h"
@@ -35,9 +36,11 @@
 
 // Contains information for sending a SINGLE_USERNAME vote.
 struct SingleUsernameVoteData {
-  SingleUsernameVoteData(autofill::FieldRendererId renderer_id,
-                         const std::u16string& username_candidate_value,
-                         const FormPredictions& form_predictions);
+  SingleUsernameVoteData(
+      autofill::FieldRendererId renderer_id,
+      const std::u16string& username_value,
+      const FormPredictions& form_predictions,
+      const std::vector<const PasswordForm*>& stored_credentials);
   SingleUsernameVoteData(const SingleUsernameVoteData&);
   SingleUsernameVoteData& operator=(const SingleUsernameVoteData&);
   SingleUsernameVoteData(SingleUsernameVoteData&& other);
@@ -52,6 +55,14 @@
 
   // Predictions for the form which contains a field with |renderer_id|.
   FormPredictions form_predictions;
+
+  // Type of the value seen in the single username candidate field.
+  autofill::AutofillUploadContents::ValueType value_type;
+
+  // Information about username edits in a save/update prompt. Not calculated on
+  // Android, because it's not possible to edit credentials in prompts on
+  // Android.
+  autofill::AutofillUploadContents::SingleUsernamePromptEdit prompt_edit;
 };
 
 // This class manages vote uploads for password forms.
@@ -138,6 +149,15 @@
   // method.
   void MaybeSendSingleUsernameVote();
 
+// Not calculated on Android, because it's not possible to edit credentials in
+// prompts on Android.
+#if !defined(OS_ANDROID)
+  // Calculate whether the username value was edited in a prompt based on
+  // suggested and saved username values and whether it confirms or
+  // contradicts |single_username_vote_data_|.
+  void CalculateUsernamePromptEditState(const std::u16string& saved_username);
+#endif  // !defined(OS_ANDROID)
+
   void set_generation_popup_was_shown(bool generation_popup_was_shown) {
     generation_popup_was_shown_ = generation_popup_was_shown;
   }
@@ -183,24 +203,20 @@
   void set_single_username_vote_data(
       autofill::FieldRendererId renderer_id,
       const std::u16string& username_candidate_value,
-      const FormPredictions& form_predictions) {
+      const FormPredictions& form_predictions,
+      const std::vector<const PasswordForm*>& stored_credentials) {
     single_username_vote_data_.emplace(renderer_id, username_candidate_value,
-                                       form_predictions);
+                                       form_predictions, stored_credentials);
   }
 
   void set_suggested_username(const std::u16string& suggested_username) {
     suggested_username_ = suggested_username;
   }
 
-  void set_saved_username(const std::u16string& saved_username) {
-    saved_username_ = saved_username;
-  }
-
 #if defined(UNIT_TEST)
   const std::u16string& suggested_username() const {
     return suggested_username_;
   }
-  const std::u16string& saved_username() const { return saved_username_; }
 #endif
 
  private:
@@ -236,14 +252,23 @@
                      autofill::FieldSignature field_signature,
                      autofill::ServerFieldType field_type);
 
-  // Sets a server field type for the |field| and updates
-  // |available_field_types| and |field_info_manager| accordingly. Returns true
-  // iff a non-default type is assigned to the |field|.
-  bool SetSingleUsernameVote(
+  // On username first flow votes are uploaded both for the single username form
+  // and for the single password form. This method sets the data needed to
+  // upload vote on the username form. The vote is based on the user interaction
+  // with the save prompt (i.e. whether the suggested value was actually saved).
+  bool SetSingleUsernameVoteOnUsernameForm(
       autofill::AutofillField* field,
       autofill::ServerFieldTypeSet* available_field_types,
       autofill::FormSignature form_signature);
 
+  // On username first flow votes are uploaded both for the single username form
+  // and for the single password form. This method sets the data needed to
+  // upload vote on the password form. The vote is based on whether there was
+  // a username form that preceded the password form, and on the type of user
+  // input it had (e.g. email-like, phone-like, arbitrary string).
+  void SetSingleUsernameVoteOnPasswordForm(
+      autofill::FormStructure& form_structure);
+
   // The client which implements embedder-specific PasswordManager operations.
   PasswordManagerClient* client_;
 
@@ -292,9 +317,6 @@
 
   // The username that is suggested in a save/update prompt.
   std::u16string suggested_username_;
-
-  // The username that was saved.
-  std::u16string saved_username_;
 };
 
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/votes_uploader_unittest.cc b/components/password_manager/core/browser/votes_uploader_unittest.cc
index d51edf74..c39db2c 100644
--- a/components/password_manager/core/browser/votes_uploader_unittest.cc
+++ b/components/password_manager/core/browser/votes_uploader_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "build/build_config.h"
 #include "components/autofill/core/browser/autofill_download_manager.h"
@@ -26,6 +27,7 @@
 #include "components/password_manager/core/browser/mock_password_store_interface.h"
 #include "components/password_manager/core/browser/stub_password_manager_client.h"
 #include "components/password_manager/core/browser/vote_uploads_test_matchers.h"
+#include "components/password_manager/core/common/password_manager_features.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -34,6 +36,7 @@
 
 using autofill::AutofillDownloadManager;
 using autofill::CONFIRMATION_PASSWORD;
+using autofill::FieldRendererId;
 using autofill::FieldSignature;
 using autofill::FormData;
 using autofill::FormFieldData;
@@ -67,7 +70,7 @@
 constexpr int kNumberOfPasswordAttributes =
     static_cast<int>(PasswordAttribute::kPasswordAttributesCount);
 
-constexpr autofill::FieldRendererId kSingleUsernameRendererId(101);
+constexpr FieldRendererId kSingleUsernameRendererId(101);
 constexpr FieldSignature kSingleUsernameFieldSignature(1234);
 constexpr FormSignature kSingleUsernameFormSignature(1000);
 
@@ -80,6 +83,18 @@
   return form_predictions;
 }
 
+autofill::AutofillUploadContents::SingleUsernameData
+MakeSimpleSingleUsernameData() {
+  autofill::AutofillUploadContents::SingleUsernameData single_username_data;
+  single_username_data.set_username_form_signature(
+      kSingleUsernameFormSignature.value());
+  single_username_data.set_username_field_signature(
+      kSingleUsernameFieldSignature.value());
+  single_username_data.set_value_type(
+      autofill::AutofillUploadContents::USERNAME_LIKE);
+  return single_username_data;
+}
+
 class MockAutofillDownloadManager : public AutofillDownloadManager {
  public:
   MockAutofillDownloadManager()
@@ -222,7 +237,7 @@
   // Note that the value of the username field is deliberately altered before
   // the |form_structure| is generated from |form_data| to test the persistence.
   std::u16string prefilled_username = u"prefilled_username";
-  autofill::FieldRendererId username_field_renderer_id(123456);
+  FieldRendererId username_field_renderer_id(123456);
   const uint32_t kNumberOfHashValues = 64;
   FormData form_data;
 
@@ -232,7 +247,7 @@
 
   FormFieldData other_field;
   other_field.value = u"some_field";
-  other_field.unique_renderer_id = autofill::FieldRendererId(3234);
+  other_field.unique_renderer_id = FieldRendererId(3234);
 
   form_data.fields = {other_field, username_field};
 
@@ -448,11 +463,16 @@
   std::u16string single_username_candidate_value = u"username_candidate_value";
   votes_uploader.set_single_username_vote_data(kSingleUsernameRendererId,
                                                single_username_candidate_value,
-                                               form_predictions);
+                                               form_predictions,
+                                               /*stored_credentials=*/{});
   votes_uploader.set_suggested_username(single_username_candidate_value);
-  votes_uploader.set_saved_username(single_username_candidate_value);
+#if !defined(OS_ANDROID)
+  votes_uploader.CalculateUsernamePromptEditState(
+      /*saved_username=*/single_username_candidate_value);
+#endif  // !defined(OS_ANDROID)
 
 #if !defined(OS_ANDROID)
+  // Upload on the username form.
   ServerFieldTypeSet expected_types = {SINGLE_USERNAME};
   EXPECT_CALL(mock_autofill_download_manager_,
               StartUploadRequest(
@@ -471,6 +491,10 @@
 // Tests that a negeative vote is sent if the username candidate field
 // value contained whitespaces.
 TEST_F(VotesUploaderTest, UploadNotSingleUsernameForWhitespaces) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      features::kUsernameFirstFlowFallbackCrowdsourcing);
+
   VotesUploader votes_uploader(&client_, false);
 
   MockFieldInfoManager mock_field_manager;
@@ -482,8 +506,14 @@
   votes_uploader.set_single_username_vote_data(
       kSingleUsernameRendererId,
       /*username_candidate_value=*/u"some search query",
-      MakeSimpleSingleUsernamePredictions());
+      MakeSimpleSingleUsernamePredictions(),
+      /*stored_credentials=*/{});
+#if !defined(OS_ANDROID)
+  votes_uploader.CalculateUsernamePromptEditState(
+      /*saved_username=*/u"saved_value");
+#endif  // !defined(OS_ANDROID)
 
+  // Upload on the username form.
   ServerFieldTypeSet expected_types = {NOT_USERNAME};
   EXPECT_CALL(mock_autofill_download_manager_,
               StartUploadRequest(
@@ -494,11 +524,37 @@
                   /* pref_service= */ nullptr));
 
   votes_uploader.MaybeSendSingleUsernameVote();
+
+  // Upload on the password form for the fallback classifier.
+  autofill::AutofillUploadContents::SingleUsernameData
+      expected_single_username_data = MakeSimpleSingleUsernameData();
+  expected_single_username_data.set_value_type(
+      autofill::AutofillUploadContents::VALUE_WITH_WHITESPACE);
+#if !defined(OS_ANDROID)
+  expected_single_username_data.set_prompt_edit(
+      autofill::AutofillUploadContents::EDITED_NEGATIVE);
+#else
+  expected_single_username_data.set_prompt_edit(
+      autofill::AutofillUploadContents::EDIT_UNSPECIFIED);
+#endif  // !defined(OS_ANDROID)
+  EXPECT_CALL(
+      mock_autofill_download_manager_,
+      StartUploadRequest(
+          AllOf(SignatureIsSameAs(submitted_form_),
+                UploadedSingleUsernameDataIs(expected_single_username_data)),
+          _, _, _, _, _));
+
+  votes_uploader.UploadPasswordVote(submitted_form_, submitted_form_,
+                                    autofill::PASSWORD, std::string());
 }
 
 // Verifies that SINGLE_USERNAME vote and NOT_EDITED_IN_PROMPT vote type
 // are sent if single username candidate value was suggested and accepted.
 TEST_F(VotesUploaderTest, SingleUsernameValueSuggestedAndAccepted) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      features::kUsernameFirstFlowFallbackCrowdsourcing);
+
   MockFieldInfoManager mock_field_manager;
   ON_CALL(mock_field_manager, GetFieldType).WillByDefault(Return(UNKNOWN_TYPE));
   ON_CALL(client_, GetFieldInfoManager)
@@ -508,11 +564,15 @@
   std::u16string single_username_candidate_value = u"username_candidate_value";
   votes_uploader.set_single_username_vote_data(
       kSingleUsernameRendererId, single_username_candidate_value,
-      MakeSimpleSingleUsernamePredictions());
+      MakeSimpleSingleUsernamePredictions(), /*stored_credentials=*/{});
   votes_uploader.set_suggested_username(single_username_candidate_value);
-  votes_uploader.set_saved_username(single_username_candidate_value);
+#if !defined(OS_ANDROID)
+  votes_uploader.CalculateUsernamePromptEditState(
+      /*saved_username=*/single_username_candidate_value);
+#endif  // !defined(OS_ANDROID)
 
 #if !defined(OS_ANDROID)
+  // Upload on the username form.
   ServerFieldTypeSet expected_types = {SINGLE_USERNAME};
   EXPECT_CALL(mock_autofill_download_manager_,
               StartUploadRequest(
@@ -526,13 +586,38 @@
 #else
   EXPECT_CALL(mock_autofill_download_manager_, StartUploadRequest).Times(0);
 #endif  // !defined(OS_ANDROID)
+
   votes_uploader.MaybeSendSingleUsernameVote();
+
+  // Upload on the password form for the fallback classifier.
+  autofill::AutofillUploadContents::SingleUsernameData
+      expected_single_username_data = MakeSimpleSingleUsernameData();
+#if !defined(OS_ANDROID)
+  expected_single_username_data.set_prompt_edit(
+      autofill::AutofillUploadContents::NOT_EDITED_POSITIVE);
+#else
+  expected_single_username_data.set_prompt_edit(
+      autofill::AutofillUploadContents::EDIT_UNSPECIFIED);
+#endif  // !defined(OS_ANDROID)
+  EXPECT_CALL(
+      mock_autofill_download_manager_,
+      StartUploadRequest(
+          AllOf(SignatureIsSameAs(submitted_form_),
+                UploadedSingleUsernameDataIs(expected_single_username_data)),
+          _, _, _, _, _));
+
+  votes_uploader.UploadPasswordVote(submitted_form_, submitted_form_,
+                                    autofill::PASSWORD, std::string());
 }
 
 // Verifies that NOT_USERNAME vote and NOT_EDITED_IN_PROMPT vote type
 // are sent if value other than single username candidate was suggested and
 // accepted.
 TEST_F(VotesUploaderTest, SingleUsernameOtherValueSuggestedAndAccepted) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      features::kUsernameFirstFlowFallbackCrowdsourcing);
+
   MockFieldInfoManager mock_field_manager;
   ON_CALL(mock_field_manager, GetFieldType).WillByDefault(Return(UNKNOWN_TYPE));
   ON_CALL(client_, GetFieldInfoManager)
@@ -542,12 +627,16 @@
   std::u16string single_username_candidate_value = u"username_candidate_value";
   votes_uploader.set_single_username_vote_data(
       kSingleUsernameRendererId, single_username_candidate_value,
-      MakeSimpleSingleUsernamePredictions());
+      MakeSimpleSingleUsernamePredictions(), /*stored_credentials=*/{});
   std::u16string suggested_value = u"other_value";
   votes_uploader.set_suggested_username(suggested_value);
-  votes_uploader.set_saved_username(suggested_value);
+#if !defined(OS_ANDROID)
+  votes_uploader.CalculateUsernamePromptEditState(
+      /*saved_username=*/suggested_value);
+#endif  // !defined(OS_ANDROID)
 
 #if !defined(OS_ANDROID)
+  // Upload on the username form.
   ServerFieldTypeSet expected_types = {NOT_USERNAME};
   EXPECT_CALL(mock_autofill_download_manager_,
               StartUploadRequest(
@@ -561,13 +650,37 @@
 #else
   EXPECT_CALL(mock_autofill_download_manager_, StartUploadRequest).Times(0);
 #endif  // !defined(OS_ANDROID)
+
   votes_uploader.MaybeSendSingleUsernameVote();
+
+  // Upload on the password form for the fallback classifier.
+  autofill::AutofillUploadContents::SingleUsernameData
+      expected_single_username_data = MakeSimpleSingleUsernameData();
+#if !defined(OS_ANDROID)
+  expected_single_username_data.set_prompt_edit(
+      autofill::AutofillUploadContents::NOT_EDITED_NEGATIVE);
+#else
+  expected_single_username_data.set_prompt_edit(
+      autofill::AutofillUploadContents::EDIT_UNSPECIFIED);
+#endif  // !defined(OS_ANDROID)
+  EXPECT_CALL(
+      mock_autofill_download_manager_,
+      StartUploadRequest(
+          AllOf(SignatureIsSameAs(submitted_form_),
+                UploadedSingleUsernameDataIs(expected_single_username_data)),
+          _, _, _, _, _));
+  votes_uploader.UploadPasswordVote(submitted_form_, submitted_form_,
+                                    autofill::PASSWORD, std::string());
 }
 
 // Verifies that SINGLE_USERNAME vote and EDITED_IN_PROMPT vote type are sent
 // if value other than single username candidate was suggested, but the user
 // has inputted single username candidate value in prompt.
 TEST_F(VotesUploaderTest, SingleUsernameValueSetInPrompt) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      features::kUsernameFirstFlowFallbackCrowdsourcing);
+
   MockFieldInfoManager mock_field_manager;
   ON_CALL(mock_field_manager, GetFieldType).WillByDefault(Return(UNKNOWN_TYPE));
   ON_CALL(client_, GetFieldInfoManager)
@@ -577,10 +690,13 @@
   std::u16string single_username_candidate_value = u"username_candidate_value";
   votes_uploader.set_single_username_vote_data(
       kSingleUsernameRendererId, single_username_candidate_value,
-      MakeSimpleSingleUsernamePredictions());
+      MakeSimpleSingleUsernamePredictions(), /*stored_credentials=*/{});
   std::u16string suggested_value = u"other_value";
   votes_uploader.set_suggested_username(suggested_value);
-  votes_uploader.set_saved_username(single_username_candidate_value);
+#if !defined(OS_ANDROID)
+  votes_uploader.CalculateUsernamePromptEditState(
+      /*saved_username=*/single_username_candidate_value);
+#endif  // !defined(OS_ANDROID)
 
 #if !defined(OS_ANDROID)
   ServerFieldTypeSet expected_types = {SINGLE_USERNAME};
@@ -596,13 +712,37 @@
 #else
   EXPECT_CALL(mock_autofill_download_manager_, StartUploadRequest).Times(0);
 #endif  // !defined(OS_ANDROID)
+
   votes_uploader.MaybeSendSingleUsernameVote();
+
+  // Upload on the password form for the fallback classifier.
+  autofill::AutofillUploadContents::SingleUsernameData
+      expected_single_username_data = MakeSimpleSingleUsernameData();
+#if !defined(OS_ANDROID)
+  expected_single_username_data.set_prompt_edit(
+      autofill::AutofillUploadContents::EDITED_POSITIVE);
+#else
+  expected_single_username_data.set_prompt_edit(
+      autofill::AutofillUploadContents::EDIT_UNSPECIFIED);
+#endif  // !defined(OS_ANDROID)
+  EXPECT_CALL(
+      mock_autofill_download_manager_,
+      StartUploadRequest(
+          AllOf(SignatureIsSameAs(submitted_form_),
+                UploadedSingleUsernameDataIs(expected_single_username_data)),
+          _, _, _, _, _));
+  votes_uploader.UploadPasswordVote(submitted_form_, submitted_form_,
+                                    autofill::PASSWORD, std::string());
 }
 
 // Verifies that NOT_USERNAME vote and EDITED_IN_PROMPT vote type are sent
 // if single username candidate value was suggested, but the user has deleted
 // it in prompt.
 TEST_F(VotesUploaderTest, SingleUsernameValueDeletedInPrompt) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      features::kUsernameFirstFlowFallbackCrowdsourcing);
+
   MockFieldInfoManager mock_field_manager;
   ON_CALL(mock_field_manager, GetFieldType).WillByDefault(Return(UNKNOWN_TYPE));
   ON_CALL(client_, GetFieldInfoManager)
@@ -612,11 +752,14 @@
   std::u16string single_username_candidate_value = u"username_candidate_value";
   votes_uploader.set_single_username_vote_data(
       kSingleUsernameRendererId, single_username_candidate_value,
-      MakeSimpleSingleUsernamePredictions());
+      MakeSimpleSingleUsernamePredictions(), /*stored_credentials=*/{});
   votes_uploader.set_suggested_username(single_username_candidate_value);
-  votes_uploader.set_saved_username(u"");
+#if !defined(OS_ANDROID)
+  votes_uploader.CalculateUsernamePromptEditState(/*saved_username=*/u"");
+#endif  // !defined(OS_ANDROID)
 
 #if !defined(OS_ANDROID)
+  // Upload on the username form.
   ServerFieldTypeSet expected_types = {NOT_USERNAME};
   EXPECT_CALL(mock_autofill_download_manager_,
               StartUploadRequest(
@@ -630,13 +773,37 @@
 #else
   EXPECT_CALL(mock_autofill_download_manager_, StartUploadRequest).Times(0);
 #endif  // !defined(OS_ANDROID)
+
   votes_uploader.MaybeSendSingleUsernameVote();
+
+  // Expect upload for the password form for the fallback classifier.
+  autofill::AutofillUploadContents::SingleUsernameData
+      expected_single_username_data = MakeSimpleSingleUsernameData();
+#if !defined(OS_ANDROID)
+  expected_single_username_data.set_prompt_edit(
+      autofill::AutofillUploadContents::EDITED_NEGATIVE);
+#else
+  expected_single_username_data.set_prompt_edit(
+      autofill::AutofillUploadContents::EDIT_UNSPECIFIED);
+#endif  // !defined(OS_ANDROID)
+  EXPECT_CALL(
+      mock_autofill_download_manager_,
+      StartUploadRequest(
+          AllOf(SignatureIsSameAs(submitted_form_),
+                UploadedSingleUsernameDataIs(expected_single_username_data)),
+          _, _, _, _, _));
+  votes_uploader.UploadPasswordVote(submitted_form_, submitted_form_,
+                                    autofill::PASSWORD, std::string());
 }
 
 // Verifies that no vote is sent if the user has deleted the username value
 // suggested in prompt, and suggested value wasn't equal to single username
 // candidate value.
 TEST_F(VotesUploaderTest, NotSingleUsernameValueDeletedInPrompt) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      features::kUsernameFirstFlowFallbackCrowdsourcing);
+
   MockFieldInfoManager mock_field_manager;
   ON_CALL(mock_field_manager, GetFieldType).WillByDefault(Return(UNKNOWN_TYPE));
   ON_CALL(client_, GetFieldInfoManager)
@@ -646,14 +813,74 @@
   std::u16string single_username_candidate_value = u"username_candidate_value";
   votes_uploader.set_single_username_vote_data(
       kSingleUsernameRendererId, single_username_candidate_value,
-      MakeSimpleSingleUsernamePredictions());
+      MakeSimpleSingleUsernamePredictions(), /*stored_credentials=*/{});
   std::u16string other_value = u"other_value";
   votes_uploader.set_suggested_username(other_value);
-  votes_uploader.set_saved_username(u"");
+#if !defined(OS_ANDROID)
+  votes_uploader.CalculateUsernamePromptEditState(/*saved_username=*/u"");
+#endif  // !defined(OS_ANDROID)
 
-  // Expect no upload, as th signal is not informative to us.
-  EXPECT_CALL(mock_autofill_download_manager_, StartUploadRequest).Times(0);
+  // Expect no upload on username form, as th signal is not informative to us.
+  EXPECT_CALL(mock_autofill_download_manager_,
+              StartUploadRequest(SignatureIs(kSingleUsernameFormSignature), _,
+                                 _, _, _, _))
+      .Times(0);
   votes_uploader.MaybeSendSingleUsernameVote();
+
+  // Expect upload for the password form for the fallback classifier.
+  autofill::AutofillUploadContents::SingleUsernameData
+      expected_single_username_data = MakeSimpleSingleUsernameData();
+  expected_single_username_data.set_prompt_edit(
+      autofill::AutofillUploadContents::EDIT_UNSPECIFIED);
+  EXPECT_CALL(
+      mock_autofill_download_manager_,
+      StartUploadRequest(
+          AllOf(SignatureIsSameAs(submitted_form_),
+                UploadedSingleUsernameDataIs(expected_single_username_data)),
+          _, _, _, _, _));
+  votes_uploader.UploadPasswordVote(submitted_form_, submitted_form_,
+                                    autofill::PASSWORD, std::string());
+}
+
+// Verifies that NOT_USERNAME vote is sent on password form if no single
+// username typing had preceded single password typing.
+TEST_F(VotesUploaderTest, SingleUsernameNoUsernameCandidate) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      features::kUsernameFirstFlowFallbackCrowdsourcing);
+
+  MockFieldInfoManager mock_field_manager;
+  ON_CALL(mock_field_manager, GetFieldType).WillByDefault(Return(UNKNOWN_TYPE));
+  ON_CALL(client_, GetFieldInfoManager)
+      .WillByDefault(Return(&mock_field_manager));
+
+  VotesUploader votes_uploader(&client_, false);
+  votes_uploader.set_single_username_vote_data(
+      FieldRendererId(), std::u16string(), FormPredictions(),
+      /*stored_credentials=*/{});
+  votes_uploader.set_suggested_username(u"");
+#if !defined(OS_ANDROID)
+  votes_uploader.CalculateUsernamePromptEditState(/*saved_username=*/u"");
+#endif  // !defined(OS_ANDROID)
+
+  votes_uploader.MaybeSendSingleUsernameVote();
+
+  // Expect upload on the password form for the fallback classifier.
+  autofill::AutofillUploadContents::SingleUsernameData
+      expected_single_username_data;
+  expected_single_username_data.set_username_form_signature(0);
+  expected_single_username_data.set_username_field_signature(0);
+  expected_single_username_data.set_value_type(
+      autofill::AutofillUploadContents::NO_VALUE_TYPE);
+
+  EXPECT_CALL(
+      mock_autofill_download_manager_,
+      StartUploadRequest(
+          AllOf(SignatureIsSameAs(submitted_form_),
+                UploadedSingleUsernameDataIs(expected_single_username_data)),
+          _, _, _, _, _));
+  votes_uploader.UploadPasswordVote(submitted_form_, submitted_form_,
+                                    autofill::PASSWORD, std::string());
 }
 
 TEST_F(VotesUploaderTest, SaveSingleUsernameVote) {
@@ -662,8 +889,11 @@
   std::u16string single_username_candidate_value = u"username_candidate_value";
   votes_uploader.set_single_username_vote_data(
       kSingleUsernameRendererId, single_username_candidate_value,
-      MakeSimpleSingleUsernamePredictions());
-  votes_uploader.set_saved_username(single_username_candidate_value);
+      MakeSimpleSingleUsernamePredictions(), /*stored_credentials=*/{});
+#if !defined(OS_ANDROID)
+  votes_uploader.CalculateUsernamePromptEditState(
+      /*saved_username=*/single_username_candidate_value);
+#endif  // !defined(OS_ANDROID)
 
   // Init store and expect that adding field info is called.
   scoped_refptr<MockPasswordStoreInterface> store =
@@ -705,10 +935,14 @@
 
   votes_uploader.set_single_username_vote_data(
       kSingleUsernameRendererId, u"username_candidate_value",
-      MakeSimpleSingleUsernamePredictions());
+      MakeSimpleSingleUsernamePredictions(), /*stored_credentials=*/{});
 
-  // Expect no upload, since the vote has been already uploaded.
-  EXPECT_CALL(mock_autofill_download_manager_, StartUploadRequest).Times(0);
+  // Expect no upload on the username form, since the vote has been already
+  // uploaded.
+  EXPECT_CALL(mock_autofill_download_manager_,
+              StartUploadRequest(SignatureIs(kSingleUsernameFormSignature), _,
+                                 _, _, _, _))
+      .Times(0);
 
   votes_uploader.MaybeSendSingleUsernameVote();
 }
diff --git a/components/password_manager/ios/password_suggestion_helper.h b/components/password_manager/ios/password_suggestion_helper.h
index 23485856..3e54a47 100644
--- a/components/password_manager/ios/password_suggestion_helper.h
+++ b/components/password_manager/ios/password_suggestion_helper.h
@@ -79,7 +79,7 @@
 // with password forms.
 
 // Resets fill data, callbacks and state flags for new page. This method should
-// be called in password controller's -webState:didLoadPageWithSuccess:.
+// be called in password controller's -webState:didFinishNavigation:.
 - (void)resetForNewPage;
 
 // Prepares fill data with given password form data. Triggers callback for
diff --git a/components/password_manager/ios/shared_password_controller.mm b/components/password_manager/ios/shared_password_controller.mm
index 1ec1c6a..bba94de4 100644
--- a/components/password_manager/ios/shared_password_controller.mm
+++ b/components/password_manager/ios/shared_password_controller.mm
@@ -194,6 +194,9 @@
     return;
   }
 
+  // Clear per-page state.
+  [self.suggestionHelper resetForNewPage];
+
   auto fieldDataManager =
       UniqueIDDataTabHelper::FromWebState(_webState)->GetFieldDataManager();
   _passwordManager->PropagateFieldDataManagerInfo(
@@ -210,8 +213,6 @@
 
 - (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success {
   DCHECK_EQ(_webState, webState);
-  // Clear per-page state.
-  [self.suggestionHelper resetForNewPage];
 
   // Retrieve the identity of the page. In case the page might be malicous,
   // returns early.
diff --git a/components/password_manager/ios/shared_password_controller_unittest.mm b/components/password_manager/ios/shared_password_controller_unittest.mm
index 8dd3cf0f..aafdcef 100644
--- a/components/password_manager/ios/shared_password_controller_unittest.mm
+++ b/components/password_manager/ios/shared_password_controller_unittest.mm
@@ -152,6 +152,7 @@
 
   EXPECT_CALL(password_manager_, PropagateFieldDataManagerInfo);
   EXPECT_CALL(password_manager_, DidNavigateMainFrame(true));
+  OCMExpect([suggestion_helper_ resetForNewPage]);
   web_state_.OnNavigationFinished(&navigation_context);
 }
 
@@ -160,8 +161,6 @@
   web_state_.SetCurrentURL(GURL("https://www.chromium.org/"));
   web_state_.SetContentIsHTML(true);
 
-  OCMExpect([suggestion_helper_ resetForNewPage]);
-
   auto web_frame = web::FakeWebFrame::Create("dummy-frame-id",
                                              /*is_main_frame=*/true, GURL());
   [[[form_helper_ expect] ignoringNonObjectArgs]
@@ -193,8 +192,6 @@
   web_state_.SetCurrentURL(GURL("https://www.chromium.org/"));
   web_state_.SetContentIsHTML(false);
 
-  OCMExpect([suggestion_helper_ resetForNewPage]);
-
   [[form_helper_ reject] findPasswordFormsWithCompletionHandler:[OCMArg any]];
   OCMExpect([suggestion_helper_ processWithNoSavedCredentials]);
   EXPECT_CALL(password_manager_, OnPasswordFormsRendered);
diff --git a/components/payments/content/DEPS b/components/payments/content/DEPS
index 0b7263a..a56f10e 100644
--- a/components/payments/content/DEPS
+++ b/components/payments/content/DEPS
@@ -28,6 +28,7 @@
   "+sql",
   "+third_party/blink/public/common",
   "+third_party/blink/public/mojom/devtools/console_message.mojom.h",
+  "+third_party/blink/public/mojom/manifest",
   "+third_party/blink/public/mojom/permissions_policy",
   "+third_party/blink/public/mojom/payments",
   "+third_party/blink/public/mojom/webauthn",
diff --git a/components/payments/content/installable_payment_app_crawler.cc b/components/payments/content/installable_payment_app_crawler.cc
index dfc714b..7437c9c 100644
--- a/components/payments/content/installable_payment_app_crawler.cc
+++ b/components/payments/content/installable_payment_app_crawler.cc
@@ -26,7 +26,6 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
 #include "third_party/blink/public/common/manifest/manifest_icon_selector.h"
 #include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
 #include "ui/gfx/geometry/size.h"
diff --git a/components/pdf/browser/DEPS b/components/pdf/browser/DEPS
index 299c37b..7f323e9c 100644
--- a/components/pdf/browser/DEPS
+++ b/components/pdf/browser/DEPS
@@ -4,6 +4,7 @@
   "+mojo/public/cpp/bindings",
   "+mojo/public/cpp/system",
   "+net/base/net_errors.h",
+  "+net/http/http_response_headers.h",
   "+pdf/mojom/pdf.mojom.h",
   "+pdf/pdf_features.h",
   "+services/network/public/cpp",
diff --git a/components/pdf/browser/plugin_response_writer.cc b/components/pdf/browser/plugin_response_writer.cc
index 9870222..7b9bd7a 100644
--- a/components/pdf/browser/plugin_response_writer.cc
+++ b/components/pdf/browser/plugin_response_writer.cc
@@ -10,6 +10,7 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/strings/string_util.h"
 #include "mojo/public/c/system/types.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -17,6 +18,7 @@
 #include "mojo/public/cpp/system/data_pipe_producer.h"
 #include "mojo/public/cpp/system/string_data_source.h"
 #include "net/base/net_errors.h"
+#include "net/http/http_response_headers.h"
 #include "services/network/public/cpp/url_loader_completion_status.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
@@ -83,6 +85,8 @@
 
 void PluginResponseWriter::Start(base::OnceClosure done_callback) {
   auto response = network::mojom::URLResponseHead::New();
+  response->headers =
+      base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK");
   response->mime_type = "text/html";
   client_->OnReceiveResponse(std::move(response));
 
diff --git a/components/pdf/browser/plugin_response_writer_unittest.cc b/components/pdf/browser/plugin_response_writer_unittest.cc
index efb23d6..736812e4 100644
--- a/components/pdf/browser/plugin_response_writer_unittest.cc
+++ b/components/pdf/browser/plugin_response_writer_unittest.cc
@@ -12,6 +12,7 @@
 
 #include "base/callback.h"
 #include "base/callback_helpers.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/test/mock_callback.h"
 #include "base/test/task_environment.h"
@@ -20,6 +21,7 @@
 #include "mojo/public/cpp/system/data_pipe.h"
 #include "mojo/public/cpp/system/data_pipe_drainer.h"
 #include "net/base/net_errors.h"
+#include "net/http/http_response_headers.h"
 #include "services/network/public/cpp/url_loader_completion_status.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -113,6 +115,7 @@
 
     EXPECT_CALL(mock_client_, OnReceiveResponse)
         .WillOnce([](network::mojom::URLResponseHeadPtr head) {
+          EXPECT_EQ(200, head->headers->response_code());
           EXPECT_EQ("text/html", head->mime_type);
         });
 
diff --git a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionUtil.java b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionUtil.java
index 5f6fa71..e7f2414 100644
--- a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionUtil.java
+++ b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionUtil.java
@@ -4,7 +4,7 @@
 
 package org.chromium.components.permissions;
 
-import androidx.core.os.BuildCompat;
+import android.os.Build;
 
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.components.content_settings.ContentSettingsType;
@@ -44,7 +44,7 @@
         // targeting SDK version 31. Therefore enable support based on the current device's
         // software's SDK version as opposed to Chrome's targetSdkVersion. See:
         // https://developer.android.com/about/versions/12/approximate-location
-        return BuildCompat.isAtLeastS()
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
                 && PermissionsAndroidFeatureList.isEnabled(
                         PermissionsAndroidFeatureList
                                 .ANDROID_APPROXIMATE_LOCATION_PERMISSION_SUPPORT);
diff --git a/components/reporting/client/mock_report_queue.h b/components/reporting/client/mock_report_queue.h
index 15340bdb..94f8de7 100644
--- a/components/reporting/client/mock_report_queue.h
+++ b/components/reporting/client/mock_report_queue.h
@@ -27,6 +27,12 @@
               (const override));
 
   MOCK_METHOD(void, Flush, (Priority, ReportQueue::FlushCallback), (override));
+
+  MOCK_METHOD(
+      (base::OnceCallback<void(StatusOr<std::unique_ptr<ReportQueue>>)>),
+      PrepareToAttachActualQueue,
+      (),
+      (const override));
 };
 
 }  // namespace reporting
diff --git a/components/reporting/client/mock_report_queue_provider.h b/components/reporting/client/mock_report_queue_provider.h
index 3356ccc..9a49e99 100644
--- a/components/reporting/client/mock_report_queue_provider.h
+++ b/components/reporting/client/mock_report_queue_provider.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/sequenced_task_runner.h"
 #include "components/reporting/client/report_queue.h"
 #include "components/reporting/client/report_queue_provider.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -34,6 +35,13 @@
               CreateNewQueue,
               (std::unique_ptr<ReportQueueConfiguration> config),
               (override));
+
+  MOCK_METHOD(
+      (StatusOr<std::unique_ptr<ReportQueue, base::OnTaskRunnerDeleter>>),
+      CreateNewSpeculativeQueue,
+      (),
+      (override));
+
   MOCK_METHOD(void, InitOnCompletedCalled, (), (const));
 
  private:
diff --git a/components/reporting/client/report_queue.h b/components/reporting/client/report_queue.h
index c057117..41f16e8 100644
--- a/components/reporting/client/report_queue.h
+++ b/components/reporting/client/report_queue.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_REPORTING_CLIENT_REPORT_QUEUE_H_
 
 #include <memory>
+#include <queue>
 #include <string>
 #include <utility>
 
@@ -20,7 +21,7 @@
 namespace reporting {
 
 // A |ReportQueue| is not meant to be created directly, instead it is
-// instantiated by |ReportingClient|. |ReportQueue| allows a user
+// instantiated by |ReportQueueProvider|. |ReportQueue| allows a user
 // to |Enqueue| a message for delivery to a handler specified by the
 // |Destination| held by the provided |ReportQueueConfiguration|.
 // |ReportQueue| implementation handles scheduling storage and
@@ -37,7 +38,7 @@
 //     std::move(done_cb).Run(config_result.status());
 //     return;
 //   }
-//   // Asynchronously instantiate ReportingQueue.
+//   // Asynchronously instantiate ReportQueue.
 //   base::ThreadPool::PostTask(
 //       FROM_HERE,
 //       base::BindOnce(
@@ -65,6 +66,40 @@
 //           std::move(important_message), std::move(done_cb),
 //           std::move(config_result.ValueOrDie())));
 // }
+//
+// |SpeculativeReportQueueImpl| is an extension to |ReportQueue| which allows
+// to speculatively enqueue records before the actual |ReportQueue| is created
+// (which may be delayed by inability to initialize |ReportClient|).
+// Instantiated by |ReportQueueProvider| and can be used anywhere |ReportQueue|
+// fits. Note however, that records enqueued before actual |ReportQueue|
+// is ready may be lost, e.g. if the machine reboots, so for the records
+// that need to be definiately recorded |ReportQueue| is preferable.
+//
+// Example Usage:
+// void SendMessage(google::protobuf::LessImportantMessage
+// less_important_message,
+//                  reporting::ReportQueue::EnqueueCallback done_cb) {
+//   // Create configuration.
+//   auto config_result = reporting::ReportQueueConfiguration::Create(...);
+//   // Bail out if configuration failed to create.
+//   if (!config_result.ok()) {
+//     std::move(done_cb).Run(config_result.status());
+//     return;
+//   }
+//   // Synchronously instantiate SpeculativeReportQueueImpl, returning it as
+//   // ReportQueue still.
+//   auto report_queue_result =
+//       reporting::ReportQueueProvider::CreateSpeculativeQueue(
+//           std::move(config));
+//   if (!report_queue_result.ok()) {
+//     std::move(done_cb).Run(config_result.status());
+//     return;
+//   }
+//   // Enqueue event (store it in memory only until the actual queue is
+//   // created).
+//   report_queue_result.ValueOrDie()->Enqueue(
+//       std::move(less_important_message), std::move(done_cb));
+// }
 
 class ReportQueue {
  public:
@@ -109,6 +144,11 @@
   // Returns error if cannot start upload.
   virtual void Flush(Priority priority, FlushCallback callback) = 0;
 
+  // Prepares a callback to attach actual queue to the speculative.
+  // Implemented only in SpeculativeReportQueue, CHECKs in a regular one.
+  virtual base::OnceCallback<void(StatusOr<std::unique_ptr<ReportQueue>>)>
+  PrepareToAttachActualQueue() const = 0;
+
  protected:
   virtual void AddRecord(base::StringPiece record,
                          Priority priority,
diff --git a/components/reporting/client/report_queue_provider.cc b/components/reporting/client/report_queue_provider.cc
index 2d36cf9..c5605a1 100644
--- a/components/reporting/client/report_queue_provider.cc
+++ b/components/reporting/client/report_queue_provider.cc
@@ -39,6 +39,7 @@
 
 ReportQueueProvider::~ReportQueueProvider() = default;
 
+// static
 void ReportQueueProvider::CreateQueue(
     std::unique_ptr<ReportQueueConfiguration> config,
     CreateReportQueueCallback create_cb) {
@@ -58,6 +59,34 @@
                      base::Unretained(instance)));
 }
 
+// static
+StatusOr<std::unique_ptr<ReportQueue, base::OnTaskRunnerDeleter>>
+ReportQueueProvider::CreateSpeculativeQueue(
+    std::unique_ptr<ReportQueueConfiguration> config) {
+  if (!IsEncryptedReportingPipelineEnabled()) {
+    Status not_enabled = Status(
+        error::FAILED_PRECONDITION,
+        "The Encrypted Reporting Pipeline is not enabled. Please enable it on "
+        "the command line using --enable-features=EncryptedReportingPipeline");
+    VLOG(1) << not_enabled;
+    return not_enabled;
+  }
+  auto* instance = GetInstance();
+  // Instantiate speculative queue.
+  auto speculative_queue_result = instance->CreateNewSpeculativeQueue();
+  if (!speculative_queue_result.ok()) {
+    return speculative_queue_result.status();
+  }
+  // Initiate underlying queue creation.
+  auto speculative_queue = std::move(speculative_queue_result.ValueOrDie());
+  instance->create_request_queue_->Push(
+      CreateReportQueueRequest(std::move(config),
+                               speculative_queue->PrepareToAttachActualQueue()),
+      base::BindOnce(&ReportQueueProvider::OnPushComplete,
+                     base::Unretained(instance)));
+  return speculative_queue;
+}
+
 void ReportQueueProvider::OnPushComplete() {
   init_state_tracker_->GetInitState(base::BindOnce(
       &ReportQueueProvider::OnInitState, base::Unretained(this)));
diff --git a/components/reporting/client/report_queue_provider.h b/components/reporting/client/report_queue_provider.h
index 5d961f6..1e3cb3a9 100644
--- a/components/reporting/client/report_queue_provider.h
+++ b/components/reporting/client/report_queue_provider.h
@@ -103,6 +103,12 @@
   static void CreateQueue(std::unique_ptr<ReportQueueConfiguration> config,
                           CreateReportQueueCallback queue_cb);
 
+  // Synchronously creates a speculative queue based on the configuration.
+  // Returns result as a smart pointer to ReportQueue with the on-thread
+  // deleter.
+  static StatusOr<std::unique_ptr<ReportQueue, base::OnTaskRunnerDeleter>>
+  CreateSpeculativeQueue(std::unique_ptr<ReportQueueConfiguration> config);
+
   // Instantiates ReportQueueProvider singleton based on the overall process
   // state and will refer to StorageModuleInterface and optional Uploader
   // accordingly.
@@ -310,6 +316,8 @@
   // error.
   virtual CreateReportQueueResponse CreateNewQueue(
       std::unique_ptr<ReportQueueConfiguration> config) = 0;
+  virtual StatusOr<std::unique_ptr<ReportQueue, base::OnTaskRunnerDeleter>>
+  CreateNewSpeculativeQueue() = 0;
 
   void OnPushComplete();
   void OnInitState(bool provider_configured);
diff --git a/components/reporting/client/report_queue_provider_unittest.cc b/components/reporting/client/report_queue_provider_unittest.cc
index 8b0e443..9cab930 100644
--- a/components/reporting/client/report_queue_provider_unittest.cc
+++ b/components/reporting/client/report_queue_provider_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/task/thread_pool.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
+#include "build/build_config.h"
 #include "components/reporting/client/mock_report_queue.h"
 #include "components/reporting/client/mock_report_queue_provider.h"
 #include "components/reporting/client/report_queue.h"
@@ -46,7 +47,13 @@
       base::BindRepeating([]() { return Status::StatusOK(); });
 };
 
-TEST_F(ReportQueueProviderTest, CreateAndGetQueue) {
+// Disable the test on Linux tsan due to flaky: crbug.com/1233804.
+#if defined(OS_LINUX) && defined(THREAD_SANITIZER)
+#define MAYBE_CreateAndGetQueue DISABLED_CreateAndGetQueue
+#else
+#define MAYBE_CreateAndGetQueue CreateAndGetQueue
+#endif
+TEST_F(ReportQueueProviderTest, MAYBE_CreateAndGetQueue) {
   std::unique_ptr<MockReportQueueProvider> provider =
       std::make_unique<NiceMock<MockReportQueueProvider>>();
   report_queue_provider_test_helper::SetForTesting(provider.get());
diff --git a/components/reporting/storage/storage_queue_unittest.cc b/components/reporting/storage/storage_queue_unittest.cc
index a1bb616..30aecb2 100644
--- a/components/reporting/storage/storage_queue_unittest.cc
+++ b/components/reporting/storage/storage_queue_unittest.cc
@@ -826,7 +826,16 @@
   }
 }
 
-TEST_P(StorageQueueTest, WriteAndRepeatedlyUploadWithConfirmationsAndReopen) {
+// Disable on Linux and Chrome OS due to flaky. crbug.com/1232644
+#if defined(OS_CHROMEOS) || defined(OS_LINUX)
+#define MAYBE_WriteAndRepeatedlyUploadWithConfirmationsAndReopen \
+  DISABLED_WriteAndRepeatedlyUploadWithConfirmationsAndReopen
+#else
+#define MAYBE_WriteAndRepeatedlyUploadWithConfirmationsAndReopen \
+  WriteAndRepeatedlyUploadWithConfirmationsAndReopen
+#endif
+TEST_P(StorageQueueTest,
+       MAYBE_WriteAndRepeatedlyUploadWithConfirmationsAndReopen) {
   CreateTestStorageQueueOrDie(BuildStorageQueueOptionsPeriodic());
 
   WriteStringOrDie(kData[0]);
diff --git a/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc b/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc
index c648585..ba4e532 100644
--- a/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc
+++ b/components/safe_browsing/content/browser/web_ui/safe_browsing_ui.cc
@@ -1348,6 +1348,35 @@
   }
   event_dict.SetKey("reused_password_type", base::Value(reused_password_type));
 
+  std::string reused_password_account_type;
+  switch (event.reused_password_account_type().account_type()) {
+    case LoginReputationClientRequest::PasswordReuseEvent::
+        ReusedPasswordAccountType::UNKNOWN:
+      reused_password_account_type = "UNKNOWN";
+      break;
+    case LoginReputationClientRequest::PasswordReuseEvent::
+        ReusedPasswordAccountType::GSUITE:
+      reused_password_account_type = "GSUITE";
+      break;
+    case LoginReputationClientRequest::PasswordReuseEvent::
+        ReusedPasswordAccountType::GMAIL:
+      reused_password_account_type = "GMAIL";
+      break;
+    case LoginReputationClientRequest::PasswordReuseEvent::
+        ReusedPasswordAccountType::NON_GAIA_ENTERPRISE:
+      reused_password_account_type = "NON_GAIA_ENTERPRISE";
+      break;
+    case LoginReputationClientRequest::PasswordReuseEvent::
+        ReusedPasswordAccountType::SAVED_PASSWORD:
+      reused_password_account_type = "SAVED_PASSWORD";
+      break;
+  }
+  event_dict.SetKey("reused_password_account_type",
+                    base::Value(reused_password_account_type));
+  event_dict.SetKey(
+      "is_account_syncing",
+      base::Value(event.reused_password_account_type().is_account_syncing()));
+
   return std::move(event_dict);
 }
 
diff --git a/components/services/app_service/public/cpp/BUILD.gn b/components/services/app_service/public/cpp/BUILD.gn
index 22a4b50..b79c02c 100644
--- a/components/services/app_service/public/cpp/BUILD.gn
+++ b/components/services/app_service/public/cpp/BUILD.gn
@@ -180,6 +180,15 @@
   deps = [ "//components/services/app_service/public/mojom" ]
 }
 
+source_set("crosapi_utils") {
+  sources = [
+    "crosapi_utils.cc",
+    "crosapi_utils.h",
+  ]
+
+  deps = [ "//components/services/app_service/public/mojom" ]
+}
+
 source_set("unit_tests") {
   testonly = true
 
diff --git a/components/services/app_service/public/cpp/crosapi_utils.cc b/components/services/app_service/public/cpp/crosapi_utils.cc
new file mode 100644
index 0000000..d8f9821a
--- /dev/null
+++ b/components/services/app_service/public/cpp/crosapi_utils.cc
@@ -0,0 +1,18 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/services/app_service/public/cpp/crosapi_utils.h"
+
+namespace apps_util {
+
+std::vector<apps::mojom::AppPtr> CloneApps(
+    const std::vector<apps::mojom::AppPtr>& clone_from) {
+  std::vector<apps::mojom::AppPtr> clone_to;
+  for (const auto& app : clone_from) {
+    clone_to.push_back(app->Clone());
+  }
+  return clone_to;
+}
+
+}  // namespace apps_util
diff --git a/components/services/app_service/public/cpp/crosapi_utils.h b/components/services/app_service/public/cpp/crosapi_utils.h
new file mode 100644
index 0000000..81fbc54f
--- /dev/null
+++ b/components/services/app_service/public/cpp/crosapi_utils.h
@@ -0,0 +1,23 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_CROSAPI_UTILS_H_
+#define COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_CROSAPI_UTILS_H_
+
+// Utility functions for App Service crosapi usage.
+
+#include <memory>
+#include <vector>
+
+#include "components/services/app_service/public/mojom/types.mojom.h"
+
+namespace apps_util {
+
+// Clone a list of apps::mojom::AppPtr.
+std::vector<apps::mojom::AppPtr> CloneApps(
+    const std::vector<apps::mojom::AppPtr>& clone_from);
+
+}  // namespace apps_util
+
+#endif  // COMPONENTS_SERVICES_APP_SERVICE_PUBLIC_CPP_CROSAPI_UTILS_H_
diff --git a/components/test/data/history/history.45.sql b/components/test/data/history/history.45.sql
new file mode 100644
index 0000000..6142f165
--- /dev/null
+++ b/components/test/data/history/history.45.sql
@@ -0,0 +1,34 @@
+PRAGMA foreign_keys=OFF;
+BEGIN TRANSACTION;
+CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
+INSERT INTO meta VALUES('mmap_status','-1');
+INSERT INTO meta VALUES('version','45');
+INSERT INTO meta VALUES('last_compatible_version','16');
+CREATE TABLE urls(id INTEGER PRIMARY KEY AUTOINCREMENT,url LONGVARCHAR,title LONGVARCHAR,visit_count INTEGER DEFAULT 0 NOT NULL,typed_count INTEGER DEFAULT 0 NOT NULL,last_visit_time INTEGER NOT NULL,hidden INTEGER DEFAULT 0 NOT NULL);
+CREATE TABLE visits(id INTEGER PRIMARY KEY,url INTEGER NOT NULL,visit_time INTEGER NOT NULL,from_visit INTEGER,transition INTEGER DEFAULT 0 NOT NULL,segment_id INTEGER,visit_duration INTEGER DEFAULT 0 NOT NULL,incremented_omnibox_typed_score BOOLEAN DEFAULT FALSE NOT NULL,publicly_routable BOOLEAN DEFAULT FALSE NOT NULL);
+CREATE TABLE visit_source(id INTEGER PRIMARY KEY,source INTEGER NOT NULL);
+CREATE TABLE keyword_search_terms (keyword_id INTEGER NOT NULL,url_id INTEGER NOT NULL,term LONGVARCHAR NOT NULL,normalized_term LONGVARCHAR NOT NULL);
+CREATE TABLE downloads (id INTEGER PRIMARY KEY,guid VARCHAR NOT NULL,current_path LONGVARCHAR NOT NULL,target_path LONGVARCHAR NOT NULL,start_time INTEGER NOT NULL,received_bytes INTEGER NOT NULL,total_bytes INTEGER NOT NULL,state INTEGER NOT NULL,danger_type INTEGER NOT NULL,interrupt_reason INTEGER NOT NULL,hash BLOB NOT NULL,end_time INTEGER NOT NULL,opened INTEGER NOT NULL,last_access_time INTEGER NOT NULL,transient INTEGER NOT NULL,referrer VARCHAR NOT NULL,site_url VARCHAR NOT NULL,tab_url VARCHAR NOT NULL,tab_referrer_url VARCHAR NOT NULL,http_method VARCHAR NOT NULL,by_ext_id VARCHAR NOT NULL,by_ext_name VARCHAR NOT NULL,etag VARCHAR NOT NULL,last_modified VARCHAR NOT NULL,mime_type VARCHAR(255) NOT NULL,original_mime_type VARCHAR(255) NOT NULL);
+CREATE TABLE downloads_url_chains (id INTEGER NOT NULL,chain_index INTEGER NOT NULL,url LONGVARCHAR NOT NULL, PRIMARY KEY (id, chain_index) );
+CREATE TABLE downloads_slices (download_id INTEGER NOT NULL,offset INTEGER NOT NULL,received_bytes INTEGER NOT NULL,finished INTEGER NOT NULL DEFAULT 0,PRIMARY KEY (download_id, offset) );
+CREATE TABLE segments (id INTEGER PRIMARY KEY,name VARCHAR,url_id INTEGER NON NULL);
+CREATE TABLE segment_usage (id INTEGER PRIMARY KEY,segment_id INTEGER NOT NULL,time_slot INTEGER NOT NULL,visit_count INTEGER DEFAULT 0 NOT NULL);
+CREATE TABLE typed_url_sync_metadata (storage_key INTEGER PRIMARY KEY NOT NULL,value BLOB);
+CREATE TABLE content_annotations(visit_id INTEGER PRIMARY KEY,floc_protected_score NUMERIC,categories VARCHAR,page_topics_model_version INTEGER,annotation_flags INTEGER NOT NULL);
+CREATE TABLE context_annotations(visit_id INTEGER PRIMARY KEY,context_annotation_flags INTEGER NOT NULL,duration_since_last_visit INTEGER,page_end_reason INTEGER);
+CREATE TABLE clusters(cluster_id INTEGER PRIMARY KEY,score NUMERIC NOT NULL);
+CREATE TABLE clusters_and_visits(cluster_id INTEGER NOT NULL,visit_id INTEGER NOT NULL,score NUMERIC NOT NULL,PRIMARY KEY(cluster_id,visit_id))WITHOUT ROWID;
+DELETE FROM sqlite_sequence;
+CREATE INDEX visits_url_index ON visits (url);
+CREATE INDEX visits_from_index ON visits (from_visit);
+CREATE INDEX visits_time_index ON visits (visit_time);
+CREATE INDEX keyword_search_terms_index1 ON keyword_search_terms (keyword_id, normalized_term);
+CREATE INDEX keyword_search_terms_index2 ON keyword_search_terms (url_id);
+CREATE INDEX keyword_search_terms_index3 ON keyword_search_terms (term);
+CREATE INDEX segments_name ON segments(name);
+CREATE INDEX segments_url_id ON segments(url_id);
+CREATE INDEX segment_usage_time_slot_segment_id ON segment_usage(time_slot, segment_id);
+CREATE INDEX segments_usage_seg_id ON segment_usage(segment_id);
+CREATE INDEX clusters_for_visit ON clusters_and_visits(visit_id);
+CREATE INDEX urls_url_index ON urls (url);
+COMMIT;
\ No newline at end of file
diff --git a/components/webapps/browser/BUILD.gn b/components/webapps/browser/BUILD.gn
index 359a423..dc131450 100644
--- a/components/webapps/browser/BUILD.gn
+++ b/components/webapps/browser/BUILD.gn
@@ -118,6 +118,8 @@
     ":browser",
     "//base",
     "//skia",
+    "//third_party/blink/public/common:headers",
+    "//third_party/blink/public/mojom:mojom_platform_headers",
     "//url",
   ]
 }
diff --git a/components/webapps/browser/android/add_to_homescreen_data_fetcher.cc b/components/webapps/browser/android/add_to_homescreen_data_fetcher.cc
index 431fe6d..132b738 100644
--- a/components/webapps/browser/android/add_to_homescreen_data_fetcher.cc
+++ b/components/webapps/browser/android/add_to_homescreen_data_fetcher.cc
@@ -34,6 +34,8 @@
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/common/manifest/manifest.h"
 #include "third_party/blink/public/common/manifest/manifest_icon_selector.h"
+#include "third_party/blink/public/common/manifest/manifest_util.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "ui/gfx/codec/png_codec.h"
 #include "ui/gfx/favicon_size.h"
 #include "url/gurl.h"
@@ -228,7 +230,7 @@
 
   is_waiting_for_manifest_ = false;
 
-  if (!data.manifest.IsEmpty()) {
+  if (!blink::IsEmptyManifest(data.manifest)) {
     base::RecordAction(base::UserMetricsAction("webapps.AddShortcut.Manifest"));
     shortcut_info_.UpdateFromManifest(data.manifest);
     shortcut_info_.manifest_url = data.manifest_url;
@@ -236,7 +238,7 @@
 
   // Do this after updating from the manifest for the case where a site has
   // a manifest with name and standalone specified, but no icons.
-  if (data.manifest.IsEmpty() || !data.primary_icon) {
+  if (blink::IsEmptyManifest(data.manifest) || !data.primary_icon) {
     observer_->OnUserTitleAvailable(shortcut_info_.user_title,
                                     shortcut_info_.url,
                                     /*is_webapk_compatible=*/false);
diff --git a/components/webapps/browser/android/add_to_homescreen_data_fetcher_unittest.cc b/components/webapps/browser/android/add_to_homescreen_data_fetcher_unittest.cc
index 6f78aac..6e3abfc 100644
--- a/components/webapps/browser/android/add_to_homescreen_data_fetcher_unittest.cc
+++ b/components/webapps/browser/android/add_to_homescreen_data_fetcher_unittest.cc
@@ -29,8 +29,8 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/test_renderer_host.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
 #include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "ui/gfx/image/image_unittest_util.h"
 #include "url/gurl.h"
 
@@ -104,12 +104,12 @@
 };
 
 // Builds WebAPK compatible blink::Manifest.
-blink::Manifest BuildDefaultManifest() {
-  blink::Manifest manifest;
-  manifest.name = base::ASCIIToUTF16(kDefaultManifestName);
-  manifest.short_name = base::ASCIIToUTF16(kDefaultManifestShortName);
-  manifest.start_url = GURL(kDefaultStartUrl);
-  manifest.display = kDefaultManifestDisplayMode;
+blink::mojom::ManifestPtr BuildDefaultManifest() {
+  auto manifest = blink::mojom::Manifest::New();
+  manifest->name = base::ASCIIToUTF16(kDefaultManifestName);
+  manifest->short_name = base::ASCIIToUTF16(kDefaultManifestShortName);
+  manifest->start_url = GURL(kDefaultStartUrl);
+  manifest->display = kDefaultManifestDisplayMode;
 
   blink::Manifest::ImageResource primary_icon;
   primary_icon.type = u"image/png";
@@ -117,7 +117,7 @@
   primary_icon.purpose.push_back(
       blink::mojom::ManifestImageResource_Purpose::ANY);
   primary_icon.src = GURL(kDefaultIconUrl);
-  manifest.icons.push_back(primary_icon);
+  manifest->icons.push_back(primary_icon);
 
   return manifest;
 }
@@ -139,7 +139,7 @@
       code = NO_ACCEPTABLE_ICON;
       is_installable = false;
     } else if (params.valid_manifest && params.has_worker) {
-      if (!IsManifestValidForWebApp(manifest_,
+      if (!IsManifestValidForWebApp(*manifest_,
                                     true /* check_webapp_manifest_display */)) {
         code = valid_manifest_->errors.at(0);
         is_installable = false;
@@ -159,7 +159,7 @@
     if (code != NO_ERROR_DETECTED)
       errors.push_back(code);
     std::move(callback).Run(
-        {std::move(errors), GURL(kDefaultManifestUrl), manifest_,
+        {std::move(errors), GURL(kDefaultManifestUrl), *manifest_,
          params.valid_primary_icon ? primary_icon_url_ : GURL(),
          params.valid_primary_icon ? primary_icon_.get() : nullptr,
          params.prefer_maskable_icon, GURL() /* splash_icon_url */,
@@ -171,11 +171,12 @@
 
   void SetInstallable(bool is_installable) { is_installable_ = is_installable; }
 
-  void SetManifest(const blink::Manifest& manifest) {
-    manifest_ = manifest;
+  void SetManifest(blink::mojom::ManifestPtr manifest) {
+    DCHECK(manifest);
+    manifest_ = std::move(manifest);
 
-    if (!manifest.icons.empty()) {
-      primary_icon_url_ = manifest_.icons[0].src;
+    if (!manifest_->icons.empty()) {
+      primary_icon_url_ = manifest_->icons[0].src;
       primary_icon_ = std::make_unique<SkBitmap>(
           gfx::test::CreateBitmap(kIconSizePx, kIconSizePx));
     }
@@ -190,7 +191,7 @@
   }
 
  private:
-  blink::Manifest manifest_;
+  blink::mojom::ManifestPtr manifest_ = blink::mojom::Manifest::New();
   GURL primary_icon_url_;
   std::unique_ptr<SkBitmap> primary_icon_;
 
@@ -276,8 +277,8 @@
     histograms.ExpectTotalCount("Webapp.AddToHomescreenDialog.Timeout", 1);
   }
 
-  void SetManifest(const blink::Manifest& manifest) {
-    installable_manager_->SetManifest(manifest);
+  void SetManifest(blink::mojom::ManifestPtr manifest) {
+    installable_manager_->SetManifest(std::move(manifest));
   }
 
   void SetInstallable(bool is_installable) {
@@ -330,9 +331,9 @@
 TEST_F(AddToHomescreenDataFetcherTest, NoIconManifest) {
   // Test a manifest with no icons. This should use the short name and have
   // a generated icon (empty icon url).
-  blink::Manifest manifest = BuildDefaultManifest();
-  manifest.icons.clear();
-  SetManifest(manifest);
+  blink::mojom::ManifestPtr manifest = BuildDefaultManifest();
+  manifest->icons.clear();
+  SetManifest(std::move(manifest));
 
   base::HistogramTester histograms;
   ObserverWaiter waiter;
@@ -469,8 +470,7 @@
 
 TEST_F(AddToHomescreenDataFetcherTest, InstallableManifest) {
   // Test a site that has an offline-capable service worker.
-  blink::Manifest manifest(BuildDefaultManifest());
-  SetManifest(manifest);
+  SetManifest(BuildDefaultManifest());
 
   base::HistogramTester histograms;
   ObserverWaiter waiter;
@@ -494,10 +494,10 @@
   // Manifest::short_name that Manifest::name is used as the title.
   {
     // Check the case where we have no icons.
-    blink::Manifest manifest = BuildDefaultManifest();
-    manifest.icons.clear();
-    manifest.short_name = absl::nullopt;
-    SetManifest(manifest);
+    blink::mojom::ManifestPtr manifest = BuildDefaultManifest();
+    manifest->icons.clear();
+    manifest->short_name = absl::nullopt;
+    SetManifest(std::move(manifest));
 
     ObserverWaiter waiter;
     std::unique_ptr<AddToHomescreenDataFetcher> fetcher = BuildFetcher(&waiter);
@@ -509,9 +509,9 @@
                                   kDefaultManifestName));
   }
 
-  blink::Manifest manifest(BuildDefaultManifest());
-  manifest.short_name = absl::nullopt;
-  SetManifest(manifest);
+  blink::mojom::ManifestPtr manifest = BuildDefaultManifest();
+  manifest->short_name = absl::nullopt;
+  SetManifest(std::move(manifest));
 
   {
     // Check a site with no offline-capable service worker.
@@ -566,12 +566,12 @@
   //  - The page is not WebAPK compatible.
   //  - WebApplicationInfo::title is used as the "name".
   //  - We still use the icons from the manifest.
-  blink::Manifest manifest(BuildDefaultManifest());
-  manifest.name = absl::nullopt;
-  manifest.short_name = absl::nullopt;
+  blink::mojom::ManifestPtr manifest = BuildDefaultManifest();
+  manifest->name = absl::nullopt;
+  manifest->short_name = absl::nullopt;
 
   // Check the case where we don't time out waiting for the service worker.
-  SetManifest(manifest);
+  SetManifest(std::move(manifest));
   ObserverWaiter waiter;
   std::unique_ptr<AddToHomescreenDataFetcher> fetcher = BuildFetcher(&waiter);
   RunFetcher(fetcher.get(), waiter, kWebApplicationInfoTitle,
diff --git a/components/webapps/browser/android/app_banner_manager_android.cc b/components/webapps/browser/android/app_banner_manager_android.cc
index 360e887..674bc04 100644
--- a/components/webapps/browser/android/app_banner_manager_android.cc
+++ b/components/webapps/browser/android/app_banner_manager_android.cc
@@ -39,6 +39,7 @@
 #include "net/base/url_util.h"
 #include "skia/ext/skia_utils_base.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
 using base::android::ConvertJavaStringToUTF16;
@@ -160,7 +161,7 @@
 }
 
 void AppBannerManagerAndroid::PerformInstallableWebAppCheck() {
-  if (!webapps::WebappsUtils::AreWebManifestUrlsWebApkCompatible(manifest_)) {
+  if (!webapps::WebappsUtils::AreWebManifestUrlsWebApkCompatible(manifest())) {
     Stop(URL_NOT_SUPPORTED_FOR_WEBAPK);
     return;
   }
@@ -181,7 +182,7 @@
   if (native_app_data_.is_null()) {
     a2hs_params->app_type = AddToHomescreenParams::AppType::WEBAPK;
     a2hs_params->shortcut_info = ShortcutInfo::CreateShortcutInfo(
-        manifest_url_, manifest_, primary_icon_url_);
+        manifest_url_, manifest(), primary_icon_url_);
     a2hs_params->install_source = install_source;
     a2hs_params->has_maskable_primary_icon = has_maskable_primary_icon_;
   } else {
@@ -352,12 +353,12 @@
 }
 
 bool AppBannerManagerAndroid::ShouldPerformInstallableNativeAppCheck() {
-  if (!manifest_.prefer_related_applications || java_banner_manager_.is_null())
+  if (!manifest().prefer_related_applications || java_banner_manager_.is_null())
     return false;
 
   // Ensure there is at least one related app specified that is supported on
   // the current platform.
-  for (const auto& application : manifest_.related_applications) {
+  for (const auto& application : manifest().related_applications) {
     if (base::EqualsASCII(application.platform.value_or(std::u16string()),
                           kPlatformPlay))
       return true;
@@ -368,7 +369,7 @@
 void AppBannerManagerAndroid::PerformInstallableNativeAppCheck() {
   DCHECK(ShouldPerformInstallableNativeAppCheck());
   InstallableStatusCode code = NO_ERROR_DETECTED;
-  for (const auto& application : manifest_.related_applications) {
+  for (const auto& application : manifest().related_applications) {
     std::string id =
         base::UTF16ToUTF8(application.id.value_or(std::u16string()));
     code = QueryNativeApp(application.platform.value_or(std::u16string()),
@@ -463,9 +464,8 @@
   if (native_app_data_.is_null()) {
     // Prefer the short name if it's available. It's guaranteed that at least
     // one of these is non-empty.
-    std::u16string short_name = manifest_.short_name.value_or(std::u16string());
-    return short_name.empty() ? manifest_.name.value_or(std::u16string())
-                              : short_name;
+    std::u16string short_name = manifest().short_name.value_or(u"");
+    return short_name.empty() ? manifest().name.value_or(u"") : u"";
   }
 
   return native_app_title_;
@@ -537,7 +537,7 @@
   // some time, so ensure we don't accidentally allow a new installation whilst
   // one is in flight for the current site.
   return WebappsUtils::IsWebApkInstalled(web_contents()->GetBrowserContext(),
-                                         manifest_.start_url) ||
+                                         manifest().start_url) ||
          WebappsClient::Get()->IsInstallationInProgress(web_contents(),
                                                         manifest_url_);
 }
@@ -545,7 +545,7 @@
 void AppBannerManagerAndroid::ShowAmbientBadge() {
   InstallableAmbientBadgeInfoBarDelegate::Create(
       web_contents(), weak_factory_.GetWeakPtr(), GetAppName(), primary_icon_,
-      has_maskable_primary_icon_, manifest_.start_url);
+      has_maskable_primary_icon_, manifest().start_url);
 }
 
 void AppBannerManagerAndroid::RecordExtraMetricsForInstallEvent(
diff --git a/components/webapps/browser/android/shortcut_info.cc b/components/webapps/browser/android/shortcut_info.cc
index 5bcbf6c..85cbed9 100644
--- a/components/webapps/browser/android/shortcut_info.cc
+++ b/components/webapps/browser/android/shortcut_info.cc
@@ -11,6 +11,8 @@
 #include "components/webapps/browser/android/webapps_icon_utils.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/manifest/manifest_icon_selector.h"
+#include "third_party/blink/public/common/manifest/manifest_util.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 
 namespace webapps {
 
@@ -48,10 +50,10 @@
 // static
 std::unique_ptr<ShortcutInfo> ShortcutInfo::CreateShortcutInfo(
     const GURL& manifest_url,
-    const blink::Manifest& manifest,
+    const blink::mojom::Manifest& manifest,
     const GURL& primary_icon_url) {
   auto shortcut_info = std::make_unique<ShortcutInfo>(GURL());
-  if (!manifest.IsEmpty()) {
+  if (!blink::IsEmptyManifest(manifest)) {
     shortcut_info->UpdateFromManifest(manifest);
     shortcut_info->manifest_url = manifest_url;
     shortcut_info->best_primary_icon_url = primary_icon_url;
@@ -83,7 +85,7 @@
   return shortcut_info;
 }
 
-void ShortcutInfo::UpdateFromManifest(const blink::Manifest& manifest) {
+void ShortcutInfo::UpdateFromManifest(const blink::mojom::Manifest& manifest) {
   std::u16string s_name = manifest.short_name.value_or(std::u16string());
   std::u16string f_name = manifest.name.value_or(std::u16string());
   if (!s_name.empty() || !f_name.empty()) {
@@ -123,12 +125,14 @@
   }
 
   // Set the theme color based on the manifest value, if any.
-  if (manifest.theme_color)
-    theme_color = manifest.theme_color;
+  theme_color = manifest.has_theme_color
+                    ? absl::make_optional(manifest.theme_color)
+                    : absl::nullopt;
 
   // Set the background color based on the manifest value, if any.
-  if (manifest.background_color)
-    background_color = manifest.background_color;
+  background_color = manifest.has_background_color
+                         ? absl::make_optional(manifest.background_color)
+                         : absl::nullopt;
 
   // Set the icon urls based on the icons in the manifest, if any.
   icon_urls.clear();
diff --git a/components/webapps/browser/android/shortcut_info.h b/components/webapps/browser/android/shortcut_info.h
index 258f763..db830af 100644
--- a/components/webapps/browser/android/shortcut_info.h
+++ b/components/webapps/browser/android/shortcut_info.h
@@ -13,7 +13,7 @@
 
 #include "services/device/public/mojom/screen_orientation_lock_types.mojom-shared.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "url/gurl.h"
 
@@ -55,7 +55,7 @@
   // screen.
   static std::unique_ptr<ShortcutInfo> CreateShortcutInfo(
       const GURL& manifest_url,
-      const blink::Manifest& manifest,
+      const blink::mojom::Manifest& manifest,
       const GURL& primary_icon_url);
 
   // This enum is used to back a UMA histogram, and must be treated as
@@ -119,7 +119,7 @@
   ~ShortcutInfo();
 
   // Updates the info based on the given |manifest|.
-  void UpdateFromManifest(const blink::Manifest& manifest);
+  void UpdateFromManifest(const blink::mojom::Manifest& manifest);
 
   // Updates the source of the shortcut.
   void UpdateSource(const Source source);
diff --git a/components/webapps/browser/android/shortcut_info_unittest.cc b/components/webapps/browser/android/shortcut_info_unittest.cc
index 06a4b14..4362ba7 100644
--- a/components/webapps/browser/android/shortcut_info_unittest.cc
+++ b/components/webapps/browser/android/shortcut_info_unittest.cc
@@ -41,7 +41,7 @@
 
  protected:
   ShortcutInfo info_;
-  blink::Manifest manifest_;
+  blink::mojom::Manifest manifest_;
 
   DISALLOW_COPY_AND_ASSIGN(ShortcutInfoTest);
 };
@@ -63,9 +63,11 @@
   manifest_.display = blink::mojom::DisplayMode::kFullscreen;
 
   info_.theme_color = 0xffff0000;
+  manifest_.has_theme_color = true;
   manifest_.theme_color = 0xffcc0000;
 
   info_.background_color = 0xffaa0000;
+  manifest_.has_background_color = true;
   manifest_.background_color = 0xffbb0000;
 
   info_.icon_urls.push_back("https://old.com/icon.png");
diff --git a/components/webapps/browser/android/webapps_utils.cc b/components/webapps/browser/android/webapps_utils.cc
index 7448984..e548e21 100644
--- a/components/webapps/browser/android/webapps_utils.cc
+++ b/components/webapps/browser/android/webapps_utils.cc
@@ -9,7 +9,7 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
 #include "components/webapps/browser/android/webapps_jni_headers/WebappsUtils_jni.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "url/gurl.h"
 
 namespace webapps {
@@ -45,7 +45,7 @@
 
 // static
 bool WebappsUtils::AreWebManifestUrlsWebApkCompatible(
-    const blink::Manifest& manifest) {
+    const blink::mojom::Manifest& manifest) {
   for (const auto& icon : manifest.icons) {
     if (!IsUrlWebApkCompatible(icon.src))
       return false;
diff --git a/components/webapps/browser/android/webapps_utils.h b/components/webapps/browser/android/webapps_utils.h
index 9d8019a..ab96291 100644
--- a/components/webapps/browser/android/webapps_utils.h
+++ b/components/webapps/browser/android/webapps_utils.h
@@ -5,11 +5,9 @@
 #ifndef COMPONENTS_WEBAPPS_BROWSER_ANDROID_WEBAPPS_UTILS_H_
 #define COMPONENTS_WEBAPPS_BROWSER_ANDROID_WEBAPPS_UTILS_H_
 
-class GURL;
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 
-namespace blink {
-struct Manifest;
-}
+class GURL;
 
 namespace content {
 class BrowserContext;
@@ -30,7 +28,7 @@
   // Returns whether the format of the URLs in the Web Manifest is WebAPK
   // compatible.
   static bool AreWebManifestUrlsWebApkCompatible(
-      const blink::Manifest& manifest);
+      const blink::mojom::Manifest& manifest);
 };
 
 }  // namespace webapps
diff --git a/components/webapps/browser/android/webapps_utils_unittest.cc b/components/webapps/browser/android/webapps_utils_unittest.cc
index 138219e..782c6ac 100644
--- a/components/webapps/browser/android/webapps_utils_unittest.cc
+++ b/components/webapps/browser/android/webapps_utils_unittest.cc
@@ -6,25 +6,25 @@
 
 #include "base/strings/utf_string_conversions.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
 #include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "url/gurl.h"
 
 namespace webapps {
 
 namespace {
 
-blink::Manifest GetValidManifest() {
-  blink::Manifest manifest;
-  manifest.name = u"foo";
-  manifest.short_name = u"bar";
-  manifest.start_url = GURL("http://example.com");
-  manifest.display = blink::mojom::DisplayMode::kStandalone;
+blink::mojom::ManifestPtr GetValidManifest() {
+  auto manifest = blink::mojom::Manifest::New();
+  manifest->name = u"foo";
+  manifest->short_name = u"bar";
+  manifest->start_url = GURL("http://example.com");
+  manifest->display = blink::mojom::DisplayMode::kStandalone;
 
   blink::Manifest::ImageResource icon;
   icon.type = u"image/png";
   icon.sizes.push_back(gfx::Size(144, 144));
-  manifest.icons.push_back(icon);
+  manifest->icons.push_back(icon);
 
   return manifest;
 }
@@ -32,24 +32,24 @@
 }  // anonymous namespace
 
 TEST(WebappsUtilsTest, Compatible) {
-  blink::Manifest manifest = GetValidManifest();
-  EXPECT_TRUE(WebappsUtils::AreWebManifestUrlsWebApkCompatible(manifest));
+  EXPECT_TRUE(
+      WebappsUtils::AreWebManifestUrlsWebApkCompatible(*GetValidManifest()));
 }
 
 TEST(WebappsUtilsTest, CompatibleURLHasNoPassword) {
   const GURL kUrlWithPassword("http://answer:42@life/universe/and/everything");
 
-  blink::Manifest manifest = GetValidManifest();
-  manifest.start_url = kUrlWithPassword;
-  EXPECT_FALSE(WebappsUtils::AreWebManifestUrlsWebApkCompatible(manifest));
+  blink::mojom::ManifestPtr manifest = GetValidManifest();
+  manifest->start_url = kUrlWithPassword;
+  EXPECT_FALSE(WebappsUtils::AreWebManifestUrlsWebApkCompatible(*manifest));
 
   manifest = GetValidManifest();
-  manifest.scope = kUrlWithPassword;
-  EXPECT_FALSE(WebappsUtils::AreWebManifestUrlsWebApkCompatible(manifest));
+  manifest->scope = kUrlWithPassword;
+  EXPECT_FALSE(WebappsUtils::AreWebManifestUrlsWebApkCompatible(*manifest));
 
   manifest = GetValidManifest();
-  manifest.icons[0].src = kUrlWithPassword;
-  EXPECT_FALSE(WebappsUtils::AreWebManifestUrlsWebApkCompatible(manifest));
+  manifest->icons[0].src = kUrlWithPassword;
+  EXPECT_FALSE(WebappsUtils::AreWebManifestUrlsWebApkCompatible(*manifest));
 }
 
 }  // namespace webapps
diff --git a/components/webapps/browser/banners/app_banner_manager.cc b/components/webapps/browser/banners/app_banner_manager.cc
index a283da4..398a2a2b 100644
--- a/components/webapps/browser/banners/app_banner_manager.cc
+++ b/components/webapps/browser/banners/app_banner_manager.cc
@@ -34,7 +34,9 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/common/manifest/manifest_util.h"
 #include "third_party/blink/public/mojom/installation/installation.mojom.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
 namespace webapps {
@@ -223,6 +225,7 @@
       has_maskable_primary_icon_(false),
       state_(State::INACTIVE),
       manager_(InstallableManager::FromWebContents(web_contents)),
+      manifest_(blink::mojom::Manifest::New()),
       has_sufficient_engagement_(false),
       load_finished_(false),
       status_reporter_(std::make_unique<NullStatusReporter>()),
@@ -274,8 +277,8 @@
 }
 
 bool AppBannerManager::ShouldDeferToRelatedNonWebApp() const {
-  for (const auto& related_app : manifest_.related_applications) {
-    if (manifest_.prefer_related_applications &&
+  for (const auto& related_app : manifest().related_applications) {
+    if (manifest().prefer_related_applications &&
         IsSupportedNonWebAppPlatform(
             related_app.platform.value_or(std::u16string()))) {
       return true;
@@ -287,12 +290,17 @@
 }
 
 std::string AppBannerManager::GetAppIdentifier() {
-  DCHECK(!manifest_.IsEmpty());
-  return manifest_.start_url.spec();
+  DCHECK(!blink::IsEmptyManifest(manifest()));
+  return manifest().start_url.spec();
 }
 
 std::u16string AppBannerManager::GetAppName() const {
-  return manifest_.name.value_or(std::u16string());
+  return manifest().name.value_or(std::u16string());
+}
+
+const blink::mojom::Manifest& AppBannerManager::manifest() const {
+  DCHECK(manifest_);
+  return *manifest_;
 }
 
 std::string AppBannerManager::GetBannerType() {
@@ -348,10 +356,10 @@
   }
 
   DCHECK(!data.manifest_url.is_empty());
-  DCHECK(!data.manifest.IsEmpty());
+  DCHECK(!blink::IsEmptyManifest(data.manifest));
 
   manifest_url_ = data.manifest_url;
-  manifest_ = data.manifest;
+  manifest_ = data.manifest.Clone();
 
   PerformInstallableChecks();
 }
@@ -462,7 +470,7 @@
   load_finished_ = false;
   has_sufficient_engagement_ = false;
   active_media_players_.clear();
-  manifest_ = blink::Manifest();
+  manifest_ = blink::mojom::Manifest::New();
   manifest_url_ = GURL();
   validated_url_ = GURL();
   UpdateState(State::INACTIVE);
@@ -516,7 +524,7 @@
     case InstallableWebAppCheckResult::kUnknown:
       break;
     case InstallableWebAppCheckResult::kYes_Promotable:
-      last_promotable_web_app_scope_ = manifest_.scope;
+      last_promotable_web_app_scope_ = manifest().scope;
       DCHECK(!last_promotable_web_app_scope_.is_empty());
       last_already_installed_web_app_scope_ = GURL();
       install_animation_pending_ =
@@ -524,7 +532,7 @@
               web_contents(), last_promotable_web_app_scope_);
       break;
     case InstallableWebAppCheckResult::kNo_AlreadyInstalled:
-      last_already_installed_web_app_scope_ = manifest_.scope;
+      last_already_installed_web_app_scope_ = manifest().scope;
       DCHECK(!last_already_installed_web_app_scope_.is_empty());
       last_promotable_web_app_scope_ = GURL();
       install_animation_pending_ = false;
@@ -811,11 +819,11 @@
 }
 
 const GURL& AppBannerManager::GetManifestStartUrl() const {
-  return manifest_.start_url;
+  return manifest().start_url;
 }
 
 blink::mojom::DisplayMode AppBannerManager::GetManifestDisplayMode() const {
-  return manifest_.display;
+  return manifest().display;
 }
 
 bool AppBannerManager::MaybeConsumeInstallAnimation() {
@@ -906,7 +914,7 @@
   }
 
   DCHECK(!manifest_url_.is_empty());
-  DCHECK(!manifest_.IsEmpty());
+  DCHECK(!blink::IsEmptyManifest(manifest()));
   DCHECK(!primary_icon_url_.is_empty());
   DCHECK(!primary_icon_.drawsNothing());
 
diff --git a/components/webapps/browser/banners/app_banner_manager.h b/components/webapps/browser/banners/app_banner_manager.h
index 747d12e..ed99b52 100644
--- a/components/webapps/browser/banners/app_banner_manager.h
+++ b/components/webapps/browser/banners/app_banner_manager.h
@@ -23,6 +23,7 @@
 #include "third_party/blink/public/common/manifest/manifest.h"
 #include "third_party/blink/public/mojom/app_banner/app_banner.mojom.h"
 #include "third_party/blink/public/mojom/manifest/display_mode.mojom-forward.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 #include "url/gurl.h"
 
 class SkBitmap;
@@ -186,7 +187,7 @@
   virtual std::u16string GetAppName() const;
 
   // Simple accessors:
-  const blink::Manifest& manifest() { return manifest_; }
+  const blink::mojom::Manifest& manifest() const;
   const SkBitmap& primary_icon() { return primary_icon_; }
   bool has_maskable_primary_icon() { return has_maskable_primary_icon_; }
   const GURL& validated_url() { return validated_url_; }
@@ -353,9 +354,6 @@
   // The URL of the manifest.
   GURL manifest_url_;
 
-  // The manifest object.
-  blink::Manifest manifest_;
-
   // The URL of the primary icon.
   GURL primary_icon_url_;
 
@@ -403,6 +401,10 @@
   // Fetches the data required to display a banner for the current page.
   InstallableManager* manager_;
 
+  // The manifest object. This is never null, it will instead be an empty
+  // manifest so callers don't have to worry about null checks.
+  blink::mojom::ManifestPtr manifest_;
+
   // We do not want to trigger a banner when the manager is attached to
   // a WebContents that is playing video. Banners triggering on a site in the
   // background will appear when the tab is reactivated.
diff --git a/components/webapps/browser/installable/fake_installable_manager.cc b/components/webapps/browser/installable/fake_installable_manager.cc
index 8d39e520..7f55a5f17 100644
--- a/components/webapps/browser/installable/fake_installable_manager.cc
+++ b/components/webapps/browser/installable/fake_installable_manager.cc
@@ -11,12 +11,15 @@
 #include "base/callback.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/webapps/browser/installable/installable_data.h"
+#include "third_party/blink/public/common/manifest/manifest_util.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 
 namespace webapps {
 
 FakeInstallableManager::FakeInstallableManager(
     content::WebContents* web_contents)
-    : InstallableManager(web_contents) {}
+    : InstallableManager(web_contents),
+      manifest_(blink::mojom::Manifest::New()) {}
 
 FakeInstallableManager::~FakeInstallableManager() {}
 
@@ -47,13 +50,12 @@
     content::WebContents* web_contents,
     InstallableStatusCode installable_code,
     const GURL& manifest_url,
-    std::unique_ptr<blink::Manifest> manifest) {
+    blink::mojom::ManifestPtr manifest) {
   DCHECK(manifest);
-
   FakeInstallableManager* installable_manager =
       FakeInstallableManager::CreateForWebContents(web_contents);
 
-  const bool valid_manifest = !manifest->IsEmpty();
+  const bool valid_manifest = !blink::IsEmptyManifest(manifest);
   installable_manager->manifest_url_ = manifest_url;
   installable_manager->manifest_ = std::move(manifest);
 
diff --git a/components/webapps/browser/installable/fake_installable_manager.h b/components/webapps/browser/installable/fake_installable_manager.h
index 0456c226..c062d7a 100644
--- a/components/webapps/browser/installable/fake_installable_manager.h
+++ b/components/webapps/browser/installable/fake_installable_manager.h
@@ -10,12 +10,9 @@
 #include "base/memory/weak_ptr.h"
 #include "components/webapps/browser/installable/installable_logging.h"
 #include "components/webapps/browser/installable/installable_manager.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 #include "url/gurl.h"
 
-namespace blink {
-struct Manifest;
-}
-
 namespace content {
 class WebContents;
 }
@@ -44,11 +41,11 @@
       content::WebContents* web_contents,
       InstallableStatusCode installable_code,
       const GURL& manifest_url,
-      std::unique_ptr<blink::Manifest> manifest);
+      blink::mojom::ManifestPtr manifest);
 
  private:
   GURL manifest_url_;
-  std::unique_ptr<blink::Manifest> manifest_;
+  blink::mojom::ManifestPtr manifest_;
   std::unique_ptr<InstallableData> data_;
 
   base::WeakPtrFactory<FakeInstallableManager> weak_factory_{this};
diff --git a/components/webapps/browser/installable/installable_data.cc b/components/webapps/browser/installable/installable_data.cc
index dd9f6ad5..f29affba 100644
--- a/components/webapps/browser/installable/installable_data.cc
+++ b/components/webapps/browser/installable/installable_data.cc
@@ -10,7 +10,7 @@
 
 InstallableData::InstallableData(std::vector<InstallableStatusCode> errors,
                                  const GURL& manifest_url,
-                                 const blink::Manifest& manifest,
+                                 const blink::mojom::Manifest& manifest,
                                  const GURL& primary_icon_url,
                                  const SkBitmap* primary_icon,
                                  bool has_maskable_primary_icon,
diff --git a/components/webapps/browser/installable/installable_data.h b/components/webapps/browser/installable/installable_data.h
index 4b20fad..a9061844 100644
--- a/components/webapps/browser/installable/installable_data.h
+++ b/components/webapps/browser/installable/installable_data.h
@@ -11,7 +11,7 @@
 #include "base/callback_forward.h"
 #include "base/macros.h"
 #include "components/webapps/browser/installable/installable_logging.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "url/gurl.h"
 
@@ -24,7 +24,7 @@
 struct InstallableData {
   InstallableData(std::vector<InstallableStatusCode> errors,
                   const GURL& manifest_url,
-                  const blink::Manifest& manifest,
+                  const blink::mojom::Manifest& manifest,
                   const GURL& primary_icon_url,
                   const SkBitmap* primary_icon,
                   bool has_maskable_primary_icon,
@@ -53,7 +53,7 @@
   const GURL& manifest_url;
 
   // The parsed web app manifest.
-  const blink::Manifest& manifest;
+  const blink::mojom::Manifest& manifest;
 
   // The URL of the chosen primary icon.
   const GURL& primary_icon_url;
diff --git a/components/webapps/browser/installable/installable_manager.cc b/components/webapps/browser/installable/installable_manager.cc
index 996d44c..5b418b2 100644
--- a/components/webapps/browser/installable/installable_manager.cc
+++ b/components/webapps/browser/installable/installable_manager.cc
@@ -32,6 +32,7 @@
 #include "services/network/public/cpp/is_potentially_trustworthy.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/manifest/manifest_icon_selector.h"
+#include "third_party/blink/public/common/manifest/manifest_util.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
 #include "url/origin.h"
@@ -157,7 +158,7 @@
 
 // Returns whether |manifest| specifies an SVG or PNG icon that has
 // IconPurpose::ANY, with size >= kMinimumPrimaryIconSizeInPx (or size "any").
-bool DoesManifestContainRequiredIcon(const blink::Manifest& manifest) {
+bool DoesManifestContainRequiredIcon(const blink::mojom::Manifest& manifest) {
   for (const auto& icon : manifest.icons) {
     if (!IsIconTypeSupported(icon))
       continue;
@@ -335,6 +336,9 @@
           base::BindOnce(OnDidCompleteGetPrimaryIcon, std::move(callback)));
 }
 
+InstallableManager::ManifestProperty::ManifestProperty() = default;
+InstallableManager::ManifestProperty::~ManifestProperty() = default;
+
 bool InstallableManager::IsIconFetchComplete(const IconUsage usage) const {
   const auto it = icons_.find(usage);
   if (it == icons_.end() || !it->second.fetched)
@@ -635,20 +639,20 @@
 }
 
 void InstallableManager::OnDidGetManifest(const GURL& manifest_url,
-                                          const blink::Manifest& manifest) {
+                                          blink::mojom::ManifestPtr manifest) {
   if (!GetWebContents())
     return;
 
   if (manifest_url.is_empty()) {
     manifest_->error = NO_MANIFEST;
     SetManifestDependentTasksComplete();
-  } else if (manifest.IsEmpty()) {
+  } else if (blink::IsEmptyManifest(manifest)) {
     manifest_->error = MANIFEST_EMPTY;
     SetManifestDependentTasksComplete();
   }
 
   manifest_->url = manifest_url;
-  manifest_->manifest = manifest;
+  manifest_->manifest = std::move(manifest);
   manifest_->fetched = true;
   WorkOnTask();
 }
@@ -656,7 +660,7 @@
 void InstallableManager::CheckManifestValid(
     bool check_webapp_manifest_display) {
   DCHECK(!valid_manifest_->fetched);
-  DCHECK(!manifest().IsEmpty());
+  DCHECK(!blink::IsEmptyManifest(manifest()));
 
   valid_manifest_->is_valid =
       IsManifestValidForWebApp(manifest(), check_webapp_manifest_display);
@@ -665,10 +669,10 @@
 }
 
 bool InstallableManager::IsManifestValidForWebApp(
-    const blink::Manifest& manifest,
+    const blink::mojom::Manifest& manifest,
     bool check_webapp_manifest_display) {
   bool is_valid = true;
-  if (manifest.IsEmpty()) {
+  if (blink::IsEmptyManifest(manifest)) {
     valid_manifest_->errors.push_back(MANIFEST_EMPTY);
     return false;
   }
@@ -713,7 +717,7 @@
 
 void InstallableManager::CheckServiceWorker() {
   DCHECK(!worker_->fetched);
-  DCHECK(!manifest().IsEmpty());
+  DCHECK(!blink::IsEmptyManifest(manifest()));
   // Service workers need a StorageKey (storage partitioning key), since we only
   // install for top-level frames we can assume the StorageKey will always be in
   // a 1P context. DCHECK this just to be sure.
@@ -836,7 +840,7 @@
                                                int minimum_icon_size_in_px,
                                                const IconPurpose purpose,
                                                const IconUsage usage) {
-  DCHECK(!manifest().IsEmpty());
+  DCHECK(!blink::IsEmptyManifest(manifest()));
 
   IconProperty& icon = icons_[usage];
   icon.fetched = true;
@@ -881,7 +885,7 @@
 }
 
 void InstallableManager::CheckAndFetchScreenshots() {
-  DCHECK(!manifest().IsEmpty());
+  DCHECK(!blink::IsEmptyManifest(manifest()));
   DCHECK(!is_screenshots_fetch_complete_);
 
   screenshots_downloading_ = 0;
@@ -1007,8 +1011,9 @@
   return manifest_->url;
 }
 
-const blink::Manifest& InstallableManager::manifest() const {
-  return manifest_->manifest;
+const blink::mojom::Manifest& InstallableManager::manifest() const {
+  DCHECK(manifest_->manifest);
+  return *manifest_->manifest;
 }
 
 bool InstallableManager::valid_manifest() {
diff --git a/components/webapps/browser/installable/installable_manager.h b/components/webapps/browser/installable/installable_manager.h
index e5ffc3d4..ca8134b 100644
--- a/components/webapps/browser/installable/installable_manager.h
+++ b/components/webapps/browser/installable/installable_manager.h
@@ -24,7 +24,7 @@
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "url/gurl.h"
 
@@ -119,9 +119,12 @@
   };
 
   struct ManifestProperty {
+    ManifestProperty();
+    ~ManifestProperty();
+
     InstallableStatusCode error = NO_ERROR_DETECTED;
     GURL url;
-    blink::Manifest manifest;
+    blink::mojom::ManifestPtr manifest = blink::mojom::Manifest::New();
     bool fetched = false;
   };
 
@@ -211,10 +214,10 @@
   void CheckEligiblity();
   void FetchManifest();
   void OnDidGetManifest(const GURL& manifest_url,
-                        const blink::Manifest& manifest);
+                        blink::mojom::ManifestPtr manifest);
 
   void CheckManifestValid(bool check_webapp_manifest_display);
-  bool IsManifestValidForWebApp(const blink::Manifest& manifest,
+  bool IsManifestValidForWebApp(const blink::mojom::Manifest& manifest,
                                 bool check_webapp_manifest_display);
   void CheckServiceWorker();
   void OnDidCheckHasServiceWorker(
@@ -248,7 +251,7 @@
   void WebContentsDestroyed() override;
 
   const GURL& manifest_url() const;
-  const blink::Manifest& manifest() const;
+  const blink::mojom::Manifest& manifest() const;
   bool valid_manifest();
   bool has_worker();
 
diff --git a/components/webapps/browser/installable/installable_manager_unittest.cc b/components/webapps/browser/installable/installable_manager_unittest.cc
index 990f789..1de8dc50 100644
--- a/components/webapps/browser/installable/installable_manager_unittest.cc
+++ b/components/webapps/browser/installable/installable_manager_unittest.cc
@@ -24,25 +24,25 @@
       : manager_(std::make_unique<InstallableManager>(nullptr)) {}
 
  protected:
-  static blink::Manifest GetValidManifest() {
-    blink::Manifest manifest;
-    manifest.name = u"foo";
-    manifest.short_name = u"bar";
-    manifest.start_url = GURL("http://example.com");
-    manifest.display = blink::mojom::DisplayMode::kStandalone;
+  static blink::mojom::ManifestPtr GetValidManifest() {
+    auto manifest = blink::mojom::Manifest::New();
+    manifest->name = u"foo";
+    manifest->short_name = u"bar";
+    manifest->start_url = GURL("http://example.com");
+    manifest->display = blink::mojom::DisplayMode::kStandalone;
 
     blink::Manifest::ImageResource primary_icon;
     primary_icon.type = u"image/png";
     primary_icon.sizes.push_back(gfx::Size(144, 144));
     primary_icon.purpose.push_back(IconPurpose::ANY);
-    manifest.icons.push_back(primary_icon);
+    manifest->icons.push_back(primary_icon);
 
     // No need to include the optional badge icon as it does not affect the
     // unit tests.
     return manifest;
   }
 
-  bool IsManifestValid(const blink::Manifest& manifest,
+  bool IsManifestValid(const blink::mojom::Manifest& manifest,
                        bool check_webapp_manifest_display = true) {
     // Explicitly reset the error code before running the method.
     manager_->set_valid_manifest_error(NO_ERROR_DETECTED);
@@ -59,287 +59,287 @@
 };
 
 TEST_F(InstallableManagerUnitTest, EmptyManifestIsInvalid) {
-  blink::Manifest manifest;
+  blink::mojom::Manifest manifest;
   EXPECT_FALSE(IsManifestValid(manifest));
   EXPECT_EQ(MANIFEST_EMPTY, GetErrorCode());
 }
 
 TEST_F(InstallableManagerUnitTest, CheckMinimalValidManifest) {
-  blink::Manifest manifest = GetValidManifest();
-  EXPECT_TRUE(IsManifestValid(manifest));
+  blink::mojom::ManifestPtr manifest = GetValidManifest();
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 }
 
 TEST_F(InstallableManagerUnitTest, ManifestRequiresNameOrShortName) {
-  blink::Manifest manifest = GetValidManifest();
+  blink::mojom::ManifestPtr manifest = GetValidManifest();
 
-  manifest.name = absl::nullopt;
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->name = absl::nullopt;
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
-  manifest.name = u"foo";
-  manifest.short_name = absl::nullopt;
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->name = u"foo";
+  manifest->short_name = absl::nullopt;
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
-  manifest.name = absl::nullopt;
-  EXPECT_FALSE(IsManifestValid(manifest));
+  manifest->name = absl::nullopt;
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_MISSING_NAME_OR_SHORT_NAME, GetErrorCode());
 }
 
 TEST_F(InstallableManagerUnitTest, ManifestRequiresNonEmptyNameORShortName) {
-  blink::Manifest manifest = GetValidManifest();
+  blink::mojom::ManifestPtr manifest = GetValidManifest();
 
-  manifest.name = std::u16string();
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->name = std::u16string();
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
-  manifest.name = u"foo";
-  manifest.short_name = std::u16string();
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->name = u"foo";
+  manifest->short_name = std::u16string();
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
-  manifest.name = std::u16string();
-  EXPECT_FALSE(IsManifestValid(manifest));
+  manifest->name = std::u16string();
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_MISSING_NAME_OR_SHORT_NAME, GetErrorCode());
 }
 
 TEST_F(InstallableManagerUnitTest, ManifestRequiresValidStartURL) {
-  blink::Manifest manifest = GetValidManifest();
+  blink::mojom::ManifestPtr manifest = GetValidManifest();
 
-  manifest.start_url = GURL();
-  EXPECT_FALSE(IsManifestValid(manifest));
+  manifest->start_url = GURL();
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(START_URL_NOT_VALID, GetErrorCode());
 
-  manifest.start_url = GURL("/");
-  EXPECT_FALSE(IsManifestValid(manifest));
+  manifest->start_url = GURL("/");
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(START_URL_NOT_VALID, GetErrorCode());
 }
 
 TEST_F(InstallableManagerUnitTest, ManifestSupportsImagePNG) {
-  blink::Manifest manifest = GetValidManifest();
+  blink::mojom::ManifestPtr manifest = GetValidManifest();
 
-  manifest.icons[0].type = u"image/gif";
-  EXPECT_FALSE(IsManifestValid(manifest));
+  manifest->icons[0].type = u"image/gif";
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
 
-  manifest.icons[0].type.clear();
-  EXPECT_FALSE(IsManifestValid(manifest));
+  manifest->icons[0].type.clear();
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
 
   // If the type is null, the icon src will be checked instead.
-  manifest.icons[0].src = GURL("http://example.com/icon.png");
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->icons[0].src = GURL("http://example.com/icon.png");
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
   // Capital file extension is also permissible.
-  manifest.icons[0].src = GURL("http://example.com/icon.PNG");
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->icons[0].src = GURL("http://example.com/icon.PNG");
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
   // Unsupported extensions are rejected.
-  manifest.icons[0].src = GURL("http://example.com/icon.gif");
-  EXPECT_FALSE(IsManifestValid(manifest));
+  manifest->icons[0].src = GURL("http://example.com/icon.gif");
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
 }
 
 TEST_F(InstallableManagerUnitTest, ManifestSupportsImageSVG) {
-  blink::Manifest manifest = GetValidManifest();
+  blink::mojom::ManifestPtr manifest = GetValidManifest();
 
   // The correct mimetype is image/svg+xml.
-  manifest.icons[0].type = u"image/svg";
-  EXPECT_FALSE(IsManifestValid(manifest));
+  manifest->icons[0].type = u"image/svg";
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
 
   // If the type is null, the icon src will be checked instead.
-  manifest.icons[0].type.clear();
-  manifest.icons[0].src = GURL("http://example.com/icon.svg");
+  manifest->icons[0].type.clear();
+  manifest->icons[0].src = GURL("http://example.com/icon.svg");
 // TODO(https://crbug.com/578122): Add SVG support for Android.
 #if defined(OS_ANDROID)
-  EXPECT_FALSE(IsManifestValid(manifest));
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
 #else
-  EXPECT_TRUE(IsManifestValid(manifest));
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 #endif
 
   // Capital file extension is also permissible.
-  manifest.icons[0].src = GURL("http://example.com/icon.SVG");
+  manifest->icons[0].src = GURL("http://example.com/icon.SVG");
 // TODO(https://crbug.com/578122): Add SVG support for Android.
 #if defined(OS_ANDROID)
-  EXPECT_FALSE(IsManifestValid(manifest));
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
 #else
-  EXPECT_TRUE(IsManifestValid(manifest));
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 #endif
 }
 
 TEST_F(InstallableManagerUnitTest, ManifestSupportsImageWebP) {
-  blink::Manifest manifest = GetValidManifest();
+  blink::mojom::ManifestPtr manifest = GetValidManifest();
 
-  manifest.icons[0].type = u"image/webp";
-  manifest.icons[0].src = GURL("http://example.com/");
+  manifest->icons[0].type = u"image/webp";
+  manifest->icons[0].src = GURL("http://example.com/");
 // TODO(https://crbug.com/466958): Add WebP support for Android.
 #if defined(OS_ANDROID)
-  EXPECT_FALSE(IsManifestValid(manifest));
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
 #else
-  EXPECT_TRUE(IsManifestValid(manifest));
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 #endif
 
   // If the type is null, the icon src is checked instead.
   // Case is ignored.
-  manifest.icons[0].type.clear();
-  manifest.icons[0].src = GURL("http://example.com/icon.wEBp");
+  manifest->icons[0].type.clear();
+  manifest->icons[0].src = GURL("http://example.com/icon.wEBp");
 // TODO(https://crbug.com/466958): Add WebP support for Android.
 #if defined(OS_ANDROID)
-  EXPECT_FALSE(IsManifestValid(manifest));
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
 #else
-  EXPECT_TRUE(IsManifestValid(manifest));
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 #endif
 }
 
 TEST_F(InstallableManagerUnitTest, ManifestRequiresPurposeAny) {
-  blink::Manifest manifest = GetValidManifest();
+  blink::mojom::ManifestPtr manifest = GetValidManifest();
 
   // The icon MUST have IconPurpose::ANY at least.
-  manifest.icons[0].purpose[0] = IconPurpose::MASKABLE;
-  EXPECT_FALSE(IsManifestValid(manifest));
+  manifest->icons[0].purpose[0] = IconPurpose::MASKABLE;
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
 
   // If one of the icon purposes match the requirement, it should be accepted.
-  manifest.icons[0].purpose.push_back(IconPurpose::ANY);
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->icons[0].purpose.push_back(IconPurpose::ANY);
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 }
 
 TEST_F(InstallableManagerUnitTest, ManifestRequiresMinimalSize) {
-  blink::Manifest manifest = GetValidManifest();
+  blink::mojom::ManifestPtr manifest = GetValidManifest();
 
   // The icon MUST be 144x144 size at least.
-  manifest.icons[0].sizes[0] = gfx::Size(1, 1);
-  EXPECT_FALSE(IsManifestValid(manifest));
+  manifest->icons[0].sizes[0] = gfx::Size(1, 1);
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
 
-  manifest.icons[0].sizes[0] = gfx::Size(143, 143);
-  EXPECT_FALSE(IsManifestValid(manifest));
+  manifest->icons[0].sizes[0] = gfx::Size(143, 143);
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
 
   // If one of the sizes match the requirement, it should be accepted.
-  manifest.icons[0].sizes.push_back(gfx::Size(144, 144));
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->icons[0].sizes.emplace_back(144, 144);
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
   // Higher than the required size is okay.
-  manifest.icons[0].sizes[1] = gfx::Size(200, 200);
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->icons[0].sizes[1] = gfx::Size(200, 200);
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
   // Non-square is okay.
-  manifest.icons[0].sizes[1] = gfx::Size(144, 200);
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->icons[0].sizes[1] = gfx::Size(144, 200);
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
   // The representation of the keyword 'any' should be recognized.
-  manifest.icons[0].sizes[1] = gfx::Size(0, 0);
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->icons[0].sizes[1] = gfx::Size(0, 0);
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 }
 
 TEST_F(InstallableManagerUnitTest, ManifestDisplayModes) {
-  blink::Manifest manifest = GetValidManifest();
+  blink::mojom::ManifestPtr manifest = GetValidManifest();
 
-  manifest.display = blink::mojom::DisplayMode::kUndefined;
+  manifest->display = blink::mojom::DisplayMode::kUndefined;
   EXPECT_TRUE(
-      IsManifestValid(manifest, false /* check_webapp_manifest_display */));
-  EXPECT_FALSE(IsManifestValid(manifest));
+      IsManifestValid(*manifest, false /* check_webapp_manifest_display */));
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_DISPLAY_NOT_SUPPORTED, GetErrorCode());
 
-  manifest.display = blink::mojom::DisplayMode::kBrowser;
+  manifest->display = blink::mojom::DisplayMode::kBrowser;
   EXPECT_TRUE(
-      IsManifestValid(manifest, false /* check_webapp_manifest_display */));
-  EXPECT_FALSE(IsManifestValid(manifest));
+      IsManifestValid(*manifest, false /* check_webapp_manifest_display */));
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_DISPLAY_NOT_SUPPORTED, GetErrorCode());
 
-  manifest.display = blink::mojom::DisplayMode::kMinimalUi;
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->display = blink::mojom::DisplayMode::kMinimalUi;
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
-  manifest.display = blink::mojom::DisplayMode::kStandalone;
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->display = blink::mojom::DisplayMode::kStandalone;
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
-  manifest.display = blink::mojom::DisplayMode::kFullscreen;
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->display = blink::mojom::DisplayMode::kFullscreen;
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
-  manifest.display = blink::mojom::DisplayMode::kWindowControlsOverlay;
+  manifest->display = blink::mojom::DisplayMode::kWindowControlsOverlay;
   EXPECT_TRUE(
-      IsManifestValid(manifest, false /* check_webapp_manifest_display */));
-  EXPECT_TRUE(IsManifestValid(manifest));
+      IsManifestValid(*manifest, false /* check_webapp_manifest_display */));
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
-  manifest.display = blink::mojom::DisplayMode::kTabbed;
+  manifest->display = blink::mojom::DisplayMode::kTabbed;
   EXPECT_TRUE(
-      IsManifestValid(manifest, false /* check_webapp_manifest_display */));
-  EXPECT_FALSE(IsManifestValid(manifest));
+      IsManifestValid(*manifest, false /* check_webapp_manifest_display */));
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_DISPLAY_NOT_SUPPORTED, GetErrorCode());
 }
 
 TEST_F(InstallableManagerUnitTest, ManifestDisplayOverride) {
-  blink::Manifest manifest = GetValidManifest();
+  blink::mojom::ManifestPtr manifest = GetValidManifest();
 
-  manifest.display_override.push_back(blink::mojom::DisplayMode::kMinimalUi);
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->display_override.push_back(blink::mojom::DisplayMode::kMinimalUi);
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
-  manifest.display_override.push_back(blink::mojom::DisplayMode::kBrowser);
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->display_override.push_back(blink::mojom::DisplayMode::kBrowser);
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
-  manifest.display_override.insert(manifest.display_override.begin(),
-                                   blink::mojom::DisplayMode::kStandalone);
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->display_override.insert(manifest->display_override.begin(),
+                                    blink::mojom::DisplayMode::kStandalone);
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
-  manifest.display_override.insert(manifest.display_override.begin(),
-                                   blink::mojom::DisplayMode::kStandalone);
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->display_override.insert(manifest->display_override.begin(),
+                                    blink::mojom::DisplayMode::kStandalone);
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
-  manifest.display_override.insert(manifest.display_override.begin(),
-                                   blink::mojom::DisplayMode::kBrowser);
+  manifest->display_override.insert(manifest->display_override.begin(),
+                                    blink::mojom::DisplayMode::kBrowser);
   EXPECT_TRUE(
-      IsManifestValid(manifest, false /* check_webapp_manifest_display */));
-  EXPECT_FALSE(IsManifestValid(manifest));
+      IsManifestValid(*manifest, false /* check_webapp_manifest_display */));
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_DISPLAY_OVERRIDE_NOT_SUPPORTED, GetErrorCode());
 
-  manifest.display_override.insert(
-      manifest.display_override.begin(),
+  manifest->display_override.insert(
+      manifest->display_override.begin(),
       blink::mojom::DisplayMode::kWindowControlsOverlay);
   EXPECT_TRUE(
-      IsManifestValid(manifest, false /* check_webapp_manifest_display */));
-  EXPECT_TRUE(IsManifestValid(manifest));
+      IsManifestValid(*manifest, false /* check_webapp_manifest_display */));
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 
-  manifest.display_override.insert(manifest.display_override.begin(),
-                                   blink::mojom::DisplayMode::kTabbed);
+  manifest->display_override.insert(manifest->display_override.begin(),
+                                    blink::mojom::DisplayMode::kTabbed);
   EXPECT_TRUE(
-      IsManifestValid(manifest, false /* check_webapp_manifest_display */));
-  EXPECT_FALSE(IsManifestValid(manifest));
+      IsManifestValid(*manifest, false /* check_webapp_manifest_display */));
+  EXPECT_FALSE(IsManifestValid(*manifest));
   EXPECT_EQ(MANIFEST_DISPLAY_OVERRIDE_NOT_SUPPORTED, GetErrorCode());
 }
 
 TEST_F(InstallableManagerUnitTest, FallbackToBrowser) {
-  blink::Manifest manifest = GetValidManifest();
+  blink::mojom::ManifestPtr manifest = GetValidManifest();
 
-  manifest.display = blink::mojom::DisplayMode::kBrowser;
-  manifest.display_override.push_back(blink::mojom::DisplayMode::kMinimalUi);
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->display = blink::mojom::DisplayMode::kBrowser;
+  manifest->display_override.push_back(blink::mojom::DisplayMode::kMinimalUi);
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 }
 
@@ -357,11 +357,11 @@
 
 TEST_F(InstallableManagerUnitTest_WindowControlsOverlay,
        SupportWindowControlsOverlay) {
-  blink::Manifest manifest = GetValidManifest();
+  blink::mojom::ManifestPtr manifest = GetValidManifest();
 
-  manifest.display_override.push_back(
+  manifest->display_override.push_back(
       blink::mojom::DisplayMode::kWindowControlsOverlay);
-  EXPECT_TRUE(IsManifestValid(manifest));
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 }
 
@@ -375,10 +375,10 @@
 };
 
 TEST_F(InstallableManagerUnitTest_Tabbed, SupportTabbed) {
-  blink::Manifest manifest = GetValidManifest();
+  blink::mojom::ManifestPtr manifest = GetValidManifest();
 
-  manifest.display_override.push_back(blink::mojom::DisplayMode::kTabbed);
-  EXPECT_TRUE(IsManifestValid(manifest));
+  manifest->display_override.push_back(blink::mojom::DisplayMode::kTabbed);
+  EXPECT_TRUE(IsManifestValid(*manifest));
   EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
 }
 
diff --git a/components/webapps/browser/installable/installable_task_queue.cc b/components/webapps/browser/installable/installable_task_queue.cc
index 4d9a2e0..2b51dc61 100644
--- a/components/webapps/browser/installable/installable_task_queue.cc
+++ b/components/webapps/browser/installable/installable_task_queue.cc
@@ -7,6 +7,8 @@
 #include <map>
 #include <utility>
 
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
+
 namespace webapps {
 
 InstallableTask::InstallableTask() = default;
@@ -71,7 +73,7 @@
   // Some callbacks might be already invalidated on certain resets, so we must
   // check for that.
   // Manifest is assumed to be non-null, so we create an empty one here.
-  blink::Manifest manifest;
+  blink::mojom::Manifest manifest;
   for (InstallableTask& task : tasks) {
     if (task.callback) {
       std::move(task.callback)
diff --git a/content/browser/back_forward_cache_browsertest.cc b/content/browser/back_forward_cache_browsertest.cc
index d0c38a2..0f35b7e 100644
--- a/content/browser/back_forward_cache_browsertest.cc
+++ b/content/browser/back_forward_cache_browsertest.cc
@@ -9038,63 +9038,6 @@
   presentation_service.OnDelegateDestroyed();
 }
 
-namespace {
-
-// Subclass of DocumentServiceBase for test.
-class EchoImpl final : public DocumentServiceBase<mojom::Echo> {
- public:
-  EchoImpl(RenderFrameHost* render_frame_host,
-           mojo::PendingReceiver<mojom::Echo> receiver,
-           bool* deleted)
-      : DocumentServiceBase(render_frame_host, std::move(receiver)),
-        deleted_(deleted) {}
-  ~EchoImpl() final { *deleted_ = true; }
-
-  // mojom::Echo implementation
-  void EchoString(const std::string& input, EchoStringCallback callback) final {
-    std::move(callback).Run(input);
-  }
-
- private:
-  bool* deleted_;
-};
-
-}  // namespace
-
-IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DocumentServiceBase) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
-  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
-
-  // 1) Navigate to A.
-  ASSERT_TRUE(NavigateToURL(shell(), url_a));
-  RenderFrameHostImpl* rfh_a = current_frame_host();
-  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
-
-  mojo::Remote<mojom::Echo> echo_remote;
-  bool echo_deleted = false;
-  new EchoImpl(rfh_a, echo_remote.BindNewPipeAndPassReceiver(), &echo_deleted);
-
-  // 2) Navigate to B.
-  ASSERT_TRUE(NavigateToURL(shell(), url_b));
-
-  // - Page A should be in the cache.
-  ASSERT_FALSE(delete_observer_rfh_a.deleted());
-  EXPECT_TRUE(rfh_a->IsInBackForwardCache());
-  EXPECT_FALSE(echo_deleted);
-
-  // 3) Go back.
-  web_contents()->GetController().GoBack();
-  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
-  EXPECT_FALSE(echo_deleted);
-
-  // 4) Prevent caching and navigate to B.
-  DisableBFCacheForRFHForTesting(rfh_a);
-  ASSERT_TRUE(NavigateToURL(shell(), url_b));
-  delete_observer_rfh_a.WaitUntilDeleted();
-  EXPECT_TRUE(echo_deleted);
-}
-
 IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, OutstandingFetchNotCached) {
   net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                       "/fetch");
diff --git a/content/browser/bluetooth/web_bluetooth_service_impl_browsertest.cc b/content/browser/bluetooth/web_bluetooth_service_impl_browsertest.cc
index e188a5a..fea9c11 100644
--- a/content/browser/bluetooth/web_bluetooth_service_impl_browsertest.cc
+++ b/content/browser/bluetooth/web_bluetooth_service_impl_browsertest.cc
@@ -286,8 +286,12 @@
             base::Unretained(this))) {}
   ~WebBluetoothServiceImplBrowserTest() override = default;
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    ContentBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     ASSERT_TRUE(test_server_handle_ =
                     embedded_test_server()->StartAndReturnHandle());
 
diff --git a/content/browser/devtools/devtools_issue_storage_browsertest.cc b/content/browser/devtools/devtools_issue_storage_browsertest.cc
index 877e78f..b288867b 100644
--- a/content/browser/devtools/devtools_issue_storage_browsertest.cc
+++ b/content/browser/devtools/devtools_issue_storage_browsertest.cc
@@ -222,9 +222,9 @@
             &DevToolsIssueStorageWithPrerenderBrowserTest::GetWebContents,
             base::Unretained(this))) {}
 
-  void SetUpOnMainThread() override {
-    DevToolsIssueStorageBrowserTest::SetUpOnMainThread();
-    prerender_test_helper().SetUpOnMainThread(embedded_test_server());
+  void SetUp() override {
+    prerender_test_helper().SetUp(embedded_test_server());
+    DevToolsIssueStorageBrowserTest::SetUp();
   }
 
   test::PrerenderTestHelper& prerender_test_helper() {
diff --git a/content/browser/devtools/protocol/page_handler.cc b/content/browser/devtools/protocol/page_handler.cc
index 7e1db20..078b35456f 100644
--- a/content/browser/devtools/protocol/page_handler.cc
+++ b/content/browser/devtools/protocol/page_handler.cc
@@ -54,6 +54,8 @@
 #include "content/public/common/use_zoom_for_dsf_policy.h"
 #include "net/base/filename_util.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/common/manifest/manifest_util.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/base/page_transition_types.h"
 #include "ui/gfx/codec/jpeg_codec.h"
@@ -1207,7 +1209,7 @@
 
 void PageHandler::GotManifest(std::unique_ptr<GetAppManifestCallback> callback,
                               const GURL& manifest_url,
-                              const ::blink::Manifest& parsed_manifest,
+                              ::blink::mojom::ManifestPtr parsed_manifest,
                               blink::mojom::ManifestDebugInfoPtr debug_info) {
   auto errors = std::make_unique<protocol::Array<Page::AppManifestError>>();
   bool failed = true;
@@ -1226,9 +1228,9 @@
   }
 
   std::unique_ptr<Page::AppManifestParsedProperties> parsed;
-  if (!parsed_manifest.IsEmpty()) {
+  if (!blink::IsEmptyManifest(parsed_manifest)) {
     parsed = Page::AppManifestParsedProperties::Create()
-                 .SetScope(parsed_manifest.scope.possibly_invalid_spec())
+                 .SetScope(parsed_manifest->scope.possibly_invalid_spec())
                  .Build();
   }
 
diff --git a/content/browser/devtools/protocol/page_handler.h b/content/browser/devtools/protocol/page_handler.h
index 965034e..749722d 100644
--- a/content/browser/devtools/protocol/page_handler.h
+++ b/content/browser/devtools/protocol/page_handler.h
@@ -27,7 +27,7 @@
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/render_widget_host_observer.h"
 #include "content/public/common/javascript_dialog_type.h"
-#include "third_party/blink/public/mojom/manifest/manifest_manager.mojom.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 #include "url/gurl.h"
 
 class SkBitmap;
@@ -207,7 +207,7 @@
 
   void GotManifest(std::unique_ptr<GetAppManifestCallback> callback,
                    const GURL& manifest_url,
-                   const ::blink::Manifest& parsed_manifest,
+                   ::blink::mojom::ManifestPtr parsed_manifest,
                    blink::mojom::ManifestDebugInfoPtr debug_info);
 
   // RenderWidgetHostObserver overrides.
diff --git a/content/browser/download/download_browsertest.cc b/content/browser/download/download_browsertest.cc
index 37c35cf..e0b2537 100644
--- a/content/browser/download/download_browsertest.cc
+++ b/content/browser/download/download_browsertest.cc
@@ -1438,8 +1438,12 @@
                                 base::Unretained(this))) {}
   ~DownloadPrerenderTest() override = default;
 
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    DownloadContentTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
     DownloadContentTest::SetUpOnMainThread();
     ASSERT_TRUE(embedded_test_server()->Started());
   }
diff --git a/content/browser/manifest/manifest_browsertest.cc b/content/browser/manifest/manifest_browsertest.cc
index dc6f170..609d0912 100644
--- a/content/browser/manifest/manifest_browsertest.cc
+++ b/content/browser/manifest/manifest_browsertest.cc
@@ -34,7 +34,9 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/common/manifest/manifest_util.h"
 #include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "third_party/blink/public/mojom/manifest/manifest_manager.mojom.h"
 
 namespace content {
@@ -101,13 +103,16 @@
   }
 
   void OnGetManifest(const GURL& manifest_url,
-                     const blink::Manifest& manifest) {
+                     blink::mojom::ManifestPtr manifest) {
     manifest_url_ = manifest_url;
-    manifest_ = manifest;
+    manifest_ = std::move(manifest);
     message_loop_runner_->Quit();
   }
 
-  const blink::Manifest& manifest() const { return manifest_; }
+  const blink::mojom::Manifest& manifest() const {
+    DCHECK(manifest_);
+    return *manifest_;
+  }
 
   const GURL& manifest_url() const {
     return manifest_url_;
@@ -172,7 +177,7 @@
   std::unique_ptr<MockWebContentsDelegate> mock_web_contents_delegate_;
   std::unique_ptr<net::EmbeddedTestServer> cors_embedded_test_server_;
   GURL manifest_url_;
-  blink::Manifest manifest_;
+  blink::mojom::ManifestPtr manifest_ = blink::mojom::Manifest::New();
   std::vector<std::string> console_errors_;
   std::vector<GURL> reported_manifest_urls_;
   std::vector<size_t> manifests_reported_when_favicon_url_updated_;
@@ -204,7 +209,7 @@
   ASSERT_TRUE(NavigateToURL(shell(), test_url));
 
   GetManifestAndWait();
-  EXPECT_TRUE(manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(manifest()));
   EXPECT_TRUE(manifest_url().is_empty());
   EXPECT_EQ(0, GetConsoleErrorCount());
   EXPECT_TRUE(reported_manifest_urls().empty());
@@ -220,7 +225,7 @@
   ASSERT_TRUE(NavigateToURL(shell(), test_url));
 
   GetManifestAndWait();
-  EXPECT_TRUE(manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(manifest()));
   EXPECT_FALSE(manifest_url().is_empty());
   // 1 error for syntax errors in manifest/thereisnomanifestthere.json.
   EXPECT_EQ(1, GetConsoleErrorCount());
@@ -238,7 +243,7 @@
   ASSERT_TRUE(NavigateToURL(shell(), test_url));
 
   GetManifestAndWait();
-  EXPECT_FALSE(manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
   EXPECT_FALSE(manifest_url().is_empty());
   ASSERT_EQ(test_url.GetWithoutFilename(), manifest().scope);
   EXPECT_EQ(0, GetConsoleErrorCount());
@@ -257,7 +262,7 @@
   ASSERT_TRUE(NavigateToURL(shell(), test_url));
 
   GetManifestAndWait();
-  EXPECT_TRUE(manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(manifest()));
   EXPECT_FALSE(manifest_url().is_empty());
   EXPECT_EQ(1, GetConsoleErrorCount());
   ASSERT_EQ(1u, reported_manifest_urls().size());
@@ -276,7 +281,7 @@
   ASSERT_TRUE(NavigateToURL(shell(), test_url));
 
   GetManifestAndWait();
-  EXPECT_FALSE(manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
   EXPECT_FALSE(manifest_url().is_empty());
 
   EXPECT_EQ(0, GetConsoleErrorCount());
@@ -297,7 +302,7 @@
 
   {
     GetManifestAndWait();
-    EXPECT_TRUE(manifest().IsEmpty());
+    EXPECT_TRUE(blink::IsEmptyManifest(manifest()));
     EXPECT_TRUE(manifest_url().is_empty());
     EXPECT_TRUE(reported_manifest_urls().empty());
   }
@@ -308,7 +313,7 @@
     ASSERT_TRUE(ExecJs(shell(), "setManifestTo('" + manifest_link + "')"));
 
     GetManifestAndWait();
-    EXPECT_FALSE(manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
     EXPECT_FALSE(manifest_url().is_empty());
     expected_manifest_urls.push_back(manifest_url());
     EXPECT_EQ(expected_manifest_urls, reported_manifest_urls());
@@ -319,7 +324,7 @@
     ASSERT_TRUE(ExecJs(shell(), "setManifestTo('" + manifest_link + "')"));
 
     GetManifestAndWait();
-    EXPECT_FALSE(manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
     EXPECT_FALSE(manifest_url().is_empty());
     expected_manifest_urls.push_back(manifest_url());
     EXPECT_EQ(expected_manifest_urls, reported_manifest_urls());
@@ -329,7 +334,7 @@
     ASSERT_TRUE(ExecJs(shell(), "clearManifest()"));
 
     GetManifestAndWait();
-    EXPECT_TRUE(manifest().IsEmpty());
+    EXPECT_TRUE(blink::IsEmptyManifest(manifest()));
     EXPECT_TRUE(manifest_url().is_empty());
     expected_manifest_urls.push_back(manifest_url());
     EXPECT_EQ(expected_manifest_urls, reported_manifest_urls());
@@ -348,7 +353,7 @@
   ASSERT_TRUE(NavigateToURL(shell(), test_url));
 
   GetManifestAndWait();
-  EXPECT_FALSE(manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
   EXPECT_FALSE(manifest_url().is_empty());
   EXPECT_FALSE(manifest().file_handlers.empty());
   EXPECT_EQ(0, GetConsoleErrorCount());
@@ -376,7 +381,7 @@
   ASSERT_TRUE(ExecJs(shell(), "setManifestTo('" + manifest_link + "')"));
 
   GetManifestAndWait();
-  EXPECT_TRUE(manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(manifest()));
   EXPECT_FALSE(manifest_url().is_empty());
   EXPECT_THAT(console_errors(), Contains(HasSubstr("CORS")));
   EXPECT_EQ(1, GetConsoleErrorCount());
@@ -416,7 +421,7 @@
   ASSERT_TRUE(ExecJs(shell(), "setManifestTo('" + manifest_link + "')"));
 
   GetManifestAndWait();
-  EXPECT_FALSE(manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
   EXPECT_FALSE(manifest_url().is_empty());
   EXPECT_EQ(0, GetConsoleErrorCount());
   ASSERT_EQ(1u, reported_manifest_urls().size());
@@ -447,7 +452,7 @@
   ASSERT_TRUE(ExecJs(shell(), JsReplace("setManifestTo($1)", manifest_link)));
 
   GetManifestAndWait();
-  EXPECT_TRUE(manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(manifest()));
   EXPECT_FALSE(manifest_url().is_empty());
   EXPECT_THAT(console_errors(), Contains(HasSubstr("Mixed Content")));
   ASSERT_EQ(1u, reported_manifest_urls().size());
@@ -465,7 +470,7 @@
   ASSERT_TRUE(NavigateToURL(shell(), test_url));
 
   GetManifestAndWait();
-  EXPECT_FALSE(manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
   EXPECT_FALSE(manifest_url().is_empty());
   ASSERT_EQ(test_url.GetWithoutFilename(), manifest().scope);
   EXPECT_EQ(7, GetConsoleErrorCount());
@@ -486,7 +491,7 @@
     ASSERT_TRUE(NavigateToURL(shell(), test_url));
 
     GetManifestAndWait();
-    EXPECT_FALSE(manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
     EXPECT_FALSE(manifest_url().is_empty());
     EXPECT_EQ(0, GetConsoleErrorCount());
     expected_manifest_urls.push_back(manifest_url());
@@ -502,7 +507,7 @@
     ASSERT_TRUE(NavigateToURL(shell(), test_url));
 
     GetManifestAndWait();
-    EXPECT_TRUE(manifest().IsEmpty());
+    EXPECT_TRUE(blink::IsEmptyManifest(manifest()));
     EXPECT_EQ(0, GetConsoleErrorCount());
     EXPECT_TRUE(manifest_url().is_empty());
     EXPECT_EQ(expected_manifest_urls, reported_manifest_urls());
@@ -517,7 +522,7 @@
     ASSERT_TRUE(NavigateToURL(shell(), test_url));
 
     GetManifestAndWait();
-    EXPECT_FALSE(manifest().IsEmpty());
+    EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
     EXPECT_FALSE(manifest_url().is_empty());
     EXPECT_EQ(0, GetConsoleErrorCount());
     expected_manifest_urls.push_back(manifest_url());
@@ -543,7 +548,7 @@
   }
 
   GetManifestAndWait();
-  EXPECT_FALSE(manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
   EXPECT_FALSE(manifest_url().is_empty());
   EXPECT_EQ(0, GetConsoleErrorCount());
   ASSERT_EQ(1u, reported_manifest_urls().size());
@@ -569,7 +574,7 @@
   }
 
   GetManifestAndWait();
-  EXPECT_FALSE(manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
   EXPECT_FALSE(manifest_url().is_empty());
   EXPECT_EQ(0, GetConsoleErrorCount());
   ASSERT_EQ(1u, reported_manifest_urls().size());
@@ -629,7 +634,7 @@
       shell(), custom_embedded_test_server->GetURL("/index.html")));
 
   GetManifestAndWait();
-  EXPECT_FALSE(manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
   EXPECT_FALSE(manifest_url().is_empty());
   EXPECT_EQ(0, GetConsoleErrorCount());
   ASSERT_EQ(1u, reported_manifest_urls().size());
@@ -689,7 +694,7 @@
       shell(), custom_embedded_test_server->GetURL("/index.html")));
 
   GetManifestAndWait();
-  EXPECT_FALSE(manifest().IsEmpty());
+  EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
   EXPECT_FALSE(manifest_url().is_empty());
   EXPECT_EQ(0, GetConsoleErrorCount());
   ASSERT_EQ(1u, reported_manifest_urls().size());
@@ -716,7 +721,7 @@
   // Same-origin manifest will not be fetched from a unique origin, regardless
   // of CORS headers.
   GetManifestAndWait();
-  EXPECT_TRUE(manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(manifest()));
   EXPECT_TRUE(manifest_url().is_empty());
   EXPECT_EQ(0, GetConsoleErrorCount());
   EXPECT_EQ(0u, reported_manifest_urls().size());
@@ -726,7 +731,7 @@
   ASSERT_TRUE(ExecJs(shell(), "setManifestTo('" + manifest_link + "')"));
 
   GetManifestAndWait();
-  EXPECT_TRUE(manifest().IsEmpty());
+  EXPECT_TRUE(blink::IsEmptyManifest(manifest()));
   EXPECT_TRUE(manifest_url().is_empty());
   EXPECT_EQ(0, GetConsoleErrorCount());
   EXPECT_EQ(0u, reported_manifest_urls().size());
diff --git a/content/browser/manifest/manifest_manager_host.cc b/content/browser/manifest/manifest_manager_host.cc
index 1c7dfec..deba131 100644
--- a/content/browser/manifest/manifest_manager_host.cc
+++ b/content/browser/manifest/manifest_manager_host.cc
@@ -10,7 +10,7 @@
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "url/gurl.h"
 
 namespace content {
@@ -66,7 +66,7 @@
   }
   callbacks_.Clear();
   for (auto& callback : callbacks)
-    std::move(callback).Run(GURL(), blink::Manifest());
+    std::move(callback).Run(GURL(), blink::mojom::Manifest::New());
 }
 
 void ManifestManagerHost::OnConnectionError() {
@@ -79,10 +79,10 @@
 void ManifestManagerHost::OnRequestManifestResponse(
     int request_id,
     const GURL& url,
-    const blink::Manifest& manifest) {
+    blink::mojom::ManifestPtr manifest) {
   auto callback = std::move(*callbacks_.Lookup(request_id));
   callbacks_.Remove(request_id);
-  std::move(callback).Run(url, manifest);
+  std::move(callback).Run(url, std::move(manifest));
 }
 
 void ManifestManagerHost::ManifestUrlChanged(const GURL& manifest_url) {
diff --git a/content/browser/manifest/manifest_manager_host.h b/content/browser/manifest/manifest_manager_host.h
index 0a7eaab..0086dbb2 100644
--- a/content/browser/manifest/manifest_manager_host.h
+++ b/content/browser/manifest/manifest_manager_host.h
@@ -11,13 +11,10 @@
 #include "content/public/browser/render_document_host_user_data.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 #include "third_party/blink/public/mojom/manifest/manifest_manager.mojom.h"
 #include "third_party/blink/public/mojom/manifest/manifest_observer.mojom.h"
 
-namespace blink {
-struct Manifest;
-}
-
 namespace content {
 
 class RenderFrameHostImpl;
@@ -33,7 +30,7 @@
   ~ManifestManagerHost() override;
 
   using GetManifestCallback =
-      base::OnceCallback<void(const GURL&, const blink::Manifest&)>;
+      base::OnceCallback<void(const GURL&, blink::mojom::ManifestPtr)>;
 
   // Calls the given callback with the manifest associated with the main frame.
   // If the main frame has no manifest or if getting it failed the callback will
@@ -61,7 +58,7 @@
 
   void OnRequestManifestResponse(int request_id,
                                  const GURL& url,
-                                 const blink::Manifest& manifest);
+                                 blink::mojom::ManifestPtr manifest);
 
   // blink::mojom::ManifestUrlChangeObserver:
   void ManifestUrlChanged(const GURL& manifest_url) override;
diff --git a/content/browser/media/session/media_session_impl.cc b/content/browser/media/session/media_session_impl.cc
index 7198ac08..83da123 100644
--- a/content/browser/media/session/media_session_impl.cc
+++ b/content/browser/media/session/media_session_impl.cc
@@ -1582,17 +1582,29 @@
   ContentClient* content_client = content::GetContentClient();
   const GURL& url = web_contents()->GetLastCommittedURL();
 
-  // If the url is a file then we should display a placeholder.
-  std::u16string formatted_origin =
-      url.SchemeIsFile()
-          ? content_client->GetLocalizedString(IDS_MEDIA_SESSION_FILE_SOURCE)
-          : url_formatter::FormatUrl(
-                url::Origin::Create(url).GetURL(),
-                url_formatter::kFormatUrlOmitDefaults |
-                    url_formatter::kFormatUrlOmitHTTPS |
-                    url_formatter::kFormatUrlOmitTrivialSubdomains,
-                net::UnescapeRule::SPACES, nullptr, nullptr, nullptr);
-  metadata.source_title = formatted_origin;
+  // If |url| wraps a chrome extension ID, we can display the extension
+  // name instead, which is more human-readable.
+  std::u16string source_title;
+  WebContentsDelegate* delegate = web_contents()->GetDelegate();
+  if (delegate) {
+    source_title =
+        base::UTF8ToUTF16(delegate->GetTitleForMediaControls(web_contents()));
+  }
+
+  if (source_title.empty()) {
+    // If the url is a file then we should display a placeholder.
+    source_title =
+        url.SchemeIsFile()
+            ? content_client->GetLocalizedString(IDS_MEDIA_SESSION_FILE_SOURCE)
+            : url_formatter::FormatUrl(
+                  url::Origin::Create(url).GetURL(),
+                  url_formatter::kFormatUrlOmitDefaults |
+                      url_formatter::kFormatUrlOmitHTTPS |
+                      url_formatter::kFormatUrlOmitTrivialSubdomains,
+                  net::UnescapeRule::SPACES, nullptr, nullptr, nullptr);
+  }
+
+  metadata.source_title = source_title;
 
   // If we have no artwork in |images_| or the arwork has changed then we should
   // update it with the latest artwork from the routed service.
diff --git a/content/browser/net/accept_header_browsertest.cc b/content/browser/net/accept_header_browsertest.cc
index f1f8687..dfeb392 100644
--- a/content/browser/net/accept_header_browsertest.cc
+++ b/content/browser/net/accept_header_browsertest.cc
@@ -23,6 +23,7 @@
 #include "ppapi/buildflags/buildflags.h"
 #include "third_party/blink/public/common/buildflags.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 
 #if BUILDFLAG(ENABLE_PLUGINS)
 #include "content/test/ppapi/ppapi_test.h"
diff --git a/content/browser/payments/payment_app_info_fetcher.cc b/content/browser/payments/payment_app_info_fetcher.cc
index 0d777202..18ea802 100644
--- a/content/browser/payments/payment_app_info_fetcher.cc
+++ b/content/browser/payments/payment_app_info_fetcher.cc
@@ -21,8 +21,10 @@
 #include "content/public/browser/page.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/manifest/manifest_icon_selector.h"
+#include "third_party/blink/public/common/manifest/manifest_util.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "ui/gfx/codec/png_codec.h"
 #include "url/origin.h"
 
@@ -181,7 +183,7 @@
 
 void PaymentAppInfoFetcher::SelfDeleteFetcher::FetchPaymentAppManifestCallback(
     const GURL& url,
-    const blink::Manifest& manifest) {
+    blink::mojom::ManifestPtr manifest) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   manifest_url_ = url;
@@ -196,7 +198,7 @@
     return;
   }
 
-  if (manifest.IsEmpty()) {
+  if (blink::IsEmptyManifest(manifest)) {
     WarnIfPossible(
         "Unable to download a valid payment handler web app manifest from \"" +
         manifest_url_.spec() +
@@ -209,8 +211,8 @@
   }
 
   fetched_payment_app_info_->prefer_related_applications =
-      manifest.prefer_related_applications;
-  for (const auto& related_application : manifest.related_applications) {
+      manifest->prefer_related_applications;
+  for (const auto& related_application : manifest->related_applications) {
     fetched_payment_app_info_->related_applications.emplace_back(
         StoredRelatedApplication());
     if (related_application.platform) {
@@ -226,24 +228,24 @@
     }
   }
 
-  if (!manifest.name) {
+  if (!manifest->name) {
     WarnIfPossible("The payment handler's web app manifest \"" +
                    manifest_url_.spec() +
                    "\" does not contain a \"name\" field. User may not "
                    "recognize this payment handler in UI, because it will be "
                    "labeled only by its origin.");
-  } else if (manifest.name->empty()) {
+  } else if (manifest->name->empty()) {
     WarnIfPossible(
         "The \"name\" field in the payment handler's web app manifest \"" +
         manifest_url_.spec() +
         "\" is empty. User may not recognize this payment handler in UI, "
         "because it will be labeled only by its origin.");
   } else {
-    base::UTF16ToUTF8(manifest.name->c_str(), manifest.name->length(),
+    base::UTF16ToUTF8(manifest->name->c_str(), manifest->name->length(),
                       &(fetched_payment_app_info_->name));
   }
 
-  if (manifest.icons.empty()) {
+  if (manifest->icons.empty()) {
     WarnIfPossible(
         "Unable to download the payment handler's icon, because the web app "
         "manifest \"" +
@@ -267,7 +269,7 @@
   gfx::NativeView native_view = web_contents->GetNativeView();
 
   icon_url_ = blink::ManifestIconSelector::FindBestMatchingIcon(
-      manifest.icons,
+      manifest->icons,
       payments::IconSizeCalculator::IdealIconHeight(native_view),
       payments::IconSizeCalculator::MinimumIconHeight(),
       ManifestIconDownloader::kMaxWidthToHeightRatio,
diff --git a/content/browser/payments/payment_app_info_fetcher.h b/content/browser/payments/payment_app_info_fetcher.h
index 9b14defa..cffe8082 100644
--- a/content/browser/payments/payment_app_info_fetcher.h
+++ b/content/browser/payments/payment_app_info_fetcher.h
@@ -14,7 +14,7 @@
 #include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/stored_payment_app.h"
 #include "content/public/browser/web_contents_observer.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
 namespace content {
@@ -70,7 +70,7 @@
 
     // The WebContents::GetManifestCallback.
     void FetchPaymentAppManifestCallback(const GURL& url,
-                                         const blink::Manifest& manifest);
+                                         blink::mojom::ManifestPtr manifest);
 
     // The ManifestIconDownloader::IconFetchCallback.
     void OnIconFetched(const SkBitmap& icon);
diff --git a/content/browser/prerender/prerender_browsertest.cc b/content/browser/prerender/prerender_browsertest.cc
index da15b33..4dfccdd8 100644
--- a/content/browser/prerender/prerender_browsertest.cc
+++ b/content/browser/prerender/prerender_browsertest.cc
@@ -146,13 +146,17 @@
   }
   ~PrerenderBrowserTest() override = default;
 
+  void SetUp() override {
+    prerender_helper_->SetUp(&ssl_server_);
+    ContentBrowserTest::SetUp();
+  }
+
   void SetUpOnMainThread() override {
     DCHECK_CURRENTLY_ON(BrowserThread::UI);
     host_resolver()->AddRule("*", "127.0.0.1");
     ssl_server_.AddDefaultHandlers(GetTestDataFilePath());
     ssl_server_.SetSSLConfig(
         net::test_server::EmbeddedTestServer::CERT_TEST_NAMES);
-    prerender_helper_->SetUpOnMainThread(&ssl_server_);
     ASSERT_TRUE(ssl_server_.Start());
   }
 
diff --git a/content/browser/prerender/prerender_host.h b/content/browser/prerender/prerender_host.h
index 4f95cec..b239617 100644
--- a/content/browser/prerender/prerender_host.h
+++ b/content/browser/prerender/prerender_host.h
@@ -78,7 +78,8 @@
     kStop = 24,
     kSslCertificateError = 25,
     kLoginAuthRequested = 26,
-    kMaxValue = kLoginAuthRequested,
+    kUaChangeRequiresReload = 27,
+    kMaxValue = kUaChangeRequiresReload,
   };
 
   PrerenderHost(blink::mojom::PrerenderAttributesPtr attributes,
diff --git a/content/browser/renderer_host/document_service_base_browsertest.cc b/content/browser/renderer_host/document_service_base_browsertest.cc
new file mode 100644
index 0000000..e518e22
--- /dev/null
+++ b/content/browser/renderer_host/document_service_base_browsertest.cc
@@ -0,0 +1,151 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "content/browser/renderer_host/document_service_base_echo_impl.h"
+#include "content/public/browser/document_service_base.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_features.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/prerender_test_util.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/browser/shell.h"
+#include "content/test/content_browser_test_utils_internal.h"
+#include "content/test/echo.test-mojom.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace content {
+
+class DocumentServiceBaseBrowserTest : public ContentBrowserTest {
+ public:
+  DocumentServiceBaseBrowserTest() = default;
+  ~DocumentServiceBaseBrowserTest() override = default;
+
+  void SetUpOnMainThread() override {
+    host_resolver()->AddRule("*", "127.0.0.1");
+    ASSERT_TRUE(test_server_handle_ =
+                    embedded_test_server()->StartAndReturnHandle());
+  }
+
+  WebContents* web_contents() const { return shell()->web_contents(); }
+
+ private:
+  net::test_server::EmbeddedTestServerHandle test_server_handle_;
+};
+
+class DocumentServiceBasePrerenderingBrowserTest
+    : public DocumentServiceBaseBrowserTest {
+ public:
+  DocumentServiceBasePrerenderingBrowserTest()
+      : prerender_helper_(base::BindRepeating(
+            &DocumentServiceBasePrerenderingBrowserTest::web_contents,
+            base::Unretained(this))) {}
+  ~DocumentServiceBasePrerenderingBrowserTest() override = default;
+
+  void SetUpOnMainThread() override {
+    prerender_helper_.SetUpOnMainThread(embedded_test_server());
+    DocumentServiceBaseBrowserTest::SetUpOnMainThread();
+  }
+
+  test::PrerenderTestHelper* prerender_helper() { return &prerender_helper_; }
+
+ private:
+  test::PrerenderTestHelper prerender_helper_;
+};
+
+// Tests that DocumentServiceBase is not destroyed on prerendering activation.
+IN_PROC_BROWSER_TEST_F(DocumentServiceBasePrerenderingBrowserTest,
+                       NotClosedInPrerenderingActivation) {
+  const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html");
+  const GURL kPrerenderingUrl = embedded_test_server()->GetURL("/title1.html");
+
+  // Navigate to an initial page.
+  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
+
+  int host_id = prerender_helper()->AddPrerender(kPrerenderingUrl);
+  RenderFrameHost* prerendered_frame_host =
+      prerender_helper()->GetPrerenderedMainFrameHost(host_id);
+  // We should disable proactive BrowsingInstance swap for the navigation below
+  // to ensure that the speculative RFH is going to use the same
+  // BrowsingInstance as the original RFH and it's not replaced on navigation.
+  DisableProactiveBrowsingInstanceSwapFor(prerendered_frame_host);
+
+  mojo::Remote<mojom::Echo> echo_remote;
+  bool echo_deleted = false;
+  new DocumentServiceBaseEchoImpl(
+      prerendered_frame_host, echo_remote.BindNewPipeAndPassReceiver(),
+      base::BindOnce([](bool* deleted) { *deleted = true; }, &echo_deleted));
+
+  // Activate the prerendered page.
+  prerender_helper()->NavigatePrimaryPage(kPrerenderingUrl);
+  // DocumentServiceBase should not be destroyed.
+  EXPECT_FALSE(echo_deleted);
+
+  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
+  // It should be destroyed on navigation.
+  EXPECT_TRUE(echo_deleted);
+}
+
+class DocumentServiceBaseBFCacheBrowserTest
+    : public DocumentServiceBaseBrowserTest {
+ public:
+  DocumentServiceBaseBFCacheBrowserTest() {
+    feature_list_.InitWithFeaturesAndParameters(
+        {{features::kBackForwardCache,
+          {{"TimeToLiveInBackForwardCacheInSeconds", "3600"},
+           {"enable_same_site", "true"}}}},
+        {features::kBackForwardCacheMemoryControls});
+  }
+  ~DocumentServiceBaseBFCacheBrowserTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(DocumentServiceBaseBFCacheBrowserTest,
+                       DocumentServiceBase) {
+  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
+
+  // 1) Navigate to A.
+  ASSERT_TRUE(NavigateToURL(shell(), url_a));
+  RenderFrameHost* rfh_a =
+      web_contents()->GetMainFrame();  // current_frame_host();
+  RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
+
+  mojo::Remote<mojom::Echo> echo_remote;
+  bool echo_deleted = false;
+  new DocumentServiceBaseEchoImpl(
+      rfh_a, echo_remote.BindNewPipeAndPassReceiver(),
+      base::BindOnce([](bool* deleted) { *deleted = true; }, &echo_deleted));
+
+  // 2) Navigate to B.
+  ASSERT_TRUE(NavigateToURL(shell(), url_b));
+
+  // - Page A should be in the cache.
+  ASSERT_FALSE(delete_observer_rfh_a.deleted());
+  EXPECT_EQ(rfh_a->GetLifecycleState(),
+            RenderFrameHost::LifecycleState::kInBackForwardCache);
+  EXPECT_FALSE(echo_deleted);
+
+  // 3) Go back.
+  web_contents()->GetController().GoBack();
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+  EXPECT_FALSE(echo_deleted);
+
+  // 4) Prevent caching and navigate to B.
+  DisableBFCacheForRFHForTesting(rfh_a);
+  ASSERT_TRUE(NavigateToURL(shell(), url_b));
+  delete_observer_rfh_a.WaitUntilDeleted();
+  EXPECT_TRUE(echo_deleted);
+}
+
+}  // namespace content
diff --git a/content/browser/renderer_host/document_service_base_echo_impl.cc b/content/browser/renderer_host/document_service_base_echo_impl.cc
new file mode 100644
index 0000000..cc3fcf1
--- /dev/null
+++ b/content/browser/renderer_host/document_service_base_echo_impl.cc
@@ -0,0 +1,25 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/renderer_host/document_service_base_echo_impl.h"
+
+namespace content {
+
+DocumentServiceBaseEchoImpl::DocumentServiceBaseEchoImpl(
+    RenderFrameHost* render_frame_host,
+    mojo::PendingReceiver<mojom::Echo> receiver,
+    base::OnceClosure destruction_cb)
+    : DocumentServiceBase(render_frame_host, std::move(receiver)),
+      destruction_cb_(std::move(destruction_cb)) {}
+
+DocumentServiceBaseEchoImpl::~DocumentServiceBaseEchoImpl() {
+  std::move(destruction_cb_).Run();
+}
+
+void DocumentServiceBaseEchoImpl::EchoString(const std::string& input,
+                                             EchoStringCallback callback) {
+  std::move(callback).Run(input);
+}
+
+}  // namespace content
diff --git a/content/browser/renderer_host/document_service_base_echo_impl.h b/content/browser/renderer_host/document_service_base_echo_impl.h
new file mode 100644
index 0000000..e469d79
--- /dev/null
+++ b/content/browser/renderer_host/document_service_base_echo_impl.h
@@ -0,0 +1,35 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_DOCUMENT_SERVICE_BASE_ECHO_IMPL_H_
+#define CONTENT_BROWSER_RENDERER_HOST_DOCUMENT_SERVICE_BASE_ECHO_IMPL_H_
+
+#include "base/bind.h"
+#include "content/public/browser/document_service_base.h"
+#include "content/test/echo.test-mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+
+namespace content {
+
+class RenderFrameHost;
+
+// Subclass of DocumentServiceBase for test.
+class DocumentServiceBaseEchoImpl final
+    : public DocumentServiceBase<mojom::Echo> {
+ public:
+  DocumentServiceBaseEchoImpl(RenderFrameHost* render_frame_host,
+                              mojo::PendingReceiver<mojom::Echo> receiver,
+                              base::OnceClosure destruction_cb);
+  ~DocumentServiceBaseEchoImpl() final;
+
+  // mojom::Echo implementation
+  void EchoString(const std::string& input, EchoStringCallback callback) final;
+
+ private:
+  base::OnceClosure destruction_cb_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_RENDERER_HOST_DOCUMENT_SERVICE_BASE_ECHO_IMPL_H_
diff --git a/content/browser/renderer_host/document_service_base_unittest.cc b/content/browser/renderer_host/document_service_base_unittest.cc
index 5489a67..db8755d4 100644
--- a/content/browser/renderer_host/document_service_base_unittest.cc
+++ b/content/browser/renderer_host/document_service_base_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/bind.h"
 #include "base/run_loop.h"
+#include "content/browser/renderer_host/document_service_base_echo_impl.h"
 #include "content/public/browser/back_forward_cache.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
@@ -13,7 +14,6 @@
 #include "content/public/test/test_renderer_host.h"
 #include "content/test/echo.test-mojom.h"
 #include "content/test/test_render_frame_host.h"
-#include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "url/gurl.h"
 
@@ -26,25 +26,6 @@
 const char kFooOrigin[] = "https://foo.com";
 const char kBarOrigin[] = "https://bar.com";
 
-// Subclass of DocumentServiceBase for test.
-class EchoImpl final : public DocumentServiceBase<mojom::Echo> {
- public:
-  EchoImpl(RenderFrameHost* render_frame_host,
-           mojo::PendingReceiver<mojom::Echo> receiver,
-           base::OnceClosure destruction_cb)
-      : DocumentServiceBase(render_frame_host, std::move(receiver)),
-        destruction_cb_(std::move(destruction_cb)) {}
-  ~EchoImpl() final { std::move(destruction_cb_).Run(); }
-
-  // mojom::Echo implementation
-  void EchoString(const std::string& input, EchoStringCallback callback) final {
-    std::move(callback).Run(input);
-  }
-
- private:
-  base::OnceClosure destruction_cb_;
-};
-
 // Help functions to manipulate RenderFrameHosts.
 
 // Simulates navigation and returns the final RenderFrameHost.
@@ -82,9 +63,10 @@
 
   void CreateEchoImpl(RenderFrameHost* rfh) {
     DCHECK(!is_echo_impl_alive_);
-    new EchoImpl(rfh, echo_remote_.BindNewPipeAndPassReceiver(),
-                 base::BindOnce(&DocumentServiceBaseTest::OnEchoImplDestructed,
-                                base::Unretained(this)));
+    new DocumentServiceBaseEchoImpl(
+        rfh, echo_remote_.BindNewPipeAndPassReceiver(),
+        base::BindOnce(&DocumentServiceBaseTest::OnEchoImplDestructed,
+                       base::Unretained(this)));
     is_echo_impl_alive_ = true;
   }
 
diff --git a/content/browser/renderer_host/navigation_request.h b/content/browser/renderer_host/navigation_request.h
index 97a112e9..762dd0d 100644
--- a/content/browser/renderer_host/navigation_request.h
+++ b/content/browser/renderer_host/navigation_request.h
@@ -872,7 +872,7 @@
 
   // Whether this navigation is activating an existing page (e.g. served from
   // the BackForwardCache or Prerender)
-  bool IsPageActivation() const;
+  bool IsPageActivation() const override;
 
   // See comments for |prerender_navigation_entry_|.
   void SetPrerenderNavigationEntry(
diff --git a/content/browser/renderer_host/page_impl_browsertest.cc b/content/browser/renderer_host/page_impl_browsertest.cc
index 5504711f..f411575 100644
--- a/content/browser/renderer_host/page_impl_browsertest.cc
+++ b/content/browser/renderer_host/page_impl_browsertest.cc
@@ -112,9 +112,9 @@
             base::BindRepeating(&PageImplPrerenderBrowserTest::GetWebContents,
                                 base::Unretained(this))) {}
 
-  void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
-    PageImplTest::SetUpOnMainThread();
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    PageImplTest::SetUp();
   }
 
   content::test::PrerenderTestHelper& prerender_test_helper() {
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index ea10500..847d9702 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -3289,6 +3289,7 @@
 void RenderFrameHostImpl::OnAudibleStateChanged(bool is_audible) {
   DCHECK_NE(is_audible_, is_audible);
   if (is_audible) {
+    DCHECK_NE(lifecycle_state(), LifecycleStateImpl::kPrerendering);
     GetProcess()->OnMediaStreamAdded();
   } else {
     GetProcess()->OnMediaStreamRemoved();
@@ -9158,10 +9159,19 @@
 
 void RenderFrameHostImpl::BindRestrictedCookieManager(
     mojo::PendingReceiver<network::mojom::RestrictedCookieManager> receiver) {
+  BindRestrictedCookieManagerWithOrigin(std::move(receiver),
+                                        GetIsolationInfoForSubresources(),
+                                        GetLastCommittedOrigin());
+}
+
+void RenderFrameHostImpl::BindRestrictedCookieManagerWithOrigin(
+    mojo::PendingReceiver<network::mojom::RestrictedCookieManager> receiver,
+    const net::IsolationInfo& isolation_info,
+    const url::Origin& origin) {
   static_cast<StoragePartitionImpl*>(GetProcess()->GetStoragePartition())
       ->CreateRestrictedCookieManager(
-          network::mojom::RestrictedCookieManagerRole::SCRIPT,
-          GetLastCommittedOrigin(), GetIsolationInfoForSubresources(),
+          network::mojom::RestrictedCookieManagerRole::SCRIPT, origin,
+          isolation_info,
           /* is_service_worker = */ false, GetProcess()->GetID(), routing_id(),
           std::move(receiver), CreateCookieAccessObserver());
 }
@@ -10399,12 +10409,31 @@
   DCHECK_EQ(net::OK, navigation_request->GetNetErrorCode());
   IncreaseCommitNavigationCounter();
   mojo::PendingRemote<blink::mojom::CodeCacheHost> code_cache_host;
+  mojom::CookieManagerInfoPtr cookie_manager_info;
   if (base::FeatureList::IsEnabled(
           features::kNavigationThreadingOptimizations)) {
     CreateCodeCacheHostWithIsolationKey(
         code_cache_host.InitWithNewPipeAndPassReceiver(),
         navigation_request->isolation_info_for_subresources()
             .network_isolation_key());
+
+    url::Origin origin_to_commit = navigation_request->GetOriginToCommit();
+    // Make sure the origin of the isolation info and origin to commit match,
+    // otherwise the cookie manager will crash. Sending the cookie manager here
+    // is just an optimization, so it is fine for it to be null in the case
+    // where these don't match.
+    if (common_params->url.SchemeIsHTTPOrHTTPS() &&
+        !origin_to_commit.opaque() &&
+        navigation_request->isolation_info_for_subresources()
+                .frame_origin()
+                .value() == origin_to_commit) {
+      cookie_manager_info = mojom::CookieManagerInfo::New();
+      cookie_manager_info->origin = origin_to_commit;
+      BindRestrictedCookieManagerWithOrigin(
+          cookie_manager_info->cookie_manager.InitWithNewPipeAndPassReceiver(),
+          navigation_request->isolation_info_for_subresources(),
+          origin_to_commit);
+    }
   }
   navigation_client->CommitNavigation(
       std::move(common_params), std::move(commit_params),
@@ -10414,6 +10443,7 @@
       std::move(controller), std::move(container_info),
       std::move(prefetch_loader_factory), devtools_navigation_token,
       std::move(policy_container), std::move(code_cache_host),
+      std::move(cookie_manager_info),
       BuildCommitNavigationCallback(navigation_request));
 }
 
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index 706dc67..e4140b9d 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -1686,6 +1686,10 @@
 
   void BindRestrictedCookieManager(
       mojo::PendingReceiver<network::mojom::RestrictedCookieManager> receiver);
+  void BindRestrictedCookieManagerWithOrigin(
+      mojo::PendingReceiver<network::mojom::RestrictedCookieManager> receiver,
+      const net::IsolationInfo& isolation_info,
+      const url::Origin& origin);
 
   // Requires the following preconditions, reporting a bad message otherwise.
   //
diff --git a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
index 9e582a2..23a3d514 100644
--- a/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
+++ b/content/browser/renderer_host/render_frame_host_impl_browsertest.cc
@@ -5397,6 +5397,39 @@
                    ->IsProcessShutdownDelayedForTesting());
 }
 
+// Test that multiple subframe-shutdown delays from the same source can be in
+// effect, and that cancelling one delay does not cancel the others.
+IN_PROC_BROWSER_TEST_F(RenderFrameHostImplSubframeReuseBrowserTest,
+                       MultipleDelays) {
+  // This test exercises a scenario that's only possible with
+  // --site-per-process.
+  if (!AreAllSitesIsolatedForTesting())
+    return;
+
+  // Create a test RenderProcessHostImpl.
+  ASSERT_TRUE(NavigateToURL(shell(),
+                            embedded_test_server()->GetURL(
+                                "a.com", "/cross_site_iframe_factory.html?a")));
+  RenderFrameHostImpl* rfh = root_frame_host();
+  RenderProcessHostImpl* process =
+      static_cast<RenderProcessHostImpl*>(rfh->GetProcess());
+  EXPECT_FALSE(process->IsProcessShutdownDelayedForTesting());
+
+  // Delay process shutdown twice from the same site info.
+  const SiteInfo site_info = rfh->GetSiteInstance()->GetSiteInfo();
+  const base::TimeDelta delay = base::TimeDelta::FromSeconds(5);
+  process->DelayProcessShutdown(delay, base::TimeDelta(), site_info);
+  EXPECT_TRUE(process->IsProcessShutdownDelayedForTesting());
+  process->DelayProcessShutdown(delay, base::TimeDelta(), site_info);
+  EXPECT_TRUE(process->IsProcessShutdownDelayedForTesting());
+
+  // When one delay is cancelled, the other should remain in effect.
+  process->CancelProcessShutdownDelay(site_info);
+  EXPECT_TRUE(process->IsProcessShutdownDelayedForTesting());
+  process->CancelProcessShutdownDelay(site_info);
+  EXPECT_FALSE(process->IsProcessShutdownDelayedForTesting());
+}
+
 // Tests that RenderFrameHost::ForEachRenderFrameHost visits the correct frames
 // in the correct order.
 IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest, ForEachRenderFrameHost) {
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index c0fd418..2f0cb9d 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -995,6 +995,29 @@
     return output;
   }
 
+  // Returns true if |site_info| is present in |map_| and has
+  // |render_process_host_id| in its map of processes that it is hosted by.
+  bool Contains(const SiteInfo& site_info, int render_process_host_id) {
+    auto site_info_found = map_.find(site_info);
+    if (site_info_found == map_.end())
+      return false;
+    auto counts_per_process = site_info_found->second;
+    return counts_per_process.find(render_process_host_id) !=
+           counts_per_process.end();
+  }
+
+  // Returns true if |render_process_host_id| is present for any site in |map_|.
+  bool ContainsHost(int render_process_host_id) {
+    for (auto iter : map_) {
+      auto counts_per_process = iter.second;
+      if (counts_per_process.find(render_process_host_id) !=
+          counts_per_process.end()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
  private:
   using ProcessID = int;
   using Count = int;
@@ -2289,16 +2312,16 @@
   if (IsKeepAliveRefCountDisabled() || deleting_soon_ || fast_shutdown_started_)
     return;
 
-  is_shutdown_delayed_ = true;
   IncrementKeepAliveRefCount();
 
   // Add to the delayed-shutdown tracker with the site that triggered the delay.
   if (base::FeatureList::IsEnabled(features::kSubframeShutdownDelay) &&
       ShouldTrackProcessForSite(GetBrowserContext(), this, site_info)) {
-    SiteProcessCountTracker* tracker = SiteProcessCountTracker::GetInstance(
-        GetBrowserContext(),
-        content::kDelayedShutdownSiteProcessCountTrackerKey);
-    tracker->IncrementSiteProcessCount(site_info, GetID());
+    SiteProcessCountTracker* delayed_shutdown_tracker =
+        SiteProcessCountTracker::GetInstance(
+            GetBrowserContext(),
+            content::kDelayedShutdownSiteProcessCountTrackerKey);
+    delayed_shutdown_tracker->IncrementSiteProcessCount(site_info, GetID());
   }
 
   // Don't delay shutdown longer than the maximum delay for renderer process,
@@ -2313,6 +2336,14 @@
   time_spent_running_unload_handlers_ = unload_handler_timeout;
 }
 
+bool RenderProcessHostImpl::IsProcessShutdownDelayedForTesting() {
+  SiteProcessCountTracker* delayed_shutdown_tracker =
+      SiteProcessCountTracker::GetInstance(
+          GetBrowserContext(),
+          content::kDelayedShutdownSiteProcessCountTrackerKey);
+  return delayed_shutdown_tracker->ContainsHost(GetID());
+}
+
 // static
 void RenderProcessHostImpl::AddAllowedRequestInitiatorForPlugin(
     int process_id,
@@ -5173,31 +5204,30 @@
   if (IsKeepAliveRefCountDisabled())
     return;
 
-  // Unset |is_shutdown_delayed| and remove from the delayed-shutdown tracker.
-  // This may have already been done in CancelAllProcessShutdownDelays() if the
-  // process was reused before this task executed.
-  if (is_shutdown_delayed_) {
-    if (base::FeatureList::IsEnabled(features::kSubframeShutdownDelay) &&
-        ShouldTrackProcessForSite(GetBrowserContext(), this, site_info)) {
-      SiteProcessCountTracker* tracker = SiteProcessCountTracker::GetInstance(
-          GetBrowserContext(),
-          content::kDelayedShutdownSiteProcessCountTrackerKey);
-      tracker->DecrementSiteProcessCount(site_info, GetID());
-    }
-    is_shutdown_delayed_ = false;
+  // Remove from the delayed-shutdown tracker. This may have already been done
+  // in CancelAllProcessShutdownDelays() if the process was reused before this
+  // task executed.
+  if (base::FeatureList::IsEnabled(features::kSubframeShutdownDelay) &&
+      ShouldTrackProcessForSite(GetBrowserContext(), this, site_info)) {
+    SiteProcessCountTracker* delayed_shutdown_tracker =
+        SiteProcessCountTracker::GetInstance(
+            GetBrowserContext(),
+            content::kDelayedShutdownSiteProcessCountTrackerKey);
+    if (delayed_shutdown_tracker->Contains(site_info, GetID()))
+      delayed_shutdown_tracker->DecrementSiteProcessCount(site_info, GetID());
   }
+
   DecrementKeepAliveRefCount();
 }
 
 void RenderProcessHostImpl::CancelAllProcessShutdownDelays() {
-  if (!(base::FeatureList::IsEnabled(features::kSubframeShutdownDelay) &&
-        is_shutdown_delayed_)) {
+  if (!base::FeatureList::IsEnabled(features::kSubframeShutdownDelay))
     return;
-  }
-  SiteProcessCountTracker* tracker = SiteProcessCountTracker::GetInstance(
-      GetBrowserContext(), content::kDelayedShutdownSiteProcessCountTrackerKey);
-  tracker->ClearProcessForAllSites(GetID());
-  is_shutdown_delayed_ = false;
+  SiteProcessCountTracker* delayed_shutdown_tracker =
+      SiteProcessCountTracker::GetInstance(
+          GetBrowserContext(),
+          content::kDelayedShutdownSiteProcessCountTrackerKey);
+  delayed_shutdown_tracker->ClearProcessForAllSites(GetID());
 }
 
 void RenderProcessHostImpl::BindTracedProcess(
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index 41ea8699..d3d607a 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -542,7 +542,7 @@
   void DelayProcessShutdown(const base::TimeDelta& subframe_shutdown_timeout,
                             const base::TimeDelta& unload_handler_timeout,
                             const SiteInfo& site_info);
-  bool IsProcessShutdownDelayedForTesting() { return is_shutdown_delayed_; }
+  bool IsProcessShutdownDelayedForTesting();
   // Remove the host from the delayed-shutdown tracker, if present. This does
   // not decrement |keep_alive_ref_count_|; if it was incremented by a shutdown
   // delay, it will be decremented when the delay expires. This ensures that
@@ -723,6 +723,7 @@
 
  private:
   friend class ChildProcessLauncherBrowserTest_ChildSpawnFail_Test;
+  friend class RenderFrameHostImplSubframeReuseBrowserTest_MultipleDelays_Test;
   friend class VisitRelayingRenderProcessHost;
   friend class StoragePartitonInterceptor;
   friend class RenderProcessHostTest;
@@ -1144,10 +1145,6 @@
   // delayed due to unload handlers.
   base::TimeDelta time_spent_running_unload_handlers_;
 
-  // If true, this RenderProcessHost's shutdown has been delayed by
-  // DelayProcessShutdown().
-  bool is_shutdown_delayed_ = false;
-
   // If the RenderProcessHost is being shutdown via Shutdown(), this records the
   // exit code.
   int shutdown_exit_code_;
diff --git a/content/browser/screen_orientation/screen_orientation_browsertest.cc b/content/browser/screen_orientation/screen_orientation_browsertest.cc
index 60afea7..91cfc6d 100644
--- a/content/browser/screen_orientation/screen_orientation_browsertest.cc
+++ b/content/browser/screen_orientation/screen_orientation_browsertest.cc
@@ -436,9 +436,9 @@
   }
 
   // ScreenOrientationBrowserTest:
-  void SetUpOnMainThread() override {
-    prerender_helper_.SetUpOnMainThread(embedded_test_server());
-    ScreenOrientationBrowserTest::SetUpOnMainThread();
+  void SetUp() override {
+    prerender_helper_.SetUp(embedded_test_server());
+    ScreenOrientationBrowserTest::SetUp();
   }
 
   content::WebContents* web_contents() { return shell()->web_contents(); }
diff --git a/content/browser/service_sandbox_type.h b/content/browser/service_sandbox_type.h
index cce460a..4b58315d 100644
--- a/content/browser/service_sandbox_type.h
+++ b/content/browser/service_sandbox_type.h
@@ -31,22 +31,6 @@
              : sandbox::policy::SandboxType::kNoSandbox;
 }
 
-// device::mojom::XRDeviceService
-namespace device {
-namespace mojom {
-class XRDeviceService;
-}
-}  // namespace device
-template <>
-inline sandbox::policy::SandboxType
-content::GetServiceSandboxType<device::mojom::XRDeviceService>() {
-#if defined(OS_WIN)
-  return sandbox::policy::SandboxType::kXrCompositing;
-#else
-  return sandbox::policy::SandboxType::kUtility;
-#endif  // !OS_WIN
-}
-
 // media::mojom::CdmServiceBroker
 namespace media {
 namespace mojom {
diff --git a/content/browser/service_worker/service_worker_test_utils.cc b/content/browser/service_worker/service_worker_test_utils.cc
index bc80e65e..231925d 100644
--- a/content/browser/service_worker/service_worker_test_utils.cc
+++ b/content/browser/service_worker/service_worker_test_utils.cc
@@ -87,6 +87,7 @@
       const base::UnguessableToken& devtools_navigation_token,
       blink::mojom::PolicyContainerPtr policy_container,
       mojo::PendingRemote<blink::mojom::CodeCacheHost> code_cache_host,
+      mojom::CookieManagerInfoPtr cookie_manager_info,
       CommitNavigationCallback callback) override {
     std::move(on_received_callback_).Run(std::move(container_info));
     std::move(callback).Run(MinimalDidCommitNavigationLoadParams(), nullptr);
@@ -247,7 +248,7 @@
       mojo::ScopedDataPipeConsumerHandle(), nullptr, nullptr, absl::nullopt,
       nullptr, std::move(info), mojo::NullRemote(),
       base::UnguessableToken::Create(), CreateStubPolicyContainer(),
-      mojo::NullRemote(),
+      mojo::NullRemote(), nullptr,
       base::BindOnce(
           [](mojom::DidCommitProvisionalLoadParamsPtr validated_params,
              mojom::DidCommitProvisionalLoadInterfaceParamsPtr
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 05ed8e6..163f438 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -1149,6 +1149,14 @@
   return false;
 }
 
+std::string WebContentsImpl::GetTitleForMediaControls() {
+  OPTIONAL_TRACE_EVENT0("content", "WebContentsImpl::GetTitleForMediaControls");
+
+  if (!delegate_)
+    return std::string();
+  return delegate_->GetTitleForMediaControls(this);
+}
+
 // Returns the NavigationController for the primary FrameTree, i.e. the one
 // whose URL is shown in the omnibox. With MPArch we can have multiple
 // FrameTrees in one WebContents and each has its own NavigationController.
@@ -1699,6 +1707,7 @@
   renderer_preferences_.user_agent_override = ua_override;
 
   // Send the new override string to all renderers in the current page.
+  // TODO(https://crbug.com/1235984): Sync for all FrameTree instance.
   SyncRendererPrefs();
 
   // Reload the page if a load is currently in progress to avoid having
@@ -1706,12 +1715,28 @@
   // No need to reload if the current entry matches that of the
   // NavigationRequest supplied to DidStartNavigation() as NavigationRequest
   // handles it.
-  NavigationEntry* entry = GetController().GetVisibleEntry();
-  if (IsLoading() && entry != nullptr && entry->GetIsOverridingUserAgent() &&
-      (!frame_tree_.root()->navigation_request() ||
-       frame_tree_.root()->navigation_request()->ua_change_requires_reload())) {
-    GetController().Reload(ReloadType::BYPASSING_CACHE, true);
-  }
+  ForEachFrameTree(base::BindRepeating([](FrameTree* frame_tree) {
+    if (!frame_tree->IsLoading())
+      return;
+    NavigationEntry* entry = frame_tree->controller().GetVisibleEntry();
+    if (!entry || !entry->GetIsOverridingUserAgent())
+      return;
+    if (frame_tree->root()->navigation_request() &&
+        !frame_tree->root()
+             ->navigation_request()
+             ->ua_change_requires_reload()) {
+      return;
+    }
+    if (frame_tree->is_prerendering()) {
+      // Just cancel if the FrameTree is for prerendering, as prerendered
+      // page may not allow another navigation including a reload, depending
+      // on conditions.
+      frame_tree->GetMainFrame()->CancelPrerendering(
+          PrerenderHost::FinalStatus::kUaChangeRequiresReload);
+    } else {
+      frame_tree->controller().Reload(ReloadType::BYPASSING_CACHE, true);
+    }
+  }));
 
   observers_.NotifyObservers(&WebContentsObserver::UserAgentOverrideSet,
                              ua_override);
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 5c0f27f..6560cf0 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -332,6 +332,11 @@
   void SetPrimaryMainFrameImportance(ChildProcessImportance importance);
 #endif
 
+  // Returns the human-readable name for title in Media Controls.
+  // If the returned value is an empty string, it means that there is no
+  // human-readable name.
+  std::string GetTitleForMediaControls();
+
   // WebContents ------------------------------------------------------
   WebContentsDelegate* GetDelegate() override;
   void SetDelegate(WebContentsDelegate* delegate) override;
diff --git a/content/browser/xr/service/xr_device_service.cc b/content/browser/xr/service/xr_device_service.cc
index 4b727e4..ee1501f 100644
--- a/content/browser/xr/service/xr_device_service.cc
+++ b/content/browser/xr/service/xr_device_service.cc
@@ -7,7 +7,6 @@
 #include "base/callback_helpers.h"
 #include "base/no_destructor.h"
 #include "build/build_config.h"
-#include "content/browser/service_sandbox_type.h"
 #include "content/public/browser/gpu_client.h"
 #include "content/public/browser/service_process_host.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index ed602f39..ad320f8 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -395,6 +395,7 @@
                kThrottleDisplayNoneAndVisibilityHiddenCrossOriginIframes},
           {"TrustedDOMTypes", features::kTrustedDOMTypes},
           {"UserAgentClientHint", blink::features::kUserAgentClientHint},
+          {"WebAppLaunchHandler", blink::features::kWebAppEnableLaunchHandler},
           {"WebAppLinkCapturing", blink::features::kWebAppEnableLinkCapturing},
           {"WebAppTabStrip", features::kDesktopPWAsTabStrip},
           {"WebAppWindowControlsOverlay",
diff --git a/content/common/navigation_client.mojom b/content/common/navigation_client.mojom
index 1c18f19..6dae31bd 100644
--- a/content/common/navigation_client.mojom
+++ b/content/common/navigation_client.mojom
@@ -10,6 +10,7 @@
 import "services/network/public/mojom/host_resolver.mojom";
 import "services/network/public/mojom/url_loader.mojom";
 import "services/network/public/mojom/url_loader_factory.mojom";
+import "services/network/public/mojom/restricted_cookie_manager.mojom";
 import "services/network/public/mojom/url_response_head.mojom";
 import "third_party/blink/public/mojom/commit_result/commit_result.mojom";
 import "third_party/blink/public/mojom/navigation/navigation_params.mojom";
@@ -181,6 +182,14 @@
   bool is_client_redirect = false;
 };
 
+struct CookieManagerInfo {
+  // The origin |cookie_manager| is associated with.
+  url.mojom.Origin origin;
+
+  // A cookie manager which can be used for |origin|.
+  pending_remote<network.mojom.RestrictedCookieManager> cookie_manager;
+};
+
 interface NavigationClient {
   // Tells the renderer that a navigation is ready to commit.
   //
@@ -217,6 +226,12 @@
   // cases, and thus shouldn't be trusted.
   // TODO(crbug.com/783506): Replace devtools navigation token with the generic
   // navigation token that can be passed from renderer to the browser.
+  //
+  // |code_cache_host| and |cookie_manager_info| are sent as optimizations so
+  // the renderer doesn't need to request them from the browser. These may be
+  // null if the NavigationThreadingOptimizations feature is disabled.
+  // |cookie_manager_info| may also be null for non HTTP/HTTPS navigations, or
+  // if the origin is opaque.
   CommitNavigation(
       blink.mojom.CommonNavigationParams common_params,
       blink.mojom.CommitNavigationParams request_params,
@@ -230,7 +245,8 @@
       pending_remote<network.mojom.URLLoaderFactory>? prefetch_loader_factory,
       mojo_base.mojom.UnguessableToken devtools_navigation_token,
       blink.mojom.PolicyContainer policy_container,
-      pending_remote<blink.mojom.CodeCacheHost>? code_cache_host)
+      pending_remote<blink.mojom.CodeCacheHost>? code_cache_host,
+      CookieManagerInfo? cookie_manager_info)
       => (DidCommitProvisionalLoadParams params,
           DidCommitProvisionalLoadInterfaceParams? interface_params);
 
diff --git a/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java b/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java
index f7bcb30c..3d4c02e 100644
--- a/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java
@@ -31,7 +31,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.base.BuildInfo;
 import org.chromium.base.Log;
 import org.chromium.base.PackageManagerUtils;
 import org.chromium.base.UserData;
@@ -1404,7 +1403,8 @@
     @CalledByNative
     /* package */ void onDragUpdate(@TouchSelectionDraggableType int type, float x, float y) {
         // If this is for longpress drag selector, we can only have mangifier on S and above.
-        if (type == TouchSelectionDraggableType.LONGPRESS && !BuildInfo.isAtLeastS()) {
+        if (type == TouchSelectionDraggableType.LONGPRESS
+                && Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
             return;
         }
 
diff --git a/content/public/browser/document_service_base.h b/content/public/browser/document_service_base.h
index a7d9457f..e92c412 100644
--- a/content/public/browser/document_service_base.h
+++ b/content/public/browser/document_service_base.h
@@ -20,9 +20,6 @@
 
 namespace content {
 
-class NavigationHandle;
-class RenderFrameHost;
-
 // Base class for mojo interface implementations tied to a document's lifetime.
 // The service will be destroyed when any of the following happens:
 // 1. mojo interface connection error happened,
@@ -86,11 +83,16 @@
 
     if (!navigation_handle->HasCommitted() ||
         navigation_handle->IsSameDocument() ||
-        navigation_handle->IsServedFromBackForwardCache()) {
+        navigation_handle->IsPageActivation()) {
       return;
     }
 
     if (navigation_handle->GetRenderFrameHost() == render_frame_host_) {
+      // DocumentServiceBase is destroyed either when RenderFrameHost is
+      // destroyed (covered by RenderFrameDeleted) or when a new document
+      // commits in the same RenderFrameHost (covered by DidFinishNavigation).
+      // Only committed non-same-document non-bfcache non-prerendering
+      // activation navigations replace a document in existing RenderFrameHost.
       DVLOG(1) << __func__ << ": Close connection on navigation.";
       Close();
     }
diff --git a/content/public/browser/navigation_handle.h b/content/public/browser/navigation_handle.h
index 85025f2..e8f89b0 100644
--- a/content/public/browser/navigation_handle.h
+++ b/content/public/browser/navigation_handle.h
@@ -224,6 +224,10 @@
   // Whether the navigation is restoring a page from back-forward cache.
   virtual bool IsServedFromBackForwardCache() = 0;
 
+  // Whether this navigation is activating an existing page (e.g. served from
+  // the BackForwardCache or Prerender)
+  virtual bool IsPageActivation() const = 0;
+
   // Navigation control flow --------------------------------------------------
 
   // The net error code if an error happened prior to commit. Otherwise it will
diff --git a/content/public/browser/page.h b/content/public/browser/page.h
index 4b8a056a..575b13e7 100644
--- a/content/public/browser/page.h
+++ b/content/public/browser/page.h
@@ -10,13 +10,10 @@
 #include "content/common/content_export.h"
 #include "content/public/browser/render_frame_host.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 #include "third_party/perfetto/include/perfetto/tracing/traced_value_forward.h"
 #include "url/gurl.h"
 
-namespace blink {
-struct Manifest;
-}  // namespace blink
-
 namespace content {
 
 // Page represents a collection of documents with the same main document.
@@ -68,7 +65,7 @@
   // frame document's manifest. The url will be empty if the document specifies
   // no manifest, and the manifest will be empty if any other failures occurred.
   using GetManifestCallback =
-      base::OnceCallback<void(const GURL&, const blink::Manifest&)>;
+      base::OnceCallback<void(const GURL&, blink::mojom::ManifestPtr)>;
 
   // Requests the manifest URL and the Manifest of the main frame's document.
   virtual void GetManifest(GetManifestCallback callback) = 0;
diff --git a/content/public/browser/web_contents_delegate.cc b/content/public/browser/web_contents_delegate.cc
index a5b63a55..4a57554 100644
--- a/content/public/browser/web_contents_delegate.cc
+++ b/content/public/browser/web_contents_delegate.cc
@@ -236,6 +236,11 @@
   return std::string();
 }
 
+std::string WebContentsDelegate::GetTitleForMediaControls(
+    WebContents* web_contents) {
+  return {};
+}
+
 #if defined(OS_ANDROID)
 bool WebContentsDelegate::ShouldBlockMediaRequest(const GURL& url) {
   return false;
diff --git a/content/public/browser/web_contents_delegate.h b/content/public/browser/web_contents_delegate.h
index 78d340f..a4909e6 100644
--- a/content/public/browser/web_contents_delegate.h
+++ b/content/public/browser/web_contents_delegate.h
@@ -549,6 +549,13 @@
       WebContents* web_contents,
       blink::mojom::MediaStreamType type);
 
+  // Returns the human-readable name for title in Media Controls.
+  // If the returned value is an empty string, it means that there is no
+  // human-readable name.
+  // For example, this returns an extension name for title instead of extension
+  // url.
+  virtual std::string GetTitleForMediaControls(WebContents* web_contents);
+
 #if defined(OS_ANDROID)
   // Returns true if the given media should be blocked to load.
   virtual bool ShouldBlockMediaRequest(const GURL& url);
diff --git a/content/public/test/mock_navigation_handle.h b/content/public/test/mock_navigation_handle.h
index 580a73c4..20b95c0 100644
--- a/content/public/test/mock_navigation_handle.h
+++ b/content/public/test/mock_navigation_handle.h
@@ -68,6 +68,11 @@
   bool IsServedFromBackForwardCache() override {
     return is_served_from_bfcache_;
   }
+  bool IsPageActivation() const override {
+    MockNavigationHandle* handle = const_cast<MockNavigationHandle*>(this);
+    return handle->IsPrerenderedPageActivation() ||
+           handle->IsServedFromBackForwardCache();
+  }
   RenderFrameHost* GetParentFrame() override {
     return render_frame_host_ ? render_frame_host_->GetParent() : nullptr;
   }
diff --git a/content/public/test/prerender_test_util.cc b/content/public/test/prerender_test_util.cc
index ad8ff7ca..57f37dca 100644
--- a/content/public/test/prerender_test_util.cc
+++ b/content/public/test/prerender_test_util.cc
@@ -227,14 +227,6 @@
   EXPECT_FALSE(http_server->Started());
   http_server->RegisterRequestMonitor(base::BindRepeating(
       &PrerenderTestHelper::MonitorResourceRequest, base::Unretained(this)));
-  has_set_up_ = true;
-}
-
-void PrerenderTestHelper::SetUpOnMainThread(
-    net::test_server::EmbeddedTestServer* http_server) {
-  EXPECT_TRUE(content::BrowserThread::CurrentlyOn(BrowserThread::UI));
-  if (!has_set_up_)
-    SetUp(http_server);
 }
 
 int PrerenderTestHelper::GetHostForUrl(const GURL& gurl) {
diff --git a/content/public/test/prerender_test_util.h b/content/public/test/prerender_test_util.h
index 0201bba..53195f9 100644
--- a/content/public/test/prerender_test_util.h
+++ b/content/public/test/prerender_test_util.h
@@ -85,11 +85,12 @@
   PrerenderTestHelper& operator=(const PrerenderTestHelper&) = delete;
 
   // This installs a network monitor on the http server. Be sure to call this
-  // before starting the server. It does not matter which of these you use (you
-  // can use either or both).
-  // TODO(crbug.com/1230090): we should migrate to SetUp.
+  // before starting the server. This is typically done from SetUp, but it is
+  // fine to call from SetUpOnMainThread if ordering constraints make that
+  // impossible (eg, if the test helper is created later to avoid problematic
+  // creation/destruction relative to other ScopedFeatureLists or if the fixture
+  // creates test server after SetUp).
   void SetUp(net::test_server::EmbeddedTestServer* http_server);
-  void SetUpOnMainThread(net::test_server::EmbeddedTestServer* http_server);
 
   // Attempts to lookup the host for the given |gurl|. Returns
   // RenderFrameHost::kNoFrameTreeNodeId upon failure.
@@ -155,7 +156,6 @@
   base::OnceClosure monitor_callback_ GUARDED_BY(lock_);
   base::Lock lock_;
   WebContents::Getter get_web_contents_fn_;
-  bool has_set_up_ = false;
 };
 
 }  // namespace test
diff --git a/content/renderer/navigation_client.cc b/content/renderer/navigation_client.cc
index 09424a98..907216b 100644
--- a/content/renderer/navigation_client.cc
+++ b/content/renderer/navigation_client.cc
@@ -35,6 +35,7 @@
     const base::UnguessableToken& devtools_navigation_token,
     blink::mojom::PolicyContainerPtr policy_container,
     mojo::PendingRemote<blink::mojom::CodeCacheHost> code_cache_host,
+    mojom::CookieManagerInfoPtr cookie_manager_info,
     CommitNavigationCallback callback) {
   // TODO(ahemery): The reset should be done when the navigation did commit
   // (meaning at a later stage). This is not currently possible because of
@@ -49,7 +50,7 @@
       std::move(controller_service_worker_info), std::move(container_info),
       std::move(prefetch_loader_factory), devtools_navigation_token,
       std::move(policy_container), std::move(code_cache_host),
-      std::move(callback));
+      std::move(cookie_manager_info), std::move(callback));
 }
 
 void NavigationClient::CommitFailedNavigation(
diff --git a/content/renderer/navigation_client.h b/content/renderer/navigation_client.h
index 5c17215..4269744c 100644
--- a/content/renderer/navigation_client.h
+++ b/content/renderer/navigation_client.h
@@ -36,6 +36,7 @@
       const base::UnguessableToken& devtools_navigation_token,
       blink::mojom::PolicyContainerPtr policy_container,
       mojo::PendingRemote<blink::mojom::CodeCacheHost> code_cache_host,
+      mojom::CookieManagerInfoPtr cookie_manager_info,
       CommitNavigationCallback callback) override;
   void CommitFailedNavigation(
       blink::mojom::CommonNavigationParamsPtr common_params,
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index e4aca46..dab9185e 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -125,6 +125,7 @@
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/not_implemented_url_loader_factory.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/public/mojom/restricted_cookie_manager.mojom.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "services/service_manager/public/mojom/interface_provider.mojom.h"
@@ -2630,6 +2631,7 @@
     const base::UnguessableToken& devtools_navigation_token,
     blink::mojom::PolicyContainerPtr policy_container,
     mojo::PendingRemote<blink::mojom::CodeCacheHost> code_cache_host,
+    mojom::CookieManagerInfoPtr cookie_manager_info,
     mojom::NavigationClient::CommitNavigationCallback commit_callback) {
   DCHECK(navigation_client_impl_);
   DCHECK(!blink::IsRendererDebugURL(common_params->url));
@@ -2673,7 +2675,7 @@
       std::move(subresource_loader_factories), std::move(subresource_overrides),
       std::move(controller_service_worker_info), std::move(container_info),
       std::move(prefetch_loader_factory), std::move(code_cache_host),
-      std::move(document_state));
+      std::move(cookie_manager_info), std::move(document_state));
 
   // Perform a "loadDataWithBaseURL" navigation. This is different from a normal
   // data: URL navigation in various ways:
@@ -2809,6 +2811,7 @@
     mojo::PendingRemote<network::mojom::URLLoaderFactory>
         prefetch_loader_factory,
     mojo::PendingRemote<blink::mojom::CodeCacheHost> code_cache_host,
+    mojom::CookieManagerInfoPtr cookie_manager_info,
     std::unique_ptr<DocumentState> document_state,
     std::unique_ptr<WebNavigationParams> navigation_params) {
   // Here, creator means either the parent frame or the window opener.
@@ -2928,6 +2931,7 @@
   DCHECK(!pending_loader_factories_);
   pending_loader_factories_ = std::move(new_loader_factories);
   pending_code_cache_host_ = std::move(code_cache_host);
+  pending_cookie_manager_info_ = std::move(cookie_manager_info);
 
   base::WeakPtr<RenderFrameImpl> weak_self = weak_factory_.GetWeakPtr();
   frame_->CommitNavigation(std::move(navigation_params),
@@ -2938,6 +2942,7 @@
 
   pending_loader_factories_ = nullptr;
   pending_code_cache_host_.reset();
+  pending_cookie_manager_info_.reset();
 }
 
 void RenderFrameImpl::CommitFailedNavigation(
@@ -3867,6 +3872,16 @@
   frame_->GetDocumentLoader()->SetCodeCacheHost(
       std::move(pending_code_cache_host_));
 
+  // TODO(crbug.com/888079): Turn this into a DCHECK for origin equality when
+  // the linked bug is fixed. Currently sometimes the browser and renderer
+  // disagree on the origin during commit navigation.
+  if (pending_cookie_manager_info_ &&
+      pending_cookie_manager_info_->origin ==
+          frame_->GetDocument().GetSecurityOrigin()) {
+    frame_->GetDocument().SetCookieManager(
+        std::move(pending_cookie_manager_info_->cookie_manager));
+  }
+
   DidCommitNavigationInternal(
       commit_type, transition, permissions_policy_header,
       document_policy_header,
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index 73ad48a..79a92f0c 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -441,6 +441,7 @@
       const base::UnguessableToken& devtools_navigation_token,
       blink::mojom::PolicyContainerPtr policy_container,
       mojo::PendingRemote<blink::mojom::CodeCacheHost> code_cache_host,
+      mojom::CookieManagerInfoPtr cookie_manager_info,
       mojom::NavigationClient::CommitNavigationCallback commit_callback);
   void CommitFailedNavigation(
       blink::mojom::CommonNavigationParamsPtr common_params,
@@ -948,6 +949,7 @@
       mojo::PendingRemote<network::mojom::URLLoaderFactory>
           prefetch_loader_factory,
       mojo::PendingRemote<blink::mojom::CodeCacheHost> code_cache_host,
+      mojom::CookieManagerInfoPtr cookie_manager_info,
       std::unique_ptr<DocumentState> document_state,
       std::unique_ptr<blink::WebNavigationParams> navigation_params);
 
@@ -1348,6 +1350,7 @@
   scoped_refptr<blink::ChildURLLoaderFactoryBundle> pending_loader_factories_;
 
   mojo::PendingRemote<blink::mojom::CodeCacheHost> pending_code_cache_host_;
+  mojom::CookieManagerInfoPtr pending_cookie_manager_info_;
 
   scoped_refptr<blink::WebFrameRequestBlocker> frame_request_blocker_;
 
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index b557efb..cd2ab4c 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -76,6 +76,8 @@
     "../browser/media/session/mock_media_session_service_impl.h",
     "../browser/presentation/presentation_test_utils.cc",
     "../browser/presentation/presentation_test_utils.h",
+    "../browser/renderer_host/document_service_base_echo_impl.cc",
+    "../browser/renderer_host/document_service_base_echo_impl.h",
     "../browser/renderer_host/input/mock_input_router.cc",
     "../browser/renderer_host/input/mock_input_router.h",
     "../browser/renderer_host/mock_render_widget_host.cc",
@@ -1199,6 +1201,7 @@
     "../browser/renderer_host/back_forward_cache_metrics_browsertest.cc",
     "../browser/renderer_host/blocked_scheme_navigation_browsertest.cc",
     "../browser/renderer_host/cookie_browsertest.cc",
+    "../browser/renderer_host/document_service_base_browsertest.cc",
     "../browser/renderer_host/embedding_token_browsertest.cc",
     "../browser/renderer_host/frame_tree_browsertest.cc",
     "../browser/renderer_host/input/autoscroll_browsertest.cc",
diff --git a/content/test/data/forms/form_controls_browsertest_checkbox_android.png b/content/test/data/forms/form_controls_browsertest_checkbox_android.png
index d819a9b..258b74f3 100644
--- a/content/test/data/forms/form_controls_browsertest_checkbox_android.png
+++ b/content/test/data/forms/form_controls_browsertest_checkbox_android.png
Binary files differ
diff --git a/content/test/data/forms/form_controls_browsertest_multi_select_android.png b/content/test/data/forms/form_controls_browsertest_multi_select_android.png
index c166623..edaf6cb 100644
--- a/content/test/data/forms/form_controls_browsertest_multi_select_android.png
+++ b/content/test/data/forms/form_controls_browsertest_multi_select_android.png
Binary files differ
diff --git a/content/test/data/forms/form_controls_browsertest_select_android.png b/content/test/data/forms/form_controls_browsertest_select_android.png
index 16c390a..0225ac4 100644
--- a/content/test/data/forms/form_controls_browsertest_select_android.png
+++ b/content/test/data/forms/form_controls_browsertest_select_android.png
Binary files differ
diff --git a/content/test/test_render_frame.cc b/content/test/test_render_frame.cc
index acc658f..43c8226 100644
--- a/content/test/test_render_frame.cc
+++ b/content/test/test_render_frame.cc
@@ -267,7 +267,7 @@
       blink::mojom::PolicyContainer::New(
           blink::mojom::PolicyContainerPolicies::New(),
           mock_policy_container_host.BindNewEndpointAndPassDedicatedRemote()),
-      mojo::NullRemote() /* code_cache_host */,
+      mojo::NullRemote() /* code_cache_host */, nullptr,
       base::BindOnce(&MockFrameHost::DidCommitProvisionalLoad,
                      base::Unretained(mock_frame_host_.get())));
 }
diff --git a/device/vr/public/mojom/BUILD.gn b/device/vr/public/mojom/BUILD.gn
index 8e8f893..d772679 100644
--- a/device/vr/public/mojom/BUILD.gn
+++ b/device/vr/public/mojom/BUILD.gn
@@ -66,6 +66,7 @@
     ":vr_service",
     "//gpu/ipc/common:interfaces",
     "//mojo/public/mojom/base",
+    "//sandbox/policy/mojom",
     "//skia/public/mojom",
     "//ui/gfx/geometry/mojom",
   ]
diff --git a/device/vr/public/mojom/isolated_xr_service.mojom b/device/vr/public/mojom/isolated_xr_service.mojom
index fa6c3fba..e4b7e19 100644
--- a/device/vr/public/mojom/isolated_xr_service.mojom
+++ b/device/vr/public/mojom/isolated_xr_service.mojom
@@ -9,6 +9,7 @@
 [EnableIf=is_win]
 import "gpu/ipc/common/luid.mojom";
 import "mojo/public/mojom/base/time.mojom";
+import "sandbox/policy/mojom/sandbox.mojom";
 import "services/viz/public/mojom/compositing/frame_sink_id.mojom";
 import "services/viz/public/mojom/gpu.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
@@ -195,8 +196,14 @@
   RequestDevices(pending_remote<IsolatedXRRuntimeProviderClient> client);
 };
 
+[EnableIf=is_win]
+const sandbox.mojom.Sandbox kXrSandbox = sandbox.mojom.Sandbox.kXrCompositing;
+[EnableIfNot=is_win]
+const sandbox.mojom.Sandbox kXrSandbox = sandbox.mojom.Sandbox.kUtility;
+
 // The main interface for the XR Device Service. Called from the browser
 // process.
+[ServiceSandbox=kXrSandbox]
 interface XRDeviceService {
   // Binds a IsolatedXRRuntimeProvider pipe in the service and passes along
   // the device service host.
diff --git a/extensions/browser/extension_host.cc b/extensions/browser/extension_host.cc
index 7e38155..3f788731 100644
--- a/extensions/browser/extension_host.cc
+++ b/extensions/browser/extension_host.cc
@@ -466,6 +466,11 @@
   delegate_->ExitPictureInPicture();
 }
 
+std::string ExtensionHost::GetTitleForMediaControls(
+    content::WebContents* web_contents) {
+  return extension() ? extension()->name() : std::string();
+}
+
 void ExtensionHost::RecordStopLoadingUMA() {
   CHECK(load_start_.get());
   if (extension_host_type_ == mojom::ViewType::kExtensionBackgroundPage) {
diff --git a/extensions/browser/extension_host.h b/extensions/browser/extension_host.h
index 30572655..e8b4ce11 100644
--- a/extensions/browser/extension_host.h
+++ b/extensions/browser/extension_host.h
@@ -136,6 +136,8 @@
       const viz::SurfaceId& surface_id,
       const gfx::Size& natural_size) override;
   void ExitPictureInPicture() override;
+  std::string GetTitleForMediaControls(
+      content::WebContents* web_contents) override;
 
   // ExtensionRegistryObserver:
   void OnExtensionReady(content::BrowserContext* browser_context,
diff --git a/gpu/ipc/service/dcomp_texture_win.cc b/gpu/ipc/service/dcomp_texture_win.cc
index 13421561..76ea29a 100644
--- a/gpu/ipc/service/dcomp_texture_win.cc
+++ b/gpu/ipc/service/dcomp_texture_win.cc
@@ -176,7 +176,7 @@
       this, mailbox, viz::BGRA_8888, GetSize(), gfx::ColorSpace::CreateSRGB(),
       kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType,
       /*usage=*/SHARED_IMAGE_USAGE_DISPLAY, params, attribs,
-      /*use_passthrough=*/false);
+      /*use_passthrough=*/true);
 
   channel_->shared_image_stub()->factory()->RegisterBacking(
       std::move(shared_image), /*allow_legacy_mailbox=*/false);
diff --git a/infra/config/generated/commit-queue.cfg b/infra/config/generated/commit-queue.cfg
index dd7bef8..806fa01 100644
--- a/infra/config/generated/commit-queue.cfg
+++ b/infra/config/generated/commit-queue.cfg
@@ -178,7 +178,7 @@
         includable_only: true
       }
       builders {
-        name: "chromium/try/android-11-x86-fyi-rel"
+        name: "chromium/try/android-11-x86-rel"
         includable_only: true
       }
       builders {
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index 7e1c12e0..bca3fa4 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -28832,6 +28832,105 @@
       }
     }
     builders {
+      name: "android-11-x86-rel"
+      swarming_host: "chromium-swarm.appspot.com"
+      swarming_tags: "vpython:native-python-wrapper"
+      dimensions: "builderless:1"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Ubuntu-18.04"
+      dimensions: "pool:luci.chromium.ci"
+      dimensions: "ssd:0"
+      exe {
+        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
+        cipd_version: "refs/heads/master"
+        cmd: "luciexe"
+      }
+      properties:
+        '{'
+        '  "$build/goma": {'
+        '    "enable_ats": true,'
+        '    "jobs": 150,'
+        '    "rpc_extra_params": "?prod",'
+        '    "server_host": "goma.chromium.org",'
+        '    "use_luci_auth": true'
+        '  },'
+        '  "$kitchen": {'
+        '    "devshell": true,'
+        '    "git_auth": true'
+        '  },'
+        '  "$recipe_engine/isolated": {'
+        '    "server": "https://isolateserver.appspot.com"'
+        '  },'
+        '  "$recipe_engine/resultdb/test_presentation": {'
+        '    "column_keys": [],'
+        '    "grouping_keys": ['
+        '      "status",'
+        '      "v.test_suite"'
+        '    ]'
+        '  },'
+        '  "builder_group": "chromium.android",'
+        '  "recipe": "chromium"'
+        '}'
+      execution_timeout_secs: 10800
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "chromium.resultdb.result_sink"
+        value: 100
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink.gtests_local"
+        value: 100
+      }
+      experiments {
+        key: "chromium.resultdb.result_sink.junit_tests"
+        value: 100
+      }
+      experiments {
+        key: "luci.use_realms"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "luci-resultdb"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://(chrome/test:|content/test:fuchsia_)telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "android-12-x64-fyi-rel"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
@@ -57478,7 +57577,7 @@
       }
     }
     builders {
-      name: "android-11-x86-fyi-rel"
+      name: "android-11-x86-rel"
       swarming_host: "chromium-swarm.appspot.com"
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "builderless:1"
@@ -80149,7 +80248,7 @@
         path: "win_toolchain"
       }
       build_numbers: YES
-      service_account: "chromium-mini-orchestrator@chops-service-accounts.iam.gserviceaccount.com"
+      service_account: "chromium-orchestrator@chops-service-accounts.iam.gserviceaccount.com"
       task_template_canary_percentage {
         value: 5
       }
diff --git a/infra/config/generated/luci-milo.cfg b/infra/config/generated/luci-milo.cfg
index 0e8b2d6930..78933dd 100644
--- a/infra/config/generated/luci-milo.cfg
+++ b/infra/config/generated/luci-milo.cfg
@@ -3090,6 +3090,11 @@
     short_name: "p-cov"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/android-11-x86-rel"
+    category: "builder_tester|x86"
+    short_name: "11"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/android-marshmallow-x86-rel"
     category: "builder_tester|x86"
     short_name: "M"
@@ -13404,7 +13409,7 @@
     name: "buildbucket/luci.chromium.try/android-10-arm64-rel"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/android-11-x86-fyi-rel"
+    name: "buildbucket/luci.chromium.try/android-11-x86-rel"
   }
   builders {
     name: "buildbucket/luci.chromium.try/android-12-x64-fyi-rel"
@@ -14452,7 +14457,7 @@
     name: "buildbucket/luci.chromium.try/android-10-arm64-rel"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/android-11-x86-fyi-rel"
+    name: "buildbucket/luci.chromium.try/android-11-x86-rel"
   }
   builders {
     name: "buildbucket/luci.chromium.try/android-12-x64-fyi-rel"
diff --git a/infra/config/generated/luci-scheduler.cfg b/infra/config/generated/luci-scheduler.cfg
index 6a54c3c..2002c1b 100644
--- a/infra/config/generated/luci-scheduler.cfg
+++ b/infra/config/generated/luci-scheduler.cfg
@@ -4247,13 +4247,13 @@
   }
 }
 job {
-  id: "android-11-x86-fyi-rel"
+  id: "android-11-x86-rel"
   realm: "ci"
   acl_sets: "ci"
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.chromium.ci"
-    builder: "android-11-x86-fyi-rel"
+    builder: "android-11-x86-rel"
   }
 }
 job {
@@ -4728,17 +4728,6 @@
   }
 }
 job {
-  id: "android-pie-x86-fyi-rel"
-  realm: "ci"
-  schedule: "triggered"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.chromium.ci"
-    builder: "android-pie-x86-fyi-rel"
-  }
-}
-job {
   id: "android-pie-x86-rel"
   realm: "ci"
   acl_sets: "ci"
@@ -7399,7 +7388,7 @@
   triggers: "Win10 FYI x64 SkiaRenderer Dawn Release (NVIDIA)"
   triggers: "Windows deterministic"
   triggers: "android-10-arm64-rel"
-  triggers: "android-11-x86-fyi-rel"
+  triggers: "android-11-x86-rel"
   triggers: "android-12-x64-fyi-rel"
   triggers: "android-angle-arm64-builder"
   triggers: "android-angle-chromium-arm64-builder"
@@ -7430,7 +7419,6 @@
   triggers: "android-pie-arm64-coverage-experimental-rel"
   triggers: "android-pie-arm64-rel"
   triggers: "android-pie-arm64-wpt-rel-non-cq"
-  triggers: "android-pie-x86-fyi-rel"
   triggers: "android-pie-x86-rel"
   triggers: "android-web-platform-pie-x86-fyi-rel"
   triggers: "android-weblayer-pie-x86-wpt-fyi-rel"
diff --git a/infra/config/generated/realms.cfg b/infra/config/generated/realms.cfg
index b5cd467..98ab324 100644
--- a/infra/config/generated/realms.cfg
+++ b/infra/config/generated/realms.cfg
@@ -249,7 +249,6 @@
   bindings {
     role: "role/buildbucket.builderServiceAccount"
     principals: "user:chromium-cipd-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-    principals: "user:chromium-mini-orchestrator@chops-service-accounts.iam.gserviceaccount.com"
     principals: "user:chromium-orchestrator@chops-service-accounts.iam.gserviceaccount.com"
     principals: "user:chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
     principals: "user:chromium-try-gpu-builder@chops-service-accounts.iam.gserviceaccount.com"
diff --git a/infra/config/subprojects/chromium/ci.star b/infra/config/subprojects/chromium/ci.star
index b57f5e6..6e347c6 100644
--- a/infra/config/subprojects/chromium/ci.star
+++ b/infra/config/subprojects/chromium/ci.star
@@ -981,6 +981,15 @@
 )
 
 ci.android_builder(
+    name = "android-11-x86-rel",
+    console_view_entry = consoles.console_view_entry(
+        category = "builder_tester|x86",
+        short_name = "11",
+    ),
+    os = os.LINUX_BIONIC_REMOVE,
+)
+
+ci.android_builder(
     name = "android-weblayer-marshmallow-x86-rel-tests",
     console_view_entry = consoles.console_view_entry(
         category = "tester|weblayer",
@@ -1143,7 +1152,8 @@
     ),
 )
 
-# TODO(hypan): remove this once there is no associated disabled tests
+# TODO(crbug.com/1022533#c40): Remove this builder once there are no associated
+# disabled tests.
 ci.android_fyi_builder(
     name = "android-pie-x86-fyi-rel",
     console_view_entry = consoles.console_view_entry(
@@ -1151,9 +1161,14 @@
         short_name = "rel",
     ),
     goma_jobs = goma.jobs.J150,
-    schedule = "triggered",  # triggered manually via Scheduler UI
+    # Set to an empty list to avoid chromium-gitiles-trigger triggering new
+    # builds. Also we don't set any `schedule` since this builder is for
+    # reference only and should not run any new builds.
+    triggered_by = [],
 )
 
+# TODO(crbug.com/1137474): Remove this builder once there are no associated
+# disabled tests.
 ci.android_fyi_builder(
     name = "android-11-x86-fyi-rel",
     console_view_entry = consoles.console_view_entry(
@@ -1161,6 +1176,10 @@
         short_name = "rel",
     ),
     os = os.LINUX_BIONIC_REMOVE,
+    # Set to an empty list to avoid chromium-gitiles-trigger triggering new
+    # builds. Also we don't set any `schedule` since this builder is for
+    # reference only and should not run any new builds.
+    triggered_by = [],
 )
 
 ci.android_fyi_builder(
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index eb7d0a6..e219cfa 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -289,7 +289,7 @@
 )
 
 try_.chromium_android_builder(
-    name = "android-11-x86-fyi-rel",
+    name = "android-11-x86-rel",
 )
 
 try_.chromium_android_builder(
@@ -1287,7 +1287,7 @@
     properties = {
         "compilator": "linux-rel-compilator",
     },
-    service_account = "chromium-mini-orchestrator@chops-service-accounts.iam.gserviceaccount.com",
+    service_account = "chromium-orchestrator@chops-service-accounts.iam.gserviceaccount.com",
     tryjob = try_.job(
         experiment_percentage = 100,
     ),
diff --git a/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/BUILD.gn b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/BUILD.gn
index 68fe604..f25624ad 100644
--- a/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/BUILD.gn
+++ b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/BUILD.gn
@@ -42,3 +42,22 @@
   sources = [ "consistency_layout_delegate.h" ]
   deps = [ "//ui/base" ]
 }
+
+source_set("eg2_tests") {
+  defines = [ "CHROME_EARL_GREY_2" ]
+  configs += [
+    "//build/config/compiler:enable_arc",
+    "//build/config/ios:xctest_config",
+  ]
+  testonly = true
+  sources = [ "consistency_promo_signin_coordinator_egtest.mm" ]
+  deps = [
+    "//base",
+    "//ios/chrome/browser/ui/authentication:eg_test_support+eg2",
+    "//ios/chrome/test/earl_grey:eg_test_support+eg2",
+    "//ios/testing/earl_grey:eg_test_support+eg2",
+    "//ios/third_party/earl_grey2:test_lib",
+    "//ui/base",
+  ]
+  frameworks = [ "UIKit.framework" ]
+}
diff --git a/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_view_controller.mm b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_view_controller.mm
index ce56505..7dfdd130 100644
--- a/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_view_controller.mm
+++ b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_default_account/consistency_default_account_view_controller.mm
@@ -101,12 +101,14 @@
   self.navigationItem.leftBarButtonItem = leftItem;
 
   // Set the skip button in the right bar button item.
-  UIBarButtonItem* anotherButton = [[UIBarButtonItem alloc]
+  UIBarButtonItem* skipButton = [[UIBarButtonItem alloc]
       initWithTitle:l10n_util::GetNSString(IDS_IOS_CONSISTENCY_PROMO_SKIP)
               style:UIBarButtonItemStylePlain
              target:self
              action:@selector(skipButtonAction:)];
-  self.navigationItem.rightBarButtonItem = anotherButton;
+  skipButton.accessibilityIdentifier =
+      kWebSigninSkipButtonAccessibilityIdentifier;
+  self.navigationItem.rightBarButtonItem = skipButton;
   // Replace the controller view by the scroll view.
   UIScrollView* scrollView = [[UIScrollView alloc] init];
   scrollView.translatesAutoresizingMaskIntoConstraints = NO;
diff --git a/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_promo_signin_coordinator_egtest.mm b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_promo_signin_coordinator_egtest.mm
new file mode 100644
index 0000000..6471ce8
--- /dev/null
+++ b/ios/chrome/browser/ui/authentication/signin/consistency_promo_signin/consistency_promo_signin_coordinator_egtest.mm
@@ -0,0 +1,41 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/authentication/signin_earl_grey.h"
+#import "ios/chrome/browser/ui/authentication/signin_earl_grey_app_interface.h"
+#import "ios/chrome/browser/ui/authentication/signin_earl_grey_ui.h"
+#import "ios/chrome/browser/ui/authentication/signin_matchers.h"
+#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
+#import "ios/chrome/test/earl_grey/chrome_test_case.h"
+#import "ios/testing/earl_grey/earl_grey_test.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+// ConsistencyPromoSigninCoordinator EarlGrey tests.
+// Note: Since those tests are not using real identities, it is not possible
+// to test when the user signs in using the web sign-in consistency dialog.
+// This limitation is related to cookies reason. The web sign-in consistency
+// dialog waits for the cookies to be set before closing. This doesn't work
+// with fake chrome identities.
+@interface ConsistencyPromoSigninCoordinatorTestCase : ChromeTestCase
+@end
+
+@implementation ConsistencyPromoSigninCoordinatorTestCase
+
+// Tests that ConsistencyPromoSigninCoordinator shows up, and then skips it.
+- (void)testDismissConsistencyPromoSignin {
+  FakeChromeIdentity* fakeIdentity = [SigninEarlGrey fakeIdentity1];
+  [SigninEarlGrey addFakeIdentity:fakeIdentity];
+  [SigninEarlGreyAppInterface triggerConsistencyPromoSigninDialog];
+  [SigninEarlGreyUI verifyWebSigninIsVisible:YES];
+  [[EarlGrey
+      selectElementWithMatcher:chrome_test_util::WebSigninSkipButtonMatcher()]
+      performAction:grey_tap()];
+  [ChromeEarlGreyUI waitForAppToIdle];
+  [SigninEarlGreyUI verifyWebSigninIsVisible:NO];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/authentication/signin/signin_constants.h b/ios/chrome/browser/ui/authentication/signin/signin_constants.h
index 04daa27..8f64dda 100644
--- a/ios/chrome/browser/ui/authentication/signin/signin_constants.h
+++ b/ios/chrome/browser/ui/authentication/signin/signin_constants.h
@@ -54,6 +54,9 @@
 // Name of accessiblity identifier for "Continue As..." button that signs in
 // the primary account user for the web sign-in consistency sheet.
 extern NSString* const kWebSigninContinueAsButtonAccessibilityIdentifier;
+// Name of accessibility identifier for "Skip" button in the web sign-in
+// consistency sheet.
+extern NSString* const kWebSigninSkipButtonAccessibilityIdentifier;
 
 // Action that is required to do to complete the sign-in. This action is in
 // charge of the SigninCoordinator's owner.
diff --git a/ios/chrome/browser/ui/authentication/signin/signin_constants.mm b/ios/chrome/browser/ui/authentication/signin/signin_constants.mm
index 8d9ab27..1695922 100644
--- a/ios/chrome/browser/ui/authentication/signin/signin_constants.mm
+++ b/ios/chrome/browser/ui/authentication/signin/signin_constants.mm
@@ -8,15 +8,17 @@
 #error "This file requires ARC support."
 #endif
 
-NSString* const kUserSigninAttemptedNotification = @"kUserSigninAttempted";
+NSString* const kUserSigninAttemptedNotification = @"UserSigninAttempted";
 NSString* const kSkipSigninAccessibilityIdentifier =
-    @"kSkipSigninAccessibilityIdentifier";
+    @"SkipSigninAccessibilityIdentifier";
 NSString* const kAddAccountAccessibilityIdentifier =
-    @"kAddAccountAccessibilityIdentifier";
+    @"AddAccountAccessibilityIdentifier";
 NSString* const kConfirmationAccessibilityIdentifier =
-    @"kConfirmationAccessibilityIdentifier";
-NSString* const kMoreAccessibilityIdentifier = @"kMoreAccessibilityIdentifier";
+    @"ConfirmationAccessibilityIdentifier";
+NSString* const kMoreAccessibilityIdentifier = @"MoreAccessibilityIdentifier";
 NSString* const kWebSigninAccessibilityIdentifier =
-    @"kWebSigninAccessibilityIdentifier";
+    @"WebSigninAccessibilityIdentifier";
 NSString* const kWebSigninContinueAsButtonAccessibilityIdentifier =
-    @"kWebSigninContinueAsButtonAccessibilityIdentifier";
+    @"WebSigninContinueAsButtonAccessibilityIdentifier";
+NSString* const kWebSigninSkipButtonAccessibilityIdentifier =
+    @"WebSigninSkipButtonAccessibilityIdentifier";
diff --git a/ios/chrome/browser/ui/authentication/signin_earl_grey_app_interface.h b/ios/chrome/browser/ui/authentication/signin_earl_grey_app_interface.h
index 8fe61cf..cc84dba 100644
--- a/ios/chrome/browser/ui/authentication/signin_earl_grey_app_interface.h
+++ b/ios/chrome/browser/ui/authentication/signin_earl_grey_app_interface.h
@@ -57,6 +57,10 @@
 // SceneController, without any UI interaction to open the dialog.
 + (void)triggerReauthDialogWithFakeIdentity:(FakeChromeIdentity*)identity;
 
+// Triggers the web sign-in consistency dialog. This is done by calling
+// directly the current SceneController.
++ (void)triggerConsistencyPromoSigninDialog;
+
 // Sign-in matchers
 
 // Returns a matcher for an identity picker cell for |email|.
diff --git a/ios/chrome/browser/ui/authentication/signin_earl_grey_app_interface.mm b/ios/chrome/browser/ui/authentication/signin_earl_grey_app_interface.mm
index 8abdbf54..4ef8bdd 100644
--- a/ios/chrome/browser/ui/authentication/signin_earl_grey_app_interface.mm
+++ b/ios/chrome/browser/ui/authentication/signin_earl_grey_app_interface.mm
@@ -25,6 +25,7 @@
 #import "ios/public/provider/chrome/browser/signin/fake_chrome_identity_interaction_manager.h"
 #import "ios/public/provider/chrome/browser/signin/fake_chrome_identity_service.h"
 #import "ios/testing/earl_grey/earl_grey_app.h"
+#import "net/base/mac/url_conversions.h"
 #include "url/gurl.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -95,12 +96,6 @@
               ->HasPrimaryAccount(signin::ConsentLevel::kSignin);
 }
 
-+ (id<GREYMatcher>)identityCellMatcherForEmail:(NSString*)email {
-  return grey_allOf(grey_accessibilityID(email),
-                    grey_kindOfClass([TableViewIdentityCell class]),
-                    grey_sufficientlyVisible(), nil);
-}
-
 + (BOOL)hasPrimaryIdentity {
   ChromeBrowserState* browserState =
       chrome_test_util::GetOriginalBrowserState();
@@ -136,4 +131,21 @@
   [sceneController showSignin:command baseViewController:baseViewController];
 }
 
++ (void)triggerConsistencyPromoSigninDialog {
+  NSURL* url = [NSURL URLWithString:@"http://www.example.com"];
+  const GURL gURL = net::GURLWithNSURL(url);
+  UIViewController* baseViewController =
+      chrome_test_util::GetActiveViewController();
+  SceneController* sceneController =
+      chrome_test_util::GetForegroundActiveSceneController();
+  [sceneController showConsistencyPromoFromViewController:baseViewController
+                                                      URL:gURL];
+}
+
++ (id<GREYMatcher>)identityCellMatcherForEmail:(NSString*)email {
+  return grey_allOf(grey_accessibilityID(email),
+                    grey_kindOfClass([TableViewIdentityCell class]),
+                    grey_sufficientlyVisible(), nil);
+}
+
 @end
diff --git a/ios/chrome/browser/ui/authentication/signin_matchers.h b/ios/chrome/browser/ui/authentication/signin_matchers.h
index 4fbd5fa..41016e7f 100644
--- a/ios/chrome/browser/ui/authentication/signin_matchers.h
+++ b/ios/chrome/browser/ui/authentication/signin_matchers.h
@@ -17,6 +17,9 @@
 // Returns a matcher for the link to Advanced Sync Settings options.
 id<GREYMatcher> SettingsLink();
 
+// Returns a matcher for the skip button in the web sign-in consistency dialog.
+id<GREYMatcher> WebSigninSkipButtonMatcher();
+
 }  // namespace chrome_test_util
 
 #endif  // IOS_CHROME_BROWSER_UI_AUTHENTICATION_SIGNIN_MATCHERS_H_
diff --git a/ios/chrome/browser/ui/authentication/signin_matchers.mm b/ios/chrome/browser/ui/authentication/signin_matchers.mm
index 1b4e7b2..b08b6c4a 100644
--- a/ios/chrome/browser/ui/authentication/signin_matchers.mm
+++ b/ios/chrome/browser/ui/authentication/signin_matchers.mm
@@ -4,6 +4,7 @@
 
 #import "ios/chrome/browser/ui/authentication/signin_matchers.h"
 
+#import "ios/chrome/browser/ui/authentication/signin/signin_constants.h"
 #import "ios/chrome/browser/ui/authentication/signin_earl_grey_app_interface.h"
 #import "ios/testing/earl_grey/earl_grey_test.h"
 
@@ -23,4 +24,10 @@
                     grey_interactable(), nil);
 }
 
+id<GREYMatcher> WebSigninSkipButtonMatcher() {
+  return grey_allOf(
+      grey_accessibilityID(kWebSigninSkipButtonAccessibilityIdentifier),
+      grey_sufficientlyVisible(), nil);
+}
+
 }  // namespace chrome_test_util
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_earl_grey_ui.mm b/ios/chrome/browser/ui/bookmarks/bookmark_earl_grey_ui.mm
index d0cd053..d371d0a 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_earl_grey_ui.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_earl_grey_ui.mm
@@ -53,10 +53,6 @@
 
 namespace chrome_test_util {
 
-id<GREYMatcher> StarButton() {
-  return ButtonWithAccessibilityLabelId(IDS_TOOLTIP_STAR);
-}
-
 id<GREYMatcher> BookmarksContextMenuEditButton() {
   // Making sure the edit button we're selecting is not on the bottom bar via
   // exclusion by accessibility ID and ancestry.
diff --git a/ios/chrome/browser/ui/settings/password/passwords_settings_egtest.mm b/ios/chrome/browser/ui/settings/password/passwords_settings_egtest.mm
index dbe3954..b415912 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_settings_egtest.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_settings_egtest.mm
@@ -1532,13 +1532,6 @@
 // Checks that attempts to edit a username to a value which is already used for
 // the same domain fails.
 - (void)testEditUsernameFails {
-#if !TARGET_IPHONE_SIMULATOR
-  // TODO(crbug.com/1140400): Fix test for iPad devices.
-  if ([ChromeEarlGrey isIPadIdiom]) {
-    EARL_GREY_TEST_SKIPPED(@"Test disabled on iPad devices.");
-  }
-#endif
-
   GREYAssert(
       [PasswordSettingsAppInterface saveExamplePassword:@"concrete password"
                                                userName:@"concrete username1"
@@ -1570,7 +1563,7 @@
       performAction:grey_clearText()];
 
   [[EarlGrey selectElementWithMatcher:PasswordDetailUsername()]
-      performAction:grey_typeText(@"concrete username2")];
+      performAction:grey_replaceText(@"concrete username2")];
 
   [[EarlGrey selectElementWithMatcher:NavigationBarDoneButton()]
       assertWithMatcher:grey_allOf(grey_sufficientlyVisible(),
diff --git a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_coordinator.mm b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_coordinator.mm
index 369086d2..0f443fb 100644
--- a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_coordinator.mm
+++ b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_coordinator.mm
@@ -62,9 +62,6 @@
   self.mediator.incognito = self.browser->GetBrowserState()->IsOffTheRecord();
   self.mediator.consumer = self.viewController;
   self.mediator.webStateList = self.browser->GetWebStateList();
-  self.mediator.bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(
-      self.browser->GetBrowserState());
-  self.mediator.prefService = self.browser->GetBrowserState()->GetPrefs();
   self.mediator.webContentAreaOverlayPresenter = OverlayPresenter::FromBrowser(
       self.browser, OverlayModality::kWebContentArea);
 }
diff --git a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm
index 8f3fbf0..d478839 100644
--- a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm
+++ b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm
@@ -71,11 +71,6 @@
   return std::move(http_response);
 }
 
-// Returns a matcher for the bookmark button.
-id<GREYMatcher> BookmarkButton() {
-  return chrome_test_util::ButtonWithAccessibilityLabelId(IDS_TOOLTIP_STAR);
-}
-
 // Returns a matcher for the visible share button.
 id<GREYMatcher> ShareButton() {
   return grey_allOf(grey_accessibilityID(kToolbarShareButtonIdentifier),
@@ -212,7 +207,6 @@
 
     CheckVisibilityInToolbar(ShareButton(), ButtonVisibilityNone);
     CheckVisibilityInToolbar(ReloadButton(), ButtonVisibilityNone);
-    CheckVisibilityInToolbar(BookmarkButton(), ButtonVisibilityNone);
 
     // Those buttons are hidden by the keyboard.
     CheckVisibilityInToolbar(BackButton(), ButtonVisibilityNone);
@@ -225,7 +219,6 @@
 
     CheckVisibilityInToolbar(ShareButton(), ButtonVisibilityNone);
     CheckVisibilityInToolbar(ReloadButton(), ButtonVisibilityNone);
-    CheckVisibilityInToolbar(BookmarkButton(), ButtonVisibilityNone);
 
     CheckVisibilityInToolbar(BackButton(), ButtonVisibilitySecondary);
     CheckVisibilityInToolbar(ForwardButton(), ButtonVisibilitySecondary);
@@ -243,7 +236,6 @@
 
     CheckVisibilityInToolbar(ShareButton(), ButtonVisibilityNone);
     CheckVisibilityInToolbar(ReloadButton(), ButtonVisibilityNone);
-    CheckVisibilityInToolbar(BookmarkButton(), ButtonVisibilityNone);
 
     CheckVisibilityInToolbar(BackButton(), ButtonVisibilityNone);
     CheckVisibilityInToolbar(ForwardButton(), ButtonVisibilityNone);
@@ -255,7 +247,6 @@
 
     CheckVisibilityInToolbar(ShareButton(), ButtonVisibilityPrimary);
     CheckVisibilityInToolbar(ReloadButton(), ButtonVisibilityPrimary);
-    CheckVisibilityInToolbar(BookmarkButton(), ButtonVisibilityNone);
 
     CheckVisibilityInToolbar(BackButton(), ButtonVisibilityPrimary);
     CheckVisibilityInToolbar(ForwardButton(), ButtonVisibilityPrimary);
@@ -276,7 +267,6 @@
 
   CheckVisibilityInToolbar(ShareButton(), ButtonVisibilityPrimary);
   CheckVisibilityInToolbar(ReloadButton(), ButtonVisibilityPrimary);
-  CheckVisibilityInToolbar(BookmarkButton(), ButtonVisibilityNone);
   CheckVisibilityInToolbar(TabGridButton(), ButtonVisibilityPrimary);
 
   CheckVisibilityInToolbar(BackButton(), ButtonVisibilityPrimary);
diff --git a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view.h b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view.h
index aa845a6..47dc6754b 100644
--- a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view.h
+++ b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view.h
@@ -33,8 +33,6 @@
 @property(nonatomic, strong, readonly) ToolbarButton* reloadButton;
 // Button to display the share menu.
 @property(nonatomic, strong, readonly) ToolbarButton* shareButton;
-// Button to manage the bookmarks of this page.
-@property(nonatomic, strong, readonly) ToolbarButton* bookmarkButton;
 // Button to display the tools menu.
 @property(nonatomic, strong, readonly) ToolbarToolsMenuButton* toolsMenuButton;
 // Button to create a new tab.
diff --git a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view_controller.mm b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view_controller.mm
index 1e00732..4750751 100644
--- a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view_controller.mm
+++ b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view_controller.mm
@@ -199,14 +199,6 @@
                             completion:nil];
 }
 
-- (void)setPageBookmarked:(BOOL)bookmarked {
-  self.view.bookmarkButton.spotlighted = bookmarked;
-}
-
-- (void)setBookmarkEnabled:(BOOL)enabled {
-  self.view.bookmarkButton.enabled = enabled;
-}
-
 - (void)setVoiceSearchEnabled:(BOOL)enabled {
   // No-op, should be handled by the location bar.
 }
@@ -367,8 +359,6 @@
     base::RecordAction(base::UserMetricsAction("MobileToolbarReload"));
   } else if (sender == self.view.stopButton) {
     base::RecordAction(base::UserMetricsAction("MobileToolbarStop"));
-  } else if (sender == self.view.bookmarkButton) {
-    base::RecordAction(base::UserMetricsAction("MobileToolbarToggleBookmark"));
   } else if (sender == self.view.toolsMenuButton) {
     base::RecordAction(base::UserMetricsAction("MobileToolbarShowMenu"));
   } else if (sender == self.view.tabGridButton) {
diff --git a/ios/chrome/browser/ui/toolbar/buttons/BUILD.gn b/ios/chrome/browser/ui/toolbar/buttons/BUILD.gn
index d7386ac..a164fd53 100644
--- a/ios/chrome/browser/ui/toolbar/buttons/BUILD.gn
+++ b/ios/chrome/browser/ui/toolbar/buttons/BUILD.gn
@@ -31,8 +31,6 @@
     "resources:tab_toolbar_button_halo_color",
     "resources:tab_toolbar_button_halo_color_incognito",
     "resources:toolbar_back",
-    "resources:toolbar_bookmark",
-    "resources:toolbar_bookmark_active",
     "resources:toolbar_forward",
     "resources:toolbar_new_tab_page",
     "resources:toolbar_reload",
diff --git a/ios/chrome/browser/ui/toolbar/buttons/resources/BUILD.gn b/ios/chrome/browser/ui/toolbar/buttons/resources/BUILD.gn
index db70905d..2f760996 100644
--- a/ios/chrome/browser/ui/toolbar/buttons/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/toolbar/buttons/resources/BUILD.gn
@@ -42,15 +42,6 @@
   ]
 }
 
-imageset("toolbar_bookmark_active") {
-  sources = [
-    "toolbar_bookmark_active.imageset/Contents.json",
-    "toolbar_bookmark_active.imageset/toolbar_bookmark_active.png",
-    "toolbar_bookmark_active.imageset/toolbar_bookmark_active@2x.png",
-    "toolbar_bookmark_active.imageset/toolbar_bookmark_active@3x.png",
-  ]
-}
-
 imageset("toolbar_reload") {
   sources = [
     "toolbar_reload.imageset/Contents.json",
@@ -91,12 +82,3 @@
     "toolbar_forward.imageset/toolbar_forward@3x.png",
   ]
 }
-
-imageset("toolbar_bookmark") {
-  sources = [
-    "toolbar_bookmark.imageset/Contents.json",
-    "toolbar_bookmark.imageset/toolbar_bookmark.png",
-    "toolbar_bookmark.imageset/toolbar_bookmark@2x.png",
-    "toolbar_bookmark.imageset/toolbar_bookmark@3x.png",
-  ]
-}
diff --git a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark.imageset/Contents.json b/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark.imageset/Contents.json
deleted file mode 100644
index cf9da17..0000000
--- a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark.imageset/Contents.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-    "images": [
-        {
-            "idiom": "universal",
-            "scale": "1x",
-            "filename": "toolbar_bookmark.png"
-        },
-        {
-            "idiom": "universal",
-            "scale": "2x",
-            "filename": "toolbar_bookmark@2x.png"
-        },
-        {
-            "idiom": "universal",
-            "scale": "3x",
-            "filename": "toolbar_bookmark@3x.png"
-        }
-    ],
-    "info": {
-        "version": 1,
-        "author": "xcode"
-    }
-}
diff --git a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark.imageset/toolbar_bookmark.png b/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark.imageset/toolbar_bookmark.png
deleted file mode 100644
index 3f3268a6..0000000
--- a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark.imageset/toolbar_bookmark.png
+++ /dev/null
Binary files differ
diff --git a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark.imageset/toolbar_bookmark@2x.png b/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark.imageset/toolbar_bookmark@2x.png
deleted file mode 100644
index 7f2bae7..0000000
--- a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark.imageset/toolbar_bookmark@2x.png
+++ /dev/null
Binary files differ
diff --git a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark.imageset/toolbar_bookmark@3x.png b/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark.imageset/toolbar_bookmark@3x.png
deleted file mode 100644
index c81e8b90..0000000
--- a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark.imageset/toolbar_bookmark@3x.png
+++ /dev/null
Binary files differ
diff --git a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark_active.imageset/Contents.json b/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark_active.imageset/Contents.json
deleted file mode 100644
index c686531..0000000
--- a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark_active.imageset/Contents.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-    "images": [
-        {
-            "idiom": "universal",
-            "scale": "1x",
-            "filename": "toolbar_bookmark_active.png"
-        },
-        {
-            "idiom": "universal",
-            "scale": "2x",
-            "filename": "toolbar_bookmark_active@2x.png"
-        },
-        {
-            "idiom": "universal",
-            "scale": "3x",
-            "filename": "toolbar_bookmark_active@3x.png"
-        }
-    ],
-    "info": {
-        "version": 1,
-        "author": "xcode"
-    }
-}
diff --git a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark_active.imageset/toolbar_bookmark_active.png b/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark_active.imageset/toolbar_bookmark_active.png
deleted file mode 100644
index 74fa4714..0000000
--- a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark_active.imageset/toolbar_bookmark_active.png
+++ /dev/null
Binary files differ
diff --git a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark_active.imageset/toolbar_bookmark_active@2x.png b/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark_active.imageset/toolbar_bookmark_active@2x.png
deleted file mode 100644
index 0380d1a..0000000
--- a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark_active.imageset/toolbar_bookmark_active@2x.png
+++ /dev/null
Binary files differ
diff --git a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark_active.imageset/toolbar_bookmark_active@3x.png b/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark_active.imageset/toolbar_bookmark_active@3x.png
deleted file mode 100644
index a7c5a0ed..0000000
--- a/ios/chrome/browser/ui/toolbar/buttons/resources/toolbar_bookmark_active.imageset/toolbar_bookmark_active@3x.png
+++ /dev/null
Binary files differ
diff --git a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_actions_handler.h b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_actions_handler.h
index a2fea5d..8ee3d4f 100644
--- a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_actions_handler.h
+++ b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_actions_handler.h
@@ -52,9 +52,6 @@
 // Action when the stop button is tapped.
 - (void)stopAction;
 
-// Action when the bookmark button is tapped.
-- (void)bookmarkAction;
-
 // Action when the search button is tapped.
 - (void)searchAction:(id)sender;
 
diff --git a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_actions_handler.mm b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_actions_handler.mm
index bc4b9362..e1b1408e 100644
--- a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_actions_handler.mm
+++ b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_actions_handler.mm
@@ -54,10 +54,6 @@
   self.navigationAgent->StopLoading();
 }
 
-- (void)bookmarkAction {
-  [self.dispatcher bookmarkCurrentPage];
-}
-
 - (void)searchAction:(id)sender {
   [self.dispatcher closeFindInPage];
   UIView* senderView = base::mac::ObjCCastStrict<UIView>(sender);
diff --git a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.h b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.h
index 39e21cc..1766569 100644
--- a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.h
+++ b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.h
@@ -49,8 +49,6 @@
 - (ToolbarButton*)reloadButton;
 // Stop ToolbarButton.
 - (ToolbarButton*)stopButton;
-// Bookmark ToolbarButton.
-- (ToolbarButton*)bookmarkButton;
 // ToolbarButton to create a new tab.
 - (ToolbarButton*)openNewTabButton;
 // Button to cancel the edit of the location bar.
diff --git a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.mm b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.mm
index 9264968b..e46a48c 100644
--- a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.mm
+++ b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.mm
@@ -145,26 +145,6 @@
   return stopButton;
 }
 
-- (ToolbarButton*)bookmarkButton {
-  ToolbarButton* bookmarkButton = [ToolbarButton
-      toolbarButtonWithImage:[UIImage imageNamed:@"toolbar_bookmark"]];
-  [bookmarkButton setImage:[UIImage imageNamed:@"toolbar_bookmark_active"]
-                  forState:kControlStateSpotlighted];
-  [self configureButton:bookmarkButton width:kAdaptiveToolbarButtonWidth];
-  bookmarkButton.adjustsImageWhenHighlighted = NO;
-  [bookmarkButton
-      setImage:[bookmarkButton imageForState:UIControlStateHighlighted]
-      forState:UIControlStateSelected];
-  bookmarkButton.accessibilityLabel = l10n_util::GetNSString(IDS_TOOLTIP_STAR);
-  [bookmarkButton addTarget:self.actionHandler
-                     action:@selector(bookmarkAction)
-           forControlEvents:UIControlEventTouchUpInside];
-
-  bookmarkButton.visibilityMask =
-      self.visibilityConfiguration.bookmarkButtonVisibility;
-  return bookmarkButton;
-}
-
 - (ToolbarButton*)openNewTabButton {
   ToolbarNewTabButton* newTabButton = [ToolbarNewTabButton
       toolbarButtonWithImage:[UIImage imageNamed:@"toolbar_new_tab_page"]];
diff --git a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_visibility_configuration.h b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_visibility_configuration.h
index e10edcc..7e16ff5 100644
--- a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_visibility_configuration.h
+++ b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_visibility_configuration.h
@@ -36,8 +36,6 @@
     ToolbarComponentVisibility reloadButtonVisibility;
 @property(nonatomic, readonly) ToolbarComponentVisibility stopButtonVisibility;
 @property(nonatomic, readonly)
-    ToolbarComponentVisibility bookmarkButtonVisibility;
-@property(nonatomic, readonly)
     ToolbarComponentVisibility voiceSearchButtonVisibility;
 @property(nonatomic, readonly)
     ToolbarComponentVisibility contractButtonVisibility;
diff --git a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_visibility_configuration.mm b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_visibility_configuration.mm
index e1b671b..3340f79 100644
--- a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_visibility_configuration.mm
+++ b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_visibility_configuration.mm
@@ -91,15 +91,6 @@
   }
 }
 
-- (ToolbarComponentVisibility)bookmarkButtonVisibility {
-  switch (self.type) {
-    case PRIMARY:
-      return ToolbarComponentVisibilityRegularWidthRegularHeight;
-    case SECONDARY:
-      return ToolbarComponentVisibilityNone;
-  }
-}
-
 - (ToolbarComponentVisibility)voiceSearchButtonVisibility {
   switch (self.type) {
     case PRIMARY:
diff --git a/ios/chrome/browser/ui/toolbar/primary_toolbar_view.mm b/ios/chrome/browser/ui/toolbar/primary_toolbar_view.mm
index 28e3b95a..fe227e3e 100644
--- a/ios/chrome/browser/ui/toolbar/primary_toolbar_view.mm
+++ b/ios/chrome/browser/ui/toolbar/primary_toolbar_view.mm
@@ -70,8 +70,6 @@
 #pragma mark** Buttons in the trailing stack view. **
 // Button to display the share menu, redefined as readwrite.
 @property(nonatomic, strong, readwrite) ToolbarButton* shareButton;
-// Button to manage the bookmarks of this page, redefined as readwrite.
-@property(nonatomic, strong, readwrite) ToolbarButton* bookmarkButton;
 // Button to display the tools menu, redefined as readwrite.
 @property(nonatomic, strong, readwrite) ToolbarToolsMenuButton* toolsMenuButton;
 
@@ -111,7 +109,6 @@
 @synthesize trailingStackView = _trailingStackView;
 @synthesize trailingStackViewButtons = _trailingStackViewButtons;
 @synthesize shareButton = _shareButton;
-@synthesize bookmarkButton = _bookmarkButton;
 @synthesize toolsMenuButton = _toolsMenuButton;
 @synthesize cancelButton = _cancelButton;
 @synthesize collapsedToolbarButton = _collapsedToolbarButton;
@@ -252,7 +249,6 @@
 // Sets the trailing stack view.
 - (void)setUpTrailingStackView {
   self.shareButton = [self.buttonFactory shareButton];
-  self.bookmarkButton = [self.buttonFactory bookmarkButton];
   self.tabGridButton = [self.buttonFactory tabGridButton];
   self.toolsMenuButton = [self.buttonFactory toolsMenuButton];
 
diff --git a/ios/chrome/browser/ui/toolbar/secondary_toolbar_view.mm b/ios/chrome/browser/ui/toolbar/secondary_toolbar_view.mm
index 2b2af0a..df17569 100644
--- a/ios/chrome/browser/ui/toolbar/secondary_toolbar_view.mm
+++ b/ios/chrome/browser/ui/toolbar/secondary_toolbar_view.mm
@@ -172,10 +172,6 @@
   return nil;
 }
 
-- (ToolbarButton*)bookmarkButton {
-  return nil;
-}
-
 - (MDCProgressView*)progressBar {
   return nil;
 }
diff --git a/ios/chrome/browser/ui/toolbar/toolbar_consumer.h b/ios/chrome/browser/ui/toolbar/toolbar_consumer.h
index 84742de..5c9a798 100644
--- a/ios/chrome/browser/ui/toolbar/toolbar_consumer.h
+++ b/ios/chrome/browser/ui/toolbar/toolbar_consumer.h
@@ -21,10 +21,6 @@
 // added, |addedInBackground| is set to YES if the tab is added in background.
 // NO otherwise.
 - (void)setTabCount:(int)tabCount addedInBackground:(BOOL)addedInBackground;
-// Sets the bookmarks status of the page.
-- (void)setPageBookmarked:(BOOL)bookmarked;
-// Sets whether the bookmark button is enabled or not.
-- (void)setBookmarkEnabled:(BOOL)enabled;
 // Sets whether the voice search is enabled or not.
 - (void)setVoiceSearchEnabled:(BOOL)enabled;
 // Sets whether the share menu is enabled.
diff --git a/ios/chrome/browser/ui/toolbar/toolbar_mediator.h b/ios/chrome/browser/ui/toolbar/toolbar_mediator.h
index b5fbb81..1a3b0a9 100644
--- a/ios/chrome/browser/ui/toolbar/toolbar_mediator.h
+++ b/ios/chrome/browser/ui/toolbar/toolbar_mediator.h
@@ -7,14 +7,10 @@
 
 #import <Foundation/Foundation.h>
 
-namespace bookmarks {
-class BookmarkModel;
-}
 namespace web {
 class WebState;
 }
 class OverlayPresenter;
-class PrefService;
 @protocol ToolbarConsumer;
 class WebStateList;
 
@@ -29,12 +25,6 @@
 // number of Webstates.
 @property(nonatomic, assign) WebStateList* webStateList;
 
-// The bookmarks model to know if the page is bookmarked.
-@property(nonatomic, assign) bookmarks::BookmarkModel* bookmarkModel;
-
-// Pref service to retrieve preference values.
-@property(nonatomic, assign) PrefService* prefService;
-
 // The consumer for this object. This can change during the lifetime of this
 // object and may be nil.
 @property(nonatomic, strong) id<ToolbarConsumer> consumer;
diff --git a/ios/chrome/browser/ui/toolbar/toolbar_mediator.mm b/ios/chrome/browser/ui/toolbar/toolbar_mediator.mm
index 34a7e259..3626915 100644
--- a/ios/chrome/browser/ui/toolbar/toolbar_mediator.mm
+++ b/ios/chrome/browser/ui/toolbar/toolbar_mediator.mm
@@ -6,16 +6,10 @@
 
 #include "base/memory/ptr_util.h"
 #include "base/strings/sys_string_conversions.h"
-#include "components/bookmarks/browser/bookmark_model.h"
-#include "components/bookmarks/common/bookmark_pref_names.h"
-#import "components/prefs/ios/pref_observer_bridge.h"
-#include "components/prefs/pref_change_registrar.h"
-#include "components/prefs/pref_service.h"
 #include "ios/chrome/browser/chrome_url_constants.h"
 #import "ios/chrome/browser/overlays/public/overlay_presenter.h"
 #import "ios/chrome/browser/overlays/public/overlay_presenter_observer_bridge.h"
 #include "ios/chrome/browser/policy/policy_features.h"
-#include "ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h"
 #import "ios/chrome/browser/ui/ntp/ntp_util.h"
 #import "ios/chrome/browser/ui/toolbar/toolbar_consumer.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
@@ -31,10 +25,8 @@
 #error "This file requires ARC support."
 #endif
 
-@interface ToolbarMediator () <BookmarkModelBridgeObserver,
-                               CRWWebStateObserver,
+@interface ToolbarMediator () <CRWWebStateObserver,
                                OverlayPresenterObserving,
-                               PrefObserverDelegate,
                                WebStateListObserving>
 
 // The current web state associated with the toolbar.
@@ -49,12 +41,6 @@
 @implementation ToolbarMediator {
   std::unique_ptr<web::WebStateObserverBridge> _webStateObserver;
   std::unique_ptr<WebStateListObserverBridge> _webStateListObserver;
-  // Bridge to register for bookmark changes.
-  std::unique_ptr<bookmarks::BookmarkModelBridge> _bookmarkModelBridge;
-  // Pref observer to track changes to prefs.
-  std::unique_ptr<PrefObserverBridge> _prefObserverBridge;
-  // Registrar for pref changes notifications.
-  std::unique_ptr<PrefChangeRegistrar> _prefChangeRegistrar;
   std::unique_ptr<OverlayPresenterObserverBridge> _overlayObserver;
 }
 
@@ -77,7 +63,6 @@
 - (void)updateConsumerForWebState:(web::WebState*)webState {
   [self updateNavigationBackAndForwardStateForWebState:webState];
   [self updateShareMenuForWebState:webState];
-  [self updateBookmarksForWebState:webState];
 }
 
 - (void)disconnect {
@@ -94,9 +79,6 @@
     _webStateObserver.reset();
     _webState = nullptr;
   }
-  _bookmarkModelBridge.reset();
-  _prefChangeRegistrar.reset();
-  _prefObserverBridge.reset();
 }
 
 #pragma mark - CRWWebStateObserver
@@ -234,29 +216,6 @@
   }
 }
 
-- (void)setBookmarkModel:(bookmarks::BookmarkModel*)bookmarkModel {
-  _bookmarkModel = bookmarkModel;
-  if (self.webState && self.consumer) {
-    [self updateConsumer];
-  }
-  _bookmarkModelBridge.reset();
-  if (bookmarkModel) {
-    _bookmarkModelBridge =
-        std::make_unique<bookmarks::BookmarkModelBridge>(self, bookmarkModel);
-  }
-}
-
-- (void)setPrefService:(PrefService*)prefService {
-  _prefService = prefService;
-  _prefChangeRegistrar = std::make_unique<PrefChangeRegistrar>();
-  _prefChangeRegistrar->Init(prefService);
-  _prefObserverBridge.reset(new PrefObserverBridge(self));
-  _prefObserverBridge->ObserveChangesForPreference(
-      bookmarks::prefs::kEditBookmarksEnabled, _prefChangeRegistrar.get());
-
-  [self.consumer setBookmarkEnabled:[self isEditBookmarksEnabled]];
-}
-
 - (void)setWebContentAreaOverlayPresenter:
     (OverlayPresenter*)webContentAreaOverlayPresenter {
   if (_webContentAreaOverlayPresenter)
@@ -292,7 +251,6 @@
     [self.consumer
         setLoadingProgressFraction:self.webState->GetLoadingProgress()];
   }
-  [self updateBookmarksForWebState:self.webState];
   [self updateShareMenuForWebState:self.webState];
 }
 
@@ -305,15 +263,6 @@
   [self.consumer setCanGoBack:webState->GetNavigationManager()->CanGoBack()];
 }
 
-// Updates the bookmark state of the consumer.
-- (void)updateBookmarksForWebState:(web::WebState*)webState {
-  if (self.webState) {
-    GURL URL = webState->GetVisibleURL();
-    [self.consumer setPageBookmarked:self.bookmarkModel &&
-                                     self.bookmarkModel->IsBookmarked(URL)];
-  }
-}
-
 // Updates the Share Menu button of the consumer.
 - (void)updateShareMenuForWebState:(web::WebState*)webState {
   if (!self.webState)
@@ -327,47 +276,6 @@
                                      !self.webContentAreaShowingOverlay];
 }
 
-#pragma mark - Other private methods
-
-// Returns YES if user is allowed to edit any bookmarks.
-- (BOOL)isEditBookmarksEnabled {
-    return self.prefService->GetBoolean(
-        bookmarks::prefs::kEditBookmarksEnabled);
-}
-
-#pragma mark - BookmarkModelBridgeObserver
-
-// If an added or removed bookmark is the same as the current url, update the
-// toolbar so the star highlight is kept in sync.
-- (void)bookmarkNodeChildrenChanged:
-    (const bookmarks::BookmarkNode*)bookmarkNode {
-  [self updateBookmarksForWebState:self.webState];
-}
-
-// If all bookmarks are removed, update the toolbar so the star highlight is
-// kept in sync.
-- (void)bookmarkModelRemovedAllNodes {
-  [self updateBookmarksForWebState:self.webState];
-}
-
-// In case we are on a bookmarked page before the model is loaded.
-- (void)bookmarkModelLoaded {
-  [self updateBookmarksForWebState:self.webState];
-}
-
-- (void)bookmarkNodeChanged:(const bookmarks::BookmarkNode*)bookmarkNode {
-  // No-op -- required by BookmarkModelBridgeObserver but not used.
-}
-- (void)bookmarkNode:(const bookmarks::BookmarkNode*)bookmarkNode
-     movedFromParent:(const bookmarks::BookmarkNode*)oldParent
-            toParent:(const bookmarks::BookmarkNode*)newParent {
-  // No-op -- required by BookmarkModelBridgeObserver but not used.
-}
-- (void)bookmarkNodeDeleted:(const bookmarks::BookmarkNode*)node
-                 fromFolder:(const bookmarks::BookmarkNode*)folder {
-  // No-op -- required by BookmarkModelBridgeObserver but not used.
-}
-
 #pragma mark - OverlayPresesenterObserving
 
 - (void)overlayPresenter:(OverlayPresenter*)presenter
@@ -381,11 +289,4 @@
   self.webContentAreaShowingOverlay = NO;
 }
 
-#pragma mark - PrefObserverDelegate
-
-- (void)onPreferenceChanged:(const std::string&)preferenceName {
-  if (preferenceName == bookmarks::prefs::kEditBookmarksEnabled)
-    [self.consumer setBookmarkEnabled:[self isEditBookmarksEnabled]];
-}
-
 @end
diff --git a/ios/chrome/browser/ui/toolbar/toolbar_mediator_unittest.mm b/ios/chrome/browser/ui/toolbar/toolbar_mediator_unittest.mm
index c7d578f9..2d6e1be 100644
--- a/ios/chrome/browser/ui/toolbar/toolbar_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/toolbar/toolbar_mediator_unittest.mm
@@ -9,13 +9,6 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
-#include "components/bookmarks/browser/bookmark_model.h"
-#include "components/bookmarks/browser/bookmark_node.h"
-#include "components/bookmarks/common/bookmark_pref_names.h"
-#include "components/bookmarks/test/bookmark_test_helpers.h"
-#include "components/prefs/pref_registry_simple.h"
-#include "components/prefs/testing_pref_service.h"
-#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
 #include "ios/chrome/browser/chrome_url_constants.h"
 #import "ios/chrome/browser/ui/toolbar/test/toolbar_test_navigation_manager.h"
@@ -41,9 +34,6 @@
 #error "This file requires ARC support."
 #endif
 
-using bookmarks::BookmarkNode;
-using bookmarks::BookmarkModel;
-
 namespace {
 
 // Test VoiceSearchProvider that allow overriding whether voice search
@@ -100,7 +90,6 @@
 
 static const int kNumberOfWebStates = 3;
 static const char kTestUrl[] = "http://www.chromium.org";
-static const char kTestUrl2[] = "http://www.notChromium.org";
 
 class ToolbarMediatorTest : public PlatformTest {
  public:
@@ -109,15 +98,7 @@
             std::make_unique<TestToolbarMediatorChromeBrowserProvider>()) {
     TestChromeBrowserState::Builder test_cbs_builder;
 
-    bool success = state_dir_.CreateUniqueTempDir();
-    DCHECK(success);
-    test_cbs_builder.SetPath(state_dir_.GetPath());
-
     chrome_browser_state_ = test_cbs_builder.Build();
-    chrome_browser_state_->CreateBookmarkModel(false);
-    bookmark_model_ = ios::BookmarkModelFactory::GetForBrowserState(
-        chrome_browser_state_.get());
-    bookmarks::test::WaitForBookmarkModelToLoad(bookmark_model_);
     std::unique_ptr<ToolbarTestNavigationManager> navigation_manager =
         std::make_unique<ToolbarTestNavigationManager>();
     navigation_manager_ = navigation_manager.get();
@@ -130,11 +111,6 @@
     consumer_ = OCMProtocolMock(@protocol(ToolbarConsumer));
     strict_consumer_ = OCMStrictProtocolMock(@protocol(ToolbarConsumer));
     SetUpWebStateList();
-
-    prefs_ = std::make_unique<TestingPrefServiceSimple>();
-    prefs_->registry()->RegisterBooleanPref(
-        bookmarks::prefs::kEditBookmarksEnabled,
-        /*default_value=*/true);
   }
 
   // Explicitly disconnect the mediator so there won't be any WebStateList
@@ -142,11 +118,6 @@
   ~ToolbarMediatorTest() override { [mediator_ disconnect]; }
 
  protected:
-  // A state directory that outlives |task_environment_| is needed because
-  // CreateHistoryService/CreateBookmarkModel use the directory to host
-  // databases. See https://crbug.com/546640 for more details.
-  base::ScopedTempDir state_dir_;
-
   web::WebTaskEnvironment task_environment_;
   void SetUpWebStateList() {
     web_state_list_ = std::make_unique<WebStateList>(&web_state_list_delegate_);
@@ -158,15 +129,6 @@
     }
   }
 
-  void SetUpBookmarks() {
-    bookmark_model_ = ios::BookmarkModelFactory::GetForBrowserState(
-        chrome_browser_state_.get());
-    GURL URL = GURL(kTestUrl);
-    const BookmarkNode* defaultFolder = bookmark_model_->mobile_node();
-    bookmark_model_->AddURL(defaultFolder, defaultFolder->children().size(),
-                            base::SysNSStringToUTF16(@"Test bookmark 1"), URL);
-  }
-
   void InsertNewWebState(int index) {
     auto web_state = std::make_unique<web::FakeWebState>();
     web_state->SetBrowserState(chrome_browser_state_.get());
@@ -196,144 +158,11 @@
   id consumer_;
   id strict_consumer_;
   std::unique_ptr<TestChromeBrowserState> chrome_browser_state_;
-  BookmarkModel* bookmark_model_;
-  std::unique_ptr<TestingPrefServiceSimple> prefs_;
 
  private:
   std::unique_ptr<ToolbarTestWebState> test_web_state_;
 };
 
-// Test that the consumer bookmarks status is only updated when the page is
-// added to the bookmark model.
-TEST_F(ToolbarMediatorTest, TestToolbarAddedToBookmarks) {
-  web_state_->SetCurrentURL(GURL(kTestUrl));
-  mediator_.webStateList = web_state_list_.get();
-  SetUpActiveWebState();
-  mediator_.consumer = consumer_;
-  mediator_.bookmarkModel = bookmark_model_;
-
-  // Add a different bookmark and verify it is not set as bookmarked.
-  OCMExpect([consumer_ setPageBookmarked:NO]);
-  bookmark_model_ = ios::BookmarkModelFactory::GetForBrowserState(
-      chrome_browser_state_.get());
-  GURL URL = GURL(kTestUrl2);
-  const BookmarkNode* defaultFolder = bookmark_model_->mobile_node();
-  bookmark_model_->AddURL(defaultFolder, defaultFolder->children().size(),
-                          base::SysNSStringToUTF16(@"Test bookmark 1"), URL);
-  EXPECT_OCMOCK_VERIFY(consumer_);
-
-  // Bookmark the page and check it is set.
-  OCMExpect([consumer_ setPageBookmarked:YES]);
-  bookmark_model_ = ios::BookmarkModelFactory::GetForBrowserState(
-      chrome_browser_state_.get());
-  URL = GURL(kTestUrl);
-  bookmark_model_->AddURL(defaultFolder, defaultFolder->children().size(),
-                          base::SysNSStringToUTF16(@"Test bookmark 2"), URL);
-
-  EXPECT_OCMOCK_VERIFY(consumer_);
-  bookmark_model_->RemoveAllUserBookmarks();
-}
-
-// Test that the consumer bookmarks status is only updated when the page is
-// removed from the bookmark model.
-TEST_F(ToolbarMediatorTest, TestToolbarRemovedFromBookmarks) {
-  SetUpBookmarks();
-  bookmark_model_ = ios::BookmarkModelFactory::GetForBrowserState(
-      chrome_browser_state_.get());
-  GURL URL = GURL(kTestUrl2);
-  const BookmarkNode* defaultFolder = bookmark_model_->mobile_node();
-  bookmark_model_->AddURL(defaultFolder, defaultFolder->children().size(),
-                          base::SysNSStringToUTF16(@"Test bookmark 1"), URL);
-  web_state_->SetCurrentURL(GURL(kTestUrl));
-  mediator_.webStateList = web_state_list_.get();
-  SetUpActiveWebState();
-  mediator_.consumer = consumer_;
-  mediator_.bookmarkModel = bookmark_model_;
-
-  // Removes another bookmark and check it is still bookmarked.
-  OCMExpect([consumer_ setPageBookmarked:YES]);
-  std::vector<const BookmarkNode*> vec;
-  bookmark_model_->GetNodesByURL(GURL(kTestUrl2), &vec);
-  bookmark_model_->Remove(vec.front());
-  EXPECT_OCMOCK_VERIFY(consumer_);
-  vec.clear();
-
-  // Removes the page from the bookmarks and check it is updated.
-  OCMExpect([consumer_ setPageBookmarked:NO]);
-  bookmark_model_->GetNodesByURL(GURL(kTestUrl), &vec);
-  bookmark_model_->Remove(vec.front());
-  EXPECT_OCMOCK_VERIFY(consumer_);
-  bookmark_model_->RemoveAllUserBookmarks();
-}
-
-// Test that the consumer bookmarks status is updated when the page is
-// bookmarked.
-TEST_F(ToolbarMediatorTest, TestToolbarBookmarked) {
-  SetUpBookmarks();
-  OCMExpect([consumer_ setPageBookmarked:YES]);
-
-  web_state_->SetCurrentURL(GURL(kTestUrl));
-  mediator_.bookmarkModel = bookmark_model_;
-  mediator_.webStateList = web_state_list_.get();
-  SetUpActiveWebState();
-  mediator_.consumer = consumer_;
-
-  EXPECT_OCMOCK_VERIFY(consumer_);
-  bookmark_model_->RemoveAllUserBookmarks();
-}
-
-// Test that the consumer bookmarks status is updated when the page is
-// bookmarked, when the bookmarkModel is set last.
-TEST_F(ToolbarMediatorTest, TestToolbarBookmarkedModelSetLast) {
-  SetUpBookmarks();
-  OCMExpect([consumer_ setPageBookmarked:NO]);
-  OCMExpect([consumer_ setShareMenuEnabled:YES]);
-
-  web_state_->SetCurrentURL(GURL(kTestUrl));
-  mediator_.webStateList = web_state_list_.get();
-  SetUpActiveWebState();
-  mediator_.consumer = consumer_;
-  mediator_.bookmarkModel = bookmark_model_;
-
-  EXPECT_OCMOCK_VERIFY(consumer_);
-  bookmark_model_->RemoveAllUserBookmarks();
-}
-
-// Test that the consumer bookmarks status is updated when the page is
-// NOT bookmarked.
-TEST_F(ToolbarMediatorTest, TestToolbarNotBookmarked) {
-  SetUpBookmarks();
-  OCMExpect([consumer_ setPageBookmarked:NO]);
-
-  web_state_->SetCurrentURL(GURL(kTestUrl2));
-  mediator_.bookmarkModel = bookmark_model_;
-  mediator_.webStateList = web_state_list_.get();
-  SetUpActiveWebState();
-  mediator_.consumer = consumer_;
-
-  EXPECT_OCMOCK_VERIFY(consumer_);
-  bookmark_model_->RemoveAllUserBookmarks();
-}
-
-// Tests that the bookmark button is disabled when the EditBookmarksEnabled pref
-// is false.
-TEST_F(ToolbarMediatorTest, TestBookmarkDisabled) {
-  OCMExpect([consumer_ setBookmarkEnabled:YES]);
-  SetUpBookmarks();
-  mediator_.consumer = consumer_;
-  mediator_.prefService = prefs_.get();
-  EXPECT_OCMOCK_VERIFY(consumer_);
-
-  OCMExpect([consumer_ setBookmarkEnabled:NO]);
-  prefs_->SetBoolean(bookmarks::prefs::kEditBookmarksEnabled, false);
-  EXPECT_OCMOCK_VERIFY(consumer_);
-
-  OCMExpect([consumer_ setBookmarkEnabled:YES]);
-  prefs_->SetBoolean(bookmarks::prefs::kEditBookmarksEnabled, true);
-  EXPECT_OCMOCK_VERIFY(consumer_);
-
-  bookmark_model_->RemoveAllUserBookmarks();
-}
 
 // Test no setup is being done on the Toolbar if there's no Webstate.
 TEST_F(ToolbarMediatorTest, TestToolbarSetupWithNoWebstate) {
@@ -346,15 +175,12 @@
 
 // Test no setup is being done on the Toolbar if there's no active Webstate.
 TEST_F(ToolbarMediatorTest, TestToolbarSetupWithNoActiveWebstate) {
-  SetUpBookmarks();
   mediator_.webStateList = web_state_list_.get();
   mediator_.consumer = consumer_;
-  mediator_.bookmarkModel = bookmark_model_;
 
   [[consumer_ reject] setCanGoForward:NO];
   [[consumer_ reject] setCanGoBack:NO];
   [[consumer_ reject] setLoadingState:YES];
-  [[consumer_ reject] setPageBookmarked:NO];
 }
 
 // Test no WebstateList related setup is being done on the Toolbar if there's no
@@ -451,11 +277,9 @@
 // Tests the Toolbar is updated when the Webstate observer method
 // DidLoadPageWithSuccess is triggered by OnPageLoaded.
 TEST_F(ToolbarMediatorTest, TestDidLoadPageWithSucess) {
-  SetUpBookmarks();
   mediator_.webStateList = web_state_list_.get();
   SetUpActiveWebState();
   mediator_.consumer = consumer_;
-  mediator_.bookmarkModel = bookmark_model_;
 
   navigation_manager_->set_can_go_forward(true);
   navigation_manager_->set_can_go_back(true);
@@ -465,18 +289,15 @@
 
   [[consumer_ verify] setCanGoForward:YES];
   [[consumer_ verify] setCanGoBack:YES];
-  [[consumer_ verify] setPageBookmarked:YES];
   [[consumer_ verify] setShareMenuEnabled:YES];
 }
 
 // Tests the Toolbar is updated when the Webstate observer method
 // didFinishNavigation is called.
 TEST_F(ToolbarMediatorTest, TestDidFinishNavigation) {
-  SetUpBookmarks();
   mediator_.webStateList = web_state_list_.get();
   SetUpActiveWebState();
   mediator_.consumer = consumer_;
-  mediator_.bookmarkModel = bookmark_model_;
 
   navigation_manager_->set_can_go_forward(true);
   navigation_manager_->set_can_go_back(true);
@@ -487,18 +308,15 @@
 
   [[consumer_ verify] setCanGoForward:YES];
   [[consumer_ verify] setCanGoBack:YES];
-  [[consumer_ verify] setPageBookmarked:YES];
   [[consumer_ verify] setShareMenuEnabled:YES];
 }
 
 // Tests the Toolbar is updated when the Webstate observer method
 // didChangeVisibleSecurityState is called.
 TEST_F(ToolbarMediatorTest, TestDidChangeVisibleSecurityState) {
-  SetUpBookmarks();
   mediator_.webStateList = web_state_list_.get();
   SetUpActiveWebState();
   mediator_.consumer = consumer_;
-  mediator_.bookmarkModel = bookmark_model_;
 
   navigation_manager_->set_can_go_forward(true);
   navigation_manager_->set_can_go_back(true);
@@ -508,7 +326,6 @@
 
   [[consumer_ verify] setCanGoForward:YES];
   [[consumer_ verify] setCanGoBack:YES];
-  [[consumer_ verify] setPageBookmarked:YES];
   [[consumer_ verify] setShareMenuEnabled:YES];
 }
 
@@ -583,11 +400,9 @@
 TEST_F(ToolbarMediatorTest, TestUpdateConsumerForWebState) {
   VoiceSearchProvider provider;
 
-  SetUpBookmarks();
   mediator_.webStateList = web_state_list_.get();
   SetUpActiveWebState();
   mediator_.consumer = consumer_;
-  mediator_.bookmarkModel = bookmark_model_;
 
   auto navigation_manager = std::make_unique<ToolbarTestNavigationManager>();
   navigation_manager->set_can_go_forward(true);
@@ -600,7 +415,6 @@
 
   OCMExpect([consumer_ setCanGoForward:YES]);
   OCMExpect([consumer_ setCanGoBack:YES]);
-  OCMExpect([consumer_ setPageBookmarked:YES]);
   OCMExpect([consumer_ setShareMenuEnabled:YES]);
 
   [mediator_ updateConsumerForWebState:test_web_state.get()];
diff --git a/ios/chrome/test/earl_grey2/BUILD.gn b/ios/chrome/test/earl_grey2/BUILD.gn
index 5be1396..0029f99 100644
--- a/ios/chrome/test/earl_grey2/BUILD.gn
+++ b/ios/chrome/test/earl_grey2/BUILD.gn
@@ -137,6 +137,7 @@
   deps = [
     "//ios/chrome/browser/ui/authentication/signin:eg2_tests",
     "//ios/chrome/browser/ui/authentication/signin/advanced_settings_signin:eg2_tests",
+    "//ios/chrome/browser/ui/authentication/signin/consistency_promo_signin:eg2_tests",
     "//ios/chrome/browser/ui/authentication/signin/user_signin:eg2_tests",
   ]
   data_deps = [ ":ios_chrome_eg2tests" ]
diff --git a/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java b/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java
index 4ab375e4..1f10ffa 100644
--- a/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java
+++ b/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java
@@ -28,7 +28,6 @@
 import android.os.Process;
 import android.provider.Settings;
 
-import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.annotations.CalledByNative;
@@ -557,7 +556,7 @@
         mHasBluetoothPermission = hasPermission(
                 android.Manifest.permission.BLUETOOTH);
 
-        if (BuildInfo.isAtLeastS()) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
             mHasBluetoothPermission &= ApiHelperForS.hasBluetoothConnectPermission();
         }
 
diff --git a/media/cdm/BUILD.gn b/media/cdm/BUILD.gn
index a6ee601..a3e98ae 100644
--- a/media/cdm/BUILD.gn
+++ b/media/cdm/BUILD.gn
@@ -125,6 +125,8 @@
 
   if (is_win) {
     sources += [
+      "cdm_preference_data.cc",
+      "cdm_preference_data.h",
       "win/media_foundation_cdm.cc",
       "win/media_foundation_cdm.h",
       "win/media_foundation_cdm_factory.cc",
diff --git a/media/cdm/cdm_auxiliary_helper.cc b/media/cdm/cdm_auxiliary_helper.cc
index f9db2d8..75d70e7 100644
--- a/media/cdm/cdm_auxiliary_helper.cc
+++ b/media/cdm/cdm_auxiliary_helper.cc
@@ -7,6 +7,11 @@
 #include "media/base/cdm_context.h"
 #include "media/cdm/cdm_helpers.h"
 
+#if defined(OS_WIN)
+#include "media/cdm/cdm_preference_data.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#endif  // defined(OS_WIN)
+
 namespace media {
 
 CdmAuxiliaryHelper::CdmAuxiliaryHelper() = default;
@@ -50,9 +55,13 @@
 }
 
 #if defined(OS_WIN)
-void CdmAuxiliaryHelper::GetCdmOriginId(GetCdmOriginIdCB callback) {
-  std::move(callback).Run(base::UnguessableToken::Null());
+void CdmAuxiliaryHelper::GetCdmPreferenceData(GetCdmPreferenceDataCB callback) {
+  std::move(callback).Run(std::make_unique<CdmPreferenceData>(
+      base::UnguessableToken::Null(), absl::nullopt));
 }
+
+void CdmAuxiliaryHelper::SetCdmClientToken(
+    const std::vector<uint8_t>& client_token) {}
 #endif  // defined(OS_WIN)
 
 }  // namespace media
diff --git a/media/cdm/cdm_auxiliary_helper.h b/media/cdm/cdm_auxiliary_helper.h
index e580a70..3d4cbd0e7 100644
--- a/media/cdm/cdm_auxiliary_helper.h
+++ b/media/cdm/cdm_auxiliary_helper.h
@@ -69,7 +69,8 @@
   void GetStorageId(uint32_t version, StorageIdCB callback) override;
 
 #if defined(OS_WIN)
-  void GetCdmOriginId(GetCdmOriginIdCB callback) override;
+  void GetCdmPreferenceData(GetCdmPreferenceDataCB callback) override;
+  void SetCdmClientToken(const std::vector<uint8_t>& client_token) override;
 #endif  // defined(OS_WIN)
 
  private:
diff --git a/media/cdm/cdm_document_service.h b/media/cdm/cdm_document_service.h
index 180776b..4329bcf 100644
--- a/media/cdm/cdm_document_service.h
+++ b/media/cdm/cdm_document_service.h
@@ -13,6 +13,10 @@
 #include "build/build_config.h"
 #include "media/base/media_export.h"
 
+#if defined(OS_WIN)
+#include "media/cdm/cdm_preference_data.h"
+#endif  // defined(OS_WIN)
+
 namespace media {
 
 class MEDIA_EXPORT CdmDocumentService {
@@ -31,8 +35,10 @@
       base::OnceCallback<void(uint32_t version,
                               const std::vector<uint8_t>& storage_id)>;
 
-  using GetCdmOriginIdCB =
-      base::OnceCallback<void(const base::UnguessableToken&)>;
+#if defined(OS_WIN)
+  using GetCdmPreferenceDataCB =
+      base::OnceCallback<void(std::unique_ptr<CdmPreferenceData>)>;
+#endif  // defined(OS_WIN)
 
   // Allows authorized services to verify that the underlying platform is
   // trusted. An example of a trusted platform is a Chrome OS device in
@@ -60,10 +66,14 @@
   virtual void GetStorageId(uint32_t version, StorageIdCB callback) = 0;
 
 #if defined(OS_WIN)
-  // Gets the origin ID associated with the origin of the CDM. The origin ID is
-  // used in place of the origin when hiding the concrete origin is needed. The
-  // origin ID is also user resettable by clearing the browsing data.
-  virtual void GetCdmOriginId(GetCdmOriginIdCB callback) = 0;
+  // Gets the cdm preference data for the origin associated with the CDM.
+  virtual void GetCdmPreferenceData(GetCdmPreferenceDataCB callback) = 0;
+
+  // Sets the client token for the origin associated with the CDM. The token is
+  // set by the content during license exchange. The token is then saved in the
+  // Pref Service so that it can be reused next time the CDM request a new
+  // license for that origin.
+  virtual void SetCdmClientToken(const std::vector<uint8_t>& client_token) = 0;
 #endif  // defined(OS_WIN)
 };
 
diff --git a/media/cdm/cdm_pref_service.h b/media/cdm/cdm_pref_service.h
deleted file mode 100644
index 99e403c..0000000
--- a/media/cdm/cdm_pref_service.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_CDM_CDM_PREF_SERVICE_H_
-#define MEDIA_CDM_CDM_PREF_SERVICE_H_
-
-#include "base/callback.h"
-#include "base/unguessable_token.h"
-#include "media/base/media_export.h"
-#include "url/origin.h"
-
-namespace media {
-
-// Manages reads and writes to the user prefs service related to CDM usage.
-// The service itself is a per-frame interface that lives in the browser process
-// and will be called by a client living in the utility process hosting the CDM.
-class MEDIA_EXPORT CdmPrefService {
- public:
-  using GetCdmOriginIdCB =
-      base::OnceCallback<void(const base::UnguessableToken&)>;
-
-  CdmPrefService() = default;
-  virtual ~CdmPrefService() = default;
-
-  CdmPrefService(const CdmPrefService&) = delete;
-  CdmPrefService& operator=(const CdmPrefService&) = delete;
-
-  // Gets the origin ID associated with the origin of the CDM. The origin ID is
-  // used in place of the origin when hiding the concrete origin is needed. The
-  // origin ID is also user resettable by clearing the browsing data.
-  virtual void GetCdmOriginId(GetCdmOriginIdCB callback) = 0;
-};
-
-}  // namespace media
-
-#endif  // MEDIA_CDM_CDM_PREF_SERVICE_H_
diff --git a/media/cdm/cdm_preference_data.cc b/media/cdm/cdm_preference_data.cc
new file mode 100644
index 0000000..a3a6a5e
--- /dev/null
+++ b/media/cdm/cdm_preference_data.cc
@@ -0,0 +1,18 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/cdm/cdm_preference_data.h"
+
+namespace media {
+
+CdmPreferenceData::CdmPreferenceData() = default;
+
+CdmPreferenceData::CdmPreferenceData(
+    base::UnguessableToken origin_id,
+    absl::optional<std::vector<uint8_t>> client_token)
+    : origin_id(origin_id), client_token(client_token) {}
+
+CdmPreferenceData::~CdmPreferenceData() = default;
+
+}  // namespace media
diff --git a/media/cdm/cdm_preference_data.h b/media/cdm/cdm_preference_data.h
new file mode 100644
index 0000000..ca418b56
--- /dev/null
+++ b/media/cdm/cdm_preference_data.h
@@ -0,0 +1,30 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_CDM_CDM_PREFERENCE_DATA_H_
+#define MEDIA_CDM_CDM_PREFERENCE_DATA_H_
+
+#include <vector>
+
+#include "base/unguessable_token.h"
+#include "media/base/media_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace media {
+struct MEDIA_EXPORT CdmPreferenceData {
+  CdmPreferenceData();
+  CdmPreferenceData(base::UnguessableToken origin_id,
+                    absl::optional<std::vector<uint8_t>> client_token);
+
+  CdmPreferenceData(const CdmPreferenceData& other) = delete;
+  CdmPreferenceData& operator=(const CdmPreferenceData& other) = delete;
+
+  ~CdmPreferenceData();
+
+  base::UnguessableToken origin_id;
+  absl::optional<std::vector<uint8_t>> client_token;
+};
+}  // namespace media
+
+#endif  // MEDIA_CDM_CDM_PREFERENCE_DATA_H_
diff --git a/media/cdm/mock_helpers.h b/media/cdm/mock_helpers.h
index 8c3c878..8ae30c94 100644
--- a/media/cdm/mock_helpers.h
+++ b/media/cdm/mock_helpers.h
@@ -53,7 +53,10 @@
   void GetStorageId(uint32_t version, StorageIdCB callback) override;
 
 #if defined(OS_WIN)
-  MOCK_METHOD(void, GetCdmOriginId, (GetCdmOriginIdCB callback), (override));
+  MOCK_METHOD(void,
+              GetCdmPreferenceData,
+              (GetCdmPreferenceDataCB callback),
+              (override));
 #endif  // defined(OS_WIN)
 
  private:
diff --git a/media/cdm/win/media_foundation_cdm_factory.cc b/media/cdm/win/media_foundation_cdm_factory.cc
index ebac525e..784b6198 100644
--- a/media/cdm/win/media_foundation_cdm_factory.cc
+++ b/media/cdm/win/media_foundation_cdm_factory.cc
@@ -197,7 +197,7 @@
   DCHECK(cdm_config.allow_distinctive_identifier);
 
   // Don't cache `cdm_origin_id` in this class since user can clear it any time.
-  helper_->GetCdmOriginId(base::BindOnce(
+  helper_->GetCdmPreferenceData(base::BindOnce(
       &MediaFoundationCdmFactory::OnCdmOriginIdObtained,
       weak_factory_.GetWeakPtr(), key_system, cdm_config, session_message_cb,
       session_closed_cb, session_keys_change_cb, session_expiration_update_cb,
@@ -212,8 +212,14 @@
     const SessionKeysChangeCB& session_keys_change_cb,
     const SessionExpirationUpdateCB& session_expiration_update_cb,
     CdmCreatedCB cdm_created_cb,
-    const base::UnguessableToken& cdm_origin_id) {
-  if (cdm_origin_id.is_empty()) {
+    const std::unique_ptr<CdmPreferenceData> cdm_preference_data) {
+  if (!cdm_preference_data) {
+    std::move(cdm_created_cb)
+        .Run(nullptr, "Failed to get the CDM preference data.");
+    return;
+  }
+
+  if (cdm_preference_data->origin_id.is_empty()) {
     std::move(cdm_created_cb).Run(nullptr, "Failed to get the CDM origin ID.");
     return;
   }
@@ -221,7 +227,7 @@
   auto cdm = base::MakeRefCounted<MediaFoundationCdm>(
       base::BindRepeating(&MediaFoundationCdmFactory::CreateMfCdm,
                           weak_factory_.GetWeakPtr(), key_system, cdm_config,
-                          cdm_origin_id),
+                          cdm_preference_data->origin_id),
       base::BindRepeating(&MediaFoundationCdmFactory::IsTypeSupported,
                           weak_factory_.GetWeakPtr(), key_system),
       session_message_cb, session_closed_cb, session_keys_change_cb,
diff --git a/media/cdm/win/media_foundation_cdm_factory.h b/media/cdm/win/media_foundation_cdm_factory.h
index 0bb3880..a56ffc9 100644
--- a/media/cdm/win/media_foundation_cdm_factory.h
+++ b/media/cdm/win/media_foundation_cdm_factory.h
@@ -62,7 +62,7 @@
       const SessionKeysChangeCB& session_keys_change_cb,
       const SessionExpirationUpdateCB& session_expiration_update_cb,
       CdmCreatedCB cdm_created_cb,
-      const base::UnguessableToken& cdm_origin_id);
+      const std::unique_ptr<CdmPreferenceData> cdm_preference_data);
 
   HRESULT GetCdmFactory(
       const std::string& key_system,
diff --git a/media/cdm/win/media_foundation_cdm_factory_unittest.cc b/media/cdm/win/media_foundation_cdm_factory_unittest.cc
index 31073d9..6dbde8d 100644
--- a/media/cdm/win/media_foundation_cdm_factory_unittest.cc
+++ b/media/cdm/win/media_foundation_cdm_factory_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "media/cdm/win/media_foundation_cdm_factory.h"
 
+#include <memory>
+
 #include "base/bind.h"
 #include "base/test/gmock_callback_support.h"
 #include "base/test/mock_callback.h"
@@ -15,6 +17,7 @@
 #include "media/cdm/mock_helpers.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 using base::test::RunOnceCallback;
 using ::testing::_;
@@ -99,8 +102,9 @@
   COM_EXPECT_CALL(mf_cdm_factory_, CreateContentDecryptionModuleAccess(
                                        NotNull(), NotNull(), _, _))
       .WillOnce(DoAll(SetComPointee<3>(mf_cdm_access_.Get()), Return(S_OK)));
-  EXPECT_CALL(*cdm_helper_, GetCdmOriginId(_))
-      .WillOnce(RunOnceCallback<0>(base::UnguessableToken::Create()));
+  EXPECT_CALL(*cdm_helper_, GetCdmPreferenceData(_))
+      .WillOnce(RunOnceCallback<0>(std::make_unique<CdmPreferenceData>(
+          base::UnguessableToken::Create(), absl::nullopt)));
   COM_EXPECT_CALL(mf_cdm_access_, CreateContentDecryptionModule(NotNull(), _))
       .WillOnce(DoAll(SetComPointee<1>(mf_cdm_.Get()), Return(S_OK)));
 
@@ -111,8 +115,9 @@
 TEST_F(MediaFoundationCdmFactoryTest, CreateCdmFactoryFail) {
   SetCreateCdmFactoryCallbackForTesting(/*expect_success=*/false);
 
-  EXPECT_CALL(*cdm_helper_, GetCdmOriginId(_))
-      .WillOnce(RunOnceCallback<0>(base::UnguessableToken::Create()));
+  EXPECT_CALL(*cdm_helper_, GetCdmPreferenceData(_))
+      .WillOnce(RunOnceCallback<0>(std::make_unique<CdmPreferenceData>(
+          base::UnguessableToken::Create(), absl::nullopt)));
 
   EXPECT_CALL(cdm_created_cb_, Run(IsNull(), _));
   Create();
@@ -123,8 +128,9 @@
 
   COM_EXPECT_CALL(mf_cdm_factory_, IsTypeSupported(NotNull(), IsNull()))
       .WillOnce(Return(FALSE));
-  EXPECT_CALL(*cdm_helper_, GetCdmOriginId(_))
-      .WillOnce(RunOnceCallback<0>(base::UnguessableToken::Create()));
+  EXPECT_CALL(*cdm_helper_, GetCdmPreferenceData(_))
+      .WillOnce(RunOnceCallback<0>(std::make_unique<CdmPreferenceData>(
+          base::UnguessableToken::Create(), absl::nullopt)));
 
   EXPECT_CALL(cdm_created_cb_, Run(IsNull(), _));
   Create();
@@ -138,8 +144,9 @@
   COM_EXPECT_CALL(mf_cdm_factory_, CreateContentDecryptionModuleAccess(
                                        NotNull(), NotNull(), _, _))
       .WillOnce(Return(E_FAIL));
-  EXPECT_CALL(*cdm_helper_, GetCdmOriginId(_))
-      .WillOnce(RunOnceCallback<0>(base::UnguessableToken::Create()));
+  EXPECT_CALL(*cdm_helper_, GetCdmPreferenceData(_))
+      .WillOnce(RunOnceCallback<0>(std::make_unique<CdmPreferenceData>(
+          base::UnguessableToken::Create(), absl::nullopt)));
 
   EXPECT_CALL(cdm_created_cb_, Run(IsNull(), _));
   Create();
@@ -148,8 +155,9 @@
 TEST_F(MediaFoundationCdmFactoryTest, NullCdmOriginIdFail) {
   SetCreateCdmFactoryCallbackForTesting(/*expect_success=*/true);
 
-  EXPECT_CALL(*cdm_helper_, GetCdmOriginId(_))
-      .WillOnce(RunOnceCallback<0>(base::UnguessableToken::Null()));
+  EXPECT_CALL(*cdm_helper_, GetCdmPreferenceData(_))
+      .WillOnce(RunOnceCallback<0>(std::make_unique<CdmPreferenceData>(
+          base::UnguessableToken::Null(), absl::nullopt)));
 
   EXPECT_CALL(cdm_created_cb_, Run(IsNull(), _));
   Create();
@@ -163,8 +171,9 @@
   COM_EXPECT_CALL(mf_cdm_factory_, CreateContentDecryptionModuleAccess(
                                        NotNull(), NotNull(), _, _))
       .WillOnce(DoAll(SetComPointee<3>(mf_cdm_access_.Get()), Return(S_OK)));
-  EXPECT_CALL(*cdm_helper_, GetCdmOriginId(_))
-      .WillOnce(RunOnceCallback<0>(base::UnguessableToken::Create()));
+  EXPECT_CALL(*cdm_helper_, GetCdmPreferenceData(_))
+      .WillOnce(RunOnceCallback<0>(std::make_unique<CdmPreferenceData>(
+          base::UnguessableToken::Create(), absl::nullopt)));
   COM_EXPECT_CALL(mf_cdm_access_, CreateContentDecryptionModule(NotNull(), _))
       .WillOnce(DoAll(SetComPointee<1>(mf_cdm_.Get()), Return(E_FAIL)));
 
diff --git a/media/filters/h264_bitstream_buffer.cc b/media/filters/h264_bitstream_buffer.cc
index ea18646..3d6a8c9a 100644
--- a/media/filters/h264_bitstream_buffer.cc
+++ b/media/filters/h264_bitstream_buffer.cc
@@ -50,7 +50,7 @@
   reg_ = base::HostToNet64(reg_);
 
   // Make sure we have enough space. Grow() will CHECK() on allocation failure.
-  if (pos_ + bytes_in_reg < capacity_)
+  if (pos_ + bytes_in_reg > capacity_)
     Grow();
 
   memcpy(data_ + pos_, &reg_, bytes_in_reg);
diff --git a/media/gpu/mac/vt_video_decode_accelerator_mac.cc b/media/gpu/mac/vt_video_decode_accelerator_mac.cc
index 89d91a1..428dd4e8 100644
--- a/media/gpu/mac/vt_video_decode_accelerator_mac.cc
+++ b/media/gpu/mac/vt_video_decode_accelerator_mac.cc
@@ -1586,22 +1586,13 @@
       gl_params.is_cleared = true;
       gpu::SharedImageBackingGLCommon::UnpackStateAttribs gl_attribs;
 
-      // A GL texture id is needed to create the legacy mailbox, which requires
-      // that the GL context be made current.
-      const bool kCreateLegacyMailbox = true;
-      if (!gl_client_.make_context_current.Run()) {
-        DLOG(ERROR) << "Failed to make context current";
-        NotifyError(PLATFORM_FAILURE, SFT_PLATFORM_ERROR);
-        return false;
-      }
-
       auto shared_image = std::make_unique<gpu::SharedImageBackingGLImage>(
           gl_image, mailbox, viz_resource_format, plane_size, color_space,
           kTopLeft_GrSurfaceOrigin, kOpaque_SkAlphaType, shared_image_usage,
           gl_params, gl_attribs, gl_client_.is_passthrough);
 
       const bool success = shared_image_stub->factory()->RegisterBacking(
-          std::move(shared_image), kCreateLegacyMailbox);
+          std::move(shared_image), /*allow_legacy_mailbox=*/false);
       if (!success) {
         DLOG(ERROR) << "Failed to register shared image";
         NotifyError(PLATFORM_FAILURE, SFT_PLATFORM_ERROR);
diff --git a/media/gpu/test/video_encoder/decoder_buffer_validator.cc b/media/gpu/test/video_encoder/decoder_buffer_validator.cc
index d083670..7e30725 100644
--- a/media/gpu/test/video_encoder/decoder_buffer_validator.cc
+++ b/media/gpu/test/video_encoder/decoder_buffer_validator.cc
@@ -354,9 +354,9 @@
   }
 
   if (metadata.vp9 &&
-      metadata.vp9->has_reference != !metadata.vp9->p_diffs.empty()) {
-    LOG(ERROR)
-        << "Inconsistent metadata, has_reference implies p_diffs is non-empty.";
+      metadata.vp9->inter_pic_predicted != !metadata.vp9->p_diffs.empty()) {
+    LOG(ERROR) << "Inconsistent metadata, inter_pic_predicted implies p_diffs "
+                  "is non-empty.";
     return false;
   }
 
diff --git a/media/gpu/test/video_encoder/video_encoder_client.cc b/media/gpu/test/video_encoder/video_encoder_client.cc
index d3d2cc7..14d91ab 100644
--- a/media/gpu/test/video_encoder/video_encoder_client.cc
+++ b/media/gpu/test/video_encoder/video_encoder_client.cc
@@ -376,6 +376,12 @@
       current_stats_.num_encoded_frames_per_layer[spatial_id][temporal_id]++;
       current_stats_.encoded_frames_size_per_layer[spatial_id][temporal_id] +=
           metadata.payload_size_bytes;
+    } else if (metadata.h264.has_value()) {
+      uint8_t temporal_id = metadata.h264->temporal_idx;
+      ASSERT_EQ(current_stats_.num_spatial_layers, 1u);
+      current_stats_.num_encoded_frames_per_layer[0][temporal_id]++;
+      current_stats_.encoded_frames_size_per_layer[0][temporal_id] +=
+          metadata.payload_size_bytes;
     }
   }
 
diff --git a/media/gpu/vaapi/vp9_svc_layers.cc b/media/gpu/vaapi/vp9_svc_layers.cc
index 2419530..3c0f411 100644
--- a/media/gpu/vaapi/vp9_svc_layers.cc
+++ b/media/gpu/vaapi/vp9_svc_layers.cc
@@ -388,9 +388,10 @@
                                          temporal_pattern_size_]
           .layer_index();
   // If |frame_num_| is zero, it refers only lower spatial layer.
-  // |has_reference| is true if a frame in the same spatial layer is referred.
+  // |inter_pic_predicted| is true if a frame in the same spatial layer is
+  // referred.
   if (frame_num_ != 0)
-    metadata->has_reference = !reference_frame_indices.empty();
+    metadata->inter_pic_predicted = !reference_frame_indices.empty();
   metadata->reference_lower_spatial_layers =
       frame_num_ == 0 && (spatial_idx_ != 0);
   metadata->temporal_idx = temp_temporal_layers_id;
diff --git a/media/gpu/vaapi/vp9_svc_layers_unittest.cc b/media/gpu/vaapi/vp9_svc_layers_unittest.cc
index aadcd46..fb01ff9 100644
--- a/media/gpu/vaapi/vp9_svc_layers_unittest.cc
+++ b/media/gpu/vaapi/vp9_svc_layers_unittest.cc
@@ -152,8 +152,8 @@
       true, false, false};
   EXPECT_EQ(frame_hdr.refresh_frame_flags & ~(0b111111u), 0u);
   EXPECT_EQ(ref_frames_used, kExpectedRefFramesUsed);
-  EXPECT_EQ(metadata.has_reference, !metadata.p_diffs.empty());
-  EXPECT_EQ(metadata.has_reference, !key_pic);
+  EXPECT_EQ(metadata.inter_pic_predicted, !metadata.p_diffs.empty());
+  EXPECT_EQ(metadata.inter_pic_predicted, !key_pic);
   if (key_pic) {
     EXPECT_TRUE(metadata.reference_lower_spatial_layers);
     EXPECT_EQ(metadata.referenced_by_upper_spatial_layers,
diff --git a/media/mojo/mojom/BUILD.gn b/media/mojo/mojom/BUILD.gn
index 2236d42e..cd3ee69 100644
--- a/media/mojo/mojom/BUILD.gn
+++ b/media/mojo/mojom/BUILD.gn
@@ -621,6 +621,23 @@
     ]
   }
 
+  if (is_win) {
+    cpp_typemaps += [
+      {
+        types = [
+          {
+            mojom = "media.mojom.CdmPreferenceData"
+            cpp = "::std::unique_ptr<::media::CdmPreferenceData>"
+            move_only = true
+          },
+        ]
+        traits_headers = [ "cdm_preference_data_mojom_traits.h" ]
+        traits_sources = [ "cdm_preference_data_mojom_traits.cc" ]
+        traits_public_deps = [ "//media" ]
+      },
+    ]
+  }
+
   cpp_typemaps += shared_typemaps
   blink_cpp_typemaps = shared_typemaps
 
diff --git a/media/mojo/mojom/cdm_document_service.mojom b/media/mojo/mojom/cdm_document_service.mojom
index e395693..524becd 100644
--- a/media/mojo/mojom/cdm_document_service.mojom
+++ b/media/mojo/mojom/cdm_document_service.mojom
@@ -7,6 +7,20 @@
 
 import "mojo/public/mojom/base/unguessable_token.mojom";
 
+// Contains data linked to an origin that the CDM stores in the Preference
+// Service.
+[EnableIf=is_win]
+struct CdmPreferenceData {
+  // The origin ID of the document associated with the CDM. The origin ID
+  // is used in place of the origin when hiding the concrete origin is needed.
+  // The origin ID is also user resettable by clearing the browsing data.
+  mojo_base.mojom.UnguessableToken origin_id;
+
+  // The token is set by the CDM. The token is then saved in the Pref Service so
+  // that it can be reused by the CDM for that same origin in the future.
+  array<uint8>? client_token;
+};
+
 // The service itself is associated with a RenderFrameHost in the browser
 // process and will be called by a client living in the utility process hosting
 // the CDM.
@@ -41,10 +55,15 @@
   [EnableIf=is_chromeos_ash]
   IsVerifiedAccessEnabled() => (bool enabled);
 
-  // Gets the origin ID of the frame associated with the CDM. The origin ID is
-  // used in place of the origin when hiding the concrete origin is needed. The
-  // origin ID is also user resettable by clearing the browsing data.
-  // - `cdm_origin_id`: The origin id of the frame associated with the CDM.
+  // Gets the CDM preference data for the origin associated with the CDM.
+  // - `cdm_preference_data`: The CDM preference data for the origin associated
+  //                          with the CDM.
   [EnableIf=is_win]
-  GetCdmOriginId() => (mojo_base.mojom.UnguessableToken cdm_origin_id);
+  GetCdmPreferenceData() => (CdmPreferenceData cdm_preference_data);
+
+  // Sets the client token for the origin associated with the CDM. The token is
+  // set by the CDM. The token is then saved in the Pref Service so that it can
+  // be reused by the CDM for that same origin in the future.
+  [EnableIf=is_win]
+  SetCdmClientToken(array<uint8> client_token);
 };
diff --git a/media/mojo/mojom/cdm_preference_data_mojom_traits.cc b/media/mojo/mojom/cdm_preference_data_mojom_traits.cc
new file mode 100644
index 0000000..8d098f9
--- /dev/null
+++ b/media/mojo/mojom/cdm_preference_data_mojom_traits.cc
@@ -0,0 +1,28 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/mojo/mojom/cdm_preference_data_mojom_traits.h"
+
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace mojo {
+
+// static
+bool StructTraits<media::mojom::CdmPreferenceDataDataView,
+                  std::unique_ptr<media::CdmPreferenceData>>::
+    Read(media::mojom::CdmPreferenceDataDataView input,
+         std::unique_ptr<media::CdmPreferenceData>* output) {
+  base::UnguessableToken origin_id;
+  if (!input.ReadOriginId(&origin_id))
+    return false;
+
+  absl::optional<std::vector<uint8_t>> client_token;
+  if (!input.ReadClientToken(&client_token))
+    return false;
+
+  *output = std::make_unique<media::CdmPreferenceData>(origin_id, client_token);
+  return true;
+}
+
+}  // namespace mojo
diff --git a/media/mojo/mojom/cdm_preference_data_mojom_traits.h b/media/mojo/mojom/cdm_preference_data_mojom_traits.h
new file mode 100644
index 0000000..426668f
--- /dev/null
+++ b/media/mojo/mojom/cdm_preference_data_mojom_traits.h
@@ -0,0 +1,35 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_MOJO_MOJOM_CDM_PREFERENCE_DATA_MOJOM_TRAITS_H_
+#define MEDIA_MOJO_MOJOM_CDM_PREFERENCE_DATA_MOJOM_TRAITS_H_
+
+#include <vector>
+
+#include "base/unguessable_token.h"
+#include "media/cdm/cdm_preference_data.h"
+#include "media/mojo/mojom/cdm_document_service.mojom.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<media::mojom::CdmPreferenceDataDataView,
+                    std::unique_ptr<media::CdmPreferenceData>> {
+  static base::UnguessableToken origin_id(
+      const std::unique_ptr<media::CdmPreferenceData>& input) {
+    return input->origin_id;
+  }
+
+  static absl::optional<std::vector<uint8_t>> client_token(
+      const std::unique_ptr<media::CdmPreferenceData>& input) {
+    return input->client_token;
+  }
+
+  static bool Read(media::mojom::CdmPreferenceDataDataView input,
+                   std::unique_ptr<media::CdmPreferenceData>* output);
+};
+
+}  // namespace mojo
+
+#endif  // MEDIA_MOJO_MOJOM_CDM_PREFERENCE_DATA_MOJOM_TRAITS_H_
diff --git a/media/mojo/mojom/video_encode_accelerator.mojom b/media/mojo/mojom/video_encode_accelerator.mojom
index b1f7a69..a9f5c40 100644
--- a/media/mojo/mojom/video_encode_accelerator.mojom
+++ b/media/mojo/mojom/video_encode_accelerator.mojom
@@ -201,7 +201,7 @@
 };
 
 struct Vp9Metadata {
-  bool has_reference;
+  bool inter_pic_predicted;
   bool temporal_up_switch;
   bool referenced_by_upper_spatial_layers;
   bool reference_lower_spatial_layers;
diff --git a/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc b/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc
index 10c3fd1..e1bdbd7 100644
--- a/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc
+++ b/media/mojo/mojom/video_encode_accelerator_mojom_traits.cc
@@ -167,7 +167,7 @@
 bool StructTraits<media::mojom::Vp9MetadataDataView, media::Vp9Metadata>::Read(
     media::mojom::Vp9MetadataDataView data,
     media::Vp9Metadata* out_metadata) {
-  out_metadata->has_reference = data.has_reference();
+  out_metadata->inter_pic_predicted = data.inter_pic_predicted();
   out_metadata->temporal_up_switch = data.temporal_up_switch();
   out_metadata->referenced_by_upper_spatial_layers =
       data.referenced_by_upper_spatial_layers();
diff --git a/media/mojo/mojom/video_encode_accelerator_mojom_traits.h b/media/mojo/mojom/video_encode_accelerator_mojom_traits.h
index 99bf03b1..e5cadb2 100644
--- a/media/mojo/mojom/video_encode_accelerator_mojom_traits.h
+++ b/media/mojo/mojom/video_encode_accelerator_mojom_traits.h
@@ -173,8 +173,8 @@
 template <>
 class StructTraits<media::mojom::Vp9MetadataDataView, media::Vp9Metadata> {
  public:
-  static bool has_reference(const media::Vp9Metadata& vp9) {
-    return vp9.has_reference;
+  static bool inter_pic_predicted(const media::Vp9Metadata& vp9) {
+    return vp9.inter_pic_predicted;
   }
   static bool temporal_up_switch(const media::Vp9Metadata& vp9) {
     return vp9.temporal_up_switch;
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 0dbc1ca..0322bb6 100644
--- a/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc
+++ b/media/mojo/mojom/video_encode_accelerator_mojom_traits_unittest.cc
@@ -135,7 +135,7 @@
   input_metadata.vp8.reset();
 
   Vp9Metadata vp9;
-  vp9.has_reference = true;
+  vp9.inter_pic_predicted = true;
   vp9.temporal_up_switch = true;
   vp9.referenced_by_upper_spatial_layers = true;
   vp9.reference_lower_spatial_layers = true;
diff --git a/media/mojo/services/mojo_cdm_helper.cc b/media/mojo/services/mojo_cdm_helper.cc
index ed5a312..ab559d9a 100644
--- a/media/mojo/services/mojo_cdm_helper.cc
+++ b/media/mojo/services/mojo_cdm_helper.cc
@@ -50,9 +50,15 @@
 }
 
 #if defined(OS_WIN)
-void MojoCdmHelper::GetCdmOriginId(GetCdmOriginIdCB callback) {
+void MojoCdmHelper::GetCdmPreferenceData(GetCdmPreferenceDataCB callback) {
   ConnectToCdmDocumentService();
-  cdm_document_service_->GetCdmOriginId(std::move(callback));
+  cdm_document_service_->GetCdmPreferenceData(std::move(callback));
+}
+
+void MojoCdmHelper::SetCdmClientToken(
+    const std::vector<uint8_t>& client_token) {
+  ConnectToCdmDocumentService();
+  cdm_document_service_->SetCdmClientToken(client_token);
 }
 #endif  // defined(OS_WIN)
 
diff --git a/media/mojo/services/mojo_cdm_helper.h b/media/mojo/services/mojo_cdm_helper.h
index 4e889a2..4522a8e3 100644
--- a/media/mojo/services/mojo_cdm_helper.h
+++ b/media/mojo/services/mojo_cdm_helper.h
@@ -48,7 +48,8 @@
                          ChallengePlatformCB callback) final;
   void GetStorageId(uint32_t version, StorageIdCB callback) final;
 #if defined(OS_WIN)
-  void GetCdmOriginId(GetCdmOriginIdCB callback) final;
+  void GetCdmPreferenceData(GetCdmPreferenceDataCB callback) final;
+  void SetCdmClientToken(const std::vector<uint8_t>& client_token) final;
 #endif  // defined(OS_WIN)
 
   // MojoCdmFileIO::Delegate implementation.
diff --git a/media/remoting/proto_enum_utils.cc b/media/remoting/proto_enum_utils.cc
index 1bd578b..ce46988 100644
--- a/media/remoting/proto_enum_utils.cc
+++ b/media/remoting/proto_enum_utils.cc
@@ -34,8 +34,9 @@
     CASE_RETURN_OTHER(kCodecALAC);
     CASE_RETURN_OTHER(kCodecAC3);
     CASE_RETURN_OTHER(kCodecMpegHAudio);
+    default:
+      return absl::nullopt;
   }
-  return absl::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 absl::optional<openscreen::cast::AudioDecoderConfig::Codec>
@@ -61,8 +62,9 @@
     CASE_RETURN_OTHER(kCodecALAC);
     CASE_RETURN_OTHER(kCodecAC3);
     CASE_RETURN_OTHER(kCodecMpegHAudio);
+    default:
+      return absl::nullopt;
   }
-  return absl::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 absl::optional<SampleFormat> ToMediaSampleFormat(
@@ -75,6 +77,7 @@
     CASE_RETURN_OTHER(kSampleFormatS16);
     CASE_RETURN_OTHER(kSampleFormatS32);
     CASE_RETURN_OTHER(kSampleFormatF32);
+    CASE_RETURN_OTHER(kSampleFormatPlanarU8);
     CASE_RETURN_OTHER(kSampleFormatPlanarS16);
     CASE_RETURN_OTHER(kSampleFormatPlanarF32);
     CASE_RETURN_OTHER(kSampleFormatPlanarS32);
@@ -83,11 +86,8 @@
     CASE_RETURN_OTHER(kSampleFormatEac3);
     CASE_RETURN_OTHER(kSampleFormatMpegHAudio);
     default:
-      // TODO(crbug.com/1231734): Update this to return kSampleFormatPlanarU8
-      // when it is added to Open Screen.
       return absl::nullopt;
   }
-  return absl::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 absl::optional<openscreen::cast::AudioDecoderConfig::SampleFormat>
@@ -100,6 +100,7 @@
     CASE_RETURN_OTHER(kSampleFormatS16);
     CASE_RETURN_OTHER(kSampleFormatS32);
     CASE_RETURN_OTHER(kSampleFormatF32);
+    CASE_RETURN_OTHER(kSampleFormatPlanarU8);
     CASE_RETURN_OTHER(kSampleFormatPlanarS16);
     CASE_RETURN_OTHER(kSampleFormatPlanarF32);
     CASE_RETURN_OTHER(kSampleFormatPlanarS32);
@@ -107,12 +108,9 @@
     CASE_RETURN_OTHER(kSampleFormatAc3);
     CASE_RETURN_OTHER(kSampleFormatEac3);
     CASE_RETURN_OTHER(kSampleFormatMpegHAudio);
-    case kSampleFormatPlanarU8:
-      // TODO(crbug.com/1231734): Update this to return the right format when
-      // it is added to Open Screen.
+    default:
       return absl::nullopt;
   }
-  return absl::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 absl::optional<ChannelLayout> ToMediaChannelLayout(
@@ -153,8 +151,9 @@
     CASE_RETURN_OTHER(CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC);
     CASE_RETURN_OTHER(CHANNEL_LAYOUT_4_1_QUAD_SIDE);
     CASE_RETURN_OTHER(CHANNEL_LAYOUT_BITSTREAM);
+    default:
+      return absl::nullopt;
   }
-  return absl::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 absl::optional<openscreen::cast::AudioDecoderConfig::ChannelLayout>
@@ -195,8 +194,9 @@
     CASE_RETURN_OTHER(CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC);
     CASE_RETURN_OTHER(CHANNEL_LAYOUT_4_1_QUAD_SIDE);
     CASE_RETURN_OTHER(CHANNEL_LAYOUT_BITSTREAM);
+    default:
+      return absl::nullopt;
   }
-  return absl::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 absl::optional<VideoCodec> ToMediaVideoCodec(
@@ -215,8 +215,9 @@
     CASE_RETURN_OTHER(kCodecHEVC);
     CASE_RETURN_OTHER(kCodecDolbyVision);
     CASE_RETURN_OTHER(kCodecAV1);
+    default:
+      return absl::nullopt;
   }
-  return absl::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 absl::optional<openscreen::cast::VideoDecoderConfig::Codec>
@@ -235,8 +236,9 @@
     CASE_RETURN_OTHER(kCodecHEVC);
     CASE_RETURN_OTHER(kCodecDolbyVision);
     CASE_RETURN_OTHER(kCodecAV1);
+    default:
+      return absl::nullopt;
   }
-  return absl::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 absl::optional<VideoCodecProfile> ToMediaVideoCodecProfile(
@@ -274,8 +276,9 @@
     CASE_RETURN_OTHER(AV1PROFILE_PROFILE_MAIN);
     CASE_RETURN_OTHER(AV1PROFILE_PROFILE_HIGH);
     CASE_RETURN_OTHER(AV1PROFILE_PROFILE_PRO);
+    default:
+      return absl::nullopt;
   }
-  return absl::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 absl::optional<openscreen::cast::VideoDecoderConfig::Profile>
@@ -313,8 +316,9 @@
     CASE_RETURN_OTHER(AV1PROFILE_PROFILE_MAIN);
     CASE_RETURN_OTHER(AV1PROFILE_PROFILE_HIGH);
     CASE_RETURN_OTHER(AV1PROFILE_PROFILE_PRO);
+    default:
+      return absl::nullopt;
   }
-  return absl::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 absl::optional<VideoPixelFormat> ToMediaVideoPixelFormat(
@@ -353,8 +357,9 @@
     // PIXEL_FORMAT_UYVY, PIXEL_FORMAT_RGB32 and PIXEL_FORMAT_Y8 are deprecated.
     case openscreen::cast::VideoDecoderConfig_Format_PIXEL_FORMAT_RGB32:
       return absl::nullopt;
+    default:
+      return absl::nullopt;
   }
-  return absl::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 absl::optional<BufferingState> ToMediaBufferingState(
@@ -364,8 +369,9 @@
   switch (value) {
     CASE_RETURN_OTHER(BUFFERING_HAVE_NOTHING);
     CASE_RETURN_OTHER(BUFFERING_HAVE_ENOUGH);
+    default:
+      return absl::nullopt;
   }
-  return absl::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 absl::optional<openscreen::cast::RendererClientOnBufferingStateChange::State>
@@ -375,8 +381,9 @@
   switch (value) {
     CASE_RETURN_OTHER(BUFFERING_HAVE_NOTHING);
     CASE_RETURN_OTHER(BUFFERING_HAVE_ENOUGH);
+    default:
+      return absl::nullopt;
   }
-  return absl::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 absl::optional<DemuxerStream::Status> ToDemuxerStreamStatus(
@@ -388,8 +395,9 @@
     CASE_RETURN_OTHER(kAborted);
     CASE_RETURN_OTHER(kConfigChanged);
     CASE_RETURN_OTHER(kError);
+    default:
+      return absl::nullopt;
   }
-  return absl::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 absl::optional<openscreen::cast::DemuxerStreamReadUntilCallback::Status>
@@ -401,8 +409,9 @@
     CASE_RETURN_OTHER(kAborted);
     CASE_RETURN_OTHER(kConfigChanged);
     CASE_RETURN_OTHER(kError);
+    default:
+      return absl::nullopt;
   }
-  return absl::nullopt;  // Not a 'default' to ensure compile-time checks.
 }
 
 }  // namespace remoting
diff --git a/media/video/h264_parser.cc b/media/video/h264_parser.cc
index c972658..1e7a658 100644
--- a/media/video/h264_parser.cc
+++ b/media/video/h264_parser.cc
@@ -4,6 +4,7 @@
 
 #include "media/video/h264_parser.h"
 
+#include <cstring>
 #include <limits>
 #include <memory>
 
diff --git a/media/video/video_encode_accelerator.cc b/media/video/video_encode_accelerator.cc
index 81f8036..7480f62 100644
--- a/media/video/video_encode_accelerator.cc
+++ b/media/video/video_encode_accelerator.cc
@@ -201,7 +201,7 @@
 }
 
 bool operator==(const Vp9Metadata& l, const Vp9Metadata& r) {
-  return l.has_reference == r.has_reference &&
+  return l.inter_pic_predicted == r.inter_pic_predicted &&
          l.temporal_up_switch == r.temporal_up_switch &&
          l.referenced_by_upper_spatial_layers ==
              r.referenced_by_upper_spatial_layers &&
diff --git a/media/video/video_encode_accelerator.h b/media/video/video_encode_accelerator.h
index 7414091..d275b40 100644
--- a/media/video/video_encode_accelerator.h
+++ b/media/video/video_encode_accelerator.h
@@ -68,8 +68,7 @@
   Vp9Metadata(const Vp9Metadata&);
 
   // True iff this layer frame is dependent on previously coded frame(s).
-  // TODO: rename |has_reference| to |inter_pic_predicted| follow webrtc.
-  bool has_reference = false;
+  bool inter_pic_predicted = false;
   // True iff this frame only references TL0 frames.
   bool temporal_up_switch = false;
   // True iff frame is referenced by upper spatial layer frame.
diff --git a/mojo/core/channel_mac.cc b/mojo/core/channel_mac.cc
index 71877bf..51ccc32 100644
--- a/mojo/core/channel_mac.cc
+++ b/mojo/core/channel_mac.cc
@@ -284,6 +284,14 @@
       return false;
     }
 
+    send_port_ = base::mac::ScopedMachSendRight(message->msgh_remote_port);
+
+    if (!RequestSendDeadNameNotification()) {
+      send_port_.reset();
+      OnError(Error::kConnectionFailed);
+      return false;
+    }
+
     // Record the audit token of the sender. All messages received by the
     // channel must be from this same sender.
     auto* trailer = buffer.Object<mach_msg_audit_trailer_t>();
@@ -291,13 +299,6 @@
     memcpy(peer_audit_token_.get(), &trailer->msgh_audit,
            sizeof(audit_token_t));
 
-    send_port_ = base::mac::ScopedMachSendRight(message->msgh_remote_port);
-
-    if (!RequestSendDeadNameNotification()) {
-      OnError(Error::kConnectionFailed);
-      return false;
-    }
-
     base::AutoLock lock(write_lock_);
     handshake_done_ = true;
     SendPendingMessagesLocked();
diff --git a/native_client_sdk/src/build_tools/build_version.py b/native_client_sdk/src/build_tools/build_version.py
index 08a5959..8c527920 100644
--- a/native_client_sdk/src/build_tools/build_version.py
+++ b/native_client_sdk/src/build_tools/build_version.py
@@ -30,7 +30,7 @@
   '''
   info = FetchCommitPosition()
   _, ref, revision = ParseCommitPosition(info.revision)
-  if ref == 'refs/heads/master':
+  if ref in ['refs/heads/master', 'refs/heads/main']:
     return 'trunk.%s' % revision
   return ChromeVersionNoTrunk()
 
@@ -75,7 +75,7 @@
 
   Returns:
     A value like:
-    0178d4831bd36b5fb9ff477f03dc43b11626a6dc-refs/heads/master@{#292238}
+    0178d4831bd36b5fb9ff477f03dc43b11626a6dc-refs/heads/main@{#292238}
   '''
   return FetchCommitPosition().revision
 
@@ -125,9 +125,9 @@
   '''Parse a Chrome commit position into its components.
 
   Given a commit position like:
-    0178d4831bd36b5fb9ff477f03dc43b11626a6dc-refs/heads/master@{#292238}
+    0178d4831bd36b5fb9ff477f03dc43b11626a6dc-refs/heads/main@{#292238}
   Returns:
-    ("0178d4831bd36b5fb9ff477f03dc43b11626a6dc", "refs/heads/master", "292238")
+    ("0178d4831bd36b5fb9ff477f03dc43b11626a6dc", "refs/heads/main", "292238")
   '''
   m = re.match(r'([0-9a-fA-F]+)(?:-([^@]+)@{#(\d+)})?', commit_position)
   if m:
diff --git a/net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java b/net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java
index cb46c77..13c92cc 100644
--- a/net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java
+++ b/net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java
@@ -28,7 +28,6 @@
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.CalledByNativeUnchecked;
@@ -215,7 +214,7 @@
      */
     private static WifiInfo getWifiInfo() {
         if (haveAccessWifiState()) {
-            if (BuildInfo.isAtLeastS()) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                 // On Android S+, need to use NetworkCapabilities to get the WifiInfo.
                 ConnectivityManager connectivityManager =
                         (ConnectivityManager) ContextUtils.getApplicationContext().getSystemService(
diff --git a/remoting/host/BUILD.gn b/remoting/host/BUILD.gn
index 8efc776..82763d69 100644
--- a/remoting/host/BUILD.gn
+++ b/remoting/host/BUILD.gn
@@ -174,46 +174,6 @@
   ]
 }
 
-source_set("headers") {
-  sources = [
-    "action_executor.h",
-    "audio_capturer.h",
-    "audio_silence_detector.h",
-    "audio_volume_filter.h",
-    "basic_desktop_environment.h",
-    "chromoting_messages.h",
-    "chromoting_param_traits.h",
-    "desktop_and_cursor_conditional_composer.h",
-    "desktop_environment.h",
-    "desktop_environment_options.h",
-    "evaluate_capability.h",
-    "host_export.h",
-    "input_injector.h",
-    "sas_injector.h",
-    "screen_controls.h",
-    "screen_resolution.h",
-  ]
-  if (is_win) {
-    sources += [
-      "host_main.h",
-      "worker_process_ipc_delegate.h",
-    ]
-  }
-  if (!is_chromeos_ash) {
-    sources += [ "me2me_desktop_environment.h" ]
-  }
-
-  deps = [
-    "//base",
-    "//ipc",
-    "//remoting/base",
-    "//remoting/protocol",
-  ]
-  if (is_win) {
-    deps += [ "//remoting/host/win:headers" ]
-  }
-}
-
 # This must be a static library instead of a source set because
 # remoting_unittests requires that remoting_me2me_host.cc not be pulled in,
 # which in turn depends on remoting_me2me_host_static which isn't part of that
@@ -224,14 +184,19 @@
 static_library("common") {
   sources = [
     "action_executor.cc",
+    "action_executor.h",
     "action_message_handler.cc",
     "action_message_handler.h",
     "audio_capturer.cc",
+    "audio_capturer.h",
     "audio_silence_detector.cc",
+    "audio_silence_detector.h",
     "audio_volume_filter.cc",
+    "audio_volume_filter.h",
     "backoff_timer.cc",
     "backoff_timer.h",
     "basic_desktop_environment.cc",
+    "basic_desktop_environment.h",
     "branding.cc",
     "branding.h",
     "chromoting_host.cc",
@@ -239,7 +204,9 @@
     "chromoting_host_context.cc",
     "chromoting_host_context.h",
     "chromoting_messages.cc",
+    "chromoting_messages.h",
     "chromoting_param_traits.cc",
+    "chromoting_param_traits.h",
     "chromoting_param_traits_impl.h",
     "client_session.cc",
     "client_session.h",
@@ -256,6 +223,7 @@
     "desktop_and_cursor_composer_notifier.cc",
     "desktop_and_cursor_composer_notifier.h",
     "desktop_and_cursor_conditional_composer.cc",
+    "desktop_and_cursor_conditional_composer.h",
     "desktop_capturer_checker.cc",
     "desktop_capturer_checker.h",
     "desktop_capturer_proxy.cc",
@@ -263,7 +231,9 @@
     "desktop_display_info.cc",
     "desktop_display_info.h",
     "desktop_display_info_loader.h",
+    "desktop_environment.h",
     "desktop_environment_options.cc",
+    "desktop_environment_options.h",
     "desktop_process.cc",
     "desktop_process.h",
     "desktop_resizer.h",
@@ -275,6 +245,7 @@
     "desktop_session_proxy.cc",
     "desktop_session_proxy.h",
     "evaluate_capability.cc",
+    "evaluate_capability.h",
     "forward_process_stats_agent.cc",
     "forward_process_stats_agent.h",
     "ftl_echo_message_listener.cc",
@@ -294,6 +265,7 @@
     "host_event_logger.h",
     "host_experiment_session_plugin.cc",
     "host_experiment_session_plugin.h",
+    "host_export.h",
     "host_extension_session_manager.cc",
     "host_extension_session_manager.h",
     "host_power_save_blocker.cc",
@@ -309,6 +281,7 @@
     "host_window.h",
     "host_window_proxy.cc",
     "host_window_proxy.h",
+    "input_injector.h",
     "ipc_action_executor.cc",
     "ipc_action_executor.h",
     "ipc_audio_capturer.cc",
@@ -360,7 +333,10 @@
     "resizing_host_observer.cc",
     "resizing_host_observer.h",
     "resources.h",
+    "sas_injector.h",
+    "screen_controls.h",
     "screen_resolution.cc",
+    "screen_resolution.h",
     "server_log_entry_host.cc",
     "server_log_entry_host.h",
     "shutdown_watchdog.cc",
@@ -400,7 +376,6 @@
     ":base",
     ":client_session_control",
     ":clipboard",
-    ":headers",
     ":host_settings",
     ":remote_open_url_support",
     "//base:i18n",
@@ -541,7 +516,10 @@
       ]
     }
   } else {
-    sources += [ "me2me_desktop_environment.cc" ]
+    sources += [
+      "me2me_desktop_environment.cc",
+      "me2me_desktop_environment.h",
+    ]
   }
 
   if (is_mac) {
@@ -600,6 +578,20 @@
       "url_forwarder_configurator_win.cc",
       "url_forwarder_configurator_win.h",
       "usage_stats_consent_win.cc",
+
+      # TODO(crbug.com/1184893): These are in //remoting/host/win:remoting_core, but we can't add a
+      # dep for it because it would make a circular dependency. These should be
+      # moved to a new build target that both can depend on if possible.
+      "host_main.h",
+      "win/core_resource.h",
+      "win/etw_trace_consumer.h",
+      "win/host_event_file_logger.h",
+      "win/host_event_logger.h",
+      "win/host_event_windows_event_logger.h",
+      "win/host_service.h",
+      "win/unprivileged_process_delegate.h",
+      "win/wts_session_process_delegate.h",
+      "worker_process_ipc_delegate.h",
     ]
     libs += [ "crypt32.lib" ]
     deps += [
@@ -610,7 +602,6 @@
       "//media/gpu:gpu",
       "//mojo/core/embedder:embedder",
       "//remoting/host/win",
-      "//remoting/host/win:headers",
       "//remoting/host/win:messages",
       "//remoting/host/win:remoting_lib_idl",
     ]
@@ -639,7 +630,6 @@
 
   deps = [
     ":client_session_control",
-    ":headers",
     "//remoting/codec:encoder",
     "//remoting/host/file_transfer:common",
     "//remoting/host/security_key:security_key",
@@ -723,7 +713,6 @@
     ":base",
     ":client_session_control",
     ":common",
-    ":headers",
     ":test_support",
     "//build:branding_buildflags",
     "//build:chromeos_buildflags",
@@ -805,7 +794,6 @@
     sources = [ "setup/start_host_entry_point.cc" ]
 
     deps = [
-      ":headers",
       "//third_party/libjingle_xmpp",
       "//third_party/webrtc_overrides:webrtc_component",
     ]
@@ -936,7 +924,6 @@
 
     deps = [
       ":base",
-      ":headers",
       ":host_settings",
       "//base",
       "//base:i18n",
@@ -1000,7 +987,6 @@
       deps = [
         ":base",
         ":common",
-        ":headers",
         "//base",
         "//base:debugging_buildflags",
         "//mojo/core/embedder:embedder",
@@ -1022,7 +1008,6 @@
         ]
 
         deps = [
-          ":headers",
           ":remoting_me2me_host_static",
           "//base",
           "//mojo/core/embedder:embedder",
@@ -1091,7 +1076,6 @@
       ]
       deps = [
         ":common",
-        ":headers",
         ":host_settings",
         ":logging",
         ":remoting_me2me_host_static",
diff --git a/remoting/host/file_transfer/BUILD.gn b/remoting/host/file_transfer/BUILD.gn
index 0cae43d..05cd80f0 100644
--- a/remoting/host/file_transfer/BUILD.gn
+++ b/remoting/host/file_transfer/BUILD.gn
@@ -12,7 +12,6 @@
 
   deps = [
     "//base",
-    "//remoting/host:headers",
     "//remoting/protocol",
     "//remoting/resources",
   ]
@@ -42,7 +41,6 @@
       "//ipc:ipc",
       "//remoting/host:base",
       "//remoting/host:common",
-      "//remoting/host/win:headers",
     ]
   } else {
     sources += [ "get_desktop_directory.cc" ]
diff --git a/remoting/host/it2me/BUILD.gn b/remoting/host/it2me/BUILD.gn
index b8e6632..f994919 100644
--- a/remoting/host/it2me/BUILD.gn
+++ b/remoting/host/it2me/BUILD.gn
@@ -85,7 +85,6 @@
     "//remoting/base:authorization",
     "//remoting/host:base",
     "//remoting/host:common",
-    "//remoting/host:headers",
     "//remoting/host/native_messaging:native_messaging",
     "//remoting/protocol",
     "//remoting/resources",
@@ -110,10 +109,7 @@
     deps += [ "//ui/base:base" ]
   }
   if (is_win) {
-    deps += [
-      "//remoting/host/win:elevated_native_messaging_host",
-      "//remoting/host/win:headers",
-    ]
+    deps += [ "//remoting/host/win:elevated_native_messaging_host" ]
   }
 }
 
@@ -289,7 +285,6 @@
       deps = [
         ":common",
         "//remoting/host",
-        "//remoting/host:headers",
         "//remoting/host/native_messaging",
         "//remoting/proto",
         "//third_party/webrtc_overrides:webrtc_component",
diff --git a/remoting/host/linux/BUILD.gn b/remoting/host/linux/BUILD.gn
index 2d4c589..15908d6 100644
--- a/remoting/host/linux/BUILD.gn
+++ b/remoting/host/linux/BUILD.gn
@@ -132,7 +132,6 @@
       "//remoting/base:breakpad",
       "//remoting/host",
       "//remoting/host:base",
-      "//remoting/host:headers",
       "//remoting/host/native_messaging",
       "//remoting/host/setup",
       "//services/network:network_service",
diff --git a/remoting/host/mac/BUILD.gn b/remoting/host/mac/BUILD.gn
index 63ea007..8527779e 100644
--- a/remoting/host/mac/BUILD.gn
+++ b/remoting/host/mac/BUILD.gn
@@ -197,7 +197,6 @@
     "//remoting/base:breakpad",
     "//remoting/host",
     "//remoting/host:base",
-    "//remoting/host:headers",
     "//remoting/host:remoting_infoplist_strings",
     "//remoting/host/native_messaging",
     "//remoting/host/setup",
diff --git a/remoting/host/win/BUILD.gn b/remoting/host/win/BUILD.gn
index aa0c0d9..1afd1b05b 100644
--- a/remoting/host/win/BUILD.gn
+++ b/remoting/host/win/BUILD.gn
@@ -91,27 +91,6 @@
   deps = [ "//remoting/base" ]
 }
 
-source_set("headers") {
-  sources = [
-    "core_resource.h",
-    "etw_trace_consumer.h",
-    "host_event_file_logger.h",
-    "host_event_logger.h",
-    "host_event_windows_event_logger.h",
-    "host_service.h",
-    "unprivileged_process_delegate.h",
-    "windows_event_logger.h",
-    "worker_process_launcher.h",
-    "wts_session_process_delegate.h",
-    "wts_terminal_monitor.h",
-  ]
-  deps = [
-    "//base",
-    "//ipc",
-    "//net",
-  ]
-}
-
 source_set("win") {
   sources = [
     "audio_volume_filter_win.cc",
@@ -142,8 +121,11 @@
     "window_station_and_desktop.cc",
     "window_station_and_desktop.h",
     "windows_event_logger.cc",
+    "windows_event_logger.h",
     "worker_process_launcher.cc",
+    "worker_process_launcher.h",
     "wts_terminal_monitor.cc",
+    "wts_terminal_monitor.h",
     "wts_terminal_observer.h",
   ]
 
@@ -162,14 +144,11 @@
 
   public_deps = [ ":security_descriptor" ]
   deps = [
-    ":headers",
     "//base:i18n",
     "//components/policy/core/common",
     "//crypto",
     "//ipc",
     "//remoting/base",
-    "//remoting/host:base",
-    "//remoting/host:headers",
     "//remoting/host/security_key",
     "//remoting/host/win:messages",
     "//remoting/host/win:remoting_lib_idl",
@@ -195,18 +174,16 @@
     "event_trace_data.cc",
     "event_trace_data.h",
     "event_trace_data_unittest.cc",
+    "launch_native_messaging_host_process.cc",
+    "launch_native_messaging_host_process.h",
     "rdp_client_unittest.cc",
     "worker_process_launcher_unittest.cc",
   ]
 
   deps = [
     ":elevated_native_messaging_host",
-    ":headers",
-    "//base/test:test_support",
     "//ipc",
-    "//remoting/host:base",
     "//remoting/host:common",
-    "//remoting/host:headers",
     "//remoting/host:test_support",
     "//remoting/host/it2me:common",
     "//remoting/host/native_messaging",
@@ -306,7 +283,6 @@
     ":remoting_core",
     ":remoting_windows_resources",
     "//base:clang_profiling_buildflags",
-    "//remoting/host:headers",
   ]
 
   sources = [
@@ -336,8 +312,6 @@
     ":dpi_aware_exe_manifest",
     ":remoting_core",
     ":remoting_windows_resources",
-    "//base:clang_profiling_buildflags",
-    "//remoting/host:headers",
   ]
 
   sources = [
@@ -360,15 +334,9 @@
   sources = [
     "elevated_native_messaging_host.cc",
     "elevated_native_messaging_host.h",
-    "launch_native_messaging_host_process.cc",
-    "launch_native_messaging_host_process.h",
   ]
   deps = [
-    ":security_descriptor",
     "//base",
-    "//extensions/browser/api/messaging:native_messaging",
-    "//ipc",
-    "//remoting/host:base",
     "//remoting/host/native_messaging",
   ]
 }
@@ -423,6 +391,7 @@
     "chromoting_module.cc",
     "chromoting_module.h",
     "core.cc",
+    "core.h",
     "etw_trace_consumer.cc",
     "etw_trace_controller.cc",
     "etw_trace_controller.h",
@@ -431,6 +400,8 @@
     "host_event_file_logger.cc",
     "host_event_windows_event_logger.cc",
     "host_service.cc",
+    "launch_native_messaging_host_process.cc",
+    "launch_native_messaging_host_process.h",
     "rdp_desktop_session.cc",
     "rdp_desktop_session.h",
     "set_up_url_forwarder_dialog.cc",
@@ -438,10 +409,21 @@
     "unprivileged_process_delegate.cc",
     "url_forwarder_configurator_main.cc",
     "wts_session_process_delegate.cc",
+
+    # TODO(crbug.com/1184893): These files are also listed in //remoting/host:common, so they should probably be moved to a shared target they can both depend on.
+    "//remoting/host/host_main.h",
+    "//remoting/host/worker_process_ipc_delegate.h",
+    "core_resource.h",
+    "etw_trace_consumer.h",
+    "host_event_file_logger.h",
+    "host_event_logger.h",
+    "host_event_windows_event_logger.h",
+    "host_service.h",
+    "unprivileged_process_delegate.h",
+    "wts_session_process_delegate.h",
   ]
   public_deps = [ ":elevated_native_messaging_host" ]
   deps = [
-    ":headers",
     ":messages",
     ":remoting_lib_idl",
     ":remoting_lib_ps",
@@ -452,7 +434,6 @@
     "//base:i18n",
     "//base/allocator",
     "//base/third_party/dynamic_annotations",
-    "//build:chromeos_buildflags",
     "//build/win:default_exe_manifest",
     "//ipc",
     "//mojo/core/embedder:embedder",
@@ -460,10 +441,7 @@
     "//mojo/public/cpp/system",
     "//net",
     "//remoting/base",
-    "//remoting/base:authorization",
     "//remoting/base:breakpad",
-    "//remoting/host:base",
-    "//remoting/host:headers",
     "//remoting/host:host",
     "//remoting/host:host_settings",
     "//remoting/host:logging",
@@ -474,8 +452,6 @@
     "//remoting/host/setup",
     "//remoting/protocol",
     "//sandbox/win:sandbox",  # Should always use Windows version
-    "//services/network:network_service",
-    "//services/network/public/cpp",
     "//services/network/public/mojom",
     "//third_party/webrtc_overrides:webrtc_component",
     "//ui/base",
@@ -514,8 +490,6 @@
   deps = [
     ":remoting_core",
     ":remoting_windows_resources",
-    "//base:clang_profiling_buildflags",
-    "//remoting/host:headers",
   ]
 
   if (is_official_build) {
diff --git a/sandbox/policy/mojom/sandbox.mojom b/sandbox/policy/mojom/sandbox.mojom
index d15aacd..6dbd704 100644
--- a/sandbox/policy/mojom/sandbox.mojom
+++ b/sandbox/policy/mojom/sandbox.mojom
@@ -15,4 +15,13 @@
   // use limited operating system services. Prefer to use this sandbox
   // if possible.
   kService,
+
+  // Hosts generic utilities with limited access to system services.
+  // On some platforms, may be slightly less locked down than |kService|.
+  // For instance, it allows dynamic code and wider access to APIs on Windows.
+  kUtility,
+
+  // |kXrCompositing| hosts XR Device Service on Windows.
+  [EnableIf=is_win]
+  kXrCompositing,
 };
diff --git a/sandbox/policy/sandbox_type.h b/sandbox/policy/sandbox_type.h
index 4c53684..21584dba 100644
--- a/sandbox/policy/sandbox_type.h
+++ b/sandbox/policy/sandbox_type.h
@@ -125,6 +125,12 @@
   switch (mojo_sandbox) {
     case sandbox::mojom::Sandbox::kService:
       return sandbox::policy::SandboxType::kService;
+    case sandbox::mojom::Sandbox::kUtility:
+      return sandbox::policy::SandboxType::kUtility;
+#if defined(OS_WIN)
+    case sandbox::mojom::Sandbox::kXrCompositing:
+      return sandbox::policy::SandboxType::kXrCompositing;
+#endif  // OS_WIN
   }
 }
 
diff --git a/services/network/BUILD.gn b/services/network/BUILD.gn
index f7f4a2a7..54ddc05a 100644
--- a/services/network/BUILD.gn
+++ b/services/network/BUILD.gn
@@ -273,6 +273,11 @@
       "//crypto",
       "//third_party/boringssl",
     ]
+
+    sources += [
+      "radio_monitor_android.cc",
+      "radio_monitor_android.h",
+    ]
   }
 
   if (is_win) {
diff --git a/services/network/first_party_sets/BUILD.gn b/services/network/first_party_sets/BUILD.gn
index ea464527d..0e1c680 100644
--- a/services/network/first_party_sets/BUILD.gn
+++ b/services/network/first_party_sets/BUILD.gn
@@ -105,3 +105,19 @@
     "//third_party/libprotobuf-mutator",
   ]
 }
+
+fuzzer_test("first_party_set_parser_map_fuzzer") {
+  sources = [ "test/first_party_set_parser_map_fuzzer.cc" ]
+  deps = [
+    ":first_party_sets",
+    ":firstpartysets_proto",
+    "//base",
+    "//net:net",
+    "//net:net_fuzzer_test_support",
+    "//third_party/libprotobuf-mutator",
+  ]
+}
+
+proto_library("firstpartysets_proto") {
+  sources = [ "test/first_party_set_parser_map_fuzzer.proto" ]
+}
diff --git a/services/network/first_party_sets/first_party_set_parser.cc b/services/network/first_party_sets/first_party_set_parser.cc
index 7ddeb18..43aa46d 100644
--- a/services/network/first_party_sets/first_party_set_parser.cc
+++ b/services/network/first_party_sets/first_party_set_parser.cc
@@ -117,17 +117,17 @@
   return !maybe_members_list->GetList().empty();
 }
 
-// Deserializes a JSON-encoded string obtained from `SerializeFirstPartySets()`
-// into a map. Returns empty map if the value is invalid.
+}  // namespace
+
 base::flat_map<net::SchemefulSite, net::SchemefulSite>
-DeserializeFirstPartySets(const std::string& value) {
+FirstPartySetParser::DeserializeFirstPartySets(base::StringPiece value) {
   if (value.empty())
     return {};
 
   std::unique_ptr<base::Value> value_deserialized =
       JSONStringValueDeserializer(value).Deserialize(
           nullptr /* error_code */, nullptr /* error_message */);
-  if (!value_deserialized->is_dict())
+  if (!value_deserialized || !value_deserialized->is_dict())
     return {};
 
   base::flat_map<net::SchemefulSite, net::SchemefulSite> map;
@@ -165,10 +165,7 @@
   return map;
 }
 
-// Returns a serialized JSON-encoded string representation of the input.
-// The owner -> owner entry is removed from the serialized representation
-// for brevity in the file.
-std::string SerializeFirstPartySets(
+std::string FirstPartySetParser::SerializeFirstPartySets(
     const base::flat_map<net::SchemefulSite, net::SchemefulSite>& sets) {
   base::DictionaryValue dict;
   for (const auto& it : sets) {
@@ -184,8 +181,6 @@
   return dict_serialized;
 }
 
-}  // namespace
-
 absl::optional<net::SchemefulSite>
 FirstPartySetParser::CanonicalizeRegisteredDomain(
     const base::StringPiece origin_string,
@@ -212,27 +207,4 @@
   return map;
 }
 
-base::flat_map<net::SchemefulSite, net::SchemefulSite>
-FirstPartySetParser::LoadSetsFromDisk(const base::FilePath& path) {
-  std::string result;
-  if (!base::ReadFileToString(path, &result)) {
-    LOG(ERROR) << "Failed loading serialized First-Party Sets file from"
-               << path.MaybeAsASCII();
-    return {};
-  }
-  return DeserializeFirstPartySets(result);
-}
-
-bool FirstPartySetParser::MaybeWriteSetsToDisk(
-    const base::flat_map<net::SchemefulSite, net::SchemefulSite>& map,
-    const base::FilePath& path) {
-  std::string serialized = SerializeFirstPartySets(map);
-  bool success = base::WriteFile(path, serialized);
-  if (!success) {
-    LOG(ERROR) << "Failed writing serialized First-Party Sets to file "
-               << path.MaybeAsASCII();
-  }
-  return success;
-}
-
 }  // namespace network
diff --git a/services/network/first_party_sets/first_party_set_parser.h b/services/network/first_party_sets/first_party_set_parser.h
index be48866..56b7db5 100644
--- a/services/network/first_party_sets/first_party_set_parser.h
+++ b/services/network/first_party_sets/first_party_set_parser.h
@@ -42,24 +42,21 @@
       const base::StringPiece origin_string,
       bool emit_errors);
 
-  // Generates an in-memory First-Party Sets map from JSON file stored in
-  // `path`. This function checks the validity of the domains and the
-  // disjointness of the FPSs.
+  // Deserializes a JSON-encoded string obtained from
+  // `SerializeFirstPartySets()` into a map. This function checks the validity
+  // of the domains and the disjointness of the FPSs.
   //
-  // Returns an empty map when no previously stored First-Party Sets exists,
-  // deserialization fails, or the sets are invalid.
+  // Returns an empty map when deserialization fails, or the sets are invalid.
   static base::flat_map<net::SchemefulSite, net::SchemefulSite>
-  LoadSetsFromDisk(const base::FilePath& path);
+  DeserializeFirstPartySets(base::StringPiece value);
 
-  // Writes First-Party Sets as a serialized JSON-encoded string to location
-  // defined by `path`. This function does not check or have any special
-  // handling for the content of `sets`, e.g. opaque origins are just serialized
-  // as "null".
-  //
-  // Returns whether or not a map is successfully written.
-  static bool MaybeWriteSetsToDisk(
-      const base::flat_map<net::SchemefulSite, net::SchemefulSite>& sets,
-      const base::FilePath& path);
+  // Returns a serialized JSON-encoded string representation of the input. This
+  // function does not check or have any special handling for the content of
+  // `sets`, e.g. opaque origins are just serialized as "null".
+  // The owner -> owner entry is removed from the serialized representation for
+  // brevity.
+  static std::string SerializeFirstPartySets(
+      const base::flat_map<net::SchemefulSite, net::SchemefulSite>& sets);
 };
 
 }  // namespace network
diff --git a/services/network/first_party_sets/first_party_set_parser_unittest.cc b/services/network/first_party_sets/first_party_set_parser_unittest.cc
index 673e994..0dd0d38 100644
--- a/services/network/first_party_sets/first_party_set_parser_unittest.cc
+++ b/services/network/first_party_sets/first_party_set_parser_unittest.cc
@@ -434,68 +434,36 @@
               IsEmpty());
 }
 
-class FirstPartySetParserTest : public ::testing::Test {
- public:
-  FirstPartySetParserTest() {
-    CHECK(persisted_sets_dir_.CreateUniqueTempDir());
-    CHECK(PathExists(persisted_sets_dir_.GetPath()));
-    persisted_sets_path_ =
-        persisted_sets_dir_.GetPath().Append(FILE_PATH_LITERAL("sets.json"));
-  }
-
- protected:
-  base::ScopedTempDir persisted_sets_dir_;
-  base::FilePath persisted_sets_path_;
-};
-
-TEST_F(FirstPartySetParserTest, WriteSets) {
-  const base::flat_map<net::SchemefulSite, net::SchemefulSite> input = {
-      {net::SchemefulSite(GURL("https://member1.test")),
-       net::SchemefulSite(GURL("https://example1.test"))},
-      {net::SchemefulSite(GURL("https://example1.test")),
-       net::SchemefulSite(GURL("https://example1.test"))}};
-
-  EXPECT_TRUE(
-      FirstPartySetParser::MaybeWriteSetsToDisk(input, persisted_sets_path_));
-  std::string result;
-  ASSERT_TRUE(base::ReadFileToString(persisted_sets_path_, &result));
-  EXPECT_EQ(R"({"https://member1.test":"https://example1.test"})", result);
+TEST(FirstPartySetParser, SerializeFirstPartySets) {
+  EXPECT_EQ(R"({"https://member1.test":"https://example1.test"})",
+            FirstPartySetParser::SerializeFirstPartySets(
+                {{net::SchemefulSite(GURL("https://member1.test")),
+                  net::SchemefulSite(GURL("https://example1.test"))},
+                 {net::SchemefulSite(GURL("https://example1.test")),
+                  net::SchemefulSite(GURL("https://example1.test"))}}));
 }
 
-TEST_F(FirstPartySetParserTest, WriteSetsWithOpaqueOrigin) {
-  const base::flat_map<net::SchemefulSite, net::SchemefulSite> input = {
-      {net::SchemefulSite(GURL("https://member1.test")),
-       net::SchemefulSite(GURL(""))}};
-
-  EXPECT_TRUE(
-      FirstPartySetParser::MaybeWriteSetsToDisk(input, persisted_sets_path_));
-  std::string result;
-  ASSERT_TRUE(base::ReadFileToString(persisted_sets_path_, &result));
-  EXPECT_EQ(R"({"https://member1.test":"null"})", result);
+TEST(FirstPartySetParser, SerializeFirstPartySetsWithOpaqueOrigin) {
+  EXPECT_EQ(R"({"https://member1.test":"null"})",
+            FirstPartySetParser::SerializeFirstPartySets(
+                {{net::SchemefulSite(GURL("https://member1.test")),
+                  net::SchemefulSite(GURL(""))}}));
 }
 
-TEST_F(FirstPartySetParserTest, WriteEmptySets) {
-  EXPECT_TRUE(
-      FirstPartySetParser::MaybeWriteSetsToDisk({}, persisted_sets_path_));
-  std::string result;
-  ASSERT_TRUE(base::ReadFileToString(persisted_sets_path_, &result));
-  EXPECT_EQ("{}", result);
+TEST(FirstPartySetParser, SerializeFirstPartySetsEmptySet) {
+  EXPECT_EQ("{}", FirstPartySetParser::SerializeFirstPartySets({}));
 }
 
-TEST_F(FirstPartySetParserTest, WriteSetsToEmtpyPath) {
-  EXPECT_FALSE(FirstPartySetParser::MaybeWriteSetsToDisk({}, {}));
-}
-
-TEST_F(FirstPartySetParserTest, LoadSets) {
+TEST(FirstPartySetParser, DeserializeFirstPartySets) {
   const std::string input =
       R"({"https://member1.test":"https://example1.test",
           "https://member3.test":"https://example1.test",
           "https://member2.test":"https://example2.test"})";
   // Sanity check that the input is actually valid JSON.
   ASSERT_TRUE(base::JSONReader::Read(input));
-  ASSERT_TRUE(base::WriteFile(persisted_sets_path_, input));
+
   EXPECT_THAT(
-      FirstPartySetParser::LoadSetsFromDisk(persisted_sets_path_),
+      FirstPartySetParser::DeserializeFirstPartySets(input),
       UnorderedElementsAre(Pair(SerializesTo("https://member1.test"),
                                 SerializesTo("https://example1.test")),
                            Pair(SerializesTo("https://member3.test"),
@@ -508,33 +476,20 @@
                                 SerializesTo("https://example2.test"))));
 }
 
-TEST_F(FirstPartySetParserTest, LoadEmptySets) {
-  EXPECT_THAT(FirstPartySetParser::LoadSetsFromDisk(persisted_sets_path_),
-              IsEmpty());
-}
-
-TEST_F(FirstPartySetParserTest, LoadSetsFromEmptyPath) {
-  base::FilePath path;
-  EXPECT_THAT(FirstPartySetParser::LoadSetsFromDisk(path), IsEmpty());
-}
-
-TEST_F(FirstPartySetParserTest, LoadSetsFromNonExistentFile) {
-  base::FilePath path =
-      persisted_sets_dir_.GetPath().Append(FILE_PATH_LITERAL("non-sets.json"));
-  EXPECT_THAT(FirstPartySetParser::LoadSetsFromDisk(path), IsEmpty());
+TEST(FirstPartySetParser, DeserializeFirstPartySetsEmptySet) {
+  EXPECT_THAT(FirstPartySetParser::DeserializeFirstPartySets("{}"), IsEmpty());
 }
 
 // Same member appear twice with different owner is not considered invalid
 // content and wouldn't end up returning an empty map, since
 // base::DictionaryValue automatically handles duplicated keys.
-TEST_F(FirstPartySetParserTest, LoadSetsDuplicatedKey) {
+TEST(FirstPartySetParser, DeserializeFirstPartySetsDuplicatedKey) {
   const std::string input =
       R"({"https://member1.test":"https://example1.test",
           "https://member1.test":"https://example2.test"})";
   ASSERT_TRUE(base::JSONReader::Read(input));
-  ASSERT_TRUE(base::WriteFile(persisted_sets_path_, input));
   EXPECT_THAT(
-      FirstPartySetParser::LoadSetsFromDisk(persisted_sets_path_),
+      FirstPartySetParser::DeserializeFirstPartySets(input),
       UnorderedElementsAre(Pair(SerializesTo("https://member1.test"),
                                 SerializesTo("https://example2.test")),
                            Pair(SerializesTo("https://example2.test"),
@@ -542,15 +497,14 @@
 }
 
 // Singleton set is ignored.
-TEST_F(FirstPartySetParserTest, LoadSetsSingletonSet) {
+TEST(FirstPartySetParser, DeserializeFirstPartySetsSingletonSet) {
   const std::string input =
       R"({"https://example1.test":"https://example1.test",
           "https://member1.test":"https://example2.test",
           "https://example2.test":"https://example2.test"})";
   ASSERT_TRUE(base::JSONReader::Read(input));
-  ASSERT_TRUE(base::WriteFile(persisted_sets_path_, input));
   EXPECT_THAT(
-      FirstPartySetParser::LoadSetsFromDisk(persisted_sets_path_),
+      FirstPartySetParser::DeserializeFirstPartySets(input),
       UnorderedElementsAre(Pair(SerializesTo("https://member1.test"),
                                 SerializesTo("https://example2.test")),
                            Pair(SerializesTo("https://example2.test"),
@@ -558,14 +512,26 @@
 }
 
 class FirstPartySetParserInvalidContentTest
-    : public FirstPartySetParserTest,
-      public testing::WithParamInterface<std::string> {};
+    : public testing::Test,
+      public testing::WithParamInterface<std::tuple<bool, std::string>> {
+ public:
+  FirstPartySetParserInvalidContentTest() {
+    valid_json_ = std::get<0>(GetParam());
+    input_ = std::get<1>(GetParam());
+  }
+  bool is_valid_json() { return valid_json_; }
+  const std::string& input() { return input_; }
 
-TEST_P(FirstPartySetParserInvalidContentTest, LoadSets) {
-  std::string input = GetParam();
-  ASSERT_TRUE(base::JSONReader::Read(input));
-  ASSERT_TRUE(base::WriteFile(persisted_sets_path_, input));
-  EXPECT_THAT(FirstPartySetParser::LoadSetsFromDisk(persisted_sets_path_),
+ private:
+  bool valid_json_;
+  std::string input_;
+};
+
+TEST_P(FirstPartySetParserInvalidContentTest, DeserializeFirstPartySets) {
+  if (is_valid_json())
+    ASSERT_TRUE(base::JSONReader::Read(input()));
+
+  EXPECT_THAT(FirstPartySetParser::DeserializeFirstPartySets(input()),
               IsEmpty());
 }
 
@@ -573,18 +539,26 @@
     InvalidContent,
     FirstPartySetParserInvalidContentTest,
     testing::Values(
+        // The input is not valid JSON.
+        std::make_tuple(false, "//"),
         // The serialized object is type of array.
-        R"(["https://member1.test","https://example1.test"])",
+        std::make_tuple(true,
+                        R"(["https://member1.test","https://example1.test"])"),
         // The serialized string is type of map that contains non-URL key.
-        R"({"member1":"https://example1.test"})",
+        std::make_tuple(true, R"({"member1":"https://example1.test"})"),
         // The serialized string is type of map that contains non-URL value.
-        R"({"https://member1.test":"example1"})",
+        std::make_tuple(true, R"({"https://member1.test":"example1"})"),
+        // The serialized string is type of map that contains opaque origin.
+        std::make_tuple(true, R"({"https://member1.test":""})"),
+        std::make_tuple(true, R"({"":"https://example1.test"})"),
         // The serialized string is type of map that contains non-string value.
-        R"({"https://member1.test":1})",
+        std::make_tuple(true, R"({"https://member1.test":1})"),
         // Nondisjoint set. The same site shows up both as member and owner.
-        R"({"https://member1.test":"https://example1.test",
-            "https://member2.test":"https://member1.test"})",
-        R"({"https://member1.test":"https://example1.test",
-            "https://example1.test":"https://example2.test"})"));
+        std::make_tuple(true,
+                        R"({"https://member1.test":"https://example1.test",
+            "https://member2.test":"https://member1.test"})"),
+        std::make_tuple(true,
+                        R"({"https://member1.test":"https://example1.test",
+            "https://example1.test":"https://example2.test"})")));
 
 }  // namespace network
diff --git a/services/network/first_party_sets/test/first_party_set_parser_fuzzer.cc b/services/network/first_party_sets/test/first_party_set_parser_fuzzer.cc
index 8824918..a385438b 100644
--- a/services/network/first_party_sets/test/first_party_set_parser_fuzzer.cc
+++ b/services/network/first_party_sets/test/first_party_set_parser_fuzzer.cc
@@ -14,6 +14,14 @@
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   base::StringPiece string_input(reinterpret_cast<const char*>(data), size);
   FirstPartySetParser::ParseSetsFromComponentUpdater(string_input);
+
+  base::flat_map<net::SchemefulSite, net::SchemefulSite> deserialized =
+      FirstPartySetParser::DeserializeFirstPartySets(string_input);
+  std::string serialized_input =
+      FirstPartySetParser::SerializeFirstPartySets(deserialized);
+  CHECK(deserialized ==
+        FirstPartySetParser::DeserializeFirstPartySets(serialized_input));
+
   return 0;
 }
 
diff --git a/services/network/first_party_sets/test/first_party_set_parser_json_fuzzer.cc b/services/network/first_party_sets/test/first_party_set_parser_json_fuzzer.cc
index 807212db..89ce6c0e 100644
--- a/services/network/first_party_sets/test/first_party_set_parser_json_fuzzer.cc
+++ b/services/network/first_party_sets/test/first_party_set_parser_json_fuzzer.cc
@@ -20,4 +20,11 @@
     std::cout << native_input << std::endl;
 
   network::FirstPartySetParser::ParseSetsFromComponentUpdater(native_input);
+
+  base::flat_map<net::SchemefulSite, net::SchemefulSite> deserialized =
+      network::FirstPartySetParser::DeserializeFirstPartySets(native_input);
+  std::string serialized_input =
+      network::FirstPartySetParser::SerializeFirstPartySets(deserialized);
+  CHECK(deserialized == network::FirstPartySetParser::DeserializeFirstPartySets(
+                            serialized_input));
 }
diff --git a/services/network/first_party_sets/test/first_party_set_parser_map_fuzzer.cc b/services/network/first_party_sets/test/first_party_set_parser_map_fuzzer.cc
new file mode 100644
index 0000000..0153778
--- /dev/null
+++ b/services/network/first_party_sets/test/first_party_set_parser_map_fuzzer.cc
@@ -0,0 +1,62 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/network/first_party_sets/first_party_set_parser.h"
+
+#include <stdlib.h>
+#include <iostream>
+
+#include "net/base/schemeful_site.h"
+#include "services/network/first_party_sets/test/first_party_set_parser_map_fuzzer.pb.h"
+#include "testing/libfuzzer/proto/lpm_interface.h"
+#include "url/gurl.h"
+
+static const GURL kSiteTestCases[5] = {
+    GURL("https://site-0.test"), GURL("https://site-1.test"),
+    GURL("https://site-2.test"), GURL("https://site-3.test"),
+    GURL("https://site-4.test")};
+
+net::SchemefulSite GetSchemefulSite(const firstpartysets::proto::Site& site) {
+  return net::SchemefulSite(kSiteTestCases[site.site_test_case_index()]);
+}
+
+base::flat_map<net::SchemefulSite, net::SchemefulSite> ConvertProtoToMap(
+    const firstpartysets::proto::FirstPartySets& sets) {
+  base::flat_map<net::SchemefulSite, net::SchemefulSite> map;
+  for (const firstpartysets::proto::SitePair& item : sets.items()) {
+    auto member_or_owner = GetSchemefulSite(item.member_or_owner());
+    auto owner = GetSchemefulSite(item.owner());
+    map.emplace(std::move(member_or_owner), std::move(owner));
+  }
+  return map;
+}
+
+bool AreEquivalent(
+    base::flat_map<net::SchemefulSite, net::SchemefulSite>& native_input,
+    base::flat_map<net::SchemefulSite, net::SchemefulSite>& output) {
+  if (native_input.empty() && output.empty())
+    return true;
+
+  auto is_owner_entry = [](const auto& pair) {
+    return pair.first == pair.second;
+  };
+  base::EraseIf(native_input, is_owner_entry);
+  base::EraseIf(output, is_owner_entry);
+
+  return native_input == output;
+}
+
+DEFINE_PROTO_FUZZER(const firstpartysets::proto::FirstPartySets& input) {
+  if (getenv("LPM_DUMP_NATIVE_INPUT"))
+    std::cout << input.DebugString() << std::endl;
+
+  base::flat_map<net::SchemefulSite, net::SchemefulSite> native_input =
+      ConvertProtoToMap(input);
+
+  base::flat_map<net::SchemefulSite, net::SchemefulSite> deserialized =
+      network::FirstPartySetParser::DeserializeFirstPartySets(
+          network::FirstPartySetParser::SerializeFirstPartySets(native_input));
+
+  CHECK(deserialized.empty() || AreEquivalent(native_input, deserialized));
+}
diff --git a/services/network/first_party_sets/test/first_party_set_parser_map_fuzzer.proto b/services/network/first_party_sets/test/first_party_set_parser_map_fuzzer.proto
new file mode 100644
index 0000000..2d9d611
--- /dev/null
+++ b/services/network/first_party_sets/test/first_party_set_parser_map_fuzzer.proto
@@ -0,0 +1,31 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+package firstpartysets.proto;
+
+message FirstPartySets {
+  repeated SitePair items = 1;
+}
+
+message SitePair {
+  required Site member_or_owner = 1;
+  required Site owner = 2;
+}
+
+enum SiteTestCaseIndex {
+  SITE_0 = 0;
+  SITE_1 = 1;
+  SITE_2 = 2;
+  SITE_3 = 3;
+  SITE_4 = 4;
+}
+
+// In order to efficiently fuzz the logic, we don't want to worry
+// about all the possible URL parsing. For this reason, we generate
+// one of up to 5 sites.
+message Site {
+  required SiteTestCaseIndex site_test_case_index = 1;
+}
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index 1f90498..0f142e3 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -1696,6 +1696,8 @@
     const GURL& original_url,
     bool allow_credentials,
     const net::NetworkIsolationKey& network_isolation_key) {
+  DCHECK(!require_network_isolation_key_ || !network_isolation_key.IsEmpty());
+
   GURL url = GetHSTSRedirect(original_url);
 
   // |PreconnectSockets| may receive arguments from the renderer, which is not
diff --git a/services/network/public/cpp/features.cc b/services/network/public/cpp/features.cc
index e2c8927..5fd4e5f 100644
--- a/services/network/public/cpp/features.cc
+++ b/services/network/public/cpp/features.cc
@@ -251,5 +251,10 @@
   return base::saturated_cast<uint32_t>(kLoaderChunkSize.Get());
 }
 
+// Enable recording UMAs for network activities which can wake-up radio on
+// Android.
+const base::Feature kRecordRadioWakeupTrigger{
+    "RecordRadioWakeupTrigger", base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
 }  // namespace network
diff --git a/services/network/public/cpp/features.h b/services/network/public/cpp/features.h
index b2cc9b7..f482031 100644
--- a/services/network/public/cpp/features.h
+++ b/services/network/public/cpp/features.h
@@ -94,6 +94,9 @@
 COMPONENT_EXPORT(NETWORK_CPP)
 extern uint32_t GetLoaderChunkSize();
 
+COMPONENT_EXPORT(NETWORK_CPP)
+extern const base::Feature kRecordRadioWakeupTrigger;
+
 }  // namespace features
 }  // namespace network
 
diff --git a/services/network/radio_monitor_android.cc b/services/network/radio_monitor_android.cc
new file mode 100644
index 0000000..5c78db7
--- /dev/null
+++ b/services/network/radio_monitor_android.cc
@@ -0,0 +1,54 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/network/radio_monitor_android.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "services/network/public/cpp/features.h"
+
+namespace network {
+
+// static
+RadioMonitorAndroid& RadioMonitorAndroid::GetInstance() {
+  static base::NoDestructor<RadioMonitorAndroid> s_instance;
+  return *s_instance;
+}
+
+RadioMonitorAndroid::RadioMonitorAndroid() = default;
+
+void RadioMonitorAndroid::MaybeRecordRadioWakeupTrigger(
+    const net::NetworkTrafficAnnotationTag& traffic_annotation) {
+  DCHECK(base::FeatureList::IsEnabled(features::kRecordRadioWakeupTrigger));
+  if (ShouldRecordRadioWakeupTrigger()) {
+    base::UmaHistogramSparse(kUmaNamePossibleWakeupTrigger,
+                             traffic_annotation.unique_id_hash_code);
+  }
+}
+
+bool RadioMonitorAndroid::IsRadioUtilsSupported() {
+  return base::android::RadioUtils::IsSupported() ||
+         radio_activity_override_for_testing_.has_value() ||
+         radio_type_override_for_testing_.has_value();
+}
+
+bool RadioMonitorAndroid::ShouldRecordRadioWakeupTrigger() {
+  if (!IsRadioUtilsSupported())
+    return false;
+
+  base::android::RadioConnectionType radio_type =
+      radio_type_override_for_testing_.value_or(
+          base::android::RadioUtils::GetConnectionType());
+  if (radio_type != base::android::RadioConnectionType::kCell)
+    return false;
+
+  absl::optional<base::android::RadioDataActivity> radio_activity =
+      radio_activity_override_for_testing_.has_value()
+          ? radio_activity_override_for_testing_
+          : base::android::RadioUtils::GetCellDataActivity();
+
+  return radio_activity.has_value() &&
+         *radio_activity == base::android::RadioDataActivity::kDormant;
+}
+
+}  // namespace network
diff --git a/services/network/radio_monitor_android.h b/services/network/radio_monitor_android.h
new file mode 100644
index 0000000..257c6d2a
--- /dev/null
+++ b/services/network/radio_monitor_android.h
@@ -0,0 +1,68 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_NETWORK_RADIO_MONITOR_ANDROID_H_
+#define SERVICES_NETWORK_RADIO_MONITOR_ANDROID_H_
+
+#include "base/android/radio_utils.h"
+#include "base/component_export.h"
+#include "base/no_destructor.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace network {
+
+constexpr char kUmaNamePossibleWakeupTrigger[] =
+    "Network.Radio.PossibleWakeupTrigger";
+
+// Checks radio states and records histograms when network activities may
+// trigger power-consuming radio state changes like wake-ups.
+class COMPONENT_EXPORT(NETWORK_SERVICE) RadioMonitorAndroid {
+ public:
+  static RadioMonitorAndroid& GetInstance();
+
+  RadioMonitorAndroid(const RadioMonitorAndroid&) = delete;
+  RadioMonitorAndroid& operator=(const RadioMonitorAndroid&) = delete;
+  RadioMonitorAndroid(RadioMonitorAndroid&&) = delete;
+  RadioMonitorAndroid& operator=(RadioMonitorAndroid&&) = delete;
+
+  // Records a traffic annotation hash ID when a network request annotated with
+  // `traffic_annotation` likely wake-ups radio.
+  void MaybeRecordRadioWakeupTrigger(
+      const net::NetworkTrafficAnnotationTag& traffic_annotation);
+
+  // These override radio states for testing.
+  void OverrideRadioActivityForTesting(
+      absl::optional<base::android::RadioDataActivity> radio_activity) {
+    radio_activity_override_for_testing_ = radio_activity;
+  }
+  void OverrideRadioTypeForTesting(
+      absl::optional<base::android::RadioConnectionType> radio_type) {
+    radio_type_override_for_testing_ = radio_type;
+  }
+
+ private:
+  friend class base::NoDestructor<RadioMonitorAndroid>;
+  RadioMonitorAndroid();
+  ~RadioMonitorAndroid() = delete;
+
+  // Returns true when RadioUtils is available or any radio states are
+  // overridden for testing.
+  bool IsRadioUtilsSupported();
+
+  // Returns true when radio data activity is dormant.
+  // TODO(crbug.com/1232623): Consider optimizing this function. This function
+  // uses Android's platform APIs which add non-negligible overheads.
+  bool ShouldRecordRadioWakeupTrigger();
+
+  // Radio state overrides for testing.
+  absl::optional<base::android::RadioDataActivity>
+      radio_activity_override_for_testing_;
+  absl::optional<base::android::RadioConnectionType>
+      radio_type_override_for_testing_;
+};
+
+}  // namespace network
+
+#endif  // SERVICES_NETWORK_RADIO_MONITOR_ANDROID_H_
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index d22c722..407c08e 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -28,6 +28,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
+#include "build/build_config.h"
 #include "mojo/public/cpp/system/simple_watcher.h"
 #include "net/base/elements_upload_data_stream.h"
 #include "net/base/isolation_info.h"
@@ -88,6 +89,10 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/origin.h"
 
+#if defined(OS_ANDROID)
+#include "services/network/radio_monitor_android.h"
+#endif
+
 namespace network {
 
 namespace {
@@ -633,6 +638,13 @@
         *factory_params_->top_frame_id, keepalive_request_size_);
   }
 
+#if defined(OS_ANDROID)
+  if (base::FeatureList::IsEnabled(features::kRecordRadioWakeupTrigger)) {
+    RadioMonitorAndroid::GetInstance().MaybeRecordRadioWakeupTrigger(
+        traffic_annotation);
+  }
+#endif
+
   // Resolve elements from request_body and prepare upload data.
   if (request.request_body.get()) {
     OpenFilesForUpload(request);
diff --git a/services/network/url_loader_unittest.cc b/services/network/url_loader_unittest.cc
index 3f0858c..e5d07bcd 100644
--- a/services/network/url_loader_unittest.cc
+++ b/services/network/url_loader_unittest.cc
@@ -110,6 +110,11 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 
+#if defined(OS_ANDROID)
+#include "base/android/radio_utils.h"
+#include "services/network/radio_monitor_android.h"
+#endif
+
 namespace network {
 
 namespace {
@@ -561,7 +566,10 @@
     net::QuicSimpleTestServer::Start();
     net::URLRequestFailedJob::AddUrlHandler();
 
-    scoped_feature_list_.InitAndEnableFeature(features::kAcceptCHFrame);
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kAcceptCHFrame,
+                              features::kRecordRadioWakeupTrigger},
+        /*disabled_features=*/{});
   }
   ~URLLoaderTest() override {
     net::URLRequestFilter::GetInstance()->ClearHandlers();
@@ -6910,4 +6918,48 @@
   EXPECT_THAT(Load(url), IsError(net::OK));
   EXPECT_FALSE(accept_ch_frame_observer.called());
 }
+
+#if defined(OS_ANDROID)
+
+TEST_F(URLLoaderTest, RecordRadioWakeupTrigger_Record) {
+  base::HistogramTester histograms;
+
+  RadioMonitorAndroid::GetInstance().OverrideRadioActivityForTesting(
+      base::android::RadioDataActivity::kDormant);
+  RadioMonitorAndroid::GetInstance().OverrideRadioTypeForTesting(
+      base::android::RadioConnectionType::kCell);
+
+  LoadAndCompareFile("simple_page.html");
+
+  histograms.ExpectTotalCount(kUmaNamePossibleWakeupTrigger, 1);
+}
+
+TEST_F(URLLoaderTest, RecordRadioWakeupTrigger_RadioTypeIsNotCell) {
+  base::HistogramTester histograms;
+
+  RadioMonitorAndroid::GetInstance().OverrideRadioActivityForTesting(
+      base::android::RadioDataActivity::kDormant);
+  RadioMonitorAndroid::GetInstance().OverrideRadioTypeForTesting(
+      base::android::RadioConnectionType::kWifi);
+
+  LoadAndCompareFile("simple_page.html");
+
+  histograms.ExpectTotalCount(kUmaNamePossibleWakeupTrigger, 0);
+}
+
+TEST_F(URLLoaderTest, RecordRadioWakeupTrigger_RadioActivityIsNotDormant) {
+  base::HistogramTester histograms;
+
+  RadioMonitorAndroid::GetInstance().OverrideRadioActivityForTesting(
+      base::android::RadioDataActivity::kInOut);
+  RadioMonitorAndroid::GetInstance().OverrideRadioTypeForTesting(
+      base::android::RadioConnectionType::kCell);
+
+  LoadAndCompareFile("simple_page.html");
+
+  histograms.ExpectTotalCount(kUmaNamePossibleWakeupTrigger, 0);
+}
+
+#endif  // defined(OS_ANDROID)
+
 }  // namespace network
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h
index ec33324..39478f04 100644
--- a/skia/config/SkUserConfig.h
+++ b/skia/config/SkUserConfig.h
@@ -207,8 +207,6 @@
 
 #define SK_SUPPORT_LEGACY_AAA_CHOICE
 
-#define SK_DISABLE_ATLAS_PATH_RENDERER_WITH_COVERAGE_AA
-
 #define SK_SUPPORT_LEGACY_DRAWLOOPER
 
 #define SK_SUPPORT_LEGACY_RUNTIME_EFFECTS
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index e91d9fc..45df1e9 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -69,4878 +69,6 @@
       "all"
     ]
   },
-  "android-11-x86-fyi-rel": {
-    "gtest_tests": [
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "absl_hardening_tests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "absl_hardening_tests",
-        "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "android_browsertests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 2
-        },
-        "test": "android_browsertests",
-        "test_id_prefix": "ninja://chrome/test:android_browsertests/"
-      },
-      {
-        "args": [
-          "--test-launcher-batch-limit=1",
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "android_sync_integration_tests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "android_sync_integration_tests",
-        "test_id_prefix": "ninja://chrome/test:android_sync_integration_tests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "android_webview_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "android_webview_unittests",
-        "test_id_prefix": "ninja://android_webview/test:android_webview_unittests/"
-      },
-      {
-        "args": [
-          "angle_unittests",
-          "-v",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "angle_unittests",
-        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/",
-        "use_isolated_scripts_api": true
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.base_unittests.filter"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "base_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "base_unittests",
-        "test_id_prefix": "ninja://base:base_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "base_util_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "base_util_unittests",
-        "test_id_prefix": "ninja://base/util:base_util_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "blink_common_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "blink_common_unittests",
-        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "blink_heap_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "blink_heap_unittests",
-        "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "blink_platform_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "blink_platform_unittests",
-        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.blink_unittests.filter"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "webkit_unit_tests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "webkit_unit_tests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 6
-        },
-        "test": "blink_unittests",
-        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "boringssl_crypto_tests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "boringssl_crypto_tests",
-        "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "boringssl_ssl_tests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "boringssl_ssl_tests",
-        "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "breakpad_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "breakpad_unittests",
-        "test_id_prefix": "ninja://third_party/breakpad:breakpad_unittests/"
-      },
-      {
-        "args": [
-          "--gtest_filter=-*UsingRealWebcam*",
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "capture_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "capture_unittests",
-        "test_id_prefix": "ninja://media/capture:capture_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "cast_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cast_unittests",
-        "test_id_prefix": "ninja://media/cast:cast_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.cc_unittests.filter"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "cc_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "cc_unittests",
-        "test_id_prefix": "ninja://cc:cc_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "chrome_public_smoke_test"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "chrome_public_smoke_test",
-        "test_id_prefix": "ninja://chrome/android:chrome_public_smoke_test/"
-      },
-      {
-        "args": [
-          "--gtest_filter=-org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.test*UserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallback:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackAndShortTimeout:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackInSubFrame:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testRedirectionFromIntent*",
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--git-revision=${got_revision}",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.chrome_public_test_apk.filter",
-          "--timeout-scale=2.0"
-        ],
-        "experiment_percentage": 100,
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "chrome_public_test_apk"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "precommit_args": [
-          "--gerrit-issue=${patch_issue}",
-          "--gerrit-patchset=${patch_set}",
-          "--buildbucket-id=${buildbucket_build_id}"
-        ],
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "e2-standard-8",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chrome-gold@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 30
-        },
-        "test": "chrome_public_test_apk",
-        "test_id_prefix": "ninja://chrome/android:chrome_public_test_apk/"
-      },
-      {
-        "args": [
-          "--gtest_filter=org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.test*UserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallback:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackAndShortTimeout:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackInSubFrame:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testRedirectionFromIntent*",
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--git-revision=${got_revision}",
-          "--avd-config=../../tools/android/avd/proto/generic_playstore_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "chrome_public_test_apk_with_playstore"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "chrome_public_test_apk_with_playstore",
-        "precommit_args": [
-          "--gerrit-issue=${patch_issue}",
-          "--gerrit-patchset=${patch_set}",
-          "--buildbucket-id=${buildbucket_build_id}"
-        ],
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_playstore_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_playstore_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_playstore_android28"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chrome-gold@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "chrome_public_test_apk",
-        "test_id_prefix": "ninja://chrome/android:chrome_public_test_apk/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "components_browsertests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 2
-        },
-        "test": "components_browsertests",
-        "test_id_prefix": "ninja://components:components_browsertests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.components_unittests.filter"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "components_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 4
-        },
-        "test": "components_unittests",
-        "test_id_prefix": "ninja://components:components_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "experiment_percentage": 100,
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "content_browsertests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "e2-standard-8",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 30
-        },
-        "test": "content_browsertests",
-        "test_id_prefix": "ninja://content/test:content_browsertests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.content_shell_test_apk.filter",
-          "--timeout-scale=2.0"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "content_shell_test_apk"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 3
-        },
-        "test": "content_shell_test_apk",
-        "test_id_prefix": "ninja://content/shell/android:content_shell_test_apk/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "content_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 3
-        },
-        "test": "content_unittests",
-        "test_id_prefix": "ninja://content/test:content_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.crashpad_tests.filter"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "crashpad_tests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "crashpad_tests",
-        "test_id_prefix": "ninja://third_party/crashpad/crashpad:crashpad_tests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "crypto_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "crypto_unittests",
-        "test_id_prefix": "ninja://crypto:crypto_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "device_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "device_unittests",
-        "test_id_prefix": "ninja://device:device_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "display_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "display_unittests",
-        "test_id_prefix": "ninja://ui/display:display_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "events_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "events_unittests",
-        "test_id_prefix": "ninja://ui/events:events_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "gcm_unit_tests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gcm_unit_tests",
-        "test_id_prefix": "ninja://google_apis/gcm:gcm_unit_tests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "gfx_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gfx_unittests",
-        "test_id_prefix": "ninja://ui/gfx:gfx_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "gin_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gin_unittests",
-        "test_id_prefix": "ninja://gin:gin_unittests/"
-      },
-      {
-        "args": [
-          "--use-cmd-decoder=validating",
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.gl_tests.filter"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "gl_tests_validating"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "name": "gl_tests_validating",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gl_tests",
-        "test_id_prefix": "ninja://gpu:gl_tests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "gl_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gl_unittests",
-        "test_id_prefix": "ninja://ui/gl:gl_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "google_apis_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "google_apis_unittests",
-        "test_id_prefix": "ninja://google_apis:google_apis_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "gpu_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gpu_unittests",
-        "test_id_prefix": "ninja://gpu:gpu_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "gwp_asan_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "gwp_asan_unittests",
-        "test_id_prefix": "ninja://components/gwp_asan:gwp_asan_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "ipc_tests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ipc_tests",
-        "test_id_prefix": "ninja://ipc:ipc_tests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "jingle_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "jingle_unittests",
-        "test_id_prefix": "ninja://jingle:jingle_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "latency_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "latency_unittests",
-        "test_id_prefix": "ninja://ui/latency:latency_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "libjingle_xmpp_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "libjingle_xmpp_unittests",
-        "test_id_prefix": "ninja://third_party/libjingle_xmpp:libjingle_xmpp_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "liburlpattern_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "liburlpattern_unittests",
-        "test_id_prefix": "ninja://third_party/liburlpattern:liburlpattern_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.media_unittests.filter"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "media_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "media_unittests",
-        "test_id_prefix": "ninja://media:media_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "midi_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "midi_unittests",
-        "test_id_prefix": "ninja://media/midi:midi_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "mojo_test_apk"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "mojo_test_apk",
-        "test_id_prefix": "ninja://mojo/public/java/system:mojo_test_apk/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "mojo_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "mojo_unittests",
-        "test_id_prefix": "ninja://mojo:mojo_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "monochrome_public_bundle_fake_modules_smoke_test"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "monochrome_public_bundle_fake_modules_smoke_test",
-        "test_id_prefix": "ninja://chrome/android:monochrome_public_bundle_fake_modules_smoke_test/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "monochrome_public_bundle_smoke_test"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "monochrome_public_bundle_smoke_test",
-        "test_id_prefix": "ninja://chrome/android:monochrome_public_bundle_smoke_test/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "monochrome_public_smoke_test"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "monochrome_public_smoke_test",
-        "test_id_prefix": "ninja://chrome/android:monochrome_public_smoke_test/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.net_unittests.filter"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "net_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 3
-        },
-        "test": "net_unittests",
-        "test_id_prefix": "ninja://net:net_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "sandbox_linux_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "sandbox_linux_unittests",
-        "test_id_prefix": "ninja://sandbox/linux:sandbox_linux_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--gtest_filter=-PacLibraryTest.ActualPacMyIpAddress*"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "services_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "services_unittests",
-        "test_id_prefix": "ninja://services:services_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "shell_dialogs_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "shell_dialogs_unittests",
-        "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "skia_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "skia_unittests",
-        "test_id_prefix": "ninja://skia:skia_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "sql_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "sql_unittests",
-        "test_id_prefix": "ninja://sql:sql_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "storage_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "storage_unittests",
-        "test_id_prefix": "ninja://storage:storage_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "ui_android_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ui_android_unittests",
-        "test_id_prefix": "ninja://ui/android:ui_android_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "ui_base_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ui_base_unittests",
-        "test_id_prefix": "ninja://ui/base:ui_base_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "ui_touch_selection_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "ui_touch_selection_unittests",
-        "test_id_prefix": "ninja://ui/touch_selection:ui_touch_selection_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "unit_tests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 2
-        },
-        "test": "unit_tests",
-        "test_id_prefix": "ninja://chrome/test:unit_tests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "url_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "url_unittests",
-        "test_id_prefix": "ninja://url:url_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.viz_unittests.filter"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "viz_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "viz_unittests",
-        "test_id_prefix": "ninja://components/viz:viz_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--gtest_filter=-DownloadBrowserTest.OverrideDownloadDirectory"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "weblayer_browsertests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "weblayer_browsertests",
-        "test_id_prefix": "ninja://weblayer/test:weblayer_browsertests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--gtest_filter=-org.chromium.weblayer.test.MediaCaptureTest.*"
-        ],
-        "experiment_percentage": 100,
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "weblayer_private_instrumentation_test_apk"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "weblayer_private_instrumentation_test_apk",
-        "test_id_prefix": "ninja://weblayer/browser/android/javatests:weblayer_private_instrumentation_test_apk/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "weblayer_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "weblayer_unittests",
-        "test_id_prefix": "ninja://weblayer/test:weblayer_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
-          "--gtest_filter=-org.chromium.net.NetworkChangeNotifierTest.testNetworkChangeNotifierJavaObservers"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "webview_instrumentation_test_apk"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 7
-        },
-        "test": "webview_instrumentation_test_apk",
-        "test_id_prefix": "ninja://android_webview/test:webview_instrumentation_test_apk/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "wtf_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "wtf_unittests",
-        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "zlib_unittests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "device_os": null,
-              "device_type": null,
-              "machine_type": "n1-standard-4|e2-standard-4",
-              "os": "Ubuntu-16.04|Ubuntu-18.04",
-              "pool": "chromium.tests.avd"
-            }
-          ],
-          "named_caches": [
-            {
-              "name": "avd_generic_android30",
-              "path": ".android"
-            },
-            {
-              "name": "system_images_android_30_google_apis_x86",
-              "path": ".emulator_sdk"
-            }
-          ],
-          "optional_dimensions": {
-            "60": [
-              {
-                "caches": "avd_generic_android30"
-              }
-            ]
-          },
-          "output_links": [
-            {
-              "link": [
-                "https://luci-logdog.appspot.com/v/?s",
-                "=android%2Fswarming%2Flogcats%2F",
-                "${TASK_ID}%2F%2B%2Funified_logcats"
-              ],
-              "name": "shard #${SHARD_INDEX} logcats"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "zlib_unittests",
-        "test_id_prefix": "ninja://third_party/zlib:zlib_unittests/"
-      }
-    ]
-  },
   "android-12-x64-fyi-rel": {
     "gtest_tests": [
       {
@@ -11055,7 +6183,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.137"
+              "revision": "version:92.0.4515.138"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -11142,7 +6270,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.25"
+              "revision": "version:93.0.4577.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -11316,7 +6444,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.137"
+              "revision": "version:92.0.4515.138"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -11403,7 +6531,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.25"
+              "revision": "version:93.0.4577.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 4e0c397c..66c68e47 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -18140,6 +18140,4878 @@
       }
     ]
   },
+  "android-11-x86-rel": {
+    "gtest_tests": [
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "absl_hardening_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "absl_hardening_tests",
+        "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "android_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test": "android_browsertests",
+        "test_id_prefix": "ninja://chrome/test:android_browsertests/"
+      },
+      {
+        "args": [
+          "--test-launcher-batch-limit=1",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "android_sync_integration_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "android_sync_integration_tests",
+        "test_id_prefix": "ninja://chrome/test:android_sync_integration_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "android_webview_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "android_webview_unittests",
+        "test_id_prefix": "ninja://android_webview/test:android_webview_unittests/"
+      },
+      {
+        "args": [
+          "angle_unittests",
+          "-v",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "angle_unittests",
+        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/",
+        "use_isolated_scripts_api": true
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.base_unittests.filter"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "base_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "base_util_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_util_unittests",
+        "test_id_prefix": "ninja://base/util:base_util_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "blink_common_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "blink_heap_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_heap_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "blink_platform_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.blink_unittests.filter"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "webkit_unit_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "webkit_unit_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "boringssl_crypto_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_crypto_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "boringssl_ssl_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_ssl_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "breakpad_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "breakpad_unittests",
+        "test_id_prefix": "ninja://third_party/breakpad:breakpad_unittests/"
+      },
+      {
+        "args": [
+          "--gtest_filter=-*UsingRealWebcam*",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "capture_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "capture_unittests",
+        "test_id_prefix": "ninja://media/capture:capture_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "cast_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_unittests",
+        "test_id_prefix": "ninja://media/cast:cast_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.cc_unittests.filter"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "cc_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "chrome_public_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "chrome_public_smoke_test",
+        "test_id_prefix": "ninja://chrome/android:chrome_public_smoke_test/"
+      },
+      {
+        "args": [
+          "--gtest_filter=-org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.test*UserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallback:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackAndShortTimeout:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackInSubFrame:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testRedirectionFromIntent*",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--git-revision=${got_revision}",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.chrome_public_test_apk.filter",
+          "--timeout-scale=2.0"
+        ],
+        "experiment_percentage": 100,
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "chrome_public_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "e2-standard-8",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chrome-gold@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 30
+        },
+        "test": "chrome_public_test_apk",
+        "test_id_prefix": "ninja://chrome/android:chrome_public_test_apk/"
+      },
+      {
+        "args": [
+          "--gtest_filter=org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.test*UserGesture*:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallback:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackAndShortTimeout:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testNavigationFromXHRCallbackInSubFrame:org.chromium.chrome.browser.externalnav.UrlOverridingTest.testRedirectionFromIntent*",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--git-revision=${got_revision}",
+          "--avd-config=../../tools/android/avd/proto/generic_playstore_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "chrome_public_test_apk_with_playstore"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "chrome_public_test_apk_with_playstore",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_playstore_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_playstore_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_playstore_android28"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chrome-gold@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "chrome_public_test_apk",
+        "test_id_prefix": "ninja://chrome/android:chrome_public_test_apk/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "components_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test": "components_browsertests",
+        "test_id_prefix": "ninja://components:components_browsertests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.components_unittests.filter"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "components_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 4
+        },
+        "test": "components_unittests",
+        "test_id_prefix": "ninja://components:components_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "experiment_percentage": 100,
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "e2-standard-8",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 30
+        },
+        "test": "content_browsertests",
+        "test_id_prefix": "ninja://content/test:content_browsertests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.content_shell_test_apk.filter",
+          "--timeout-scale=2.0"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "content_shell_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 3
+        },
+        "test": "content_shell_test_apk",
+        "test_id_prefix": "ninja://content/shell/android:content_shell_test_apk/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "content_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 3
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.crashpad_tests.filter"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "crashpad_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crashpad_tests",
+        "test_id_prefix": "ninja://third_party/crashpad/crashpad:crashpad_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "crypto_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crypto_unittests",
+        "test_id_prefix": "ninja://crypto:crypto_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "device_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "device_unittests",
+        "test_id_prefix": "ninja://device:device_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "display_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "display_unittests",
+        "test_id_prefix": "ninja://ui/display:display_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "events_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "events_unittests",
+        "test_id_prefix": "ninja://ui/events:events_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gcm_unit_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gcm_unit_tests",
+        "test_id_prefix": "ninja://google_apis/gcm:gcm_unit_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gfx_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gfx_unittests",
+        "test_id_prefix": "ninja://ui/gfx:gfx_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gin_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gin_unittests",
+        "test_id_prefix": "ninja://gin:gin_unittests/"
+      },
+      {
+        "args": [
+          "--use-cmd-decoder=validating",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.gl_tests.filter"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gl_tests_validating"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "gl_tests_validating",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gl_tests",
+        "test_id_prefix": "ninja://gpu:gl_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gl_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gl_unittests",
+        "test_id_prefix": "ninja://ui/gl:gl_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "google_apis_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "google_apis_unittests",
+        "test_id_prefix": "ninja://google_apis:google_apis_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gpu_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gpu_unittests",
+        "test_id_prefix": "ninja://gpu:gpu_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gwp_asan_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gwp_asan_unittests",
+        "test_id_prefix": "ninja://components/gwp_asan:gwp_asan_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "ipc_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ipc_tests",
+        "test_id_prefix": "ninja://ipc:ipc_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "jingle_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "jingle_unittests",
+        "test_id_prefix": "ninja://jingle:jingle_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "latency_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "latency_unittests",
+        "test_id_prefix": "ninja://ui/latency:latency_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "libjingle_xmpp_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "libjingle_xmpp_unittests",
+        "test_id_prefix": "ninja://third_party/libjingle_xmpp:libjingle_xmpp_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "liburlpattern_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "liburlpattern_unittests",
+        "test_id_prefix": "ninja://third_party/liburlpattern:liburlpattern_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.media_unittests.filter"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "media_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_unittests",
+        "test_id_prefix": "ninja://media:media_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "midi_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "midi_unittests",
+        "test_id_prefix": "ninja://media/midi:midi_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "mojo_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "mojo_test_apk",
+        "test_id_prefix": "ninja://mojo/public/java/system:mojo_test_apk/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "mojo_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "mojo_unittests",
+        "test_id_prefix": "ninja://mojo:mojo_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "monochrome_public_bundle_fake_modules_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "monochrome_public_bundle_fake_modules_smoke_test",
+        "test_id_prefix": "ninja://chrome/android:monochrome_public_bundle_fake_modules_smoke_test/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "monochrome_public_bundle_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "monochrome_public_bundle_smoke_test",
+        "test_id_prefix": "ninja://chrome/android:monochrome_public_bundle_smoke_test/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "monochrome_public_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "monochrome_public_smoke_test",
+        "test_id_prefix": "ninja://chrome/android:monochrome_public_smoke_test/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.net_unittests.filter"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "net_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 3
+        },
+        "test": "net_unittests",
+        "test_id_prefix": "ninja://net:net_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "sandbox_linux_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sandbox_linux_unittests",
+        "test_id_prefix": "ninja://sandbox/linux:sandbox_linux_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--gtest_filter=-PacLibraryTest.ActualPacMyIpAddress*"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "services_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "services_unittests",
+        "test_id_prefix": "ninja://services:services_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "shell_dialogs_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "shell_dialogs_unittests",
+        "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "skia_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "skia_unittests",
+        "test_id_prefix": "ninja://skia:skia_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "sql_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sql_unittests",
+        "test_id_prefix": "ninja://sql:sql_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "storage_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "storage_unittests",
+        "test_id_prefix": "ninja://storage:storage_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "ui_android_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_android_unittests",
+        "test_id_prefix": "ninja://ui/android:ui_android_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "ui_base_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_base_unittests",
+        "test_id_prefix": "ninja://ui/base:ui_base_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "ui_touch_selection_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_touch_selection_unittests",
+        "test_id_prefix": "ninja://ui/touch_selection:ui_touch_selection_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "unit_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 2
+        },
+        "test": "unit_tests",
+        "test_id_prefix": "ninja://chrome/test:unit_tests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "url_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "url_unittests",
+        "test_id_prefix": "ninja://url:url_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.viz_unittests.filter"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "viz_unittests",
+        "test_id_prefix": "ninja://components/viz:viz_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--gtest_filter=-DownloadBrowserTest.OverrideDownloadDirectory"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "weblayer_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "weblayer_browsertests",
+        "test_id_prefix": "ninja://weblayer/test:weblayer_browsertests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--gtest_filter=-org.chromium.weblayer.test.MediaCaptureTest.*"
+        ],
+        "experiment_percentage": 100,
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "weblayer_private_instrumentation_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "weblayer_private_instrumentation_test_apk",
+        "test_id_prefix": "ninja://weblayer/browser/android/javatests:weblayer_private_instrumentation_test_apk/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "weblayer_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "weblayer_unittests",
+        "test_id_prefix": "ninja://weblayer/test:weblayer_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb",
+          "--gtest_filter=-org.chromium.net.NetworkChangeNotifierTest.testNetworkChangeNotifierJavaObservers"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "webview_instrumentation_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 7
+        },
+        "test": "webview_instrumentation_test_apk",
+        "test_id_prefix": "ninja://android_webview/test:webview_instrumentation_test_apk/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "wtf_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android30.textpb"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "zlib_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-16.04|Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "avd_generic_android30",
+              "path": ".android"
+            },
+            {
+              "name": "system_images_android_30_google_apis_x86",
+              "path": ".emulator_sdk"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "avd_generic_android30"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "zlib_unittests",
+        "test_id_prefix": "ninja://third_party/zlib:zlib_unittests/"
+      }
+    ]
+  },
   "android-arm64-proguard-rel": {
     "additional_compile_targets": [
       "all"
@@ -50487,7 +55359,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.137"
+              "revision": "version:92.0.4515.138"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -50575,7 +55447,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.25"
+              "revision": "version:93.0.4577.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -50751,7 +55623,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.137"
+              "revision": "version:92.0.4515.138"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -50839,7 +55711,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.25"
+              "revision": "version:93.0.4577.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -51088,7 +55960,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.137"
+              "revision": "version:92.0.4515.138"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -51175,7 +56047,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.25"
+              "revision": "version:93.0.4577.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -51349,7 +56221,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.137"
+              "revision": "version:92.0.4515.138"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -51436,7 +56308,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.25"
+              "revision": "version:93.0.4577.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -51685,7 +56557,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.137"
+              "revision": "version:92.0.4515.138"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -51772,7 +56644,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.25"
+              "revision": "version:93.0.4577.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -51946,7 +56818,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.137"
+              "revision": "version:92.0.4515.138"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -52033,7 +56905,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M93",
-              "revision": "version:93.0.4577.25"
+              "revision": "version:93.0.4577.27"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.dawn.json b/testing/buildbot/chromium.dawn.json
index 93e39c31f..ad5e404 100644
--- a/testing/buildbot/chromium.dawn.json
+++ b/testing/buildbot/chromium.dawn.json
@@ -60,61 +60,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--no-xvfb",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:5912-19.0.2",
-              "os": "Ubuntu-19.04",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation",
-          "--disable-toggles=use_tint_generator",
-          "--no-xvfb",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:5912-19.0.2",
-              "os": "Ubuntu-19.04",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation",
           "--enable-toggles=use_tint_generator",
           "--no-xvfb",
@@ -378,61 +323,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--no-xvfb",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation",
-          "--disable-toggles=use_tint_generator",
-          "--no-xvfb",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation",
           "--enable-toggles=use_tint_generator",
           "--no-xvfb",
@@ -696,61 +586,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--no-xvfb",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:5912-19.0.2",
-              "os": "Ubuntu-19.04",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation",
-          "--disable-toggles=use_tint_generator",
-          "--no-xvfb",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:5912-19.0.2",
-              "os": "Ubuntu-19.04",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation",
           "--enable-toggles=use_tint_generator",
           "--no-xvfb",
@@ -1014,61 +849,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--no-xvfb",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation",
-          "--disable-toggles=use_tint_generator",
-          "--no-xvfb",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-418.56",
-              "os": "Ubuntu-19.04",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation",
           "--enable-toggles=use_tint_generator",
           "--no-xvfb",
@@ -1338,65 +1118,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "display_attached": "1",
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.14.6|Mac-11.4",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation",
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "display_attached": "1",
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.14.6|Mac-11.4",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation",
           "--enable-toggles=use_tint_generator",
           "--use-gpu-in-tests",
@@ -1673,61 +1394,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "display_attached": "1",
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.15.7"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation",
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "display_attached": "1",
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.15.7"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation",
           "--enable-toggles=use_tint_generator",
           "--use-gpu-in-tests",
@@ -1996,67 +1662,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "display_attached": "1",
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-11.4",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "expiration": 21600,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation",
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "display_attached": "1",
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-11.4",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "expiration": 21600,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation",
           "--enable-toggles=use_tint_generator",
           "--use-gpu-in-tests",
@@ -2344,65 +1949,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "display_attached": "1",
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.14.6|Mac-11.4",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation",
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "display_attached": "1",
-              "gpu": "1002:6821",
-              "hidpi": "1",
-              "os": "Mac-10.14.6|Mac-11.4",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation",
           "--enable-toggles=use_tint_generator",
           "--use-gpu-in-tests",
@@ -2679,61 +2225,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "display_attached": "1",
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.15.7"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation",
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "display_attached": "1",
-              "gpu": "8086:0a2e",
-              "os": "Mac-10.15.7"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation",
           "--enable-toggles=use_tint_generator",
           "--use-gpu-in-tests",
@@ -3004,37 +2495,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-27.21.14.5148",
-              "os": "Windows-10-18363",
-              "pool": "chromium.tests.gpu"
-            },
-            {
-              "gpu": "8086:5912-26.20.100.8141|8086:3e92-26.20.100.8141",
-              "os": "Windows-10",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--use-wire",
           "--enable-toggles=use_tint_generator",
           "--use-gpu-in-tests",
@@ -3256,59 +2716,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:5912-26.20.100.8141|8086:3e92-26.20.100.8141",
-              "os": "Windows-10",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation=partial",
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:5912-26.20.100.8141|8086:3e92-26.20.100.8141",
-              "os": "Windows-10",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation=partial",
           "--enable-toggles=use_tint_generator",
           "--use-gpu-in-tests",
@@ -3564,59 +2971,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-27.21.14.5148",
-              "os": "Windows-10-18363",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation",
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-27.21.14.5148",
-              "os": "Windows-10-18363",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation",
           "--enable-toggles=use_tint_generator",
           "--use-gpu-in-tests",
@@ -3872,59 +3226,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:5912-26.20.100.8141|8086:3e92-26.20.100.8141",
-              "os": "Windows-10",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation=partial",
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:5912-26.20.100.8141|8086:3e92-26.20.100.8141",
-              "os": "Windows-10",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation=partial",
           "--enable-toggles=use_tint_generator",
           "--use-gpu-in-tests",
@@ -4180,59 +3481,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-27.21.14.5148",
-              "os": "Windows-10-18363",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation",
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-27.21.14.5148",
-              "os": "Windows-10-18363",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation",
           "--enable-toggles=use_tint_generator",
           "--use-gpu-in-tests",
@@ -4490,59 +3738,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:5912-26.20.100.8141|8086:3e92-26.20.100.8141",
-              "os": "Windows-10",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation=partial",
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:5912-26.20.100.8141|8086:3e92-26.20.100.8141",
-              "os": "Windows-10",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation=partial",
           "--enable-toggles=use_tint_generator",
           "--use-gpu-in-tests",
@@ -4796,59 +3991,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-27.21.14.5148",
-              "os": "Windows-10-18363",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation",
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-27.21.14.5148",
-              "os": "Windows-10-18363",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation",
           "--enable-toggles=use_tint_generator",
           "--use-gpu-in-tests",
@@ -5102,59 +4244,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:5912-26.20.100.8141|8086:3e92-26.20.100.8141",
-              "os": "Windows-10",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation=partial",
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "8086:5912-26.20.100.8141|8086:3e92-26.20.100.8141",
-              "os": "Windows-10",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation=partial",
           "--enable-toggles=use_tint_generator",
           "--use-gpu-in-tests",
@@ -5408,59 +4497,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-27.21.14.5148",
-              "os": "Windows-10-18363",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
-          "--enable-backend-validation",
-          "--disable-toggles=use_tint_generator",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_validation_layers_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "gpu": "10de:1cb3-27.21.14.5148",
-              "os": "Windows-10-18363",
-              "pool": "chromium.tests.gpu"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--enable-backend-validation",
           "--enable-toggles=use_tint_generator",
           "--use-gpu-in-tests",
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index f70bb729..7cc32cbf 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -86777,21 +86777,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4597.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4598.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 94.0.4597.0",
+        "name": "lacros_chrome_browsertests_Lacros version skew testing ash 94.0.4598.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v94.0.4597.0",
-              "revision": "version:94.0.4597.0"
+              "location": "lacros_version_skew_tests_v94.0.4598.0",
+              "revision": "version:94.0.4598.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -86825,21 +86825,21 @@
       },
       {
         "args": [
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4597.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4598.0/test_ash_chrome",
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 94.0.4597.0",
+        "name": "lacros_chrome_browsertests_run_in_series_Lacros version skew testing ash 94.0.4598.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v94.0.4597.0",
-              "revision": "version:94.0.4597.0"
+              "location": "lacros_version_skew_tests_v94.0.4598.0",
+              "revision": "version:94.0.4598.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 9b896ec..6626259 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -8687,35 +8687,6 @@
       },
       {
         "args": [
-          "--disable-toggles=use_tint_generator",
-          "--no-xvfb",
-          "--use-gpu-in-tests",
-          "--test-launcher-retry-limit=0",
-          "--exclusive-device-type-preference=discrete,integrated"
-        ],
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_gtest_merge.py"
-        },
-        "name": "dawn_end2end_spirv_cross_tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "containment_type": "AUTO",
-          "dimension_sets": [
-            {
-              "gpu": "10de:2184",
-              "os": "Ubuntu",
-              "pool": "chromium.tests.gpu.experimental"
-            }
-          ],
-          "expiration": 21600,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test": "dawn_end2end_tests",
-        "test_id_prefix": "ninja://third_party/dawn/src/tests:dawn_end2end_tests/"
-      },
-      {
-        "args": [
           "--use-wire",
           "--enable-toggles=use_tint_generator",
           "--no-xvfb",
diff --git a/testing/buildbot/filters/fuchsia.components_unittests.filter b/testing/buildbot/filters/fuchsia.components_unittests.filter
index b33d3c7..742a68c 100644
--- a/testing/buildbot/filters/fuchsia.components_unittests.filter
+++ b/testing/buildbot/filters/fuchsia.components_unittests.filter
@@ -89,3 +89,431 @@
 -WebDatabaseMigrationTest.*
 -WebsiteSettingsRegistryTest.Properties
 -ZeroSuggestProviderTest.*
+
+# Generated filter File
+-AddressComboboxModelTest.Add*
+-AddressComboboxModelTest.One*
+-AddressComboboxModelTest.Two*
+-AddressContactFormLabelFormatterTest.GetLabelsFor*
+-AddressEmailFormLabelFormatterTest.GetLabelsFor*
+-AddressFormLabelFormatterTest.GetLabelsForBRProfilesAndFocusedNon*
+-AddressFormLabelFormatterTest.GetLabelsForBRProfilesAndFocusedStreet*
+-AddressFormLabelFormatterTest.GetLabelsForForm*
+-AddressFormLabelFormatterTest.GetLabelsForUSProfilesAndFocusedNon*
+-AddressFormLabelFormatterTest.GetLabelsForUSProfilesAndFocusedStreet*
+-AddressPhoneFormLabelFormatterTest.GetLabelsFor*
+-AdsPageLoadMetricsObserverTest.HeavyAdBlocklistDisabled*
+-AdsPageLoadMetricsObserverTest.HeavyAdBlocklist_*
+-AdsPageLoadMetricsObserverTest.HeavyAdCpu*
+-AdsPageLoadMetricsObserverTest.HeavyAdNetworkIntervention*
+-AdsPageLoadMetricsObserverTest.HeavyAdNetworkUsageLessThanNoisedThreshold_Cpu*
+-AdsPageLoadMetricsObserverTest.HeavyAdNetworkUsageWith*
+-AdsPageLoadMetricsObserverTest.HeavyAdNetworkUsage_*
+-AdsPageLoadMetricsObserverTest.HeavyAdPageNavigated*
+-AdsPageLoadMetricsObserverTest.HeavyAdPageReloadPrivacy*
+-AdsPageLoadMetricsObserverTest.HeavyAdPageReload_Metrics*
+-AdsPageLoadMetricsObserverTest.HeavyAdPeak*
+-AdsPageLoadMetricsObserverTest.HeavyAdPolicy*
+-AdsPageLoadMetricsObserverTest.HeavyAdTotal*
+-All/AutofillProfileComparatorTest.Are*
+-All/AutofillProfileComparatorTest.CheckStatesMergeability/1
+-All/AutofillProfileComparatorTest.CheckStatesMergeability/3
+-All/AutofillProfileComparatorTest.CheckStatesMergeability/5
+-All/AutofillProfileComparatorTest.CheckStatesMergeability/7
+-All/AutofillProfileComparatorTest.MergeAddressesDependend*
+-All/AutofillProfileComparatorTest.MergeAddressesWith*
+-All/AutofillProfileTest.Adjust*
+-All/AutofillProfileTest.Create*
+-All/AutofillProfileTest.Preview*
+-All/AutofillQuery*
+-All/AutofillServer*
+-All/AutofillUpload*
+-All/BrowserAutofillManagerLogAblationTest.TestLogging/0
+-All/BrowserAutofillManagerLogAblationTest.TestLogging/1
+-All/BrowserAutofillManagerLogAblationTest.TestLogging/1*
+-All/BrowserAutofillManagerLogAblationTest.TestLogging/2
+-All/BrowserAutofillManagerLogAblationTest.TestLogging/6
+-All/BrowserAutofillManagerLogAblationTest.TestLogging/7
+-All/BrowserAutofillManagerLogAblationTest.TestLogging/8
+-All/BrowserAutofillManagerLogAblationTest.TestLogging/9
+-All/BrowserAutofillManagerProfile*
+-All/Credit*
+-All/OnFocusOnFormFieldTest.AddressSuggestions/1
+-All/OnFocusOnFormFieldTest.AddressSuggestions_Ablation/1
+-All/OnFocusOnFormFieldTest.AddressSuggestions_AutocompleteOffNotRespected/1
+-All/OnFocusOnFormFieldTest.CreditCardSuggestions_Ablation/1
+-All/OnFocusOnFormFieldTest.CreditCardSuggestions_NonSecureContext/1
+-All/OnFocusOnFormFieldTest.CreditCardSuggestions_SecureContext/1
+-All/PermissionRequestManagerTest.CameraNotificationsGeolocationChipRequest/1
+-All/PermissionRequestManagerTest.CameraPtz*
+-All/PermissionRequestManagerTest.Duplicate*
+-All/PermissionRequestManagerTest.Forget*
+-All/PermissionRequestManagerTest.I*
+-All/PermissionRequestManagerTest.Main*
+-All/PermissionRequestManagerTest.Mic*
+-All/PermissionRequestManagerTest.Mix*
+-All/PermissionRequestManagerTest.Multiple*
+-All/PermissionRequestManagerTest.NotificationsGeolocationCameraBubbleRequest/0
+-All/PermissionRequestManagerTest.NotificationsGeolocationCameraChipRequest/1
+-All/PermissionRequestManagerTest.NotificationsSingle*
+-All/PermissionRequestManagerTest.Permission*
+-All/PermissionRequestManagerTest.Requests*
+-All/PermissionRequestManagerTest.Same*
+-All/PermissionRequestManagerTest.Selectors*
+-All/PermissionRequestManagerTest.Selector*
+-All/PermissionRequestManagerTest.Sequential*
+-All/PermissionRequestManagerTest.Single*
+-All/PermissionRequestManagerTest.ThreeRequestsOneByOneStackOrderChip/1
+-All/PermissionRequestManagerTest.ThreeRequestsStackOrderChip/1
+-All/PermissionRequestManagerTest.TwoRequestsTab*
+-All/PermissionRequestManagerTest.TwoRequestsUngrouped/0
+-All/PermissionRequestManagerTest.Ui*
+-All/PermissionRequestManagerTest.U*
+-All/PermissionRequestManagerTestQuiet*
+-All/SSLErrorNavigationThrottleTest.S*
+-All/Suggestion*
+-AppModal*
+-ArchiveManagerTest.Try*
+-AutofillAblationStudyTestInE*
+-AutofillCountryTest.Country*
+-AutofillExternalDelegateCards*
+-AutofillExternalDelegateUnitTest.DuplicateAutofill*
+-AutofillExternalDelegateUnitTest.ExternalDelegateData*
+-AutofillExternalDelegateUnitTest.ScanCreditCardPrompt*
+-AutofillExternalDelegateUnitTest.Should*
+-AutofillExternalDelegateUnitTest.Test*
+-AutofillExternalDelegateUnitTest.Update*
+-AutofillFieldFillerTest/AutofillSelectWithExpirationMonthTest.FillSelectControlWithExpirationMonth/12
+-AutofillFieldFillerTest/AutofillSelectWithExpirationMonthTest.FillSelectControlWithExpirationMonth/13
+-AutofillFieldFillerTest/AutofillSelectWithExpirationMonthTest.FillSelectControlWithExpirationMonth/6
+-AutofillFieldFillerTest/AutofillSelectWithExpirationMonthTest.FillSelectControlWithExpirationMonth/7
+-AutofillFieldFillerTest/AutofillSelectWithExpirationMonthTest.FillSelectControlWithExpirationMonth/8
+-AutofillMetricsFunnel*
+-AutofillMetricsKey*
+-AutofillMetricsTest.AddressForm*
+-AutofillMetricsTest.AddressInteracted*
+-AutofillMetricsTest.AddressSubmitted*
+-AutofillMetricsTest.AddressSuggestions*
+-AutofillMetricsTest.AddressWill*
+-AutofillMetricsTest.Company*
+-AutofillMetricsTest.CreditCardCheckout*
+-AutofillMetricsTest.CreditCardForm*
+-AutofillMetricsTest.CreditCardSubmitted*
+-AutofillMetricsTest.LogServerOffer*
+-AutofillMetricsTest.Nonsecure*
+-AutofillMetricsTest.Polled*
+-AutofillMetricsTest.ProfileCheckout*
+-AutofillMetricsTest.Queried*
+-AutofillMetricsTest.RecordStandalone*
+-AutofillMetricsTest.Should*
+-AutofillMetricsTest/AutofillMetricsFunnelTest.LogFunnelMetrics/1
+-AutofillMetricsTest/AutofillMetricsFunnelTest.LogFunnelMetrics/2
+-AutofillMetricsTest/AutofillMetricsFunnelTest.LogFunnelMetrics/3
+-AutofillMetricsTest/AutofillMetricsFunnelTest.LogFunnelMetrics/4
+-AutofillMetricsTest/AutofillMetricsIFrameTest.CreditCardFilled*
+-AutofillMetricsTest/AutofillMetricsIFrameTest.CreditCardInteracted*
+-AutofillMetricsTest/AutofillMetricsIFrameTest.CreditCardSubmitted*
+-AutofillMetricsTest/AutofillMetricsIFrameTest.CreditCardWill*
+-AutofillMetricsTest/AutofillMetricsIFrameTest.Should*
+-AutofillMetricsTest/AutofillMetricsIFrameTest.Virtual*
+-AutofillOfferManagerTest.UpdateSuggestionsWithOffer_*
+-AutofillOfferManagerTest.UpdateSuggestionsWithOffers_Eligible*
+-AutofillPatternProviderTest.Test*
+-AutofillPaymentAppTest.IsCompleteForPayment_Incomplete*
+-AutofillPaymentAppTest.IsCompleteForPayment_Invalid*
+-AutofillPaymentAppTest.IsCompleteForPayment_Multiple*
+-AutofillPaymentAppTest.IsCompleteForPayment_No*
+-AutofillStructuredAddressUtils.TestGetRewriter
+-AutofillSuggestionGeneratorTest.Create*
+-BaseSearch*
+-Bidirectional*
+-BluetoothChooserControllerTest.Multiple*
+-BluetoothScanningPromptControllerTest.Multiple*
+-BluetoothScanningPromptControllerWithDevicesAddedTest.Initial*
+-BookmarkCodec*
+-BookmarkExpanded*
+-BookmarkModel*
+-BookmarkNodeData*
+-BookmarkRemoteUpdatesHandlerTest.*
+-BookmarkRemoteUpdatesHandlerWith*
+-BookmarkSpecificsConversionsTest.Replace*
+-BookmarkSpecificsConversionsTest.ShouldCreate*
+-BookmarkSpecificsConversionsTest.ShouldLoad*
+-BookmarkSpecificsConversionsTest.ShouldNot*
+-BookmarkSpecificsConversionsTest.ShouldPrefer*
+-BookmarkSpecificsConversionsTest.ShouldUpdate*
+-BookmarkStorage*
+-BookmarkUndo*
+-BookmarkUtils*
+-BrowserAutofillManagerStructuredProfileTest.AutocompleteSuggestions_None*
+-BrowserAutofillManagerStructuredProfileTest.CrowdsourceCVCFieldAfter*
+-BrowserAutofillManagerStructuredProfileTest.CrowdsourceCVCFieldBefore*
+-BrowserAutofillManagerStructuredProfileTest.CrowdsourceNo*
+-BrowserAutofillManagerStructuredProfileTest.DeterminePossibleFieldTypesFor*
+-BrowserAutofillManagerStructuredProfileTest.Disambiguate*
+-BrowserAutofillManagerStructuredProfileTest.Display*
+-BrowserAutofillManagerStructuredProfileTest.DontOffer*
+-BrowserAutofillManagerStructuredProfileTest.FillCreditCardForm_Expired*
+-BrowserAutofillManagerStructuredProfileTest.FormSubmitted/*
+-BrowserAutofillManagerStructuredProfileTest.FormSubmittedPossible*
+-BrowserAutofillManagerStructuredProfileTest.FormSubmittedSave*
+-BrowserAutofillManagerStructuredProfileTest.FormSubmittedServer*
+-BrowserAutofillManagerStructuredProfileTest.FormSubmittedWithDefault*
+-BrowserAutofillManagerStructuredProfileTest.FormWith*
+-BrowserAutofillManagerStructuredProfileTest.GetAddress*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_Empty*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_Expired*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_For*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_Google*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_Invisible*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_Is*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_I*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_Masked*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_Match*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_Number*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_Repeated*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_Secure*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_Stop*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_Suppress*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_Unrecognized*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_Virtual*
+-BrowserAutofillManagerStructuredProfileTest.GetCreditCardSuggestions_Whitespace*
+-BrowserAutofillManagerStructuredProfileTest.GetProfileSuggestions_For*
+-BrowserAutofillManagerStructuredProfileTest.GetProfileSuggestions_MinFieldsEnforced_With*
+-BrowserAutofillManagerStructuredProfileTest.GetProfileSuggestions_Small*
+-BrowserAutofillManagerStructuredProfileTest.GetProfileSuggestions_Unrecognized*
+-BrowserAutofillManagerStructuredProfileTest.Import*
+-BrowserAutofillManagerStructuredProfileTest.OnDid*
+-BrowserAutofillManagerStructuredProfileTest.OnText*
+-BrowserAutofillManagerStructuredProfileTest.Only*
+-BrowserAutofillManagerStructuredProfileTest.ShouldShowAddress*
+-BrowserAutofillManagerStructuredProfileTest.ShouldShowCreditCardSuggestions*
+-BrowserAutofillManagerStructuredProfileTest.Test*
+-BrowserAutofillManagerStructuredProfileTest.Value*
+-BrowserAutofillManagerTest.ShouldNot*
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/0
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/1
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/1*
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/2
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/2*
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/3
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/3*
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/4
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/4*
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/5
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/5*
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/6
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/6*
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/7
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/7*
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/8
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/8*
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/9
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/90
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/90*
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/91
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/91*
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/92
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/92*
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/93
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/93*
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/94
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/94*
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/95
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/95*
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/96
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/97
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/98
+-BrowserAutofillManagerTest/ProfileMatchingTypesTest.DeterminePossibleFieldTypesForUpload/99
+-BrowserAutofillManagerTestForSharing*
+-BrowserAutofillManagerTestForVirtualCardOption.ShouldNotShowDueToExperiment*
+-BrowserAutofillManagerTestForVirtualCardOption.ShouldNotShowDueToForm*
+-BrowserAutofillManagerTestForVirtualCardOption.ShouldNotShowDueToMerchant*
+-BrowserAutofillManagerTestForVirtualCardOption.ShouldNotShowDueToMultiple*
+-BrowserAutofillManagerTestForVirtualCardOption.ShouldNotShowDueToNo*
+-BrowserAutofillManagerTestForVirtualCardOption.ShouldShow*
+-BrowserAutofillManagerTestWithMixedForms.GetSuggestions_MixedForm
+-BrowserAutofillManagerTestWithMixedForms.GetSuggestions_MixedFormUser*
+-CardUnmaskPromptControllerImplTest.Display*
+-CardUnmaskPromptControllerImplTest.LogRealPanTry*
+-CardUnmaskPromptControllerImplTest.LogUnmaskingDurationTry*
+-CardUnmaskPromptControllerImplTest.LogUnmaskingDurationVcn*
+-CardUnmaskPromptControllerImplTest.Nickname*
+-CardUnmaskPromptControllerImplTest/LoggingValidationTestForNickname.LogClosedFailed*
+-CardUnmaskPromptControllerImplTest/LoggingValidationTestForNickname.LogDurationFailed*
+-CardUnmaskPromptControllerImplTest/LoggingValidationTestForNickname.LogDurationUnmasked*
+-CardUnmaskPromptControllerImplTest/LoggingValidationTestForNickname.LogUnmaskedCardAfter*
+-ConfigDir*
+-Constrained*
+-ContactFormLabelFormatterTest.GetLabelsFor*
+-CredentialLeakDialogUtilsTest.Get*
+-CurrencyAmounts/PaymentsCurrencyFormatterTest.IsValidCurrencyFormat/0
+-CurrencyAmounts/PaymentsCurrencyFormatterTest.IsValidCurrencyFormat/1
+-CurrencyAmounts/PaymentsCurrencyFormatterTest.IsValidCurrencyFormat/10
+-CurrencyAmounts/PaymentsCurrencyFormatterTest.IsValidCurrencyFormat/11
+-CurrencyAmounts/PaymentsCurrencyFormatterTest.IsValidCurrencyFormat/12
+-CurrencyAmounts/PaymentsCurrencyFormatterTest.IsValidCurrencyFormat/13
+-CurrencyAmounts/PaymentsCurrencyFormatterTest.IsValidCurrencyFormat/14
+-CurrencyAmounts/PaymentsCurrencyFormatterTest.IsValidCurrencyFormat/15
+-CurrencyAmounts/PaymentsCurrencyFormatterTest.IsValidCurrencyFormat/18
+-CurrencyAmounts/PaymentsCurrencyFormatterTest.IsValidCurrencyFormat/19
+-CurrencyAmounts/PaymentsCurrencyFormatterTest.IsValidCurrencyFormat/22
+-CurrencyAmounts/PaymentsCurrencyFormatterTest.IsValidCurrencyFormat/23
+-CurrencyAmounts/PaymentsCurrencyFormatterTest.IsValidCurrencyFormat/26
+-CurrencyAmounts/PaymentsCurrencyFormatterTest.IsValidCurrencyFormat/47
+-CurrencyAmounts/PaymentsCurrencyFormatterTest.IsValidCurrencyFormat/5
+-DefaultSearchPolicyHandlerTest.Disabled*
+-DefaultSearchPolicyHandlerTest.Invalid
+-DefaultSearchPolicyHandlerTest.Invalid*
+-DefaultSearchPolicyHandlerTest.Missing*
+-DomDistillerRequest*
+-DomainReliabilityMonitorTest.Real*
+-Encrypted*
+-FCMInvalidationServiceLogging*
+-FilesystemProxyTest.OpenFileAppend*
+-FilesystemProxyTest.OpenFileWrite*
+-Fluent*
+-FormDataImporterTest.ImportCreditCard_1*
+-FormDataImporterTest.ImportCreditCard_2*
+-FormDataImporterTest.ImportCreditCard_Empty*
+-FormDataImporterTest.ImportCreditCard_Existing*
+-FormDataImporterTest.ImportCreditCard_Missing*
+-FormDataImporterTest.ImportCreditCard_Month*
+-FormDataImporterTest.ImportCreditCard_Same*
+-FormDataImporterTest.ImportCreditCard_Should*
+-FormDataImporterTest.ImportCreditCard_Two*
+-FormDataImporterTest.ImportCreditCard_Valid*
+-FormDataImporterTest.ImportFormData_Addresses*
+-FormDataImporterTest.ImportFormData_Hidden*
+-FormDataImporterTest.ImportFormData_ImportCreditCardRecordType_Local*
+-FormDataImporterTest.ImportFormData_OneAddressOne*
+-FormDataImporterTest.ImportFormData_Second*
+-FormDataImporterTest.ImportFormData_TwoAddressesOne*
+-FormStructureTestImpl.Autofill*
+-FormStructureTestImpl.Determine*
+-FormStructureTestImpl.RationalizeRepreatedFields_StateCountryWith*
+-Fullscreen*
+-GeolocationPermissionContextTests.Cancel*
+-GeolocationPermissionContextTests.Hash*
+-GeolocationPermissionContextTests.Same*
+-GeolocationPermissionContextTests.SinglePermissionPrompt
+-GeolocationPermissionContextTests.Tab*
+-GetProfileDescription.Empty*
+-GetProfileDescription.Name*
+-InstantiationName/BulkCheckCredentialLeakDialogUtilsTest.Buttons*
+-InstantiationName/BulkCheckCredentialLeakDialogUtilsTest.Title*
+-IntPercentageToDoublePolicyHandler.Check*
+-IntRangePolicyHandler.Check*
+-LegacyChromePolicyMigratorTest.Copy*
+-LegacyChromePolicyMigratorTest.Ignore*
+-LevelDBSiteDataStoreTest.DatabaseOpening*
+-ListPolicy*
+-LocalCardMigrationManagerTest.Get*
+-LocalCardMigrationManagerTest.Invalid*
+-LocalCardMigrationManagerTest.LogMigrationDecision*
+-LocalCardMigrationManagerTest.LogMigrationOrigin_Use*
+-LocalCardMigrationManagerTest.MigrateCreditCard_Get*
+-LocalCardMigrationManagerTest.MigrateCreditCard_Local*
+-LocalCardMigrationManagerTest.MigrateCreditCard_Migrate*
+-LocalCardMigrationManagerTest.MigrateCreditCard_MigrationAbort*
+-LocalCardMigrationManagerTest.MigrateCreditCard_No*
+-LocalCardMigrationManagerTest.MigrateCreditCard_ShouldAddMigrate*
+-LocalCardMigrationManagerTest.MigrateCreditCard_ShouldAddUploadCardSourceInRequest_Checkout*
+-LocalCardMigrationManagerTest.MigrateCreditCard_Sign*
+-LocalCardMigrationManagerTest.MigrateCreditCard_TriggerFromSubmitted*
+-LocalCardMigrationManagerTest.MigrateCreditCard_Use*
+-LocalCardMigrationManagerTest.MigrateLocal*
+-LocalCardMigrationManagerTest.MigrateServer*
+-Logger*
+-MachineEnrolledOrNot/CloudReportingPolicyHandlerTest.MachineEnrollment/0
+-ManagedBookmarksPolicyHandlerTest.Bad*
+-ManagedBookmarksPolicyHandlerTest.Unknown*
+-ManagedBookmarksPolicyHandlerTest.Wrong*
+-ManagedBookmarksTracker*
+-MediaControls*
+-MediaNotificationView*
+-MostVisitedSitesProvider*
+-MultilingualSpellCheckTest.MultilingualSpellCheckWord
+-NetExportFileWriterTest.StartWith*
+-NetworkTimeTrackerTest.NoNetworkQueryWhileSynced
+-NetworkTimeTrackerTest.Start*
+-NetworkTimeTrackerTest.Time*
+-NetworkTimeTrackerTest.Update*
+-PaintPreviewRecorderUtilsTest.TestParseGlyphs
+-PaintPreviewSerialUtils.TestSerialTypeface
+-PaintPreviewTrackerTest.TestGlyph*
+-PasswordAutofillManagerTest.Add*
+-PasswordAutofillManagerTest.Cancels*
+-PasswordAutofillManagerTest.Display*
+-PasswordAutofillManagerTest.Doesnt*
+-PasswordAutofillManagerTest.External*
+-PasswordAutofillManagerTest.Extract*
+-PasswordAutofillManagerTest.FillSuggestionPassword*
+-PasswordAutofillManagerTest.Fills*
+-PasswordAutofillManagerTest.Matching*
+-PasswordAutofillManagerTest.MaybeShowPasswordSuggestionsWithAccount*
+-PasswordAutofillManagerTest.MaybeShowPasswordSuggestionsWithGenerationSome*
+-PasswordAutofillManagerTest.MaybeShowPasswordSuggestionsWithOmitted*
+-PasswordAutofillManagerTest.Prettified*
+-PasswordAutofillManagerTest.PreviewAnd*
+-PasswordAutofillManagerTest.Show*
+-PasswordAutofillManagerTest.SuccessfullOptInMay*
+-PasswordAutofillManagerTest.Suppress*
+-PaymentRequestProfileUtilTest.GetString*
+-PaymentRequestProfileUtilTest.GetTitle*
+-PaymentRequestSpecTest.Retry*
+-PaymentRequestSpecTest.ShippingOptionsSelection_*
+-PaymentRequestStateTest.Retry*
+-PermissionContextBaseTests.TestAsk*
+-PermissionContextBaseTests.TestDismiss*
+-PermissionContextBaseTests.TestGrant*
+-PermissionContextBaseTests.TestParallel*
+-PermissionManagerTest.GetPermissionStatusDelegation
+-PersonalDataManagerMockTest.Process*
+-PolicyErrorMapTest.HasErrorWithResource
+-PolicyMapTest.Add*
+-PolicyMapTest.Set*
+-ProxyPolicyHandlerTest.ManualOptionsInvalid
+-ProxyPolicyHandlerTest.PacScriptProxyModeInvalid
+-ProxyPolicyHandlerTest.Proxy*
+-QueryTileProvider*
+-ReadingListSuggestionsProviderTest.Category*
+-Realtime*
+-RemoteSuggestionsFetcherImplTest.Empty*
+-RemoteSuggestionsFetcherImplTest.Exclusive*
+-RemoteSuggestionsFetcherImplTest.Server*
+-RemoteSuggestionsFetcherImplTest.ShouldFetchSuccessfully
+-RemoteSuggestionsFetcherImplTest.ShouldFetchSuccessfullyWhen*
+-RemoteSuggestionsFetcherImplTest.ShouldRetry*
+-RemoteSuggestionsFetcherImplTest.Support*
+-SSLErrorAssistantTest.*
+-SSLErrorClassificationTest.Network*
+-SSLErrorHandlerDateInvalidTest.Time*
+-SafeBrowsingPolicyHandlerTest.Legacy*
+-SafeBrowsingPolicyHandlerTest.New*
+-SafeBrowsingUIManagerTest.Visible*
+-Simple*
+-SpellCheckTest.Misspelled*
+-SpellCheckTest.SpellCheckText
+-SpellcheckChar*
+-SpellcheckWordIteratorTest.FindSkippableWordsKhmer
+-SpellcheckWordIteratorTest.FindSkippableWordsRussian
+-SpellcheckWordIteratorTest.Split*
+-SpellcheckWordIteratorTest.Treat*
+-SyncCycle*
+-SyncHttpBridgeTest.Compressed*
+-SyncHttpBridgeTest.Http*
+-SyncHttpBridgeTest.TestExtra*
+-SyncHttpBridgeTest.TestResponse*
+-TextEliderTest.Test*
+-TranslateMetricsLoggerImplTest.LogApplication*
+-UIDev*
+-URLAllowlistPolicyHandlerTest.ApplyPolicySettings_CheckPolicySettingsMaxFiltersLimitExceeded
+-URLAllowlistPolicyHandlerTest.Check*
+-URLBlocklistPolicyHandlerTest.ApplyPolicySettings_CheckPolicySettingsMaxFiltersLimitExceeded*
+-URLBlocklistPolicyHandlerTest.CheckPolicySettings_DisabledSchemesWrong*
+-URLBlocklistPolicyHandlerTest.CheckPolicySettings_URLBlocklistWrong*
+-UserAgentUtilsTest.UserAgentStringOrdering
+-Voice*
+-WebAppOriginAssociationFetcher*
+-Zero*
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 4100e72c..d6871fc 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -566,8 +566,7 @@
   },
   "variations_smoke_tests": {
     "label": "//chrome/test:variations_smoke_tests",
-    "script": "//testing/scripts/run_variations_smoke_tests.py",
-    "type": "script",
+    "type": "generated_script",
   },
   "chromedriver_replay_unittests": {
     "label": "//chrome/test/chromedriver:chromedriver_replay_unittests",
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 018b83c..a125688 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -29,7 +29,7 @@
       'android-lollipop-arm-rel',
     ],
     'modifications': {
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'swarming': {
           'shards': 2,
         },
@@ -174,7 +174,7 @@
   },
   'base_unittests': {
     'modifications': {
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.base_unittests.filter',
         ],
@@ -764,7 +764,7 @@
           'shards': 3,
         },
       },
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         # https://crbug.com/1039860
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.cc_unittests.filter',
@@ -880,7 +880,7 @@
         # either passing or there is more capacity.
         'experiment_percentage': 0,
       },
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.chrome_public_test_apk.filter',
           '--timeout-scale=2.0',
@@ -957,7 +957,7 @@
       'android-marshmallow-x86-rel',
     ],
     'modifications': {
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'args': [
           '--avd-config=../../tools/android/avd/proto/generic_playstore_android30.textpb',
         ],
@@ -1107,7 +1107,7 @@
       'fuchsia-fyi-x64-rel',  # https://crbug.com/961457
     ],
     'modifications': {
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'swarming': {
           'shards': 2,
         },
@@ -1147,7 +1147,7 @@
           'shards': 2,
         },
       },
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.components_unittests.filter',
         ],
@@ -1262,7 +1262,7 @@
           '--disable-features=WebRTC-H264WithOpenH264FFmpeg',
         ],
       },
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         # TODO(crbug.com/1137474): Remove after the test suite is green.
         'experiment_percentage': 100,
         # TODO(crbug.com/1137474): Revisit the shard number once failed tests
@@ -1389,7 +1389,7 @@
       'android-pie-arm64-coverage-experimental-rel', # Does not generate profraw data.
     ],
     'modifications': {
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.content_shell_test_apk.filter',
           '--timeout-scale=2.0',
@@ -1464,7 +1464,7 @@
       'CFI Linux ToT',
     ],
     'modifications': {
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.crashpad_tests.filter',
         ],
@@ -1622,7 +1622,7 @@
       'Android FYI Release (Nexus 5X)',
     ],
     'modifications': {
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.gl_tests.filter',
         ],
@@ -1956,7 +1956,7 @@
   },
   'media_unittests': {
     'modifications': {
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.media_unittests.filter',
         ],
@@ -2189,7 +2189,7 @@
           'shards': 4,
         }
       },
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.net_unittests.filter',
         ],
@@ -2313,7 +2313,7 @@
       'android-pie-arm64-coverage-experimental-rel', # Does not generate profraw data.
       'android-pie-arm64-rel',
       'android-pie-x86-rel',
-      'android-11-x86-fyi-rel',
+      'android-11-x86-rel',
       'Lollipop Phone Tester',
       'Lollipop Tablet Tester',
       'Marshmallow 64 bit Tester',
@@ -2565,7 +2565,7 @@
       },
     },
     'modifications': {
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'args': [
           # TODO(crbug.com/1234824): Fix the failed tests
           '--gtest_filter=-PacLibraryTest.ActualPacMyIpAddress*'
@@ -2663,7 +2663,7 @@
   },
   'system_webview_shell_layout_test_apk': {
     'remove_from': [
-      'android-11-x86-fyi-rel', # crbug.com/1165280
+      'android-11-x86-rel', # crbug.com/1165280
     ],
   },
   'system_webview_wpt': {
@@ -2974,7 +2974,7 @@
   },
   'viz_unittests': {
     'modifications': {
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.viz_unittests.filter',
         ],
@@ -3126,7 +3126,7 @@
           'hard_timeout': 1200,
         },
       },
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.emulator_11.blink_unittests.filter',
         ],
@@ -3140,7 +3140,7 @@
   },
   'weblayer_browsertests': {
     'modifications': {
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'args': [
           # TODO(crbug.com/1191784): Enable this test once the issue is fixed.
           '--gtest_filter=-DownloadBrowserTest.OverrideDownloadDirectory',
@@ -3162,13 +3162,13 @@
   'weblayer_bundle_test': {
     'remove_from': [
       'android-marshmallow-x86-rel-non-cq', # crbug.com/1088013
-      'android-11-x86-fyi-rel', # crbug.com/1165280
+      'android-11-x86-rel', # crbug.com/1165280
     ],
   },
   'weblayer_instrumentation_test_apk': {
     'remove_from': [
       'android-marshmallow-x86-rel-non-cq', # crbug.com/1088013
-      'android-11-x86-fyi-rel', # crbug.com/1165280
+      'android-11-x86-rel', # crbug.com/1165280
     ],
     'modifications': {
       'android-pie-arm64-rel': {
@@ -3201,7 +3201,7 @@
   'weblayer_private_instrumentation_test_apk': {
     'modifications': {
       # TODO(crbug.com/1189403): Remove the filter once the issue is fixed.
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'args': [
           '--gtest_filter=-org.chromium.weblayer.test.MediaCaptureTest.*',
         ],
@@ -3231,7 +3231,7 @@
   },
   'webview_cts_tests': {
     'remove_from': [
-      'android-11-x86-fyi-rel', # crbug.com/1165280
+      'android-11-x86-rel', # crbug.com/1165280
       'android-pie-arm64-coverage-experimental-rel', # TODO(crbug.com/1190999):
                                                      # GLIBC_2.28 Not found.
     ],
@@ -3279,7 +3279,7 @@
         # either passing or there is more capacity.
         'experiment_percentage': 0,
       },
-      'android-11-x86-fyi-rel': {
+      'android-11-x86-rel': {
         'args': [
           # TODO(crbug.com/1189746) Enable this test once the issue is fixed.
           '--gtest_filter=-org.chromium.net.NetworkChangeNotifierTest.testNetworkChangeNotifierJavaObservers',
@@ -3299,7 +3299,7 @@
   },
   'webview_ui_test_app_test_apk': {
     'remove_from': [
-      'android-11-x86-fyi-rel', # crbug.com/1165280
+      'android-11-x86-rel', # crbug.com/1165280
     ],
   },
   'xr_browser_tests': {
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 610acea..78afd99 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -2664,12 +2664,6 @@
         'linux_args': [ '--no-xvfb' ],
         'test': 'dawn_end2end_tests',
       },
-      'dawn_end2end_spirv_cross_tests': {
-        'mixins': ['dawn_end2end_gpu_test'],
-        'linux_args': [ '--no-xvfb' ],
-        'args': [ '--disable-toggles=use_tint_generator' ],
-        'test': 'dawn_end2end_tests',
-      },
       'dawn_end2end_tests': {
         'mixins': ['dawn_end2end_gpu_test'],
         'linux_args': [ '--no-xvfb' ],
@@ -2691,15 +2685,6 @@
     # For Windows. Use the D3D12 backend validation layers but without GPU-based
     # validation and shader patching. This avoids hangs on less powerful systems.
     'gpu_dawn_gtests_with_partial_validation': {
-      'dawn_end2end_validation_layers_spirv_cross_tests': {
-        'mixins': ['dawn_end2end_gpu_test'],
-        'args': [
-          '--enable-backend-validation=partial',
-          '--disable-toggles=use_tint_generator',
-        ],
-        'linux_args': [ '--no-xvfb' ],
-        'test': 'dawn_end2end_tests',
-      },
       'dawn_end2end_validation_layers_tests': {
         'mixins': ['dawn_end2end_gpu_test'],
         'args': [
@@ -2713,15 +2698,6 @@
 
     # GPU gtests that test only Dawn with backend validation layers
     'gpu_dawn_gtests_with_validation': {
-      'dawn_end2end_validation_layers_spirv_cross_tests': {
-        'mixins': ['dawn_end2end_gpu_test'],
-        'args': [
-          '--enable-backend-validation',
-          '--disable-toggles=use_tint_generator',
-        ],
-        'linux_args': [ '--no-xvfb' ],
-        'test': 'dawn_end2end_tests',
-      },
       'dawn_end2end_validation_layers_tests': {
         'mixins': ['dawn_end2end_gpu_test'],
         'args': [
@@ -5182,6 +5158,7 @@
 
     'variations_smoke_tests': {
       'variations_smoke_tests': {
+        'isolate_name': 'variations_smoke_tests',
         'resultdb': {
           'enable': True,
           'result_format': 'single'
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index fa3f70f..7268301 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -46,16 +46,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4597.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v94.0.4598.0/test_ash_chrome',
       '--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter',
     ],
-    'identifier': 'Lacros version skew testing ash 94.0.4597.0',
+    'identifier': 'Lacros version skew testing ash 94.0.4598.0',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v94.0.4597.0',
-          'revision': 'version:94.0.4597.0',
+          'location': 'lacros_version_skew_tests_v94.0.4598.0',
+          'revision': 'version:94.0.4598.0',
         },
       ],
     },
@@ -349,7 +349,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M93',
-          'revision': 'version:93.0.4577.25',
+          'revision': 'version:93.0.4577.27',
         }
       ],
     },
@@ -373,7 +373,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M92',
-          'revision': 'version:92.0.4515.137',
+          'revision': 'version:92.0.4515.138',
         }
       ],
     },
@@ -421,7 +421,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M93',
-          'revision': 'version:93.0.4577.25',
+          'revision': 'version:93.0.4577.27',
         }
       ],
     },
@@ -445,7 +445,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M92',
-          'revision': 'version:92.0.4515.137',
+          'revision': 'version:92.0.4515.138',
         }
       ],
     },
@@ -493,7 +493,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M93',
-          'revision': 'version:93.0.4577.25',
+          'revision': 'version:93.0.4577.27',
         }
       ],
     },
@@ -517,7 +517,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M92',
-          'revision': 'version:92.0.4515.137',
+          'revision': 'version:92.0.4515.138',
         }
       ],
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 6d7bf5d9..ba318fa 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -576,6 +576,19 @@
           'has_native_resultdb_integration'
         ],
       },
+      'android-11-x86-rel': {
+        'mixins': [
+          '11-x86-emulator',
+          'emulator-4-cores',
+          'has_native_resultdb_integration',
+          'linux-xenial-or-bionic',
+          'x86-64',
+        ],
+        'os_type': 'android',
+        'test_suites': {
+          'gtest_tests': 'android_11_emulator_gtests',
+        }
+      },
       'android-arm64-proguard-rel': {
         'additional_compile_targets': [
           'all',
@@ -977,19 +990,6 @@
           'all',
         ],
       },
-      'android-11-x86-fyi-rel': {
-        'mixins': [
-          '11-x86-emulator',
-          'emulator-4-cores',
-          'has_native_resultdb_integration',
-          'linux-xenial-or-bionic',
-          'x86-64',
-        ],
-        'os_type': 'android',
-        'test_suites': {
-          'gtest_tests': 'android_11_emulator_gtests',
-        }
-      },
       'android-12-x64-fyi-rel': {
         'mixins': [
           '12-x64-emulator',
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index c2718e85..6e9f343 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -175,13 +175,16 @@
             ],
             "experiments": [
                 {
-                    "name": "Enabled_20210713",
+                    "name": "EnabledWithInfobars_20210803",
                     "params": {
                         "max-number": "10",
                         "site-id": "78FakAwAN0tK1KeaPYj0PoiAHrrQ"
                     },
                     "enable_features": [
                         "ChromeSurveyNextAndroid"
+                    ],
+                    "disable_features": [
+                        "MessagesForAndroidChromeSurvey"
                     ]
                 }
             ]
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 6ddc54c..8af3dab 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -849,6 +849,11 @@
 const base::Feature kWebAppEnableIsolatedStorage{
     "WebAppEnableIsolatedStorage", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables the "launch_handler" manifest field for web apps.
+// Explainer: https://github.com/WICG/sw-launch/blob/main/launch_handler.md
+const base::Feature kWebAppEnableLaunchHandler{
+    "WebAppEnableLaunchHandler", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables declarative link capturing in web apps.
 // Explainer:
 // https://github.com/WICG/sw-launch/blob/master/declarative_link_capturing.md
@@ -1050,5 +1055,10 @@
 const base::Feature kReportAllJavascriptFrameworks{
     "ReportAllJavaScriptFrameworks", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Suppresses console errors for CORS problems which report an associated
+// inspector issue anyway.
+const base::Feature kCORSErrorsIssueOnly{"CORSErrorsIssueOnly",
+                                         base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
 }  // namespace blink
diff --git a/third_party/blink/common/manifest/manifest.cc b/third_party/blink/common/manifest/manifest.cc
index 95838cc..a3fffdb4 100644
--- a/third_party/blink/common/manifest/manifest.cc
+++ b/third_party/blink/common/manifest/manifest.cc
@@ -14,41 +14,64 @@
 
 bool Manifest::ImageResource::operator==(
     const Manifest::ImageResource& other) const {
-  return src == other.src && type == other.type && sizes == other.sizes;
+  auto AsTuple = [](const auto& item) {
+    return std::tie(item.src, item.type, item.sizes);
+  };
+  return AsTuple(*this) == AsTuple(other);
 }
 
 Manifest::ShortcutItem::ShortcutItem() = default;
 
 Manifest::ShortcutItem::~ShortcutItem() = default;
 
+bool Manifest::ShortcutItem::operator==(const ShortcutItem& other) const {
+  auto AsTuple = [](const auto& item) {
+    return std::tie(item.name, item.short_name, item.description, item.url,
+                    item.icons);
+  };
+  return AsTuple(*this) == AsTuple(other);
+}
+
+bool Manifest::FileFilter::operator==(const FileFilter& other) const {
+  auto AsTuple = [](const auto& item) {
+    return std::tie(item.name, item.accept);
+  };
+  return AsTuple(*this) == AsTuple(other);
+}
+
 Manifest::ShareTargetParams::ShareTargetParams() = default;
 
 Manifest::ShareTargetParams::~ShareTargetParams() = default;
 
+bool Manifest::ShareTargetParams::operator==(
+    const ShareTargetParams& other) const {
+  auto AsTuple = [](const auto& item) {
+    return std::tie(item.title, item.text, item.url, item.files);
+  };
+  return AsTuple(*this) == AsTuple(other);
+}
+
 Manifest::ShareTarget::ShareTarget() = default;
 
 Manifest::ShareTarget::~ShareTarget() = default;
 
+bool Manifest::ShareTarget::operator==(const ShareTarget& other) const {
+  auto AsTuple = [](const auto& item) {
+    return std::tie(item.action, item.method, item.enctype, item.params);
+  };
+  return AsTuple(*this) == AsTuple(other);
+}
+
 Manifest::RelatedApplication::RelatedApplication() = default;
 
 Manifest::RelatedApplication::~RelatedApplication() = default;
 
-Manifest::Manifest() = default;
-
-Manifest::Manifest(const Manifest& other) = default;
-
-Manifest::~Manifest() = default;
-
-bool Manifest::IsEmpty() const {
-  return !name && !short_name && !id && start_url.is_empty() &&
-         display == blink::mojom::DisplayMode::kUndefined &&
-         display_override.empty() &&
-         orientation == device::mojom::ScreenOrientationLockType::DEFAULT &&
-         icons.empty() && shortcuts.empty() && !share_target.has_value() &&
-         related_applications.empty() && file_handlers.empty() &&
-         !prefer_related_applications && !theme_color && !background_color &&
-         !gcm_sender_id && scope.is_empty() && protocol_handlers.empty() &&
-         url_handlers.empty() && !note_taking.has_value() && !isolated_storage;
+bool Manifest::RelatedApplication::operator==(
+    const RelatedApplication& other) const {
+  auto AsTuple = [](const auto& item) {
+    return std::tie(item.platform, item.url, item.id);
+  };
+  return AsTuple(*this) == AsTuple(other);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/common/manifest/manifest_mojom_traits.cc b/third_party/blink/common/manifest/manifest_mojom_traits.cc
index 8a2ff04..8d0e652 100644
--- a/third_party/blink/common/manifest/manifest_mojom_traits.cc
+++ b/third_party/blink/common/manifest/manifest_mojom_traits.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "mojo/public/cpp/base/string16_mojom_traits.h"
+#include "mojo/public/cpp/bindings/type_converter.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "ui/gfx/geometry/mojom/geometry_mojom_traits.h"
 #include "url/mojom/url_gurl_mojom_traits.h"
@@ -22,31 +23,6 @@
   absl::optional<std::u16string> string;
 };
 
-// This function should be kept in sync with IsHostValidForUrlHandler in
-// manifest_parser.cc.
-bool IsHostValidForUrlHandler(const std::string& host) {
-  if (url::HostIsIPAddress(host))
-    return true;
-
-  const size_t registry_length =
-      net::registry_controlled_domains::PermissiveGetHostRegistryLength(
-          host,
-          // Reject unknown registries (registries that don't have any matches
-          // in effective TLD names).
-          net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
-          // Skip matching private registries that allow external users to
-          // specify sub-domains, e.g. glitch.me, as this is allowed.
-          net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
-
-  // Host cannot be a TLD or invalid.
-  if (registry_length == 0 || registry_length == std::string::npos ||
-      registry_length >= host.length()) {
-    return false;
-  }
-
-  return true;
-}
-
 }  // namespace
 
 template <>
@@ -70,88 +46,6 @@
   }
 };
 
-bool StructTraits<blink::mojom::ManifestDataView, ::blink::Manifest>::Read(
-    blink::mojom::ManifestDataView data,
-    ::blink::Manifest* out) {
-  TruncatedString16 string;
-  if (!data.ReadName(&string))
-    return false;
-  out->name = std::move(string.string);
-
-  if (!data.ReadShortName(&string))
-    return false;
-  out->short_name = std::move(string.string);
-
-  if (!data.ReadDescription(&string))
-    return false;
-  out->description = std::move(string.string);
-
-  if (!data.ReadId(&string))
-    return false;
-  out->id = std::move(string.string);
-
-  if (!data.ReadGcmSenderId(&string))
-    return false;
-  out->gcm_sender_id = std::move(string.string);
-
-  if (!data.ReadStartUrl(&out->start_url))
-    return false;
-
-  if (!data.ReadIcons(&out->icons))
-    return false;
-
-  if (!data.ReadScreenshots(&out->screenshots))
-    return false;
-
-  if (!data.ReadShortcuts(&out->shortcuts))
-    return false;
-
-  if (!data.ReadShareTarget(&out->share_target))
-    return false;
-
-  if (!data.ReadFileHandlers(&out->file_handlers))
-    return false;
-
-  if (!data.ReadProtocolHandlers(&out->protocol_handlers))
-    return false;
-
-  if (!data.ReadUrlHandlers(&out->url_handlers))
-    return false;
-
-  if (!data.ReadNoteTaking(&out->note_taking))
-    return false;
-
-  if (!data.ReadRelatedApplications(&out->related_applications))
-    return false;
-
-  out->prefer_related_applications = data.prefer_related_applications();
-
-  if (data.has_theme_color())
-    out->theme_color = data.theme_color();
-
-  if (data.has_background_color())
-    out->background_color = data.background_color();
-
-  if (!data.ReadDisplay(&out->display))
-    return false;
-
-  if (!data.ReadDisplayOverride(&out->display_override))
-    return false;
-
-  if (!data.ReadOrientation(&out->orientation))
-    return false;
-
-  if (!data.ReadScope(&out->scope))
-    return false;
-
-  if (!data.ReadCaptureLinks(&out->capture_links))
-    return false;
-
-  out->isolated_storage = data.isolated_storage();
-
-  return true;
-}
-
 bool StructTraits<blink::mojom::ManifestImageResourceDataView,
                   ::blink::Manifest::ImageResource>::
     Read(blink::mojom::ManifestImageResourceDataView data,
@@ -242,22 +136,6 @@
   return true;
 }
 
-bool StructTraits<blink::mojom::ManifestUrlHandlerDataView,
-                  ::blink::Manifest::UrlHandler>::
-    Read(blink::mojom::ManifestUrlHandlerDataView data,
-         ::blink::Manifest::UrlHandler* out) {
-  if (!data.ReadOrigin(&out->origin))
-    return false;
-
-  // Make sure the origin is valid.
-  if (!IsHostValidForUrlHandler(out->origin.host()))
-    return false;
-
-  out->has_origin_wildcard = data.has_origin_wildcard();
-
-  return true;
-}
-
 bool StructTraits<blink::mojom::ManifestShareTargetParamsDataView,
                   ::blink::Manifest::ShareTargetParams>::
     Read(blink::mojom::ManifestShareTargetParamsDataView data,
@@ -297,46 +175,4 @@
   return data.ReadParams(&out->params);
 }
 
-bool StructTraits<blink::mojom::ManifestFileHandlerDataView,
-                  ::blink::Manifest::FileHandler>::
-    Read(blink::mojom::ManifestFileHandlerDataView data,
-         ::blink::Manifest::FileHandler* out) {
-  if (!data.ReadAction(&out->action))
-    return false;
-
-  if (!data.ReadName(&out->name))
-    return false;
-
-  if (!data.ReadIcons(&out->icons))
-    return false;
-
-  if (!data.ReadAccept(&out->accept))
-    return false;
-
-  return true;
-}
-
-bool StructTraits<blink::mojom::ManifestProtocolHandlerDataView,
-                  ::blink::Manifest::ProtocolHandler>::
-    Read(blink::mojom::ManifestProtocolHandlerDataView data,
-         ::blink::Manifest::ProtocolHandler* out) {
-  if (!data.ReadProtocol(&out->protocol))
-    return false;
-
-  if (!data.ReadUrl(&out->url))
-    return false;
-
-  return true;
-}
-
-bool StructTraits<blink::mojom::ManifestNoteTakingDataView,
-                  ::blink::Manifest::NoteTaking>::
-    Read(blink::mojom::ManifestNoteTakingDataView data,
-         ::blink::Manifest::NoteTaking* out) {
-  if (!data.ReadNewNoteUrl(&out->new_note_url))
-    return false;
-
-  return true;
-}
-
 }  // namespace mojo
diff --git a/third_party/blink/common/manifest/manifest_util.cc b/third_party/blink/common/manifest/manifest_util.cc
index 1c781d1..239562c 100644
--- a/third_party/blink/common/manifest/manifest_util.cc
+++ b/third_party/blink/common/manifest/manifest_util.cc
@@ -4,12 +4,27 @@
 
 #include "third_party/blink/public/common/manifest/manifest_util.h"
 
+#include "base/no_destructor.h"
 #include "base/strings/string_util.h"
+#include "third_party/blink/public/common/manifest/manifest.h"
 #include "third_party/blink/public/mojom/manifest/capture_links.mojom.h"
 #include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 
 namespace blink {
 
+bool IsEmptyManifest(const mojom::Manifest& manifest) {
+  static base::NoDestructor<mojom::ManifestPtr> empty_manifest_ptr_storage;
+  mojom::ManifestPtr& empty_manifest = *empty_manifest_ptr_storage;
+  if (!empty_manifest)
+    empty_manifest = mojom::Manifest::New();
+  return manifest == *empty_manifest;
+}
+
+bool IsEmptyManifest(const mojom::ManifestPtr& manifest) {
+  return !manifest || IsEmptyManifest(*manifest);
+}
+
 std::string DisplayModeToString(blink::mojom::DisplayMode display) {
   switch (display) {
     case blink::mojom::DisplayMode::kUndefined:
diff --git a/third_party/blink/common/privacy_budget/identifiable_token_builder.cc b/third_party/blink/common/privacy_budget/identifiable_token_builder.cc
index 210db85..f08a18a 100644
--- a/third_party/blink/common/privacy_budget/identifiable_token_builder.cc
+++ b/third_party/blink/common/privacy_budget/identifiable_token_builder.cc
@@ -83,11 +83,6 @@
   return *this;
 }
 
-IdentifiableTokenBuilder& IdentifiableTokenBuilder::AddToken(
-    IdentifiableToken token) {
-  return AddValue(token.value_);
-}
-
 IdentifiableTokenBuilder::operator IdentifiableToken() const {
   return GetToken();
 }
diff --git a/third_party/blink/perf_tests/layout/flexbox-nested-rows-and-columns-auto-overflow.html b/third_party/blink/perf_tests/layout/flexbox-nested-rows-and-columns-auto-overflow.html
new file mode 100644
index 0000000..2c11bd3
--- /dev/null
+++ b/third_party/blink/perf_tests/layout/flexbox-nested-rows-and-columns-auto-overflow.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<script src="../resources/runner.js"></script>
+<style>
+  #container {
+      display: flex;
+      flex-direction: column;
+      height: 100px;
+  }
+  .flex-row {
+      display: flex;
+      flex-direction: row;
+      overflow: hidden;
+  }
+  .flex-column {
+      display: flex;
+      flex-direction: column;
+      overflow-x: hidden;
+      overflow-y: auto;
+  }
+</style>
+
+<div id="container">
+  <div class="flex-row">
+    <div class="flex-column">
+      <div class="flex-row">
+	<div class="flex-column">
+	  <div class="flex-row">
+	    <div class="flex-column">
+	      <div class="flex-row">
+		<div class="flex-column">
+		  <div class="flex-row">
+		    <div class="flex-column">
+		      <div class="flex-row">
+			<div class="flex-column">
+			  <div class="flex-row">
+			    <div class="flex-column">
+			      <div class="flex-row">
+				<div class="flex-column">
+				  <div class="flex-row">
+				    <div class="flex-column">
+				      <div class="flex-row">
+					<div class="flex-column">
+					  <div class="flex-row">
+					    <div class="flex-column">
+					      <div class="flex-row">
+						<div class="flex-column">
+                                                  <div>
+						    <div id="descendant"></div>
+						    <div style="width:100px; height:2000px;"></div>
+						  </div>
+						</div>
+					      </div>
+					    </div>
+					  </div>
+					</div>
+				      </div>
+				    </div>
+				  </div>
+				</div>
+			      </div>
+			    </div>
+			  </div>
+			</div>
+		      </div>
+		    </div>
+		  </div>
+		</div>
+	      </div>
+	    </div>
+	  </div>
+	</div>
+      </div>
+    </div>
+  </div>
+</div>
+
+<script>
+  function runTest() {
+      document.body.offsetTop;
+      descendant.style.display = "none";
+      document.body.offsetTop;
+      descendant.style.display = "block";
+      document.body.offsetTop;
+  }
+
+  PerfTestRunner.measureRunsPerSecond({
+      description: "Nested flex containers with alternating row and column flow, with auto overflow",
+      run: runTest
+  });
+</script>
+
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 52bcc82..9e899d0c 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -341,6 +341,8 @@
 
 BLINK_COMMON_EXPORT extern const base::Feature kWebAppEnableIsolatedStorage;
 
+BLINK_COMMON_EXPORT extern const base::Feature kWebAppEnableLaunchHandler;
+
 BLINK_COMMON_EXPORT extern const base::Feature kWebAppEnableLinkCapturing;
 
 BLINK_COMMON_EXPORT extern const base::Feature kWebAppEnableManifestId;
@@ -450,6 +452,10 @@
 // detect the properties and attributes required.
 BLINK_COMMON_EXPORT extern const base::Feature kReportAllJavascriptFrameworks;
 
+// Suppresses console errors for CORS problems which report an associated
+// inspector issue anyway.
+BLINK_COMMON_EXPORT extern const base::Feature kCORSErrorsIssueOnly;
+
 }  // namespace features
 }  // namespace blink
 
diff --git a/third_party/blink/public/common/manifest/manifest.h b/third_party/blink/public/common/manifest/manifest.h
index 1f46952..1153fa2 100644
--- a/third_party/blink/public/common/manifest/manifest.h
+++ b/third_party/blink/public/common/manifest/manifest.h
@@ -11,22 +11,17 @@
 #include <string>
 #include <vector>
 
-#include "services/device/public/mojom/screen_orientation_lock_types.mojom-shared.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/common_export.h"
-#include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom-shared.h"
-#include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/size.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
 namespace blink {
 
-// The Manifest structure is an internal representation of the Manifest file
-// described in the "Manifest for Web Application" document:
-// http://w3c.github.io/manifest/
-struct BLINK_COMMON_EXPORT Manifest {
+class BLINK_COMMON_EXPORT Manifest {
+ public:
   // Structure representing an icon as per the Manifest specification, see:
   // https://w3c.github.io/manifest/#dom-imageresource
   struct BLINK_COMMON_EXPORT ImageResource {
@@ -61,6 +56,8 @@
     ShortcutItem();
     ~ShortcutItem();
 
+    bool operator==(const ShortcutItem& other) const;
+
     std::u16string name;
     absl::optional<std::u16string> short_name;
     absl::optional<std::u16string> description;
@@ -69,6 +66,8 @@
   };
 
   struct BLINK_COMMON_EXPORT FileFilter {
+    bool operator==(const FileFilter& other) const;
+
     std::u16string name;
     std::vector<std::u16string> accept;
   };
@@ -78,6 +77,8 @@
     ShareTargetParams();
     ~ShareTargetParams();
 
+    bool operator==(const ShareTargetParams& other) const;
+
     absl::optional<std::u16string> title;
     absl::optional<std::u16string> text;
     absl::optional<std::u16string> url;
@@ -89,6 +90,8 @@
     ShareTarget();
     ~ShareTarget();
 
+    bool operator==(const ShareTarget& other) const;
+
     // The URL used for sharing. Query parameters are added to this comprised of
     // keys from |params| and values from the shared data.
     GURL action;
@@ -102,37 +105,13 @@
     ShareTargetParams params;
   };
 
-  // Structure representing a File Handler.
-  struct BLINK_COMMON_EXPORT FileHandler {
-    // The URL which will be opened when the file handler is invoked.
-    GURL action;
-    std::u16string name;
-    std::vector<ImageResource> icons;
-    std::map<std::u16string, std::vector<std::u16string>> accept;
-  };
-
-  // Structure representing a Protocol Handler.
-  struct BLINK_COMMON_EXPORT ProtocolHandler {
-    std::u16string protocol;
-    GURL url;
-  };
-
-  struct BLINK_COMMON_EXPORT UrlHandler {
-    url::Origin origin;
-    bool has_origin_wildcard;
-  };
-
-  struct BLINK_COMMON_EXPORT NoteTaking {
-    // A URL for taking a new note in the web application. If valid, this web
-    // application is a note-taking application.
-    GURL new_note_url;
-  };
-
   // Structure representing a related application.
   struct BLINK_COMMON_EXPORT RelatedApplication {
     RelatedApplication();
     ~RelatedApplication();
 
+    bool operator==(const RelatedApplication& other) const;
+
     // The platform on which the application can be found. This can be any
     // string, and is interpreted by the consumer of the object. Empty if the
     // parsing failed.
@@ -147,118 +126,6 @@
     // was not present.
     absl::optional<std::u16string> id;
   };
-
-  Manifest();
-  Manifest(const Manifest& other);
-  ~Manifest();
-
-  // Returns whether this Manifest had no attribute set. A newly created
-  // Manifest is always empty.
-  bool IsEmpty() const;
-
-  // Null if the parsing failed or the field was not present.
-  absl::optional<std::u16string> name;
-
-  // Null if the parsing failed or the field was not present.
-  absl::optional<std::u16string> short_name;
-
-  // Null if the parsing failed or the field was not present.
-  absl::optional<std::u16string> description;
-
-  // Null if the start_url parsing failed or missing, otherwise defaults to
-  // start_url with origin stripped when id field is not present.
-  absl::optional<std::u16string> id;
-
-  // Empty if the parsing failed or the field was not present.
-  GURL start_url;
-
-  // Set to DisplayMode::kUndefined if the parsing failed or the field was not
-  // present.
-  blink::mojom::DisplayMode display = blink::mojom::DisplayMode::kUndefined;
-
-  // Empty if the parsing failed, the field was not present, or all the
-  // values inside the JSON array were invalid.
-  std::vector<blink::mojom::DisplayMode> display_override;
-
-  // Set to device::mojom::ScreenOrientationLockType::DEFAULT if the parsing
-  // failed or the field was not present.
-  device::mojom::ScreenOrientationLockType orientation =
-      device::mojom::ScreenOrientationLockType::DEFAULT;
-
-  // Empty if the parsing failed, the field was not present, or all the
-  // icons inside the JSON array were invalid.
-  std::vector<ImageResource> icons;
-
-  // Empty if the parsing failed, the field was not present, or all the
-  // screenshots inside the JSON array were invalid.
-  std::vector<ImageResource> screenshots;
-
-  // Empty if the parsing failed, the field was not present, or all the
-  // icons inside the JSON array were invalid.
-  std::vector<ShortcutItem> shortcuts;
-
-  // Null if parsing failed or the field was not present.
-  absl::optional<ShareTarget> share_target;
-
-  // Empty if parsing failed or the field was not present.
-  // TODO(crbug.com/829689): This field is non-standard and part of a Chrome
-  // experiment. See:
-  // https://github.com/WICG/file-handling/blob/master/explainer.md
-  std::vector<FileHandler> file_handlers;
-
-  // Empty if parsing failed or the field was not present.
-  // TODO(crbug.com/1019239): This is going into the mainline manifest spec,
-  // remove the TODO once that PR goes in.
-  // The URLProtocolHandler explainer can be found here:
-  // https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/master/URLProtocolHandler/explainer.md
-  std::vector<ProtocolHandler> protocol_handlers;
-
-  // TODO(crbug.com/1072058): This field is non-standard and part of an
-  // experiment. See:
-  // https://github.com/WICG/pwa-url-handler/blob/master/explainer.md
-  // Empty if the parsing failed, the field was not present, empty or all the
-  // entries inside the array were invalid.
-  std::vector<UrlHandler> url_handlers;
-
-  // TODO(crbug.com/1185678): This field is non-standard and part of a manifest
-  // incubation. See:
-  // https://wicg.github.io/manifest-incubations/index.html#dfn-note_taking
-  // Null if parsing failed or the field was not present.
-  absl::optional<NoteTaking> note_taking;
-
-  // Empty if the parsing failed, the field was not present, empty or all the
-  // applications inside the array were invalid. The order of the array
-  // indicates the priority of the application to use.
-  std::vector<RelatedApplication> related_applications;
-
-  // A boolean that is used as a hint for the user agent to say that related
-  // applications should be preferred over the web application. False if missing
-  // or there is a parsing failure.
-  bool prefer_related_applications = false;
-
-  // Null if field is not present or parsing failed.
-  absl::optional<SkColor> theme_color;
-
-  // Null if field is not present or parsing failed.
-  absl::optional<SkColor> background_color;
-
-  // This is a proprietary extension of the web Manifest, double-check that it
-  // is okay to use this entry.
-  // Null if parsing failed or the field was not present.
-  absl::optional<std::u16string> gcm_sender_id;
-
-  // Empty if the parsing failed. Otherwise defaults to the start URL (or
-  // document URL if start URL isn't present) with filename, query, and fragment
-  // removed.
-  GURL scope;
-
-  mojom::CaptureLinks capture_links = mojom::CaptureLinks::kUndefined;
-
-  // False if parsing failed or the field was not present.
-  // TODO(crbug.com/1212263): This field is non-standard and part of a Chrome
-  // experiment. See:
-  // https://github.com/robbiemc/pwa-isolated-storage/blob/main/explainer.md
-  bool isolated_storage = false;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/public/common/manifest/manifest_mojom_traits.h b/third_party/blink/public/common/manifest/manifest_mojom_traits.h
index 30eaf52..8b400ce 100644
--- a/third_party/blink/public/common/manifest/manifest_mojom_traits.h
+++ b/third_party/blink/public/common/manifest/manifest_mojom_traits.h
@@ -36,141 +36,6 @@
 
 template <>
 struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::ManifestDataView, ::blink::Manifest> {
-  static bool IsNull(const ::blink::Manifest& manifest) {
-    return manifest.IsEmpty();
-  }
-
-  static void SetToNull(::blink::Manifest* manifest) {
-    *manifest = ::blink::Manifest();
-  }
-
-  static absl::optional<base::StringPiece16> name(
-      const ::blink::Manifest& manifest) {
-    return internal::TruncateOptionalString16(manifest.name);
-  }
-
-  static absl::optional<base::StringPiece16> short_name(
-      const ::blink::Manifest& manifest) {
-    return internal::TruncateOptionalString16(manifest.short_name);
-  }
-
-  static absl::optional<base::StringPiece16> description(
-      const ::blink::Manifest& manifest) {
-    return internal::TruncateOptionalString16(manifest.description);
-  }
-
-  static absl::optional<base::StringPiece16> id(
-      const ::blink::Manifest& manifest) {
-    return internal::TruncateOptionalString16(manifest.id);
-  }
-
-  static absl::optional<base::StringPiece16> gcm_sender_id(
-      const ::blink::Manifest& manifest) {
-    return internal::TruncateOptionalString16(manifest.gcm_sender_id);
-  }
-
-  static const GURL& start_url(const ::blink::Manifest& manifest) {
-    return manifest.start_url;
-  }
-
-  static const GURL& scope(const ::blink::Manifest& manifest) {
-    return manifest.scope;
-  }
-
-  static blink::mojom::DisplayMode display(const ::blink::Manifest& manifest) {
-    return manifest.display;
-  }
-
-  static const std::vector<blink::mojom::DisplayMode> display_override(
-      const ::blink::Manifest& manifest) {
-    return manifest.display_override;
-  }
-
-  static device::mojom::ScreenOrientationLockType orientation(
-      const ::blink::Manifest& manifest) {
-    return manifest.orientation;
-  }
-
-  static bool has_theme_color(const ::blink::Manifest& m) {
-    return m.theme_color.has_value();
-  }
-
-  static uint32_t theme_color(const ::blink::Manifest& m) {
-    return m.theme_color.value_or(0);
-  }
-
-  static bool has_background_color(const ::blink::Manifest& m) {
-    return m.background_color.has_value();
-  }
-
-  static uint32_t background_color(const ::blink::Manifest& m) {
-    return m.background_color.value_or(0);
-  }
-
-  static const std::vector<::blink::Manifest::ImageResource>& icons(
-      const ::blink::Manifest& manifest) {
-    return manifest.icons;
-  }
-
-  static const std::vector<::blink::Manifest::ImageResource>& screenshots(
-      const ::blink::Manifest& manifest) {
-    return manifest.screenshots;
-  }
-
-  static const std::vector<::blink::Manifest::ShortcutItem>& shortcuts(
-      const ::blink::Manifest& manifest) {
-    return manifest.shortcuts;
-  }
-
-  static const absl::optional<::blink::Manifest::ShareTarget>& share_target(
-      const ::blink::Manifest& manifest) {
-    return manifest.share_target;
-  }
-
-  static const std::vector<::blink::Manifest::FileHandler>& file_handlers(
-      const ::blink::Manifest& manifest) {
-    return manifest.file_handlers;
-  }
-
-  static const std::vector<::blink::Manifest::ProtocolHandler>&
-  protocol_handlers(const ::blink::Manifest& manifest) {
-    return manifest.protocol_handlers;
-  }
-
-  static const std::vector<::blink::Manifest::UrlHandler>& url_handlers(
-      const ::blink::Manifest& manifest) {
-    return manifest.url_handlers;
-  }
-
-  static const absl::optional<::blink::Manifest::NoteTaking>& note_taking(
-      const ::blink::Manifest& manifest) {
-    return manifest.note_taking;
-  }
-
-  static const std::vector<::blink::Manifest::RelatedApplication>&
-  related_applications(const ::blink::Manifest& manifest) {
-    return manifest.related_applications;
-  }
-
-  static bool prefer_related_applications(const ::blink::Manifest& manifest) {
-    return manifest.prefer_related_applications;
-  }
-
-  static blink::mojom::CaptureLinks capture_links(
-      const ::blink::Manifest& manifest) {
-    return manifest.capture_links;
-  }
-
-  static bool isolated_storage(const ::blink::Manifest& manifest) {
-    return manifest.isolated_storage;
-  }
-
-  static bool Read(blink::mojom::ManifestDataView data, ::blink::Manifest* out);
-};
-
-template <>
-struct BLINK_COMMON_EXPORT
     StructTraits<blink::mojom::ManifestImageResourceDataView,
                  ::blink::Manifest::ImageResource> {
   static const GURL& src(const ::blink::Manifest::ImageResource& icon) {
@@ -275,24 +140,6 @@
 
 template <>
 struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::ManifestUrlHandlerDataView,
-                 ::blink::Manifest::UrlHandler> {
-  static const url::Origin& origin(
-      const ::blink::Manifest::UrlHandler& url_handler) {
-    return url_handler.origin;
-  }
-
-  static bool has_origin_wildcard(
-      const ::blink::Manifest::UrlHandler& url_handler) {
-    return url_handler.has_origin_wildcard;
-  }
-
-  static bool Read(blink::mojom::ManifestUrlHandlerDataView data,
-                   ::blink::Manifest::UrlHandler* out);
-};
-
-template <>
-struct BLINK_COMMON_EXPORT
     StructTraits<blink::mojom::ManifestShareTargetParamsDataView,
                  ::blink::Manifest::ShareTargetParams> {
   static const absl::optional<base::StringPiece16> text(
@@ -340,61 +187,6 @@
                    ::blink::Manifest::ShareTarget* out);
 };
 
-template <>
-struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::ManifestFileHandlerDataView,
-                 ::blink::Manifest::FileHandler> {
-  static const GURL& action(const ::blink::Manifest::FileHandler& entry) {
-    return entry.action;
-  }
-
-  static const std::u16string& name(
-      const ::blink::Manifest::FileHandler& entry) {
-    return entry.name;
-  }
-
-  static const std::vector<::blink::Manifest::ImageResource>& icons(
-      const ::blink::Manifest::FileHandler& entry) {
-    return entry.icons;
-  }
-
-  static const std::map<std::u16string, std::vector<std::u16string>>& accept(
-      const ::blink::Manifest::FileHandler& entry) {
-    return entry.accept;
-  }
-
-  static bool Read(blink::mojom::ManifestFileHandlerDataView data,
-                   ::blink::Manifest::FileHandler* out);
-};
-
-template <>
-struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::ManifestProtocolHandlerDataView,
-                 ::blink::Manifest::ProtocolHandler> {
-  static base::StringPiece16 protocol(
-      const ::blink::Manifest::ProtocolHandler& protocol) {
-    return internal::TruncateString16(protocol.protocol);
-  }
-  static const GURL& url(const ::blink::Manifest::ProtocolHandler& protocol) {
-    return protocol.url;
-  }
-  static bool Read(blink::mojom::ManifestProtocolHandlerDataView data,
-                   ::blink::Manifest::ProtocolHandler* out);
-};
-
-template <>
-struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::ManifestNoteTakingDataView,
-                 ::blink::Manifest::NoteTaking> {
-  static const GURL new_note_url(
-      const ::blink::Manifest::NoteTaking& note_taking) {
-    return note_taking.new_note_url;
-  }
-
-  static bool Read(blink::mojom::ManifestNoteTakingDataView data,
-                   ::blink::Manifest::NoteTaking* out);
-};
-
 }  // namespace mojo
 
 #endif  // THIRD_PARTY_BLINK_PUBLIC_COMMON_MANIFEST_MANIFEST_MOJOM_TRAITS_H_
diff --git a/third_party/blink/public/common/manifest/manifest_util.h b/third_party/blink/public/common/manifest/manifest_util.h
index 7557477..5bad33b46 100644
--- a/third_party/blink/public/common/manifest/manifest_util.h
+++ b/third_party/blink/public/common/manifest/manifest_util.h
@@ -11,9 +11,14 @@
 #include "third_party/blink/public/common/common_export.h"
 #include "third_party/blink/public/mojom/manifest/capture_links.mojom-forward.h"
 #include "third_party/blink/public/mojom/manifest/display_mode.mojom-forward.h"
+#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 
 namespace blink {
 
+// Checks whether the manifest has no fields set.
+BLINK_COMMON_EXPORT bool IsEmptyManifest(const mojom::Manifest& manifest);
+BLINK_COMMON_EXPORT bool IsEmptyManifest(const mojom::ManifestPtr& manifest);
+
 // Converts a blink::mojom::DisplayMode to a string. Returns one of
 // https://www.w3.org/TR/appmanifest/#dfn-display-modes-values. Return values
 // are lowercase. Returns an empty string for DisplayMode::kUndefined.
diff --git a/third_party/blink/public/common/privacy_budget/identifiable_surface.h b/third_party/blink/public/common/privacy_budget/identifiable_surface.h
index 3632711..b6a8422 100644
--- a/third_party/blink/public/common/privacy_budget/identifiable_surface.h
+++ b/third_party/blink/public/common/privacy_budget/identifiable_surface.h
@@ -98,11 +98,7 @@
     // [1]: //blink/public/mojom/web_feature/web_feature.mojom
     kWebFeature = 1,
 
-    // Represents a readback of a canvas. Input is the
-    // CanvasRenderingContextType.
-    //
-    // Was 2 before change to paint op serialization.
-    kCanvasReadback = 33,
+    // Reserved 2.
 
     // Represents loading a font locally based on a name lookup that is allowed
     // to match either a unique name or a family name. This occurs when a
@@ -229,6 +225,8 @@
     // (audio or video).
     kRtcRtpReceiverGetCapabilities = 32,
 
+    // Reserved 33.
+
     // Metadata that is not reported by the client. Different from
     // kReservedInternal in that the inputs are not required to be defined in
     // `ukm.xml`.
@@ -238,6 +236,13 @@
     // analysis.
     kReservedMetadata = 34,
 
+    // Represents a readback of a canvas. Input is the
+    // CanvasRenderingContextType.
+    //
+    // Was 2 before change to paint op serialization, then 33 before removing
+    // paint op serialization and using only direct canvas2d instrumentation.
+    kCanvasReadback = 35,
+
     // We can use values up to and including |kMax|.
     kMax = (1 << kTypeBits) - 1
   };
diff --git a/third_party/blink/public/common/privacy_budget/identifiable_token_builder.h b/third_party/blink/public/common/privacy_budget/identifiable_token_builder.h
index 2eeea7e..e1d3e1e9 100644
--- a/third_party/blink/public/common/privacy_budget/identifiable_token_builder.h
+++ b/third_party/blink/public/common/privacy_budget/identifiable_token_builder.h
@@ -85,7 +85,9 @@
   // when |token| is computed in parallel in order to preserve the ordering of
   // values that were seen in a concurrent sequence that cannot be
   // deterministically interleaved into the primary stream.
-  IdentifiableTokenBuilder& AddToken(IdentifiableToken token);
+  IdentifiableTokenBuilder& AddToken(IdentifiableToken token) {
+    return AddValue(token.value_);
+  }
 
   // Helper for feeding primitive types by value efficiently. Anything more
   // complicated than that should be passed in as a base::span<const uint8_t>.
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index 41d52aed..3c5f690 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -576,12 +576,6 @@
     {
       types = [
         {
-          mojom = "blink.mojom.Manifest"
-          cpp = "::blink::Manifest"
-          nullable_is_same_type = true
-        },
-
-        {
           mojom = "blink.mojom.ManifestImageResource"
           cpp = "::blink::Manifest::ImageResource"
         },
diff --git a/third_party/blink/public/mojom/css/preferred_contrast.mojom b/third_party/blink/public/mojom/css/preferred_contrast.mojom
index 7af6486..b47fff63 100644
--- a/third_party/blink/public/mojom/css/preferred_contrast.mojom
+++ b/third_party/blink/public/mojom/css/preferred_contrast.mojom
@@ -9,4 +9,5 @@
   kMore,
   kLess,
   kNoPreference,
+  kCustom,
 };
\ No newline at end of file
diff --git a/third_party/blink/public/mojom/manifest/manifest.mojom b/third_party/blink/public/mojom/manifest/manifest.mojom
index a047d434..7b74285 100644
--- a/third_party/blink/public/mojom/manifest/manifest.mojom
+++ b/third_party/blink/public/mojom/manifest/manifest.mojom
@@ -16,6 +16,8 @@
 // The Manifest structure is an internal representation of the Manifest file
 // described in the "Manifest for Web Application" document:
 // http://w3c.github.io/manifest/
+// All fields will be their default constructed values (optional types nullopt)
+// if the manifest failed to parse.
 [JavaClassName="WebManifest"]
 struct Manifest {
   mojo_base.mojom.String16? name;
@@ -24,6 +26,7 @@
 
   mojo_base.mojom.String16? description;
 
+  // Defaults to start_url with origin stripped when id field is not present.
   mojo_base.mojom.String16? id;
 
   url.mojom.Url start_url;
@@ -44,16 +47,19 @@
 
   // TODO(crbug.com/829689): This field is non-standard and part of a Chrome
   // experiment. See:
-  // https://github.com/WICG/file-handling/blob/master/explainer.md
+  // https://github.com/WICG/file-handling/blob/main/explainer.md
   // As such, this field should not be exposed by default.
   array<ManifestFileHandler> file_handlers;
 
   // TODO(crbug.com/1019239): This is going into the mainline manifest spec,
   // remove the TODO once that PR goes in.
   // The URLProtocolHandler explainer can be found here:
-  // https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/master/URLProtocolHandler/explainer.md
+  // https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/URLProtocolHandler/explainer.md
   array<ManifestProtocolHandler> protocol_handlers;
 
+  // TODO(crbug.com/1072058): This field is non-standard and part of an
+  // experiment. See:
+  // https://github.com/WICG/pwa-url-handler/blob/main/explainer.md
   array<ManifestUrlHandler> url_handlers;
 
   // TODO(crbug.com/1185678): This field is non-standard and part of a manifest
@@ -78,11 +84,13 @@
 
   mojo_base.mojom.String16? gcm_sender_id;
 
+  // Defaults to the start URL (or document URL if start URL isn't present) with
+  // filename, query, and fragment removed if not present.
   url.mojom.Url scope;
 
   // TODO(crbug.com/1163398): This field is non-standard and part of a Chrome
   // experiment. See:
-  // https://github.com/WICG/sw-launch/blob/master/declarative_link_capturing.md#proposal
+  // https://github.com/WICG/sw-launch/blob/main/declarative_link_capturing.md#proposal
   // As such, this field should not be exposed by default.
   CaptureLinks capture_links;
 
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 760df7c8..b3559c6 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -3017,8 +3017,8 @@
   kBarcodeDetectorDetect = 3711,
   kFaceDetectorDetect = 3712,
   kTextDetectorDetect = 3713,
-  kLocalStorageFirstUsedBeforeFcp = 3714,
-  kLocalStorageFirstUsedAfterFcp = 3715,
+  kOBSOLETE_LocalStorageFirstUsedBeforeFcp = 3714,
+  kOBSOLETE_LocalStorageFirstUsedAfterFcp = 3715,
   kOBSOLETE_CSSPseudoHostCompoundList = 3716,
   kOBSOLETE_CSSPseudoHostContextCompoundList = 3717,
   kOBSOLETE_CSSPseudoHostDynamicSpecificity = 3718,
@@ -3099,8 +3099,8 @@
   kWindowScreenChange = 3790,
   kXRWebGLDepthInformationTextureAttribute = 3791,
   kXRWebGLBindingGetDepthInformation = 3792,
-  kSessionStorageFirstUsedBeforeFcp = 3793,
-  kSessionStorageFirstUsedAfterFcp = 3794,
+  kOBSOLETE_SessionStorageFirstUsedBeforeFcp = 3793,
+  kOBSOLETE_SessionStorageFirstUsedAfterFcp = 3794,
   kGravitySensorConstructor = 3795,
   kElementInternalsStates = 3796,
   kWebPImage = 3797,
diff --git a/third_party/blink/public/platform/web_audio_source_provider.h b/third_party/blink/public/platform/web_audio_source_provider.h
index 1ce318e..6391ade 100644
--- a/third_party/blink/public/platform/web_audio_source_provider.h
+++ b/third_party/blink/public/platform/web_audio_source_provider.h
@@ -39,7 +39,7 @@
   // ProvideInput() gets called repeatedly to render time-slices of a continuous
   // audio stream. May be called from any thread.
   virtual void ProvideInput(const WebVector<float*>& audio_data,
-                            size_t number_of_frames) = 0;
+                            int number_of_frames) = 0;
 
   // If a client is set, we call it back when the audio format is available.
   // Must always be called from the same thread. I.e., once called on a thread,
diff --git a/third_party/blink/public/platform/webaudiosourceprovider_impl.h b/third_party/blink/public/platform/webaudiosourceprovider_impl.h
index b9f4aaa..5e15d7a 100644
--- a/third_party/blink/public/platform/webaudiosourceprovider_impl.h
+++ b/third_party/blink/public/platform/webaudiosourceprovider_impl.h
@@ -66,7 +66,7 @@
   // WebAudioSourceProvider implementation.
   void SetClient(WebAudioSourceProviderClient* client) override;
   void ProvideInput(const WebVector<float*>& audio_data,
-                    size_t number_of_frames) override;
+                    int number_of_frames) override;
 
   // RestartableAudioRendererSink implementation.
   void Initialize(const media::AudioParameters& params,
diff --git a/third_party/blink/public/web/DEPS b/third_party/blink/public/web/DEPS
index 6c0fbfa..317e571 100644
--- a/third_party/blink/public/web/DEPS
+++ b/third_party/blink/public/web/DEPS
@@ -38,6 +38,7 @@
     "+services/network/public/mojom/cors.mojom-shared.h",
     "+services/network/public/mojom/cors_origin_pattern.mojom-shared.h",
     "+services/network/public/mojom/fetch_api.mojom-shared.h",
+    "+services/network/public/mojom/restricted_cookie_manager.mojom-shared.h",
     "+services/network/public/mojom/ip_address_space.mojom-shared.h",
     "+services/network/public/mojom/link_header.mojom-shared.h",
     "+services/network/public/mojom/referrer_policy.mojom-shared.h",
diff --git a/third_party/blink/public/web/web_document.h b/third_party/blink/public/web/web_document.h
index f58217bf..b463f30c 100644
--- a/third_party/blink/public/web/web_document.h
+++ b/third_party/blink/public/web/web_document.h
@@ -34,6 +34,8 @@
 #include "net/cookies/site_for_cookies.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "services/network/public/mojom/referrer_policy.mojom-shared.h"
+#include "services/network/public/mojom/restricted_cookie_manager.mojom-shared.h"
+#include "third_party/blink/public/platform/cross_variant_mojo_util.h"
 #include "third_party/blink/public/platform/web_security_origin.h"
 #include "third_party/blink/public/platform/web_vector.h"
 #include "third_party/blink/public/web/web_draggable_region.h"
@@ -153,6 +155,11 @@
   BLINK_EXPORT void AddPostPrerenderingActivationStep(
       base::OnceClosure callback);
 
+  // Sets a cookie manager which can be used for this document.
+  BLINK_EXPORT void SetCookieManager(
+      CrossVariantMojoRemote<
+          network::mojom::RestrictedCookieManagerInterfaceBase> cookie_manager);
+
 #if INSIDE_BLINK
   BLINK_EXPORT WebDocument(Document*);
   BLINK_EXPORT WebDocument& operator=(Document*);
diff --git a/third_party/blink/renderer/core/animation/css/css_animation.cc b/third_party/blink/renderer/core/animation/css/css_animation.cc
index 5c03b68..22dc98e 100644
--- a/third_party/blink/renderer/core/animation/css/css_animation.cc
+++ b/third_party/blink/renderer/core/animation/css/css_animation.cc
@@ -13,7 +13,7 @@
 CSSAnimation::CSSAnimation(ExecutionContext* execution_context,
                            AnimationTimeline* timeline,
                            AnimationEffect* content,
-                           int animation_index,
+                           wtf_size_t animation_index,
                            const String& animation_name)
     : Animation(execution_context, timeline, content),
       animation_index_(animation_index),
diff --git a/third_party/blink/renderer/core/animation/css/css_animation.h b/third_party/blink/renderer/core/animation/css/css_animation.h
index 26879f1d..a531dac 100644
--- a/third_party/blink/renderer/core/animation/css/css_animation.h
+++ b/third_party/blink/renderer/core/animation/css/css_animation.h
@@ -19,7 +19,7 @@
   CSSAnimation(ExecutionContext*,
                AnimationTimeline*,
                AnimationEffect*,
-               int animation_index,
+               wtf_size_t animation_index,
                const String& animation_name);
 
   bool IsCSSAnimation() const final { return true; }
@@ -28,7 +28,7 @@
   Element* OwningElement() const override { return owning_element_; }
 
   const String& animationName() const { return animation_name_; }
-  int AnimationIndex() const { return animation_index_; }
+  wtf_size_t AnimationIndex() const { return animation_index_; }
   void SetAnimationIndex(wtf_size_t absolute_position) {
     animation_index_ = absolute_position;
   }
@@ -94,7 +94,7 @@
   // animation_index_ represents the absolute position of an animation within
   // the same owning element. This index helps resolve the animation ordering
   // when comparing two animations with the same owning element.
-  int animation_index_;
+  wtf_size_t animation_index_;
   AtomicString animation_name_;
 
   // When set, the web-animation API is overruling the animation-play-state
diff --git a/third_party/blink/renderer/core/animation/css/css_animation_update.h b/third_party/blink/renderer/core/animation/css/css_animation_update.h
index e310f4918..024f65b5 100644
--- a/third_party/blink/renderer/core/animation/css/css_animation_update.h
+++ b/third_party/blink/renderer/core/animation/css/css_animation_update.h
@@ -28,7 +28,7 @@
  public:
   NewCSSAnimation(AtomicString name,
                   size_t name_index,
-                  size_t position_index,
+                  wtf_size_t position_index,
                   const InertEffect& effect,
                   Timing timing,
                   StyleRuleKeyframes* style_rule,
@@ -52,7 +52,7 @@
 
   AtomicString name;
   size_t name_index;
-  size_t position_index;
+  wtf_size_t position_index;
   Member<const InertEffect> effect;
   Timing timing;
   Member<StyleRuleKeyframes> style_rule;
diff --git a/third_party/blink/renderer/core/css/css.dict b/third_party/blink/renderer/core/css/css.dict
index 5cf8bfd..3cd35b8 100644
--- a/third_party/blink/renderer/core/css/css.dict
+++ b/third_party/blink/renderer/core/css/css.dict
@@ -927,6 +927,7 @@
 "hidden-matchable"
 "more"
 "less"
+"custom"
 
 # at-rules
 "@charset"
diff --git a/third_party/blink/renderer/core/css/css_value_keywords.json5 b/third_party/blink/renderer/core/css/css_value_keywords.json5
index 6dd2582..c96876f 100644
--- a/third_party/blink/renderer/core/css/css_value_keywords.json5
+++ b/third_party/blink/renderer/core/css/css_value_keywords.json5
@@ -1407,6 +1407,7 @@
     // no-preference
     "more",
     "less",
+    "custom",
 
     // @counter-style system
     "cyclic",
diff --git a/third_party/blink/renderer/core/css/media_query_evaluator.cc b/third_party/blink/renderer/core/css/media_query_evaluator.cc
index 1d914a55..d1f712f 100644
--- a/third_party/blink/renderer/core/css/media_query_evaluator.cc
+++ b/third_party/blink/renderer/core/css/media_query_evaluator.cc
@@ -924,6 +924,8 @@
     case CSSValueID::kNoPreference:
       return preferred_contrast ==
              mojom::blink::PreferredContrast::kNoPreference;
+    case CSSValueID::kCustom:
+      return preferred_contrast == mojom::blink::PreferredContrast::kCustom;
     default:
       NOTREACHED();
       return false;
diff --git a/third_party/blink/renderer/core/css/media_query_evaluator_test.cc b/third_party/blink/renderer/core/css/media_query_evaluator_test.cc
index 815a72d..9f3dec4a 100644
--- a/third_party/blink/renderer/core/css/media_query_evaluator_test.cc
+++ b/third_party/blink/renderer/core/css/media_query_evaluator_test.cc
@@ -191,6 +191,7 @@
     {"(prefers-contrast: more)", false},
     {"(prefers-contrast: less)", false},
     {"(prefers-contrast: no-preference)", true},
+    {"(prefers-contrast: custom)", false},
     {nullptr, false}  // Do not remove the terminator line.
 };
 
@@ -199,6 +200,7 @@
     {"(prefers-contrast: more)", true},
     {"(prefers-contrast: less)", false},
     {"(prefers-contrast: no-preference)", false},
+    {"(prefers-contrast: custom)", false},
     {nullptr, false}  // Do not remove the terminator line.
 };
 
@@ -207,6 +209,16 @@
     {"(prefers-contrast: more)", false},
     {"(prefers-contrast: less)", true},
     {"(prefers-contrast: no-preference)", false},
+    {"(prefers-contrast: custom)", false},
+    {nullptr, false}  // Do not remove the terminator line.
+};
+
+MediaQueryEvaluatorTestCase g_preferscontrast_custom_cases[] = {
+    {"(prefers-contrast)", true},
+    {"(prefers-contrast: more)", false},
+    {"(prefers-contrast: less)", false},
+    {"(prefers-contrast: no-preference)", false},
+    {"(prefers-contrast: custom)", true},
     {nullptr, false}  // Do not remove the terminator line.
 };
 
@@ -504,6 +516,14 @@
     MediaQueryEvaluator media_query_evaluator(*media_values);
     TestMQEvaluator(g_preferscontrast_less_cases, media_query_evaluator);
   }
+
+  // Prefers-contrast - custom.
+  {
+    data.preferred_contrast = mojom::blink::PreferredContrast::kCustom;
+    MediaValues* media_values = MakeGarbageCollected<MediaValuesCached>(data);
+    MediaQueryEvaluator media_query_evaluator(*media_values);
+    TestMQEvaluator(g_preferscontrast_custom_cases, media_query_evaluator);
+  }
 }
 
 TEST(MediaQueryEvaluatorTest, CachedScreenSpanning) {
diff --git a/third_party/blink/renderer/core/css/media_query_exp.cc b/third_party/blink/renderer/core/css/media_query_exp.cc
index 9f64101d2..0f47524e 100644
--- a/third_party/blink/renderer/core/css/media_query_exp.cc
+++ b/third_party/blink/renderer/core/css/media_query_exp.cc
@@ -82,7 +82,7 @@
   if (RuntimeEnabledFeatures::PrefersContrastEnabled()) {
     if (media_feature == media_feature_names::kPrefersContrastMediaFeature) {
       return ident == CSSValueID::kNoPreference || ident == CSSValueID::kMore ||
-             ident == CSSValueID::kLess;
+             ident == CSSValueID::kLess || ident == CSSValueID::kCustom;
     }
   }
 
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
index e121e52..08029c8 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
@@ -3985,7 +3985,7 @@
   CSSParserTokenRange args = ConsumeFunction(range);
   // The number of repetitions for <auto-repeat> is not important at parsing
   // level because it will be computed later, let's set it to 1.
-  size_t repetitions = 1;
+  wtf_size_t repetitions = 1;
   is_auto_repeat = IdentMatches<CSSValueID::kAutoFill, CSSValueID::kAutoFit>(
       args.Peek().Id());
   CSSValueList* repeated_values;
@@ -3998,7 +3998,7 @@
     if (!repetition)
       return false;
     repetitions =
-        clampTo<size_t>(repetition->GetDoubleValue(), 0, kGridMaxTracks);
+        clampTo<wtf_size_t>(repetition->GetDoubleValue(), 0, kGridMaxTracks);
     repeated_values = CSSValueList::CreateSpaceSeparated();
   }
   if (!ConsumeCommaIncludingWhitespace(args))
@@ -4007,7 +4007,7 @@
   if (line_names)
     repeated_values->Append(*line_names);
 
-  size_t number_of_tracks = 0;
+  wtf_size_t number_of_tracks = 0;
   while (!args.AtEnd()) {
     CSSValue* track_size = ConsumeGridTrackSize(args, context);
     if (!track_size)
diff --git a/third_party/blink/renderer/core/css/style_engine_test.cc b/third_party/blink/renderer/core/css/style_engine_test.cc
index 7ede4db6..ecea7cf 100644
--- a/third_party/blink/renderer/core/css/style_engine_test.cc
+++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -1588,7 +1588,6 @@
   ColorSchemeHelper color_scheme_helper(GetDocument());
   color_scheme_helper.SetPreferredContrast(
       mojom::blink::PreferredContrast::kNoPreference);
-  color_scheme_helper.SetForcedColors(GetDocument(), ForcedColors::kNone);
 
   GetDocument().body()->setInnerHTML(R"HTML(
     <style>
@@ -1622,7 +1621,8 @@
             GetDocument().body()->GetComputedStyle()->VisitedDependentColor(
                 GetCSSPropertyColor()));
 
-  color_scheme_helper.SetForcedColors(GetDocument(), ForcedColors::kActive);
+  color_scheme_helper.SetPreferredContrast(
+      mojom::blink::PreferredContrast::kCustom);
   UpdateAllLifecyclePhases();
   EXPECT_EQ(MakeRGB(0, 0, 255),
             GetDocument().body()->GetComputedStyle()->VisitedDependentColor(
@@ -1636,7 +1636,6 @@
   ColorSchemeHelper color_scheme_helper(GetDocument());
   color_scheme_helper.SetPreferredContrast(
       mojom::blink::PreferredContrast::kNoPreference);
-  color_scheme_helper.SetForcedColors(GetDocument(), ForcedColors::kNone);
 
   GetDocument().body()->setInnerHTML(R"HTML(
     <style>
@@ -1647,6 +1646,9 @@
       @media (prefers-contrast: less) {
         body { color: orange }
       }
+      @media (prefers-contrast: custom) {
+        body { color: yellow }
+      }
     </style>
     <body></body>
   )HTML");
@@ -1669,6 +1671,13 @@
   EXPECT_EQ(MakeRGB(255, 165, 0),
             GetDocument().body()->GetComputedStyle()->VisitedDependentColor(
                 GetCSSPropertyColor()));
+
+  color_scheme_helper.SetPreferredContrast(
+      mojom::blink::PreferredContrast::kCustom);
+  UpdateAllLifecyclePhases();
+  EXPECT_EQ(MakeRGB(255, 255, 0),
+            GetDocument().body()->GetComputedStyle()->VisitedDependentColor(
+                GetCSSPropertyColor()));
 }
 
 TEST_F(StyleEngineTest, MediaQueriesChangePrefersReducedMotion) {
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index bdb5f10..a26ca4a 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -5486,6 +5486,12 @@
   return cookie_jar_->CookiesEnabled();
 }
 
+void Document::SetCookieManager(
+    mojo::PendingRemote<network::mojom::blink::RestrictedCookieManager>
+        cookie_manager) {
+  cookie_jar_->SetCookieManager(std::move(cookie_manager));
+}
+
 const AtomicString& Document::referrer() const {
   if (Loader())
     return Loader()->GetReferrer().referrer;
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index 5e8a699..6e83b137 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -39,6 +39,7 @@
 #include "base/timer/elapsed_timer.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "services/network/public/mojom/referrer_policy.mojom-blink-forward.h"
+#include "services/network/public/mojom/restricted_cookie_manager.mojom-blink-forward.h"
 #include "services/network/public/mojom/web_sandbox_flags.mojom-blink-forward.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/metrics/document_update_reason.h"
@@ -1043,6 +1044,10 @@
   void setCookie(const String&, ExceptionState&);
   bool CookiesEnabled() const;
 
+  void SetCookieManager(
+      mojo::PendingRemote<network::mojom::blink::RestrictedCookieManager>
+          cookie_manager);
+
   const AtomicString& referrer() const;
 
   String domain() const;
diff --git a/third_party/blink/renderer/core/exported/web_document.cc b/third_party/blink/renderer/core/exported/web_document.cc
index eced8953..f89529b 100644
--- a/third_party/blink/renderer/core/exported/web_document.cc
+++ b/third_party/blink/renderer/core/exported/web_document.cc
@@ -306,6 +306,12 @@
       std::move(callback));
 }
 
+void WebDocument::SetCookieManager(
+    CrossVariantMojoRemote<network::mojom::RestrictedCookieManagerInterfaceBase>
+        cookie_manager) {
+  Unwrap<Document>()->SetCookieManager(std::move(cookie_manager));
+}
+
 WebDocument::WebDocument(Document* elem) : WebNode(elem) {}
 
 DEFINE_WEB_NODE_TYPE_CASTS(WebDocument, ConstUnwrap<Node>()->IsDocumentNode())
diff --git a/third_party/blink/renderer/core/fetch/fetch_manager.cc b/third_party/blink/renderer/core/fetch/fetch_manager.cc
index 8fdccd3..899c6520 100644
--- a/third_party/blink/renderer/core/fetch/fetch_manager.cc
+++ b/third_party/blink/renderer/core/fetch/fetch_manager.cc
@@ -18,6 +18,7 @@
 #include "services/network/public/mojom/fetch_api.mojom-blink.h"
 #include "services/network/public/mojom/trust_tokens.mojom-blink.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/mojom/loader/code_cache.mojom-blink.h"
 #include "third_party/blink/public/platform/web_url_request.h"
@@ -884,7 +885,12 @@
   failed_ = true;
   if (execution_context_->IsContextDestroyed())
     return;
-  if (!message.IsEmpty()) {
+  bool issue_only =
+      base::FeatureList::IsEnabled(blink::features::kCORSErrorsIssueOnly) &&
+      issue_id;
+  if (!message.IsEmpty() && !issue_only) {
+    // CORS issues are reported via network service instrumentation, with the
+    // exception of early errors reported in FileIssueAndPerformNetworkError.
     execution_context_->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
         mojom::ConsoleMessageSource::kJavaScript,
         mojom::ConsoleMessageLevel::kError, message));
diff --git a/third_party/blink/renderer/core/frame/deprecation.cc b/third_party/blink/renderer/core/frame/deprecation.cc
index ef37dc7..bbf5274 100644
--- a/third_party/blink/renderer/core/frame/deprecation.cc
+++ b/third_party/blink/renderer/core/frame/deprecation.cc
@@ -574,15 +574,6 @@
               "contexts, and will start blocking them in Chrome 92 (July "
               "2021). See https://chromestatus.com/feature/5436853517811712 "
               "for more details."};
-    case WebFeature::kRTCPeerConnectionOfferAllowExtmapMixedFalse:
-      return {"RTCPeerConnectionOfferExtmapAllowMixedFalse", kM93,
-              "The RTCPeerConnection offerAllowExtmapMixed option is a "
-              "non-standard feature. This feature will be removed in M93 "
-              "(Canary: July 15, 2021; Stable: August 24, 2021). For "
-              "interoperability with legacy WebRTC versions that throw "
-              "errors when attempting to parse the a=extmap-allow-mixed "
-              "line in the SDP remove the line from the SDP during "
-              "signalling."};
     case WebFeature::kXHRJSONEncodingDetection:
       return {"XHRJSONEncodingDetection", kM93,
               "UTF-16 is not supported by response json in XMLHttpRequest"};
diff --git a/third_party/blink/renderer/core/frame/frame_console.cc b/third_party/blink/renderer/core/frame/frame_console.cc
index 2b4eff7..9fbf419f 100644
--- a/third_party/blink/renderer/core/frame/frame_console.cc
+++ b/third_party/blink/renderer/core/frame/frame_console.cc
@@ -30,6 +30,7 @@
 
 #include <memory>
 
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/renderer/bindings/core/v8/source_location.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
@@ -122,6 +123,12 @@
   if (error.IsCancellation() || error.IsUnactionableTrustTokensStatus())
     return;
 
+  if (error.CorsErrorStatus() &&
+      base::FeatureList::IsEnabled(blink::features::kCORSErrorsIssueOnly)) {
+    // CORS issues are reported via network service instrumentation.
+    return;
+  }
+
   StringBuilder message;
   message.Append("Failed to load resource");
   if (!error.LocalizedDescription().IsEmpty()) {
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h
index 59a8bca..f591ed4 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h
@@ -279,6 +279,10 @@
 
   static CanvasPerformanceMonitor& GetCanvasPerformanceMonitor();
 
+  virtual bool IdentifiabilityEncounteredPartiallyDigestedImage() const {
+    return false;
+  }
+
  protected:
   CanvasRenderingContext(CanvasRenderingContextHost*,
                          const CanvasContextCreationAttributesCore&,
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc
index cbecc557..f3ce73c 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc
@@ -393,31 +393,19 @@
     const CanvasRenderingContext* const context) const {
   const uint64_t context_digest =
       context ? context->IdentifiableTextToken().ToUkmMetricValue() : 0;
-  const IdentifiabilityPaintOpDigest* const identifiability_paintop_digest =
-      ResourceProvider()
-          ? &(ResourceProvider()->GetIdentifiablityPaintOpDigest())
-          : nullptr;
-  const uint64_t canvas_digest =
-      identifiability_paintop_digest
-          ? identifiability_paintop_digest->GetToken().ToUkmMetricValue()
-          : 0;
   const uint64_t context_type =
       context ? context->GetContextType()
               : CanvasRenderingContext::kContextTypeUnknown;
   const bool encountered_skipped_ops =
-      (context && context->IdentifiabilityEncounteredSkippedOps()) ||
-      (identifiability_paintop_digest &&
-       identifiability_paintop_digest->encountered_skipped_ops());
+      context && context->IdentifiabilityEncounteredSkippedOps();
   const bool encountered_sensitive_ops =
       context && context->IdentifiabilityEncounteredSensitiveOps();
   const bool encountered_partially_digested_image =
-      identifiability_paintop_digest &&
-      identifiability_paintop_digest->encountered_partially_digested_image();
+      context && context->IdentifiabilityEncounteredPartiallyDigestedImage();
   // Bits [0-3] are the context type, bits [4-6] are skipped ops, sensitive
   // ops, and partial image ops bits, respectively. The remaining bits are
   // for the canvas digest.
-  uint64_t final_digest =
-      ((context_digest ^ canvas_digest) << 7) | context_type;
+  uint64_t final_digest = (context_digest << 7) | context_type;
   if (encountered_skipped_ops)
     final_digest |= IdentifiableSurface::CanvasTaintBit::kSkipped;
   if (encountered_sensitive_ops)
diff --git a/third_party/blink/renderer/core/html/forms/html_select_menu_element.cc b/third_party/blink/renderer/core/html/forms/html_select_menu_element.cc
index 0a2b9dc5..bc9f8d2 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_menu_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_select_menu_element.cc
@@ -255,8 +255,13 @@
   // is to get named slots working in UA shadow DOM (crbug.com/1179356), and
   // then we can switch to that and use the -webkit pseudo-id selectors.
 
-  auto* button_slot = MakeGarbageCollected<HTMLSlotElement>(document);
-  button_slot->setAttribute(html_names::kNameAttr, kButtonPartName);
+  button_slot_ = MakeGarbageCollected<HTMLSlotElement>(document);
+  button_slot_->setAttribute(html_names::kNameAttr, kButtonPartName);
+  slotchange_listener_ =
+      MakeGarbageCollected<HTMLSelectMenuElement::SlotChangeEventListener>(
+          this);
+  button_slot_->addEventListener(event_type_names::kSlotchange,
+                                 slotchange_listener_, /*use_capture=*/false);
 
   button_part_ = MakeGarbageCollected<HTMLButtonElement>(document);
   button_part_->setAttribute(html_names::kPartAttr, kButtonPartName);
@@ -274,7 +279,7 @@
       MakeGarbageCollected<HTMLSelectMenuElement::ButtonPartEventListener>(
           this);
   button_part_->addEventListener(event_type_names::kClick,
-                                 button_part_listener_, false);
+                                 button_part_listener_, /*use_capture=*/false);
 
   selected_value_part_ = MakeGarbageCollected<HTMLDivElement>(document);
   selected_value_part_->setAttribute(html_names::kPartAttr,
@@ -302,8 +307,10 @@
     width: 1.2em;
     )CSS");
 
-  auto* listbox_slot = MakeGarbageCollected<HTMLSlotElement>(document);
-  listbox_slot->setAttribute(html_names::kNameAttr, kListboxPartName);
+  listbox_slot_ = MakeGarbageCollected<HTMLSlotElement>(document);
+  listbox_slot_->setAttribute(html_names::kNameAttr, kListboxPartName);
+  listbox_slot_->addEventListener(event_type_names::kSlotchange,
+                                  slotchange_listener_, /*use_capture=*/false);
 
   SetListboxPart(MakeGarbageCollected<HTMLPopupElement>(document));
   listbox_part_->setAttribute(html_names::kPartAttr, kListboxPartName);
@@ -313,13 +320,13 @@
   button_part_->AppendChild(selected_value_part_);
   button_part_->AppendChild(button_icon);
 
-  button_slot->AppendChild(button_part_);
+  button_slot_->AppendChild(button_part_);
 
   listbox_part_->appendChild(options_slot);
-  listbox_slot->appendChild(listbox_part_);
+  listbox_slot_->appendChild(listbox_part_);
 
-  root.AppendChild(button_slot);
-  root.AppendChild(listbox_slot);
+  root.AppendChild(button_slot_);
+  root.AppendChild(listbox_slot_);
 
   option_part_listener_ =
       MakeGarbageCollected<HTMLSelectMenuElement::OptionPartEventListener>(
@@ -374,10 +381,18 @@
     listbox_part_->SetOwnerSelectMenuElement(nullptr);
     listbox_part_->SetNeedsRepositioningForSelectMenu(false);
   }
-  // TODO(crbug.com/1234899) Emit console warning if new_listbox_part is null
+
   if (new_listbox_part) {
     new_listbox_part->SetOwnerSelectMenuElement(this);
+  } else {
+    GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
+        mojom::blink::ConsoleMessageSource::kRendering,
+        mojom::blink::ConsoleMessageLevel::kWarning,
+        "<selectmenu>'s default listbox was removed by an element labeled "
+        "slot=\"listbox\", and a new one was not provided. This <selectmenu> "
+        "will not be fully functional."));
   }
+
   listbox_part_ = new_listbox_part;
 }
 
@@ -465,11 +480,19 @@
     button_part_->removeEventListener(event_type_names::kClick,
                                       button_part_listener_, false);
   }
-  // TODO(crbug.com/1234899) Emit console warning if new_button_part is null
+
   if (new_button_part) {
-    new_button_part->addEventListener(event_type_names::kClick,
-                                      button_part_listener_, false);
+    new_button_part->addEventListener(
+        event_type_names::kClick, button_part_listener_, /*use_capture=*/false);
+  } else {
+    GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
+        mojom::blink::ConsoleMessageSource::kRendering,
+        mojom::blink::ConsoleMessageLevel::kWarning,
+        "<selectmenu>'s default button was removed by an element labeled "
+        "slot=\"button\", and a new one was not provided. This <selectmenu> "
+        "will not be fully functional."));
   }
+
   button_part_ = new_button_part;
 }
 
@@ -478,7 +501,7 @@
     return;
   }
 
-  SetButtonPart(FirstValidButtonPart());
+  UpdateButtonPart();
 }
 
 void HTMLSelectMenuElement::ButtonPartRemoved(Element* button_part) {
@@ -486,6 +509,10 @@
     return;
   }
 
+  UpdateButtonPart();
+}
+
+void HTMLSelectMenuElement::UpdateButtonPart() {
   SetButtonPart(FirstValidButtonPart());
 }
 
@@ -505,13 +532,28 @@
   return nullptr;
 }
 
+HTMLSlotElement* HTMLSelectMenuElement::ButtonSlot() const {
+  return button_slot_;
+}
+
+HTMLSlotElement* HTMLSelectMenuElement::ListboxSlot() const {
+  return listbox_slot_;
+}
+
 void HTMLSelectMenuElement::SelectedValuePartInserted(
     Element* new_selected_value_part) {
-  selected_value_part_ = FirstValidSelectedValuePart();
+  UpdateSelectedValuePart();
 }
 
 void HTMLSelectMenuElement::SelectedValuePartRemoved(
     Element* selected_value_part) {
+  if (selected_value_part != selected_value_part_) {
+    return;
+  }
+  UpdateSelectedValuePart();
+}
+
+void HTMLSelectMenuElement::UpdateSelectedValuePart() {
   selected_value_part_ = FirstValidSelectedValuePart();
 }
 
@@ -536,8 +578,7 @@
     return;
   }
 
-  SetListboxPart(DynamicTo<HTMLPopupElement>(FirstValidListboxPart()));
-  // TODO(crbug.com/1121840) Should the current option parts be revalidated?
+  UpdateListboxPart();
 }
 
 void HTMLSelectMenuElement::ListboxPartRemoved(Element* listbox_part) {
@@ -545,6 +586,10 @@
     return;
   }
 
+  UpdateListboxPart();
+}
+
+void HTMLSelectMenuElement::UpdateListboxPart() {
   SetListboxPart(DynamicTo<HTMLPopupElement>(FirstValidListboxPart()));
   // TODO(crbug.com/1121840) Should the current option parts be revalidated?
 }
@@ -563,8 +608,8 @@
     new_option_element->OptionInsertedIntoSelectMenuElement();
   }
 
-  new_option_part->addEventListener(event_type_names::kClick,
-                                    option_part_listener_, false);
+  new_option_part->addEventListener(
+      event_type_names::kClick, option_part_listener_, /*use_capture=*/false);
   // TODO(crbug.com/1121840) We don't want to actually change the attribute,
   // and if tabindex is already set we shouldn't override it.  So we need to
   // come up with something else here.
@@ -686,14 +731,28 @@
   }
 }
 
+void HTMLSelectMenuElement::SlotChangeEventListener::Invoke(ExecutionContext*,
+                                                            Event* event) {
+  DCHECK_EQ(event->type(), event_type_names::kSlotchange);
+  if (event->target() == select_menu_element_->ListboxSlot()) {
+    select_menu_element_->UpdateListboxPart();
+  } else if (event->target() == select_menu_element_->ButtonSlot()) {
+    select_menu_element_->UpdateButtonPart();
+    select_menu_element_->UpdateSelectedValuePart();
+  }
+}
+
 void HTMLSelectMenuElement::Trace(Visitor* visitor) const {
   visitor->Trace(button_part_listener_);
   visitor->Trace(option_part_listener_);
+  visitor->Trace(slotchange_listener_);
   visitor->Trace(select_mutation_callback_);
   visitor->Trace(button_part_);
   visitor->Trace(selected_value_part_);
   visitor->Trace(listbox_part_);
   visitor->Trace(option_parts_);
+  visitor->Trace(button_slot_);
+  visitor->Trace(listbox_slot_);
   visitor->Trace(selected_option_);
   HTMLElement::Trace(visitor);
 }
diff --git a/third_party/blink/renderer/core/html/forms/html_select_menu_element.h b/third_party/blink/renderer/core/html/forms/html_select_menu_element.h
index c6ecb932..7e2ce81 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_menu_element.h
+++ b/third_party/blink/renderer/core/html/forms/html_select_menu_element.h
@@ -44,6 +44,8 @@
   Element* FirstValidButtonPart() const;
   Element* FirstValidListboxPart() const;
   Element* FirstValidSelectedValuePart() const;
+  HTMLSlotElement* ButtonSlot() const;
+  HTMLSlotElement* ListboxSlot() const;
   void EnsureSelectedOptionIsValid();
   Element* SelectedOption();
   void SetSelectedOption(Element* selected_option);
@@ -51,10 +53,13 @@
 
   void ButtonPartInserted(Element*);
   void ButtonPartRemoved(Element*);
+  void UpdateButtonPart();
   void SelectedValuePartInserted(Element*);
   void SelectedValuePartRemoved(Element*);
+  void UpdateSelectedValuePart();
   void ListboxPartInserted(Element*);
   void ListboxPartRemoved(Element*);
+  void UpdateListboxPart();
   void OptionPartInserted(Element*);
   void OptionPartRemoved(Element*);
 
@@ -95,6 +100,21 @@
     Member<HTMLSelectMenuElement> select_menu_element_;
   };
 
+  class SlotChangeEventListener : public NativeEventListener {
+   public:
+    explicit SlotChangeEventListener(HTMLSelectMenuElement* select_menu_element)
+        : select_menu_element_(select_menu_element) {}
+    void Invoke(ExecutionContext*, Event*) override;
+
+    void Trace(Visitor* visitor) const override {
+      visitor->Trace(select_menu_element_);
+      NativeEventListener::Trace(visitor);
+    }
+
+   private:
+    Member<HTMLSelectMenuElement> select_menu_element_;
+  };
+
   static constexpr char kButtonPartName[] = "button";
   static constexpr char kSelectedValuePartName[] = "selected-value";
   static constexpr char kListboxPartName[] = "listbox";
@@ -102,6 +122,7 @@
 
   Member<ButtonPartEventListener> button_part_listener_;
   Member<OptionPartEventListener> option_part_listener_;
+  Member<SlotChangeEventListener> slotchange_listener_;
 
   Member<SelectMutationCallback> select_mutation_callback_;
 
@@ -109,6 +130,8 @@
   Member<Element> selected_value_part_;
   Member<HTMLPopupElement> listbox_part_;
   HeapLinkedHashSet<Member<Element>> option_parts_;
+  Member<HTMLSlotElement> button_slot_;
+  Member<HTMLSlotElement> listbox_slot_;
   Member<Element> selected_option_;
 };
 
diff --git a/third_party/blink/renderer/core/html/media/html_media_element.cc b/third_party/blink/renderer/core/html/media/html_media_element.cc
index d2055d5..74338c0 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element.cc
+++ b/third_party/blink/renderer/core/html/media/html_media_element.cc
@@ -4446,7 +4446,7 @@
 
 void HTMLMediaElement::AudioSourceProviderImpl::ProvideInput(
     AudioBus* bus,
-    uint32_t frames_to_process) {
+    int frames_to_process) {
   DCHECK(bus);
 
   MutexTryLocker try_locker(provide_input_lock);
diff --git a/third_party/blink/renderer/core/html/media/html_media_element.h b/third_party/blink/renderer/core/html/media/html_media_element.h
index 02afd36..4af2fe7 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element.h
+++ b/third_party/blink/renderer/core/html/media/html_media_element.h
@@ -817,7 +817,7 @@
 
     // AudioSourceProvider
     void SetClient(AudioSourceProviderClient*) override;
-    void ProvideInput(AudioBus*, uint32_t frames_to_process) override;
+    void ProvideInput(AudioBus*, int frames_to_process) override;
 
     void Trace(Visitor*) const;
 
diff --git a/third_party/blink/renderer/core/layout/column_balancer.cc b/third_party/blink/renderer/core/layout/column_balancer.cc
index 7de5389..43b8890 100644
--- a/third_party/blink/renderer/core/layout/column_balancer.cc
+++ b/third_party/blink/renderer/core/layout/column_balancer.cc
@@ -322,9 +322,9 @@
   unsigned index_with_largest_height = 0;
   LayoutUnit largest_height;
   LayoutUnit previous_offset = LogicalTopInFlowThread();
-  size_t run_count = content_runs_.size();
+  wtf_size_t run_count = content_runs_.size();
   DCHECK(run_count);
-  for (size_t i = FirstContentRunIndexInLastRow(); i < run_count; i++) {
+  for (unsigned i = FirstContentRunIndexInLastRow(); i < run_count; i++) {
     const ContentRun& run = content_runs_[i];
     LayoutUnit height = run.ColumnLogicalHeight(previous_offset);
     if (largest_height < height) {
diff --git a/third_party/blink/renderer/core/layout/flexible_box_algorithm.cc b/third_party/blink/renderer/core/layout/flexible_box_algorithm.cc
index c329d738..12bd565d 100644
--- a/third_party/blink/renderer/core/layout/flexible_box_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/flexible_box_algorithm.cc
@@ -336,7 +336,7 @@
 }
 void FlexLine::FreezeViolations(ViolationsVector& violations) {
   const ComputedStyle& flex_box_style = algorithm_->StyleRef();
-  for (size_t i = 0; i < violations.size(); ++i) {
+  for (wtf_size_t i = 0; i < violations.size(); ++i) {
     DCHECK(!violations[i]->frozen_) << i;
     const ComputedStyle& child_style = violations[i]->style_;
     LayoutUnit child_size = violations[i]->flexed_content_size_;
@@ -366,7 +366,7 @@
 
   ViolationsVector new_inflexible_items;
   const ComputedStyle& flex_box_style = algorithm_->StyleRef();
-  for (size_t i = 0; i < line_items_.size(); ++i) {
+  for (wtf_size_t i = 0; i < line_items_.size(); ++i) {
     FlexItem& flex_item = line_items_[i];
     DCHECK(!flex_item.frozen_) << i;
     float flex_factor =
@@ -406,7 +406,7 @@
   }
 
   const ComputedStyle& flex_box_style = algorithm_->StyleRef();
-  for (size_t i = 0; i < line_items_.size(); ++i) {
+  for (wtf_size_t i = 0; i < line_items_.size(); ++i) {
     FlexItem& flex_item = line_items_[i];
 
     // This check also covers out-of-flow children.
@@ -460,7 +460,7 @@
 
   int number_of_auto_margins = 0;
   bool is_horizontal = algorithm_->IsHorizontalFlow();
-  for (size_t i = 0; i < line_items_.size(); ++i) {
+  for (wtf_size_t i = 0; i < line_items_.size(); ++i) {
     const ComputedStyle& style = line_items_[i].style_;
     if (is_horizontal) {
       if (style.MarginLeft().IsAuto())
@@ -493,7 +493,7 @@
   // Recalculate the remaining free space. The adjustment for flex factors
   // between 0..1 means we can't just use remainingFreeSpace here.
   LayoutUnit total_item_size;
-  for (size_t i = 0; i < line_items_.size(); ++i)
+  for (wtf_size_t i = 0; i < line_items_.size(); ++i)
     total_item_size += line_items_[i].FlexedMarginBoxSize();
   remaining_free_space_ =
       container_main_inner_size_ - total_item_size -
@@ -534,7 +534,7 @@
 
   LayoutUnit max_descent;  // Used when align-items: baseline.
   LayoutUnit max_child_cross_axis_extent;
-  for (size_t i = 0; i < line_items_.size(); ++i) {
+  for (wtf_size_t i = 0; i < line_items_.size(); ++i) {
     FlexItem& flex_item = line_items_[i];
 
     flex_item.UpdateAutoMarginsInMainAxis(auto_margin_offset);
diff --git a/third_party/blink/renderer/core/layout/flexible_box_algorithm.h b/third_party/blink/renderer/core/layout/flexible_box_algorithm.h
index 06e3ac6..9a78ade 100644
--- a/third_party/blink/renderer/core/layout/flexible_box_algorithm.h
+++ b/third_party/blink/renderer/core/layout/flexible_box_algorithm.h
@@ -459,7 +459,7 @@
   const LayoutUnit line_break_length_;
   FlexItemVector all_items_;
   Vector<FlexLine> flex_lines_;
-  size_t next_item_index_;
+  wtf_size_t next_item_index_;
 };
 
 inline const FlexLine* FlexItem::Line() const {
diff --git a/third_party/blink/renderer/core/layout/grid.cc b/third_party/blink/renderer/core/layout/grid.cc
index db83431a..6041703e 100644
--- a/third_party/blink/renderer/core/layout/grid.cc
+++ b/third_party/blink/renderer/core/layout/grid.cc
@@ -28,12 +28,12 @@
 
 Grid::Grid(const LayoutGrid* grid) : order_iterator_(grid) {}
 
-void Grid::SetExplicitGridStart(size_t row_start, size_t column_start) {
+void Grid::SetExplicitGridStart(wtf_size_t row_start, wtf_size_t column_start) {
   explicit_row_start_ = row_start;
   explicit_column_start_ = column_start;
 }
 
-size_t Grid::ExplicitGridStart(GridTrackSizingDirection direction) const {
+wtf_size_t Grid::ExplicitGridStart(GridTrackSizingDirection direction) const {
   return direction == kForRows ? explicit_row_start_ : explicit_column_start_;
 }
 
@@ -46,11 +46,11 @@
   grid_item_area_.Set(&item, area);
 }
 
-size_t Grid::GridItemPaintOrder(const LayoutBox& item) const {
+wtf_size_t Grid::GridItemPaintOrder(const LayoutBox& item) const {
   return grid_items_indexes_map_.at(&item);
 }
 
-void Grid::SetGridItemPaintOrder(const LayoutBox& item, size_t order) {
+void Grid::SetGridItemPaintOrder(const LayoutBox& item, wtf_size_t order) {
   grid_items_indexes_map_.Set(&item, order);
 }
 
@@ -60,17 +60,17 @@
 }
 #endif
 
-void Grid::SetAutoRepeatTracks(size_t auto_repeat_rows,
-                               size_t auto_repeat_columns) {
-  DCHECK_GE(static_cast<unsigned>(kGridMaxTracks),
+void Grid::SetAutoRepeatTracks(wtf_size_t auto_repeat_rows,
+                               wtf_size_t auto_repeat_columns) {
+  DCHECK_GE(static_cast<wtf_size_t>(kGridMaxTracks),
             NumTracks(kForRows) + auto_repeat_rows);
-  DCHECK_GE(static_cast<unsigned>(kGridMaxTracks),
+  DCHECK_GE(static_cast<wtf_size_t>(kGridMaxTracks),
             NumTracks(kForColumns) + auto_repeat_columns);
   auto_repeat_rows_ = auto_repeat_rows;
   auto_repeat_columns_ = auto_repeat_columns;
 }
 
-size_t Grid::AutoRepeatTracks(GridTrackSizingDirection direction) const {
+wtf_size_t Grid::AutoRepeatTracks(GridTrackSizingDirection direction) const {
   return direction == kForRows ? auto_repeat_rows_ : auto_repeat_columns_;
 }
 
@@ -90,7 +90,7 @@
 }
 
 bool Grid::IsEmptyAutoRepeatTrack(GridTrackSizingDirection direction,
-                                  size_t line) const {
+                                  wtf_size_t line) const {
   DCHECK(HasAutoRepeatEmptyTracks(direction));
   return AutoRepeatEmptyTracks(direction)->Contains(line);
 }
@@ -128,8 +128,8 @@
 }
 
 Grid::GridIterator::GridIterator(GridTrackSizingDirection direction,
-                                 size_t fixed_track_index,
-                                 size_t varying_track_index)
+                                 wtf_size_t fixed_track_index,
+                                 wtf_size_t varying_track_index)
     : direction_(direction),
       row_index_((direction == kForColumns) ? varying_track_index
                                             : fixed_track_index),
@@ -137,11 +137,11 @@
                                                : varying_track_index),
       child_index_(0) {}
 
-ListGrid::GridCell* ListGrid::GridTrack::Find(size_t index) const {
+ListGrid::GridCell* ListGrid::GridTrack::Find(wtf_size_t index) const {
   auto orthogonal_axis = OrthogonalDirection(direction_);
   for (auto* cell = cells_.Head(); cell;
        cell = cell->NextInDirection(direction_)) {
-    size_t cell_index = cell->Index(orthogonal_axis);
+    wtf_size_t cell_index = cell->Index(orthogonal_axis);
     if (cell_index == index)
       return cell;
     if (cell_index > index)
@@ -150,7 +150,7 @@
   return nullptr;
 }
 
-static int ComparePositions(size_t first, size_t second) {
+static int ComparePositions(wtf_size_t first, wtf_size_t second) {
   return first < second ? -1 : (first != second);
 }
 
@@ -183,8 +183,8 @@
                             second->Index(ortho_direction));
   };
 
-  size_t col_index = direction_ == kForColumns ? Index() : span.StartLine();
-  size_t row_index = direction_ == kForColumns ? span.StartLine() : Index();
+  wtf_size_t col_index = direction_ == kForColumns ? Index() : span.StartLine();
+  wtf_size_t row_index = direction_ == kForColumns ? span.StartLine() : Index();
 
   auto result = cells_.Insert(
       base::WrapUnique(new GridCell(row_index, col_index)), compare_cells);
@@ -199,8 +199,10 @@
     auto ortho_direction = OrthogonalDirection(direction_);
     if (!cell->Next() ||
         (cell->Next()->Index(ortho_direction) != (index + 1))) {
-      size_t next_col_index = direction_ == kForColumns ? Index() : index + 1;
-      size_t next_row_index = direction_ == kForColumns ? index + 1 : Index();
+      wtf_size_t next_col_index =
+          direction_ == kForColumns ? Index() : index + 1;
+      wtf_size_t next_row_index =
+          direction_ == kForColumns ? index + 1 : Index();
       auto next_cell =
           base::WrapUnique(new GridCell(next_row_index, next_col_index));
       if (InsertAfter(next_cell.get(), cell).is_new_entry)
@@ -244,8 +246,8 @@
   }
 }
 
-const GridItemList& ListGrid::Cell(size_t row_index,
-                                   size_t column_index) const {
+const GridItemList& ListGrid::Cell(wtf_size_t row_index,
+                                   wtf_size_t column_index) const {
   DEFINE_STATIC_LOCAL(const GridItemList, empty_vector, ());
   for (auto* row = rows_.Head(); row; row = row->Next()) {
     if (row->Index() == row_index) {
@@ -267,8 +269,8 @@
     return ComparePositions(first->Index(), second->Index());
   };
 
-  size_t start_line = span.StartLine();
-  size_t end_line = span.EndLine();
+  wtf_size_t start_line = span.StartLine();
+  wtf_size_t end_line = span.EndLine();
 
   DoublyLinkedList<ListGrid::GridTrack>::AddResult result = tracks.Insert(
       base::WrapUnique(new GridTrack(start_line, direction)), compare_tracks);
@@ -276,7 +278,7 @@
   DCHECK(track);
 
   auto* iter = track;
-  for (size_t track_index = start_line + 1; iter && track_index < end_line;
+  for (wtf_size_t track_index = start_line + 1; iter && track_index < end_line;
        ++track_index) {
     if (!iter->Next() || track_index < iter->Next()->Index()) {
       tracks.InsertAfter(
@@ -327,8 +329,8 @@
   SetGridItemArea(item, area);
 }
 
-void ListGrid::EnsureGridSize(size_t maximum_row_size,
-                              size_t maximum_column_size) {
+void ListGrid::EnsureGridSize(wtf_size_t maximum_row_size,
+                              wtf_size_t maximum_column_size) {
   num_rows_ = std::max(num_rows_, maximum_row_size);
   num_columns_ = std::max(num_columns_, maximum_column_size);
 }
@@ -362,16 +364,16 @@
 
 std::unique_ptr<Grid::GridIterator> ListGrid::CreateIterator(
     GridTrackSizingDirection direction,
-    size_t fixed_track_index,
-    size_t varying_track_index) const {
+    wtf_size_t fixed_track_index,
+    wtf_size_t varying_track_index) const {
   return base::WrapUnique(new ListGridIterator(
       *this, direction, fixed_track_index, varying_track_index));
 }
 
 ListGridIterator::ListGridIterator(const ListGrid& grid,
                                    GridTrackSizingDirection direction,
-                                   size_t fixed_track_index,
-                                   size_t varying_track_index)
+                                   wtf_size_t fixed_track_index,
+                                   wtf_size_t varying_track_index)
     : GridIterator(direction, fixed_track_index, varying_track_index),
       grid_(grid) {}
 
@@ -383,7 +385,7 @@
   if (!cell_node_) {
     auto* track = is_row_axis ? grid_.columns_.Head() : grid_.rows_.Head();
     DCHECK(track);
-    const size_t fixed_index = is_row_axis ? column_index_ : row_index_;
+    const wtf_size_t fixed_index = is_row_axis ? column_index_ : row_index_;
     while (track && track->Index() != fixed_index)
       track = track->Next();
 
@@ -420,11 +422,11 @@
 }
 
 std::unique_ptr<GridArea> ListGridIterator::NextEmptyGridArea(
-    size_t fixed_track_span,
-    size_t varying_track_span) {
+    wtf_size_t fixed_track_span,
+    wtf_size_t varying_track_span) {
   auto FindCellOrClosest = [](ListGrid::GridCell* cell_node,
                               GridTrackSizingDirection direction,
-                              size_t index) {
+                              wtf_size_t index) {
     auto ortho_direction = OrthogonalDirection(direction);
     while (cell_node && cell_node->Index(direction) < index)
       cell_node = cell_node->NextInDirection(ortho_direction);
@@ -434,8 +436,9 @@
 
   auto CreateUniqueGridArea = [this, fixed_track_span, varying_track_span]() {
     bool is_row_axis = direction_ == kForColumns;
-    size_t row_span = is_row_axis ? varying_track_span : fixed_track_span;
-    size_t column_span = is_row_axis ? fixed_track_span : varying_track_span;
+    wtf_size_t row_span = is_row_axis ? varying_track_span : fixed_track_span;
+    wtf_size_t column_span =
+        is_row_axis ? fixed_track_span : varying_track_span;
     return std::make_unique<GridArea>(
         GridSpan::TranslatedDefiniteGridSpan(row_index_, row_index_ + row_span),
         GridSpan::TranslatedDefiniteGridSpan(column_index_,
@@ -443,10 +446,10 @@
   };
 
   auto CellIsInsideSpan = [](ListGrid::GridCell* cell_node,
-                             GridTrackSizingDirection direction, size_t start,
-                             size_t end) {
+                             GridTrackSizingDirection direction,
+                             wtf_size_t start, wtf_size_t end) {
     DCHECK(cell_node);
-    size_t cell_index = cell_node->Index(direction);
+    wtf_size_t cell_index = cell_node->Index(direction);
     return cell_index >= start && cell_index <= end;
   };
 
@@ -455,8 +458,8 @@
 
   bool is_row_axis = direction_ == kForColumns;
   auto& varying_index = is_row_axis ? row_index_ : column_index_;
-  const size_t fixed_index = is_row_axis ? column_index_ : row_index_;
-  const size_t end_fixed_span = fixed_index + fixed_track_span - 1;
+  const wtf_size_t fixed_index = is_row_axis ? column_index_ : row_index_;
+  const wtf_size_t end_fixed_span = fixed_index + fixed_track_span - 1;
   auto* track_node = tracks.Head();
   while (track_node && track_node->Index() < varying_index)
     track_node = track_node->Next();
diff --git a/third_party/blink/renderer/core/layout/grid.h b/third_party/blink/renderer/core/layout/grid.h
index d902a63a..4cf646f 100644
--- a/third_party/blink/renderer/core/layout/grid.h
+++ b/third_party/blink/renderer/core/layout/grid.h
@@ -17,19 +17,21 @@
 
 namespace blink {
 
-struct OrderedTrackIndexSetHashTraits : public HashTraits<size_t> {
+struct OrderedTrackIndexSetHashTraits : public HashTraits<wtf_size_t> {
   static const bool kEmptyValueIsZero = false;
-  static size_t EmptyValue() { return UINT_MAX; }
+  static wtf_size_t EmptyValue() { return UINT_MAX; }
 
-  static void ConstructDeletedValue(size_t& slot, bool) { slot = UINT_MAX - 1; }
-  static bool IsDeletedValue(const size_t& value) {
+  static void ConstructDeletedValue(wtf_size_t& slot, bool) {
+    slot = UINT_MAX - 1;
+  }
+  static bool IsDeletedValue(const wtf_size_t& value) {
     return value == UINT_MAX - 1;
   }
 };
 
 // TODO(svillar): Perhaps we should use references here.
 typedef Vector<LayoutBox*, 1> GridItemList;
-typedef LinkedHashSet<size_t, OrderedTrackIndexSetHashTraits>
+typedef LinkedHashSet<wtf_size_t, OrderedTrackIndexSetHashTraits>
     OrderedTrackIndexSet;
 
 class LayoutGrid;
@@ -46,13 +48,13 @@
  public:
   static std::unique_ptr<Grid> Create(const LayoutGrid*);
 
-  virtual size_t NumTracks(GridTrackSizingDirection) const = 0;
+  virtual wtf_size_t NumTracks(GridTrackSizingDirection) const = 0;
 
-  virtual void EnsureGridSize(size_t maximum_row_size,
-                              size_t maximum_column_size) = 0;
+  virtual void EnsureGridSize(wtf_size_t maximum_row_size,
+                              wtf_size_t maximum_column_size) = 0;
   virtual void Insert(LayoutBox&, const GridArea&) = 0;
 
-  virtual const GridItemList& Cell(size_t row, size_t column) const = 0;
+  virtual const GridItemList& Cell(wtf_size_t row, wtf_size_t column) const = 0;
 
   virtual ~Grid() {}
 
@@ -64,20 +66,21 @@
 
   GridSpan GridItemSpan(const LayoutBox&, GridTrackSizingDirection) const;
 
-  size_t GridItemPaintOrder(const LayoutBox&) const;
-  void SetGridItemPaintOrder(const LayoutBox&, size_t order);
+  wtf_size_t GridItemPaintOrder(const LayoutBox&) const;
+  void SetGridItemPaintOrder(const LayoutBox&, wtf_size_t order);
 
-  size_t ExplicitGridStart(GridTrackSizingDirection) const;
-  void SetExplicitGridStart(size_t row_start, size_t column_start);
+  wtf_size_t ExplicitGridStart(GridTrackSizingDirection) const;
+  void SetExplicitGridStart(wtf_size_t row_start, wtf_size_t column_start);
 
-  size_t AutoRepeatTracks(GridTrackSizingDirection) const;
-  void SetAutoRepeatTracks(size_t auto_repeat_rows, size_t auto_repeat_columns);
+  wtf_size_t AutoRepeatTracks(GridTrackSizingDirection) const;
+  void SetAutoRepeatTracks(wtf_size_t auto_repeat_rows,
+                           wtf_size_t auto_repeat_columns);
 
   void SetAutoRepeatEmptyColumns(std::unique_ptr<OrderedTrackIndexSet>);
   void SetAutoRepeatEmptyRows(std::unique_ptr<OrderedTrackIndexSet>);
 
   bool HasAutoRepeatEmptyTracks(GridTrackSizingDirection) const;
-  bool IsEmptyAutoRepeatTrack(GridTrackSizingDirection, size_t) const;
+  bool IsEmptyAutoRepeatTrack(GridTrackSizingDirection, wtf_size_t) const;
 
   OrderedTrackIndexSet* AutoRepeatEmptyTracks(GridTrackSizingDirection) const;
 
@@ -97,8 +100,8 @@
     virtual LayoutBox* NextGridItem() = 0;
 
     virtual std::unique_ptr<GridArea> NextEmptyGridArea(
-        size_t fixed_track_span,
-        size_t varying_track_span) = 0;
+        wtf_size_t fixed_track_span,
+        wtf_size_t varying_track_span) = 0;
 
     GridIterator(const GridIterator&) = delete;
     GridIterator& operator=(const GridIterator&) = delete;
@@ -109,19 +112,19 @@
     // GridIterator(grid_, kForColumns, 1) will walk over the rows of the 2nd
     // column.
     GridIterator(GridTrackSizingDirection,
-                 size_t fixed_track_index,
-                 size_t varying_track_index);
+                 wtf_size_t fixed_track_index,
+                 wtf_size_t varying_track_index);
 
     GridTrackSizingDirection direction_;
-    size_t row_index_;
-    size_t column_index_;
-    size_t child_index_;
+    wtf_size_t row_index_;
+    wtf_size_t column_index_;
+    wtf_size_t child_index_;
   };
 
   virtual std::unique_ptr<GridIterator> CreateIterator(
       GridTrackSizingDirection,
-      size_t fixed_track_index,
-      size_t varying_track_index = 0) const = 0;
+      wtf_size_t fixed_track_index,
+      wtf_size_t varying_track_index = 0) const = 0;
 
  protected:
   Grid(const LayoutGrid*);
@@ -134,16 +137,16 @@
 
   OrderIterator order_iterator_;
 
-  size_t explicit_column_start_{0};
-  size_t explicit_row_start_{0};
+  wtf_size_t explicit_column_start_{0};
+  wtf_size_t explicit_row_start_{0};
 
-  size_t auto_repeat_columns_{0};
-  size_t auto_repeat_rows_{0};
+  wtf_size_t auto_repeat_columns_{0};
+  wtf_size_t auto_repeat_rows_{0};
 
   bool needs_items_placement_{true};
 
   HashMap<const LayoutBox*, GridArea> grid_item_area_;
-  HashMap<const LayoutBox*, size_t> grid_items_indexes_map_;
+  HashMap<const LayoutBox*, wtf_size_t> grid_items_indexes_map_;
 
   std::unique_ptr<OrderedTrackIndexSet> auto_repeat_empty_columns_{nullptr};
   std::unique_ptr<OrderedTrackIndexSet> auto_repeat_empty_rows_{nullptr};
@@ -158,13 +161,13 @@
  public:
   explicit ListGrid(const LayoutGrid* grid) : Grid(grid) {}
 
-  size_t NumTracks(GridTrackSizingDirection direction) const override {
+  wtf_size_t NumTracks(GridTrackSizingDirection direction) const override {
     return direction == kForRows ? num_rows_ : num_columns_;
   }
-  const GridItemList& Cell(size_t row, size_t column) const override;
+  const GridItemList& Cell(wtf_size_t row, wtf_size_t column) const override;
   void Insert(LayoutBox&, const GridArea&) override;
-  void EnsureGridSize(size_t maximum_row_size,
-                      size_t maximum_column_size) override;
+  void EnsureGridSize(wtf_size_t maximum_row_size,
+                      wtf_size_t maximum_column_size) override;
 
   ~ListGrid() final;
 
@@ -177,9 +180,9 @@
     friend class WTF::DoublyLinkedListNode<GridCell>;
 
    public:
-    GridCell(size_t row, size_t column) : row_(row), column_(column) {}
+    GridCell(wtf_size_t row, wtf_size_t column) : row_(row), column_(column) {}
 
-    size_t Index(GridTrackSizingDirection direction) const {
+    wtf_size_t Index(GridTrackSizingDirection direction) const {
       return direction == kForRows ? row_ : column_;
     }
 
@@ -217,8 +220,8 @@
 
     GridTrackSizingDirection direction_{kForColumns};
     GridItemList items_;
-    size_t row_;
-    size_t column_;
+    wtf_size_t row_;
+    wtf_size_t column_;
   };
 
   // This class represents a track (column or row) of the grid. Each
@@ -235,16 +238,16 @@
     friend class WTF::DoublyLinkedListNode<GridTrack>;
 
    public:
-    GridTrack(size_t index, GridTrackSizingDirection direction)
+    GridTrack(wtf_size_t index, GridTrackSizingDirection direction)
         : index_(index), direction_(direction) {}
 
-    size_t Index() const { return index_; }
+    wtf_size_t Index() const { return index_; }
     DoublyLinkedList<GridCell>::AddResult Insert(GridCell*);
     DoublyLinkedList<GridCell>::AddResult InsertAfter(
         GridCell* cell,
         GridCell* insertion_point);
     DoublyLinkedList<GridCell>::AddResult Insert(LayoutBox&, const GridSpan&);
-    GridCell* Find(size_t cell_index) const;
+    GridCell* Find(wtf_size_t cell_index) const;
 
     const DoublyLinkedList<GridCell>& Cells() const { return cells_; }
 
@@ -252,7 +255,7 @@
 
    private:
     DoublyLinkedList<GridCell> cells_;
-    size_t index_;
+    wtf_size_t index_;
     GridTrackSizingDirection direction_;
 
     GridTrack* prev_;
@@ -277,11 +280,11 @@
 
   std::unique_ptr<GridIterator> CreateIterator(
       GridTrackSizingDirection,
-      size_t fixed_track_index,
-      size_t varying_track_index = 0) const override;
+      wtf_size_t fixed_track_index,
+      wtf_size_t varying_track_index = 0) const override;
 
-  size_t num_rows_{0};
-  size_t num_columns_{0};
+  wtf_size_t num_rows_{0};
+  wtf_size_t num_columns_{0};
 
   DoublyLinkedList<GridTrack> columns_;
   DoublyLinkedList<GridTrack> rows_;
@@ -293,15 +296,15 @@
  public:
   ListGridIterator(const ListGrid& grid,
                    GridTrackSizingDirection,
-                   size_t fixed_track_index,
-                   size_t varying_track_index = 0);
+                   wtf_size_t fixed_track_index,
+                   wtf_size_t varying_track_index = 0);
   ListGridIterator(const ListGridIterator&) = delete;
   ListGridIterator& operator=(const ListGridIterator&) = delete;
 
   LayoutBox* NextGridItem() override;
   std::unique_ptr<GridArea> NextEmptyGridArea(
-      size_t fixed_track_span,
-      size_t varying_track_span) override;
+      wtf_size_t fixed_track_span,
+      wtf_size_t varying_track_span) override;
 
  private:
   const ListGrid& grid_;
diff --git a/third_party/blink/renderer/core/layout/grid_test.cc b/third_party/blink/renderer/core/layout/grid_test.cc
index f4ba4789..cbc0a34e 100644
--- a/third_party/blink/renderer/core/layout/grid_test.cc
+++ b/third_party/blink/renderer/core/layout/grid_test.cc
@@ -104,17 +104,17 @@
   auto* grid = layout_grid->InternalGrid();
   ASSERT_NE(grid, nullptr);
 
-  size_t num_rows = grid->NumTracks(kForRows);
-  size_t num_cols = grid->NumTracks(kForColumns);
+  wtf_size_t num_rows = grid->NumTracks(kForRows);
+  wtf_size_t num_cols = grid->NumTracks(kForColumns);
   EXPECT_EQ(3u, num_rows);
   EXPECT_EQ(3u, num_cols);
 
   EXPECT_TRUE(grid->HasGridItems());
 
-  size_t index = 0;
-  Vector<size_t> expected_items_per_cell = {1, 2, 1, 2, 4, 2, 1, 2, 1};
-  for (size_t row = 0; row < num_rows; ++row) {
-    for (size_t col = 0; col < num_cols; ++col)
+  wtf_size_t index = 0;
+  Vector<wtf_size_t> expected_items_per_cell = {1, 2, 1, 2, 4, 2, 1, 2, 1};
+  for (wtf_size_t row = 0; row < num_rows; ++row) {
+    for (wtf_size_t col = 0; col < num_cols; ++col)
       EXPECT_EQ(expected_items_per_cell[index++], grid->Cell(row, col).size());
   }
 }
@@ -140,16 +140,16 @@
   auto* grid = layout_grid->InternalGrid();
   ASSERT_NE(grid, nullptr);
 
-  size_t num_rows = grid->NumTracks(kForRows);
-  size_t num_cols = grid->NumTracks(kForColumns);
+  wtf_size_t num_rows = grid->NumTracks(kForRows);
+  wtf_size_t num_cols = grid->NumTracks(kForColumns);
   EXPECT_EQ(1u, num_rows);
   EXPECT_EQ(3u, num_cols);
 
   EXPECT_TRUE(grid->HasGridItems());
 
-  size_t index = 0;
-  Vector<size_t> expected_items_per_cell = {2, 1, 1};
-  for (size_t col = 0; col < num_cols; ++col)
+  wtf_size_t index = 0;
+  Vector<wtf_size_t> expected_items_per_cell = {2, 1, 1};
+  for (wtf_size_t col = 0; col < num_cols; ++col)
     EXPECT_EQ(expected_items_per_cell[index++], grid->Cell(0, col).size());
 }
 
@@ -230,7 +230,7 @@
   ASSERT_NE(empty_tracks, nullptr);
   ASSERT_EQ(empty_tracks->size(), 5u);
   Vector<size_t> expected_empty_tracks = {0, 5, 6, 7, 9};
-  size_t index = 0;
+  wtf_size_t index = 0;
   for (auto track : *empty_tracks) {
     EXPECT_EQ(expected_empty_tracks[index++], track);
     EXPECT_TRUE(grid->IsEmptyAutoRepeatTrack(kForColumns, track));
diff --git a/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.cc b/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.cc
index 932e7fd3..176828a 100644
--- a/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.cc
@@ -93,12 +93,12 @@
 template <typename F>
 static void IterateGridItemsInTrackIndices(const Grid& grid,
                                            GridTrackSizingDirection direction,
-                                           Vector<size_t>& track_indices,
+                                           Vector<wtf_size_t>& track_indices,
                                            F callback) {
 #if DCHECK_IS_ON()
   HashSet<LayoutBox*> items_set;
 #endif
-  for (size_t i = 0; i < track_indices.size(); ++i) {
+  for (wtf_size_t i = 0; i < track_indices.size(); ++i) {
     auto iterator = grid.CreateIterator(direction, track_indices[i]);
     while (LayoutBox* grid_item = iterator->NextGridItem()) {
       const GridSpan& span = grid.GridItemSpan(*grid_item, direction);
@@ -128,11 +128,10 @@
   void MaximizeTracks(Vector<GridTrack>&,
                       absl::optional<LayoutUnit>& free_space) override;
   double FindUsedFlexFraction(
-      Vector<size_t>& flexible_sized_tracks_index,
+      Vector<wtf_size_t>& flexible_sized_tracks_index,
       GridTrackSizingDirection,
       absl::optional<LayoutUnit> free_space) const override;
   bool RecomputeUsedFlexFractionIfNeeded(
-      Vector<size_t>& flexible_sized_tracks_index,
       double& flex_fraction,
       Vector<LayoutUnit>& increments,
       LayoutUnit& total_growth) const override;
@@ -154,11 +153,10 @@
   void MaximizeTracks(Vector<GridTrack>&,
                       absl::optional<LayoutUnit>& free_space) override;
   double FindUsedFlexFraction(
-      Vector<size_t>& flexible_sized_tracks_index,
+      Vector<wtf_size_t>& flexible_sized_tracks_index,
       GridTrackSizingDirection,
       absl::optional<LayoutUnit> free_space) const override;
   bool RecomputeUsedFlexFractionIfNeeded(
-      Vector<size_t>& flexible_sized_tracks_index,
       double& flex_fraction,
       Vector<LayoutUnit>& increments,
       LayoutUnit& total_growth) const override {
@@ -643,9 +641,9 @@
 void DefiniteSizeStrategy::MaximizeTracks(
     Vector<GridTrack>& tracks,
     absl::optional<LayoutUnit>& free_space) {
-  size_t tracks_size = tracks.size();
+  wtf_size_t tracks_size = tracks.size();
   Vector<GridTrack*> tracks_for_distribution(tracks_size);
-  for (size_t i = 0; i < tracks_size; ++i) {
+  for (wtf_size_t i = 0; i < tracks_size; ++i) {
     tracks_for_distribution[i] = tracks.data() + i;
     tracks_for_distribution[i]->SetPlannedSize(
         tracks_for_distribution[i]->BaseSize());
@@ -659,7 +657,7 @@
 }
 
 double DefiniteSizeStrategy::FindUsedFlexFraction(
-    Vector<size_t>& flexible_sized_tracks_index,
+    Vector<wtf_size_t>& flexible_sized_tracks_index,
     GridTrackSizingDirection direction,
     absl::optional<LayoutUnit> free_space) const {
   GridSpan all_tracks_span = GridSpan::TranslatedDefiniteGridSpan(
@@ -709,7 +707,7 @@
 }
 
 double IndefiniteSizeStrategy::FindUsedFlexFraction(
-    Vector<size_t>& flexible_sized_tracks_index,
+    Vector<wtf_size_t>& flexible_sized_tracks_index,
     GridTrackSizingDirection direction,
     absl::optional<LayoutUnit>) const {
   auto all_tracks = algorithm_.Tracks(direction);
@@ -742,7 +740,6 @@
 }
 
 bool IndefiniteSizeStrategy::RecomputeUsedFlexFractionIfNeeded(
-    Vector<size_t>& flexible_sized_tracks_index,
     double& flex_fraction,
     Vector<LayoutUnit>& increments,
     LayoutUnit& total_growth) const {
@@ -771,7 +768,7 @@
       layout_grid->GuttersSize(grid, kForRows, 0, grid.NumTracks(kForRows),
                                AvailableSpace());
 
-  size_t number_of_tracks = algorithm_.Tracks(Direction()).size();
+  wtf_size_t number_of_tracks = algorithm_.Tracks(Direction()).size();
   flex_fraction = FindFrUnitSize(
       GridSpan::TranslatedDefiniteGridSpan(0, number_of_tracks), free_space);
   return true;
@@ -875,7 +872,7 @@
 
 const GridTrackSize& GridTrackSizingAlgorithm::RawGridTrackSize(
     GridTrackSizingDirection direction,
-    size_t translated_index) const {
+    wtf_size_t translated_index) const {
   bool is_row_axis = direction == kForColumns;
   const Vector<GridTrackSize, 1>& track_styles =
       is_row_axis
@@ -887,21 +884,22 @@
   const Vector<GridTrackSize, 1>& auto_track_styles =
       is_row_axis ? layout_grid_->StyleRef().GridAutoColumns().LegacyTrackList()
                   : layout_grid_->StyleRef().GridAutoRows().LegacyTrackList();
-  size_t insertion_point =
+  wtf_size_t insertion_point =
       is_row_axis
           ? layout_grid_->StyleRef().GridAutoRepeatColumnsInsertionPoint()
           : layout_grid_->StyleRef().GridAutoRepeatRowsInsertionPoint();
-  size_t auto_repeat_tracks_count = grid_.AutoRepeatTracks(direction);
+  wtf_size_t auto_repeat_tracks_count = grid_.AutoRepeatTracks(direction);
 
   // We should not use GridPositionsResolver::explicitGridXXXCount() for this
   // because the explicit grid might be larger than the number of tracks in
   // grid-template-rows|columns (if grid-template-areas is specified for
   // example).
-  size_t explicit_tracks_count = track_styles.size() + auto_repeat_tracks_count;
+  wtf_size_t explicit_tracks_count =
+      track_styles.size() + auto_repeat_tracks_count;
 
   int untranslated_index_as_int =
-      translated_index - grid_.ExplicitGridStart(direction);
-  size_t auto_track_styles_size = auto_track_styles.size();
+      static_cast<int>(translated_index - grid_.ExplicitGridStart(direction));
+  wtf_size_t auto_track_styles_size = auto_track_styles.size();
   if (untranslated_index_as_int < 0) {
     int index =
         untranslated_index_as_int % static_cast<int>(auto_track_styles_size);
@@ -911,7 +909,8 @@
     return auto_track_styles[index];
   }
 
-  size_t untranslated_index = static_cast<size_t>(untranslated_index_as_int);
+  wtf_size_t untranslated_index =
+      static_cast<wtf_size_t>(untranslated_index_as_int);
   if (untranslated_index >= explicit_tracks_count) {
     return auto_track_styles[(untranslated_index - explicit_tracks_count) %
                              auto_track_styles_size];
@@ -921,8 +920,7 @@
     return track_styles[untranslated_index];
 
   if (untranslated_index < (insertion_point + auto_repeat_tracks_count)) {
-    size_t auto_repeat_local_index =
-        untranslated_index_as_int - insertion_point;
+    wtf_size_t auto_repeat_local_index = untranslated_index - insertion_point;
     return auto_repeat_track_styles[auto_repeat_local_index %
                                     auto_repeat_track_styles.size()];
   }
@@ -948,7 +946,7 @@
 
 GridTrackSize GridTrackSizingAlgorithm::CalculateGridTrackSize(
     GridTrackSizingDirection direction,
-    size_t translated_index) const {
+    wtf_size_t translated_index) const {
   DCHECK(WasSetup());
   // Collapse empty auto repeat tracks if auto-fit.
   if (grid_.HasAutoRepeatEmptyTracks(direction) &&
@@ -1050,8 +1048,8 @@
   Vector<GridTrack>& track_list = Tracks(direction_);
   bool indefinite_height =
       direction_ == kForRows && !layout_grid_->CachedHasDefiniteLogicalHeight();
-  size_t num_tracks = track_list.size();
-  for (size_t i = 0; i < num_tracks; ++i) {
+  wtf_size_t num_tracks = track_list.size();
+  for (wtf_size_t i = 0; i < num_tracks; ++i) {
     const GridTrackSize& track_size = CalculateGridTrackSize(direction_, i);
     GridTrack& track = track_list[i];
     track.SetCachedTrackSize(track_size);
@@ -1085,7 +1083,7 @@
     const GridSpan& span,
     LayoutBox& grid_item,
     GridTrack& track) {
-  const size_t track_position = span.StartLine();
+  const wtf_size_t track_position = span.StartLine();
   const GridTrackSize& track_size =
       Tracks(direction_)[track_position].CachedTrackSize();
 
@@ -1351,8 +1349,8 @@
   if (available_logical_space > 0) {
     std::sort(tracks.begin(), tracks.end(), SortByGridTrackGrowthPotential);
 
-    size_t tracks_size = tracks.size();
-    for (size_t i = 0; i < tracks_size; ++i) {
+    wtf_size_t tracks_size = tracks.size();
+    for (wtf_size_t i = 0; i < tracks_size; ++i) {
       GridTrack& track = *tracks[i];
       LayoutUnit available_logical_space_share =
           available_logical_space / (tracks_size - i);
@@ -1382,9 +1380,9 @@
                 SortByGridTrackGrowthPotential);
     }
 
-    size_t tracks_growing_above_max_breadth_size =
+    wtf_size_t tracks_growing_above_max_breadth_size =
         grow_beyond_growth_limits_tracks->size();
-    for (size_t i = 0; i < tracks_growing_above_max_breadth_size; ++i) {
+    for (wtf_size_t i = 0; i < tracks_growing_above_max_breadth_size; ++i) {
       GridTrack* track = grow_beyond_growth_limits_tracks->at(i);
       LayoutUnit growth_share =
           available_logical_space / (tracks_growing_above_max_breadth_size - i);
@@ -1550,7 +1548,7 @@
 
   const Vector<GridTrack>& all_tracks = Tracks(direction_);
   double flex_factor_sum = 0;
-  Vector<size_t, 8> flexible_tracks_indexes;
+  Vector<wtf_size_t, 8> flexible_tracks_indexes;
   for (auto track_index : tracks_span) {
     const GridTrackSize& track_size = all_tracks[track_index].CachedTrackSize();
     if (!track_size.MaxTrackBreadth().IsFlex()) {
@@ -1574,7 +1572,7 @@
     const Vector<GridTrack>& tracks,
     double flex_factor_sum,
     LayoutUnit& left_over_space,
-    const Vector<size_t, 8>& flexible_tracks_indexes,
+    const Vector<wtf_size_t, 8>& flexible_tracks_indexes,
     std::unique_ptr<TrackIndexSet> tracks_to_treat_as_inflexible) const {
   // We want to avoid the effect of flex factors sum below 1 making the factor
   // unit size to grow exponentially.
@@ -1617,11 +1615,11 @@
     double flex_fraction,
     Vector<LayoutUnit>& increments,
     LayoutUnit& total_growth) const {
-  size_t num_flex_tracks = flexible_sized_tracks_index_.size();
+  wtf_size_t num_flex_tracks = flexible_sized_tracks_index_.size();
   DCHECK_EQ(increments.size(), num_flex_tracks);
   const Vector<GridTrack>& all_tracks = Tracks(direction_);
-  for (size_t i = 0; i < num_flex_tracks; ++i) {
-    size_t track_index = flexible_sized_tracks_index_[i];
+  for (wtf_size_t i = 0; i < num_flex_tracks; ++i) {
+    wtf_size_t track_index = flexible_sized_tracks_index_[i];
     const GridTrackSize& track_size = all_tracks[track_index].CachedTrackSize();
     DCHECK(track_size.MaxTrackBreadth().IsFlex());
     LayoutUnit old_base_size = all_tracks[track_index].BaseSize();
@@ -1646,14 +1644,13 @@
   increments.Grow(flexible_sized_tracks_index_.size());
   ComputeFlexSizedTracksGrowth(flex_fraction, increments, total_growth);
 
-  if (strategy_->RecomputeUsedFlexFractionIfNeeded(flexible_sized_tracks_index_,
-                                                   flex_fraction, increments,
+  if (strategy_->RecomputeUsedFlexFractionIfNeeded(flex_fraction, increments,
                                                    total_growth)) {
     total_growth = LayoutUnit(0);
     ComputeFlexSizedTracksGrowth(flex_fraction, increments, total_growth);
   }
 
-  size_t i = 0;
+  wtf_size_t i = 0;
   Vector<GridTrack>& all_tracks = Tracks(direction_);
   for (auto track_index : flexible_sized_tracks_index_) {
     auto& track = all_tracks[track_index];
@@ -1720,7 +1717,7 @@
 
 void GridTrackSizingAlgorithm::Setup(
     GridTrackSizingDirection direction,
-    size_t num_tracks,
+    wtf_size_t num_tracks,
     absl::optional<LayoutUnit> available_space) {
   DCHECK(needs_setup_);
   direction_ = direction;
@@ -1832,7 +1829,7 @@
 #if DCHECK_IS_ON()
 bool GridTrackSizingAlgorithm::TracksAreWiderThanMinTrackBreadth() const {
   const Vector<GridTrack>& all_tracks = Tracks(direction_);
-  for (size_t i = 0; i < all_tracks.size(); ++i) {
+  for (wtf_size_t i = 0; i < all_tracks.size(); ++i) {
     const GridTrackSize& track_size = all_tracks[i].CachedTrackSize();
     if (InitialBaseSize(track_size) > all_tracks[i].BaseSize())
       return false;
diff --git a/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.h b/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.h
index 74d9a00b..b03a57a 100644
--- a/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.h
+++ b/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.h
@@ -98,7 +98,7 @@
   // Setup() must be run before calling Run() as it configures the behaviour of
   // the algorithm.
   void Setup(GridTrackSizingDirection,
-             size_t num_tracks,
+             wtf_size_t num_tracks,
              absl::optional<LayoutUnit> available_space);
   void Run();
   void Reset();
@@ -145,9 +145,9 @@
   bool IsRelativeSizedTrackAsAuto(const GridTrackSize&,
                                   GridTrackSizingDirection) const;
   GridTrackSize CalculateGridTrackSize(GridTrackSizingDirection,
-                                       size_t translated_index) const;
+                                       wtf_size_t translated_index) const;
   const GridTrackSize& RawGridTrackSize(GridTrackSizingDirection,
-                                        size_t translated_index) const;
+                                        wtf_size_t translated_index) const;
 
   // Helper methods for step 1. initializeTrackSizes().
   LayoutUnit InitialBaseSize(const GridTrackSize&) const;
@@ -192,7 +192,7 @@
       const Vector<GridTrack>& tracks,
       double flex_factor_sum,
       LayoutUnit& left_over_space,
-      const Vector<size_t, 8>& flexible_tracks_indexes,
+      const Vector<wtf_size_t, 8>& flexible_tracks_indexes,
       std::unique_ptr<TrackIndexSet> tracks_to_treat_as_inflexible =
           nullptr) const;
   void ComputeFlexSizedTracksGrowth(double flex_fraction,
@@ -227,9 +227,9 @@
   // writing modes.
   Vector<GridTrack> columns_;
   Vector<GridTrack> rows_;
-  Vector<size_t> content_sized_tracks_index_;
-  Vector<size_t> flexible_sized_tracks_index_;
-  Vector<size_t> auto_sized_tracks_for_stretch_index_;
+  Vector<wtf_size_t> content_sized_tracks_index_;
+  Vector<wtf_size_t> flexible_sized_tracks_index_;
+  Vector<wtf_size_t> auto_sized_tracks_for_stretch_index_;
 
   GridTrackSizingDirection direction_;
 
@@ -290,11 +290,10 @@
   virtual void MaximizeTracks(Vector<GridTrack>&,
                               absl::optional<LayoutUnit>& free_space) = 0;
   virtual double FindUsedFlexFraction(
-      Vector<size_t>& flexible_sized_tracks_index,
+      Vector<wtf_size_t>& flexible_sized_tracks_index,
       GridTrackSizingDirection,
       absl::optional<LayoutUnit> initial_free_space) const = 0;
   virtual bool RecomputeUsedFlexFractionIfNeeded(
-      Vector<size_t>& flexible_sized_tracks_index,
       double& flex_fraction,
       Vector<LayoutUnit>& increments,
       LayoutUnit& total_growth) const = 0;
diff --git a/third_party/blink/renderer/core/layout/layout_block_flow_line.cc b/third_party/blink/renderer/core/layout/layout_block_flow_line.cc
index 16a98f7..35991f6f 100644
--- a/third_party/blink/renderer/core/layout/layout_block_flow_line.cc
+++ b/third_party/blink/renderer/core/layout/layout_block_flow_line.cc
@@ -87,8 +87,6 @@
 
   unsigned Count() { return total_opportunities_; }
 
-  unsigned OpportunitiesInRun(size_t run) { return runs_with_expansions_[run]; }
-
   void ComputeExpansionsForJustifiedText(BidiRun* first_run,
                                          BidiRun* trailing_space_run,
                                          LayoutUnit& total_logical_width,
@@ -96,7 +94,7 @@
     if (!total_opportunities_ || available_logical_width <= total_logical_width)
       return;
 
-    size_t i = 0;
+    wtf_size_t i = 0;
     for (BidiRun* r = first_run; r; r = r->Next()) {
       if (!r->box_ || r == trailing_space_run)
         continue;
@@ -501,14 +499,14 @@
   SetMarginEndForChild(*layout_ruby_run, LayoutUnit(-end_overhang));
 }
 
-static inline size_t FindWordMeasurement(
+static inline wtf_size_t FindWordMeasurement(
     LineLayoutText layout_text,
     int offset,
     const WordMeasurements& word_measurements,
-    size_t last_index) {
+    wtf_size_t last_index) {
   // In LTR, lastIndex should match since the order of BidiRun (visual) and
   // WordMeasurement (logical) are the same.
-  size_t size = word_measurements.size();
+  wtf_size_t size = word_measurements.size();
   if (last_index < size) {
     const WordMeasurement& word_measurement = word_measurements[last_index];
     if (word_measurement.layout_text == layout_text &&
@@ -517,7 +515,7 @@
   }
 
   // In RTL, scan the whole array because they are not the same.
-  for (size_t i = 0; i < size; ++i) {
+  for (wtf_size_t i = 0; i < size; ++i) {
     const WordMeasurement& word_measurement = word_measurements[i];
     if (word_measurement.layout_text != layout_text)
       continue;
@@ -543,7 +541,7 @@
     GlyphOverflowAndFallbackFontsMap& text_box_data_map,
     VerticalPositionCache& vertical_position_cache,
     const WordMeasurements& word_measurements,
-    size_t& word_measurements_index) {
+    wtf_size_t& word_measurements_index) {
   HashSet<const SimpleFontData*> fallback_fonts;
   GlyphOverflow glyph_overflow;
 
@@ -572,9 +570,10 @@
 
   if (can_use_cached_word_measurements) {
     int last_end_offset = run->start_;
-    size_t i = FindWordMeasurement(layout_text, last_end_offset,
-                                   word_measurements, word_measurements_index);
-    for (size_t size = word_measurements.size();
+    wtf_size_t i =
+        FindWordMeasurement(layout_text, last_end_offset, word_measurements,
+                            word_measurements_index);
+    for (wtf_size_t size = word_measurements.size();
          i < size && last_end_offset < run->stop_; ++i) {
       const WordMeasurement& word_measurement = word_measurements[i];
       if (word_measurement.start_offset == word_measurement.end_offset)
@@ -821,7 +820,7 @@
   TextJustify text_justify = StyleRef().GetTextJustify();
 
   BidiRun* r = first_run;
-  size_t word_measurements_index = 0;
+  wtf_size_t word_measurements_index = 0;
   for (; r; r = r->Next()) {
     if (!r->box_ || r->line_layout_item_.IsOutOfFlowPositioned() ||
         r->box_->IsLineBreak()) {
@@ -1453,11 +1452,11 @@
 void LayoutBlockFlow::MarkDirtyFloatsForPaintInvalidation(
     Vector<FloatWithRect>& floats) {
   NOT_DESTROYED();
-  size_t float_count = floats.size();
+  wtf_size_t float_count = floats.size();
   // Floats that did not have layout did not paint invalidations when we laid
   // them out. They would have painted by now if they had moved, but if they
   // stayed at (0, 0), they still need to be painted.
-  for (size_t i = 0; i < float_count; ++i) {
+  for (wtf_size_t i = 0; i < float_count; ++i) {
     LayoutBox* f = floats[i].object;
     if (!floats[i].ever_had_layout) {
       if (!f->Location().X() && !f->Location().Y())
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index c9d5e36d..e538cf9d 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -3549,8 +3549,7 @@
     cache_status = NGLayoutCacheStatus::kNeedsSimplifiedLayout;
 
   // Only allow simplified layout for non-replaced boxes.
-  if (RuntimeEnabledFeatures::LayoutNGReplacedEnabled() &&
-      cache_status == NGLayoutCacheStatus::kNeedsSimplifiedLayout &&
+  if (cache_status == NGLayoutCacheStatus::kNeedsSimplifiedLayout &&
       IsLayoutReplaced())
     return nullptr;
 
diff --git a/third_party/blink/renderer/core/layout/layout_frame_set.cc b/third_party/blink/renderer/core/layout/layout_frame_set.cc
index d26607e..fd4a04c 100644
--- a/third_party/blink/renderer/core/layout/layout_frame_set.cc
+++ b/third_party/blink/renderer/core/layout/layout_frame_set.cc
@@ -332,10 +332,10 @@
   if (!child)
     return;
 
-  size_t rows = rows_.sizes_.size();
-  size_t cols = cols_.sizes_.size();
-  for (size_t r = 0; r < rows; ++r) {
-    for (size_t c = 0; c < cols; ++c) {
+  wtf_size_t rows = rows_.sizes_.size();
+  wtf_size_t cols = cols_.sizes_.size();
+  for (wtf_size_t r = 0; r < rows; ++r) {
+    for (wtf_size_t c = 0; c < cols; ++c) {
       FrameEdgeInfo edge_info;
       if (child->IsFrameSet())
         edge_info = To<LayoutFrameSet>(child)->EdgeInfo();
@@ -569,15 +569,15 @@
   if (border_thickness <= 0)
     return kNoSplit;
 
-  size_t size = axis.sizes_.size();
+  wtf_size_t size = axis.sizes_.size();
   if (!size)
     return kNoSplit;
 
   int split_position = axis.sizes_[0];
-  for (size_t i = 1; i < size; ++i) {
+  for (wtf_size_t i = 1; i < size; ++i) {
     if (position >= split_position &&
         position < split_position + border_thickness)
-      return i;
+      return static_cast<int>(i);
     split_position += border_thickness + axis.sizes_[i];
   }
   return kNoSplit;
diff --git a/third_party/blink/renderer/core/layout/layout_grid.cc b/third_party/blink/renderer/core/layout/layout_grid.cc
index 2c29830..ee2de80 100644
--- a/third_party/blink/renderer/core/layout/layout_grid.cc
+++ b/third_party/blink/renderer/core/layout/layout_grid.cc
@@ -453,8 +453,8 @@
 LayoutUnit LayoutGrid::GuttersSize(
     const Grid& grid,
     GridTrackSizingDirection direction,
-    size_t start_line,
-    size_t span,
+    wtf_size_t start_line,
+    wtf_size_t span,
     absl::optional<LayoutUnit> available_size) const {
   NOT_DESTROYED();
   if (span <= 1)
@@ -473,9 +473,9 @@
   // becomes 0) or there is a non empty track before that.
 
   LayoutUnit gap_accumulator;
-  size_t end_line = start_line + span;
+  wtf_size_t end_line = start_line + span;
 
-  for (size_t line = start_line; line < end_line - 1; ++line) {
+  for (wtf_size_t line = start_line; line < end_line - 1; ++line) {
     if (!grid.IsEmptyAutoRepeatTrack(direction, line))
       gap_accumulator += gap;
   }
@@ -562,7 +562,7 @@
 
   ComputeTrackSizesForIndefiniteSize(algorithm, kForColumns);
 
-  size_t number_of_tracks = algorithm.Tracks(kForColumns).size();
+  wtf_size_t number_of_tracks = algorithm.Tracks(kForColumns).size();
   LayoutUnit total_gutters_size = GuttersSize(
       algorithm.GetGrid(), kForColumns, 0, number_of_tracks, absl::nullopt);
 
@@ -610,7 +610,7 @@
   return size;
 }
 
-size_t LayoutGrid::ComputeAutoRepeatTracksCount(
+wtf_size_t LayoutGrid::ComputeAutoRepeatTracksCount(
     GridTrackSizingDirection direction,
     absl::optional<LayoutUnit> available_size) const {
   NOT_DESTROYED();
@@ -628,7 +628,7 @@
   const auto& auto_repeat_tracks = is_row_axis
                                        ? StyleRef().GridAutoRepeatColumns()
                                        : StyleRef().GridAutoRepeatRows();
-  size_t auto_repeat_track_list_length = auto_repeat_tracks.size();
+  wtf_size_t auto_repeat_track_list_length = auto_repeat_tracks.size();
 
   if (!auto_repeat_track_list_length)
     return 0;
@@ -758,7 +758,7 @@
   LayoutUnit auto_repeat_size_with_gap =
       auto_repeat_tracks_size + gap_size * auto_repeat_track_list_length;
 
-  size_t repetitions = 1 + (free_space / auto_repeat_size_with_gap).ToInt();
+  int repetitions = 1 + (free_space / auto_repeat_size_with_gap).ToInt();
   free_space -= auto_repeat_size_with_gap * (repetitions - 1);
 
   // Provided the grid container does not have a definite size or max-size in
@@ -786,21 +786,21 @@
     return nullptr;
 
   std::unique_ptr<OrderedTrackIndexSet> empty_track_indexes;
-  size_t insertion_point =
+  wtf_size_t insertion_point =
       is_row_axis ? StyleRef().GridAutoRepeatColumnsInsertionPoint()
                   : StyleRef().GridAutoRepeatRowsInsertionPoint();
-  size_t first_auto_repeat_track =
+  wtf_size_t first_auto_repeat_track =
       insertion_point + grid.ExplicitGridStart(direction);
-  size_t last_auto_repeat_track =
+  wtf_size_t last_auto_repeat_track =
       first_auto_repeat_track + grid.AutoRepeatTracks(direction);
 
   if (!grid.HasGridItems()) {
     empty_track_indexes = std::make_unique<OrderedTrackIndexSet>();
-    for (size_t track_index = first_auto_repeat_track;
+    for (wtf_size_t track_index = first_auto_repeat_track;
          track_index < last_auto_repeat_track; ++track_index)
       empty_track_indexes->insert(track_index);
   } else {
-    for (size_t track_index = first_auto_repeat_track;
+    for (wtf_size_t track_index = first_auto_repeat_track;
          track_index < last_auto_repeat_track; ++track_index) {
       auto iterator = grid.CreateIterator(direction, track_index);
       if (!iterator->NextGridItem()) {
@@ -813,25 +813,26 @@
   return empty_track_indexes;
 }
 
-size_t LayoutGrid::ClampAutoRepeatTracks(GridTrackSizingDirection direction,
-                                         size_t auto_repeat_tracks) const {
+wtf_size_t LayoutGrid::ClampAutoRepeatTracks(
+    GridTrackSizingDirection direction,
+    wtf_size_t auto_repeat_tracks) const {
   NOT_DESTROYED();
   if (!auto_repeat_tracks)
     return 0;
 
-  size_t insertion_point =
+  wtf_size_t insertion_point =
       direction == kForColumns
           ? StyleRef().GridAutoRepeatColumnsInsertionPoint()
           : StyleRef().GridAutoRepeatRowsInsertionPoint();
 
   if (insertion_point == 0)
-    return std::min<size_t>(auto_repeat_tracks, kGridMaxTracks);
+    return std::min<wtf_size_t>(auto_repeat_tracks, kGridMaxTracks);
 
   if (insertion_point >= kGridMaxTracks)
     return 0;
 
   return std::min(auto_repeat_tracks,
-                  static_cast<size_t>(kGridMaxTracks) - insertion_point);
+                  static_cast<wtf_size_t>(kGridMaxTracks) - insertion_point);
 }
 
 // TODO(svillar): we shouldn't have to pass the available logical width as
@@ -844,10 +845,10 @@
     absl::optional<LayoutUnit> available_logical_width) const {
   NOT_DESTROYED();
   Grid& grid = algorithm.GetMutableGrid();
-  size_t auto_repeat_rows = ComputeAutoRepeatTracksCount(
+  wtf_size_t auto_repeat_rows = ComputeAutoRepeatTracksCount(
       kForRows, ConvertLayoutUnitToOptional(
                     AvailableLogicalHeightForPercentageComputation()));
-  size_t auto_repeat_columns =
+  wtf_size_t auto_repeat_columns =
       ComputeAutoRepeatTracksCount(kForColumns, available_logical_width);
 
   auto_repeat_rows = ClampAutoRepeatTracks(kForRows, auto_repeat_rows);
@@ -870,7 +871,7 @@
 #if DCHECK_IS_ON()
   DCHECK(!grid.HasAnyGridItemPaintOrder());
 #endif
-  size_t child_index = 0;
+  wtf_size_t child_index = 0;
   for (LayoutBox* child = grid.GetOrderIterator().First(); child;
        child = grid.GetOrderIterator().Next()) {
     if (child->IsOutOfFlowPositioned())
@@ -994,15 +995,16 @@
 void LayoutGrid::PopulateExplicitGridAndOrderIterator(Grid& grid) const {
   NOT_DESTROYED();
   OrderIteratorPopulator populator(grid.GetOrderIterator());
-  size_t explicit_row_start = 0;
-  size_t explicit_column_start = 0;
+  wtf_size_t explicit_row_start = 0;
+  wtf_size_t explicit_column_start = 0;
 
-  size_t auto_repeat_rows = grid.AutoRepeatTracks(kForRows);
-  size_t auto_repeat_columns = grid.AutoRepeatTracks(kForColumns);
-  size_t maximum_row_index =
+  wtf_size_t auto_repeat_rows = grid.AutoRepeatTracks(kForRows);
+  wtf_size_t auto_repeat_columns = grid.AutoRepeatTracks(kForColumns);
+  wtf_size_t maximum_row_index =
       GridPositionsResolver::ExplicitGridRowCount(StyleRef(), auto_repeat_rows);
-  size_t maximum_column_index = GridPositionsResolver::ExplicitGridColumnCount(
-      StyleRef(), auto_repeat_columns);
+  wtf_size_t maximum_column_index =
+      GridPositionsResolver::ExplicitGridColumnCount(StyleRef(),
+                                                     auto_repeat_columns);
 
   for (LayoutBox* child = FirstInFlowChildBox(); child;
        child = child->NextInFlowSiblingBox()) {
@@ -1027,7 +1029,7 @@
     } else {
       // Grow the grid for items with a definite row span, getting the largest
       // such span.
-      size_t span_size = GridPositionsResolver::SpanSizeForAutoPlacedItem(
+      wtf_size_t span_size = GridPositionsResolver::SpanSizeForAutoPlacedItem(
           child->StyleRef(), kForRows);
       maximum_row_index = std::max(maximum_row_index, span_size);
     }
@@ -1040,7 +1042,7 @@
     } else {
       // Grow the grid for items with a definite column span, getting the
       // largest such span.
-      size_t span_size = GridPositionsResolver::SpanSizeForAutoPlacedItem(
+      wtf_size_t span_size = GridPositionsResolver::SpanSizeForAutoPlacedItem(
           child->StyleRef(), kForColumns);
       maximum_column_index = std::max(maximum_column_index, span_size);
     }
@@ -1060,8 +1062,8 @@
   NOT_DESTROYED();
   GridTrackSizingDirection cross_direction =
       specified_direction == kForColumns ? kForRows : kForColumns;
-  const size_t end_of_cross_direction = grid.NumTracks(cross_direction);
-  size_t cross_direction_span_size =
+  const wtf_size_t end_of_cross_direction = grid.NumTracks(cross_direction);
+  wtf_size_t cross_direction_span_size =
       GridPositionsResolver::SpanSizeForAutoPlacedItem(grid_item.StyleRef(),
                                                        cross_direction);
   GridSpan cross_direction_positions = GridSpan::TranslatedDefiniteGridSpan(
@@ -1096,7 +1098,7 @@
     DCHECK(
         !grid.GridItemSpan(*auto_grid_item, AutoPlacementMinorAxisDirection())
              .IsTranslatedDefinite());
-    size_t minor_axis_span_size =
+    wtf_size_t minor_axis_span_size =
         GridPositionsResolver::SpanSizeForAutoPlacedItem(
             auto_grid_item->StyleRef(), AutoPlacementMinorAxisDirection());
     unsigned major_axis_initial_position = major_axis_positions.StartLine();
@@ -1124,7 +1126,8 @@
     Grid& grid,
     const Vector<LayoutBox*>& auto_grid_items) const {
   NOT_DESTROYED();
-  std::pair<size_t, size_t> auto_placement_cursor = std::make_pair(0, 0);
+  std::pair<wtf_size_t, wtf_size_t> auto_placement_cursor =
+      std::make_pair(0, 0);
   bool is_grid_auto_flow_dense = StyleRef().IsGridAutoFlowAlgorithmDense();
 
   for (auto* const auto_grid_item : auto_grid_items) {
@@ -1141,23 +1144,23 @@
 void LayoutGrid::PlaceAutoMajorAxisItemOnGrid(
     Grid& grid,
     LayoutBox& grid_item,
-    std::pair<size_t, size_t>& auto_placement_cursor) const {
+    std::pair<wtf_size_t, wtf_size_t>& auto_placement_cursor) const {
   NOT_DESTROYED();
   GridSpan minor_axis_positions =
       grid.GridItemSpan(grid_item, AutoPlacementMinorAxisDirection());
   DCHECK(!grid.GridItemSpan(grid_item, AutoPlacementMajorAxisDirection())
               .IsTranslatedDefinite());
-  size_t major_axis_span_size =
+  wtf_size_t major_axis_span_size =
       GridPositionsResolver::SpanSizeForAutoPlacedItem(
           grid_item.StyleRef(), AutoPlacementMajorAxisDirection());
 
-  const size_t end_of_major_axis =
+  const wtf_size_t end_of_major_axis =
       grid.NumTracks(AutoPlacementMajorAxisDirection());
-  size_t major_axis_auto_placement_cursor =
+  wtf_size_t major_axis_auto_placement_cursor =
       AutoPlacementMajorAxisDirection() == kForColumns
           ? auto_placement_cursor.second
           : auto_placement_cursor.first;
-  size_t minor_axis_auto_placement_cursor =
+  wtf_size_t minor_axis_auto_placement_cursor =
       AutoPlacementMajorAxisDirection() == kForColumns
           ? auto_placement_cursor.first
           : auto_placement_cursor.second;
@@ -1183,11 +1186,11 @@
           minor_axis_positions);
     }
   } else {
-    size_t minor_axis_span_size =
+    wtf_size_t minor_axis_span_size =
         GridPositionsResolver::SpanSizeForAutoPlacedItem(
             grid_item.StyleRef(), AutoPlacementMinorAxisDirection());
 
-    for (size_t major_axis_index = major_axis_auto_placement_cursor;
+    for (wtf_size_t major_axis_index = major_axis_auto_placement_cursor;
          major_axis_index < end_of_major_axis; ++major_axis_index) {
       auto iterator = grid.CreateIterator(AutoPlacementMajorAxisDirection(),
                                           major_axis_index,
@@ -1203,7 +1206,7 @@
           AutoPlacementMinorAxisDirection() == kForColumns
               ? empty_grid_area->columns.EndLine()
               : empty_grid_area->rows.EndLine();
-      const size_t end_of_minor_axis =
+      const wtf_size_t end_of_minor_axis =
           grid.NumTracks(AutoPlacementMinorAxisDirection());
       if (minor_axis_final_position_index <= end_of_minor_axis)
         break;
@@ -1253,7 +1256,7 @@
   NOT_DESTROYED();
   bool is_row_axis = direction == kForColumns;
   auto& positions = is_row_axis ? column_positions_ : row_positions_;
-  size_t num_positions = positions.size();
+  wtf_size_t num_positions = positions.size();
   LayoutUnit offset_between_tracks =
       is_row_axis ? offset_between_columns_.distribution_offset
                   : offset_between_rows_.distribution_offset;
@@ -1266,7 +1269,7 @@
   bool has_collapsed_tracks = grid_->HasAutoRepeatEmptyTracks(direction);
   LayoutUnit gap = !has_collapsed_tracks ? GridGap(direction) : LayoutUnit();
   tracks.ReserveCapacity(num_positions - 1);
-  for (size_t i = 0; i < num_positions - 2; ++i)
+  for (wtf_size_t i = 0; i < num_positions - 2; ++i)
     tracks.push_back(positions[i + 1] - positions[i] - offset_between_tracks -
                      gap);
   tracks.push_back(positions[num_positions - 1] - positions[num_positions - 2]);
@@ -1274,11 +1277,11 @@
   if (!has_collapsed_tracks)
     return tracks;
 
-  size_t remaining_empty_tracks =
+  wtf_size_t remaining_empty_tracks =
       grid_->AutoRepeatEmptyTracks(direction)->size();
-  size_t last_line = tracks.size();
+  wtf_size_t last_line = tracks.size();
   gap = GridGap(direction);
-  for (size_t i = 1; i < last_line; ++i) {
+  for (wtf_size_t i = 1; i < last_line; ++i) {
     if (grid_->IsEmptyAutoRepeatTrack(direction, i - 1)) {
       --remaining_empty_tracks;
     } else {
@@ -1481,11 +1484,11 @@
   // inline-axis direction.
   bool is_row_axis = direction == kForColumns;
   auto& tracks = track_sizing_algorithm_.Tracks(direction);
-  size_t number_of_tracks = tracks.size();
-  size_t number_of_lines = number_of_tracks + 1;
-  size_t last_line = number_of_lines - 1;
+  wtf_size_t number_of_tracks = tracks.size();
+  wtf_size_t number_of_lines = number_of_tracks + 1;
+  wtf_size_t last_line = number_of_lines - 1;
   bool has_collapsed_tracks = grid_->HasAutoRepeatEmptyTracks(direction);
-  size_t number_of_collapsed_tracks =
+  wtf_size_t number_of_collapsed_tracks =
       has_collapsed_tracks ? grid_->AutoRepeatEmptyTracks(direction)->size()
                            : 0;
   const auto& offset =
@@ -1510,8 +1513,8 @@
     // as we might not compute the gap between two consecutive tracks without
     // examining the surrounding ones.
     LayoutUnit gap = !has_collapsed_tracks ? GridGap(direction) : LayoutUnit();
-    size_t next_to_last_line = number_of_lines - 2;
-    for (size_t i = 0; i < next_to_last_line; ++i)
+    wtf_size_t next_to_last_line = number_of_lines - 2;
+    for (wtf_size_t i = 0; i < next_to_last_line; ++i)
       positions[i + 1] = positions[i] + offset.distribution_offset +
                          tracks[i].BaseSize() + gap;
     positions[last_line] =
@@ -1522,10 +1525,10 @@
     // they become 0.
     if (has_collapsed_tracks) {
       gap = GridGap(direction);
-      size_t remaining_empty_tracks = number_of_collapsed_tracks;
+      wtf_size_t remaining_empty_tracks = number_of_collapsed_tracks;
       LayoutUnit offset_accumulator;
       LayoutUnit gap_accumulator;
-      for (size_t i = 1; i < last_line; ++i) {
+      for (wtf_size_t i = 1; i < last_line; ++i) {
         if (grid_->IsEmptyAutoRepeatTrack(direction, i - 1)) {
           --remaining_empty_tracks;
           offset_accumulator += offset.distribution_offset;
@@ -1832,11 +1835,11 @@
   const LayoutBox* first_child = nullptr;
   bool is_baseline_aligned = false;
   // Finding the first grid item in grid order.
-  for (size_t column = 0;
+  for (wtf_size_t column = 0;
        !is_baseline_aligned && column < grid_->NumTracks(kForColumns);
        column++) {
     const GridItemList& cell = grid_->Cell(0, column);
-    for (size_t index = 0; index < cell.size(); index++) {
+    for (wtf_size_t index = 0; index < cell.size(); index++) {
       const LayoutBox* child = cell[index];
       DCHECK(!child->IsOutOfFlowPositioned());
       // If an item participates in baseline alignment, we select such item.
@@ -2142,7 +2145,7 @@
   if (direction == kForRows || StyleRef().IsLeftToRightDirection())
     return LayoutUnit();
 
-  int last_line = NumTracks(kForColumns, *grid_);
+  wtf_size_t last_line = NumTracks(kForColumns, *grid_);
   ContentPosition position = StyleRef().ResolvedJustifyContentPosition(
       ContentAlignmentNormalBehavior());
   if (position == ContentPosition::kEnd)
@@ -2163,7 +2166,7 @@
   if (StyleRef().IsLeftToRightDirection())
     return ClientLogicalWidth();
 
-  int last_line = NumTracks(kForColumns, *grid_);
+  wtf_size_t last_line = NumTracks(kForColumns, *grid_);
   ContentPosition position = StyleRef().ResolvedJustifyContentPosition(
       ContentAlignmentNormalBehavior());
   if (position == ContentPosition::kEnd)
@@ -2190,10 +2193,10 @@
   if (span.IsIndefinite())
     return is_row_axis ? ClientLogicalWidth() : ClientLogicalHeight();
 
-  size_t explicit_start = grid_->ExplicitGridStart(direction);
-  size_t start_line = span.UntranslatedStartLine() + explicit_start;
-  size_t end_line = span.UntranslatedEndLine() + explicit_start;
-  size_t last_line = NumTracks(direction, *grid_);
+  wtf_size_t explicit_start = grid_->ExplicitGridStart(direction);
+  wtf_size_t start_line = span.UntranslatedStartLine() + explicit_start;
+  wtf_size_t end_line = span.UntranslatedEndLine() + explicit_start;
+  wtf_size_t last_line = NumTracks(direction, *grid_);
   GridPosition start_position = direction == kForColumns
                                     ? child.StyleRef().GridColumnStart()
                                     : child.StyleRef().GridRowStart();
@@ -2288,7 +2291,7 @@
   auto& line_of_positioned_item =
       is_row_axis ? column_of_positioned_item_ : row_of_positioned_item_;
   start = is_row_axis ? BorderLogicalLeft() : BorderBefore();
-  if (absl::optional<size_t> line =
+  if (absl::optional<wtf_size_t> line =
           line_of_positioned_item.DeprecatedAtOrEmptyValue(&child)) {
     auto& positions = is_row_axis ? column_positions_ : row_positions_;
     start = positions[line.value()];
@@ -2605,20 +2608,20 @@
   return has_definite_logical_height_.value();
 }
 
-size_t LayoutGrid::NonCollapsedTracks(
+wtf_size_t LayoutGrid::NonCollapsedTracks(
     GridTrackSizingDirection direction) const {
   NOT_DESTROYED();
   auto& tracks = track_sizing_algorithm_.Tracks(direction);
-  size_t number_of_tracks = tracks.size();
+  wtf_size_t number_of_tracks = tracks.size();
   bool has_collapsed_tracks = grid_->HasAutoRepeatEmptyTracks(direction);
-  size_t number_of_collapsed_tracks =
+  wtf_size_t number_of_collapsed_tracks =
       has_collapsed_tracks ? grid_->AutoRepeatEmptyTracks(direction)->size()
                            : 0;
   return number_of_tracks - number_of_collapsed_tracks;
 }
 
-size_t LayoutGrid::NumTracks(GridTrackSizingDirection direction,
-                             const Grid& grid) const {
+wtf_size_t LayoutGrid::NumTracks(GridTrackSizingDirection direction,
+                                 const Grid& grid) const {
   NOT_DESTROYED();
   // Due to limitations in our internal representation, we cannot know the
   // number of columns from m_grid *if* there is no row (because m_grid would be
diff --git a/third_party/blink/renderer/core/layout/layout_grid.h b/third_party/blink/renderer/core/layout/layout_grid.h
index 559d442..6c45fb8 100644
--- a/third_party/blink/renderer/core/layout/layout_grid.h
+++ b/third_party/blink/renderer/core/layout/layout_grid.h
@@ -115,8 +115,8 @@
   // it out how to remove this dependency.
   LayoutUnit GuttersSize(const Grid&,
                          GridTrackSizingDirection,
-                         size_t start_line,
-                         size_t span,
+                         wtf_size_t start_line,
+                         wtf_size_t span,
                          absl::optional<LayoutUnit> available_size) const;
   bool CachedHasDefiniteLogicalHeight() const;
   bool IsBaselineAlignmentForChild(const LayoutBox& child) const;
@@ -174,11 +174,11 @@
   bool ExplicitGridDidResize(const ComputedStyle&) const;
   bool NamedGridLinesDefinitionDidChange(const ComputedStyle&) const;
 
-  size_t ComputeAutoRepeatTracksCount(
+  wtf_size_t ComputeAutoRepeatTracksCount(
       GridTrackSizingDirection,
       absl::optional<LayoutUnit> available_size) const;
-  size_t ClampAutoRepeatTracks(GridTrackSizingDirection,
-                               size_t auto_repeat_tracks) const;
+  wtf_size_t ClampAutoRepeatTracks(GridTrackSizingDirection,
+                                   wtf_size_t auto_repeat_tracks) const;
 
   std::unique_ptr<OrderedTrackIndexSet> ComputeEmptyTracksForAutoRepeat(
       Grid&,
@@ -201,7 +201,7 @@
   void PlaceAutoMajorAxisItemOnGrid(
       Grid&,
       LayoutBox&,
-      std::pair<size_t, size_t>& auto_placement_cursor) const;
+      std::pair<wtf_size_t, wtf_size_t>& auto_placement_cursor) const;
   GridTrackSizingDirection AutoPlacementMajorAxisDirection() const;
   GridTrackSizingDirection AutoPlacementMinorAxisDirection() const;
 
@@ -311,8 +311,8 @@
 
   size_t GridItemSpan(const LayoutBox&, GridTrackSizingDirection);
 
-  size_t NonCollapsedTracks(GridTrackSizingDirection) const;
-  size_t NumTracks(GridTrackSizingDirection, const Grid&) const;
+  wtf_size_t NonCollapsedTracks(GridTrackSizingDirection) const;
+  wtf_size_t NumTracks(GridTrackSizingDirection, const Grid&) const;
 
   static LayoutUnit OverrideContainingBlockContentSizeForChild(
       const LayoutBox& child,
@@ -332,7 +332,7 @@
   ContentAlignmentData offset_between_columns_;
   ContentAlignmentData offset_between_rows_;
 
-  typedef HashMap<const LayoutBox*, absl::optional<size_t>>
+  typedef HashMap<const LayoutBox*, absl::optional<wtf_size_t>>
       OutOfFlowPositionsMap;
   OutOfFlowPositionsMap column_of_positioned_item_;
   OutOfFlowPositionsMap row_of_positioned_item_;
diff --git a/third_party/blink/renderer/core/layout/layout_text_test.cc b/third_party/blink/renderer/core/layout/layout_text_test.cc
index a97da2f..becdb955 100644
--- a/third_party/blink/renderer/core/layout/layout_text_test.cc
+++ b/third_party/blink/renderer/core/layout/layout_text_test.cc
@@ -70,7 +70,8 @@
 
   std::string GetSnapCode(const LayoutText& layout_text,
                           const std::string& caret_text) {
-    return GetSnapCode(layout_text, caret_text.find('|'));
+    return GetSnapCode(layout_text,
+                       static_cast<unsigned>(caret_text.find('|')));
   }
 
   std::string GetSnapCode(const char* id, const std::string& caret_text) {
diff --git a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
index ac8b6f3..309aebb 100644
--- a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
@@ -306,11 +306,13 @@
   space_builder.SetCacheSlot(NGCacheSlot::kMeasure);
   space_builder.SetIsPaintedAtomically(true);
 
-  if (WillChildCrossSizeBeContainerCrossSize(flex_item)) {
-    if (is_column_)
-      space_builder.SetInlineAutoBehavior(NGAutoBehavior::kStretchExplicit);
-    else
-      space_builder.SetBlockAutoBehavior(NGAutoBehavior::kStretchExplicit);
+  if (!flex_item.IsReplaced()) {
+    if (WillChildCrossSizeBeContainerCrossSize(flex_item)) {
+      if (is_column_)
+        space_builder.SetInlineAutoBehavior(NGAutoBehavior::kStretchExplicit);
+      else
+        space_builder.SetBlockAutoBehavior(NGAutoBehavior::kStretchExplicit);
+    }
   }
 
   // For determining the intrinsic block-size we make %-block-sizes resolve
@@ -344,141 +346,6 @@
   return space_builder.ToConstraintSpace();
 }
 
-namespace {
-
-// This function will be superseded by
-// NGReplacedLayoutAlgorithm.ComputeMinMaxSizes, when such method exists.
-LayoutUnit ComputeIntrinsicInlineSizeForAspectRatioElement(
-    const NGBlockNode& node,
-    const NGConstraintSpace& space,
-    const absl::optional<LayoutUnit> definite_block_size,
-    const MinMaxSizes& used_min_max_block_sizes) {
-  DCHECK(node.HasAspectRatio());
-  LogicalSize aspect_ratio = node.GetAspectRatio();
-  const ComputedStyle& style = node.Style();
-  NGBoxStrut border_padding =
-      ComputeBorders(space, node) + ComputePadding(space, style);
-
-  absl::optional<LayoutUnit> intrinsic_inline;
-  absl::optional<LayoutUnit> intrinsic_block;
-
-  absl::optional<LayoutUnit> block_size_border_box;
-  if (definite_block_size.has_value()) {
-    block_size_border_box = definite_block_size;
-  } else {
-    node.IntrinsicSize(&intrinsic_inline, &intrinsic_block);
-    if (intrinsic_block) {
-      block_size_border_box = *intrinsic_block + border_padding.BlockSum();
-    }
-  }
-
-  if (block_size_border_box) {
-    LayoutUnit clamped_intrinsic_block_border_box =
-        used_min_max_block_sizes.ClampSizeToMinAndMax(*block_size_border_box);
-    return InlineSizeFromAspectRatio(border_padding, aspect_ratio,
-                                     EBoxSizing::kContentBox,
-                                     clamped_intrinsic_block_border_box);
-  }
-
-  MinMaxSizes inline_min_max = ComputeTransferredMinMaxInlineSizes(
-      aspect_ratio, used_min_max_block_sizes, border_padding,
-      EBoxSizing::kContentBox);
-
-  if (intrinsic_inline) {
-    LayoutUnit intrinsic_inline_border_box =
-        *intrinsic_inline + border_padding.InlineSum();
-    return inline_min_max.ClampSizeToMinAndMax(intrinsic_inline_border_box);
-  }
-
-  // If control flow reaches here, the item has aspect ratio only, no natural
-  // sizes. Spec says:
-  // * If the available space is definite in the inline axis, use the stretch
-  // fit into that size for the inline size and calculate the block size using
-  // the aspect ratio.
-  // https://drafts.csswg.org/css-sizing-3/#intrinsic-sizes
-  DCHECK_NE(space.AvailableSize().inline_size, kIndefiniteSize);
-  NGBoxStrut margins = ComputeMarginsForSelf(space, style);
-  return inline_min_max
-      .ClampSizeToMinAndMax(space.AvailableSize().inline_size -
-                            margins.InlineSum())
-      .ClampNegativeToZero();
-}
-
-// The value produced by this function will be available via
-// replaced_node.Layout()->IntrinsicBlockSize(), once NGReplacedLayoutAlgorithm
-// exists.
-LayoutUnit ComputeIntrinsicBlockSizeForAspectRatioElement(
-    const NGBlockNode& node,
-    const NGConstraintSpace& space,
-    const absl::optional<LayoutUnit> definite_inline_size,
-    const MinMaxSizes& used_min_max_inline_sizes) {
-  DCHECK(node.HasAspectRatio());
-  LogicalSize aspect_ratio = node.GetAspectRatio();
-  const ComputedStyle& style = node.Style();
-  NGBoxStrut border_padding =
-      ComputeBorders(space, node) + ComputePadding(space, style);
-
-  absl::optional<LayoutUnit> intrinsic_inline;
-  absl::optional<LayoutUnit> intrinsic_block;
-
-  absl::optional<LayoutUnit> inline_size_border_box;
-  if (definite_inline_size.has_value()) {
-    inline_size_border_box = definite_inline_size;
-  } else {
-    node.IntrinsicSize(&intrinsic_inline, &intrinsic_block);
-    if (intrinsic_inline) {
-      inline_size_border_box = *intrinsic_inline + border_padding.InlineSum();
-    }
-  }
-
-  if (inline_size_border_box) {
-    LayoutUnit clamped_intrinsic_inline_border_box =
-        used_min_max_inline_sizes.ClampSizeToMinAndMax(*inline_size_border_box);
-    return BlockSizeFromAspectRatio(border_padding, aspect_ratio,
-                                    EBoxSizing::kContentBox,
-                                    clamped_intrinsic_inline_border_box);
-  }
-
-  MinMaxSizes transferred_block_min_max = {LayoutUnit(), LayoutUnit::Max()};
-  if (used_min_max_inline_sizes.min_size > LayoutUnit()) {
-    transferred_block_min_max.min_size = BlockSizeFromAspectRatio(
-        border_padding, aspect_ratio, EBoxSizing::kContentBox,
-        used_min_max_inline_sizes.min_size);
-  }
-  if (used_min_max_inline_sizes.max_size != LayoutUnit::Max()) {
-    transferred_block_min_max.max_size = BlockSizeFromAspectRatio(
-        border_padding, aspect_ratio, EBoxSizing::kContentBox,
-        used_min_max_inline_sizes.max_size);
-  }
-  if (intrinsic_block) {
-    // Minimum size wins over maximum size.
-    transferred_block_min_max.max_size = std::max(
-        transferred_block_min_max.max_size, transferred_block_min_max.min_size);
-    LayoutUnit intrinsic_block_border_box =
-        *intrinsic_block + border_padding.BlockSum();
-    return transferred_block_min_max.ClampSizeToMinAndMax(
-        intrinsic_block_border_box);
-  }
-
-  // If control flow reaches here, the item has aspect ratio only, no natural
-  // sizes. Spec says:
-  // * If the available space is definite in the inline axis, use the stretch
-  // fit into that size for the inline size and calculate the block size using
-  // the aspect ratio.
-  // https://drafts.csswg.org/css-sizing-3/#intrinsic-sizes
-  DCHECK_NE(space.AvailableSize().inline_size, kIndefiniteSize);
-  NGBoxStrut margins = ComputeMarginsForSelf(space, style);
-  LayoutUnit stretch_into_available_inline_size(
-      (space.AvailableSize().inline_size - margins.InlineSum())
-          .ClampNegativeToZero());
-  return transferred_block_min_max.ClampSizeToMinAndMax(
-      BlockSizeFromAspectRatio(border_padding, aspect_ratio,
-                               EBoxSizing::kContentBox,
-                               stretch_into_available_inline_size));
-}
-
-}  // namespace
-
 void NGFlexLayoutAlgorithm::ConstructAndAppendFlexItems() {
   NGFlexChildIterator iterator(Node());
   for (NGBlockNode child = iterator.NextChild(); child;
@@ -621,44 +488,11 @@
         // containers AND children) row flex containers. I _think_ the C and D
         // cases are correctly handled by this code, which was originally
         // written for case E.
-
-        // Non-replaced AspectRatio items work fine using
-        // MinMaxSizesFunc(MinMaxSizesType::kContent).sizes.max_size below, so
-        // they don't need to use
-        // ComputeIntrinsicInlineSizeForAspectRatioElement.
-        if (child.HasAspectRatio() && child.IsReplaced()) {
-          // Legacy uses child.PreferredLogicalWidths() for this case, which is
-          // not exactly correct.
-          // ComputeIntrinsicInlineSizeForAspectRatioElement would honor the
-          // definite block size parameter by multipying it by the aspect ratio,
-          // but if control flow reaches here, we know we don't have a definite
-          // inline size. If we did, we would have fallen into the "part B"
-          // section above, not this "part C, D, E" section.
-          flex_base_border_box =
-              ComputeIntrinsicInlineSizeForAspectRatioElement(
-                  child, flex_basis_space, /* definite_block_size */
-                  absl::nullopt, min_max_sizes_in_cross_axis_direction);
-        } else {
-          flex_base_border_box =
-              MinMaxSizesFunc(MinMaxSizesType::kContent).sizes.max_size;
-        }
+        flex_base_border_box =
+            MinMaxSizesFunc(MinMaxSizesType::kContent).sizes.max_size;
       } else {
         // Parts C, D, and E for what are usually column flex containers.
-        if (child.HasAspectRatio() && child.IsReplaced()) {
-          // Legacy uses the post-layout size for this case, which isn't always
-          // correct.
-          // With regard to |absl::nullopt| in the next line:
-          // ComputeIntrinsicBlockSizeForAspectRatioElement would honor a
-          // definite inline size by multipying it by the aspect ratio, but if
-          // control flow reaches here, we know we don't have a definite inline
-          // size. If we did, we would have fallen into the "part B" section
-          // above, not this "part C, D, E" section.
-          flex_base_border_box = ComputeIntrinsicBlockSizeForAspectRatioElement(
-              child, flex_basis_space, absl::nullopt /* definite_inline_size */,
-              min_max_sizes_in_cross_axis_direction);
-        } else {
-          flex_base_border_box = IntrinsicBlockSizeFunc();
-        }
+        flex_base_border_box = IntrinsicBlockSizeFunc();
       }
     } else {
       // Part A of 9.2.3 https://drafts.csswg.org/css-flexbox/#algo-main-item
@@ -699,57 +533,10 @@
     if (algorithm_.ShouldApplyMinSizeAutoForChild(*child.GetLayoutBox())) {
       LayoutUnit content_size_suggestion;
       if (MainAxisIsInlineAxis(child)) {
-        if (child.IsReplaced() && child.HasAspectRatio()) {
-          absl::optional<LayoutUnit> definite_block_size;
-          if (!BlockLengthUnresolvable(flex_basis_space,
-                                       child_style.LogicalHeight())) {
-            definite_block_size = ResolveMainBlockLength(
-                flex_basis_space, child_style,
-                border_padding_in_child_writing_mode,
-                child_style.LogicalHeight(), IntrinsicBlockSizeFunc);
-          }
-
-          content_size_suggestion =
-              ComputeIntrinsicInlineSizeForAspectRatioElement(
-                  child, flex_basis_space, definite_block_size,
-                  min_max_sizes_in_cross_axis_direction);
-        } else {
-          content_size_suggestion =
-              MinMaxSizesFunc(MinMaxSizesType::kContent).sizes.min_size;
-        }
+        content_size_suggestion =
+            MinMaxSizesFunc(MinMaxSizesType::kContent).sizes.min_size;
       } else {
-        LayoutUnit intrinsic_block_size;
-        if (child.IsReplaced()) {
-          if (child.HasAspectRatio()) {
-            absl::optional<LayoutUnit> definite_inline_size;
-            if (!child_style.LogicalWidth().IsAuto()) {
-              definite_inline_size = ResolveMainInlineLength(
-                  flex_basis_space, child_style,
-                  border_padding_in_child_writing_mode, MinMaxSizesFunc,
-                  child_style.LogicalWidth());
-            }
-            intrinsic_block_size =
-                ComputeIntrinsicBlockSizeForAspectRatioElement(
-                    child, flex_basis_space, definite_inline_size,
-                    min_max_sizes_in_cross_axis_direction);
-          } else {
-            // This code block is needed to make
-            // flex-aspect-ratio-img-column-017.html pass, but the test may be
-            // wrong. https://github.com/web-platform-tests/wpt/issues/27653
-            absl::optional<LayoutUnit> computed_inline_size;
-            absl::optional<LayoutUnit> computed_block_size;
-            child.IntrinsicSize(&computed_inline_size, &computed_block_size);
-
-            // The 150 is for replaced elements that have no size, which SVG
-            // can have (maybe others?).
-            intrinsic_block_size =
-                computed_block_size.value_or(LayoutUnit(150)) +
-                border_padding_in_child_writing_mode.BlockSum();
-          }
-        } else {
-          intrinsic_block_size = IntrinsicBlockSizeFunc();
-        }
-        content_size_suggestion = intrinsic_block_size;
+        content_size_suggestion = IntrinsicBlockSizeFunc();
       }
       DCHECK_GE(content_size_suggestion, main_axis_border_padding);
 
@@ -955,44 +742,13 @@
       space_builder.SetIsPaintedAtomically(true);
 
       LogicalSize available_size;
-      LayoutUnit fixed_aspect_ratio_cross_size = kIndefiniteSize;
-      if (flex_item.ng_input_node_.HasAspectRatio() &&
-          flex_item.ng_input_node_.IsReplaced()) {
-        // This code derives the cross axis size from the flexed main size and
-        // the aspect ratio. We can delete this code when
-        // NGReplacedLayoutAlgorithm exists, because it will do this for us.
-        NGConstraintSpace flex_basis_space =
-            BuildSpaceForFlexBasis(flex_item.ng_input_node_);
-        const Length& cross_axis_length =
-            is_horizontal_flow_ ? child_style.Height() : child_style.Width();
-        // Only derive the cross axis size from the aspect ratio if the computed
-        // cross axis length might be indefinite. The item's cross axis length
-        // might still be definite if it is stretched, but that is checked in
-        // the |WillChildCrossSizeBeContainerCrossSize| calls below.
-        if (cross_axis_length.IsAuto() ||
-            (MainAxisIsInlineAxis(flex_item.ng_input_node_) &&
-             BlockLengthUnresolvable(flex_basis_space, cross_axis_length))) {
-          LogicalSize aspect_ratio =
-              GetMainOverCrossAspectRatio(flex_item.ng_input_node_);
-          aspect_ratio.Transpose();
-          fixed_aspect_ratio_cross_size =
-              flex_item.min_max_cross_sizes_->ClampSizeToMinAndMax(
-                  flex_item.cross_axis_border_padding_ +
-                  ComputeSizeFromAspectRatio(flex_item.flexed_content_size_,
-                                             aspect_ratio));
-        }
-      }
       if (is_column_) {
         available_size.inline_size = ChildAvailableSize().inline_size;
         available_size.block_size = flex_item.flexed_content_size_ +
                                     flex_item.main_axis_border_padding_;
         space_builder.SetIsFixedBlockSize(true);
-        if (WillChildCrossSizeBeContainerCrossSize(flex_item.ng_input_node_)) {
+        if (WillChildCrossSizeBeContainerCrossSize(flex_item.ng_input_node_))
           space_builder.SetInlineAutoBehavior(NGAutoBehavior::kStretchExplicit);
-        } else if (fixed_aspect_ratio_cross_size != kIndefiniteSize) {
-          space_builder.SetIsFixedInlineSize(true);
-          available_size.inline_size = fixed_aspect_ratio_cross_size;
-        }
         // https://drafts.csswg.org/css-flexbox/#definite-sizes
         // If the flex container has a definite main size, a flex item's
         // post-flexing main size is treated as definite, even though it can
@@ -1006,12 +762,8 @@
                                      flex_item.main_axis_border_padding_;
         available_size.block_size = ChildAvailableSize().block_size;
         space_builder.SetIsFixedInlineSize(true);
-        if (WillChildCrossSizeBeContainerCrossSize(flex_item.ng_input_node_)) {
+        if (WillChildCrossSizeBeContainerCrossSize(flex_item.ng_input_node_))
           space_builder.SetBlockAutoBehavior(NGAutoBehavior::kStretchExplicit);
-        } else if (fixed_aspect_ratio_cross_size != kIndefiniteSize) {
-          space_builder.SetIsFixedBlockSize(true);
-          available_size.block_size = fixed_aspect_ratio_cross_size;
-        }
       }
       if (DoesItemStretch(flex_item.ng_input_node_)) {
         // For stretched items, the goal of this layout is determine the
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc
index c6a8009..43b8c8d 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_fragment_items_builder.cc
@@ -222,7 +222,8 @@
 
   DCHECK(items_.IsEmpty());
   const NGFragmentItems::Span source_items = items.Items();
-  const wtf_size_t estimated_size = source_items.size();
+  const wtf_size_t estimated_size =
+      base::checked_cast<wtf_size_t>(source_items.size());
   items_.ReserveCapacity(estimated_size);
 
   // Convert offsets to logical. The logic is opposite to |ConvertToPhysical|.
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc
index 8af4586..c42e183 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.cc
@@ -629,7 +629,8 @@
 
 LayoutUnit NGInlineLayoutStateStack::ComputeInlinePositions(
     NGLogicalLineItems* line_box,
-    LayoutUnit position) {
+    LayoutUnit position,
+    bool ignore_box_margin_border_padding) {
   // At this point, children are in the visual order, and they have their
   // origins at (0, 0). Accumulate inline offset from left to right.
   for (NGLogicalLineItem& child : *line_box) {
@@ -645,22 +646,24 @@
   if (box_data_list_.IsEmpty())
     return position;
 
-  // Adjust child offsets for margin/border/padding of inline boxes.
-  for (BoxData& box_data : box_data_list_) {
-    unsigned start = box_data.fragment_start;
-    unsigned end = box_data.fragment_end;
-    DCHECK_GT(end, start);
+  if (!ignore_box_margin_border_padding) {
+    // Adjust child offsets for margin/border/padding of inline boxes.
+    for (BoxData& box_data : box_data_list_) {
+      unsigned start = box_data.fragment_start;
+      unsigned end = box_data.fragment_end;
+      DCHECK_GT(end, start);
 
-    if (box_data.margin_border_padding_line_left) {
-      line_box->MoveInInlineDirection(box_data.margin_border_padding_line_left,
-                                      start, line_box->size());
-      position += box_data.margin_border_padding_line_left;
-    }
+      if (box_data.margin_border_padding_line_left) {
+        line_box->MoveInInlineDirection(
+            box_data.margin_border_padding_line_left, start, line_box->size());
+        position += box_data.margin_border_padding_line_left;
+      }
 
-    if (box_data.margin_border_padding_line_right) {
-      line_box->MoveInInlineDirection(box_data.margin_border_padding_line_right,
-                                      end, line_box->size());
-      position += box_data.margin_border_padding_line_right;
+      if (box_data.margin_border_padding_line_right) {
+        line_box->MoveInInlineDirection(
+            box_data.margin_border_padding_line_right, end, line_box->size());
+        position += box_data.margin_border_padding_line_right;
+      }
     }
   }
 
@@ -682,8 +685,6 @@
     LayoutUnit line_left_offset =
         start_child.rect.offset.inline_offset - start_child.margin_line_left;
     LinePadding& start_padding = accumulated_padding[start];
-    start_padding.line_left += box_data.margin_border_padding_line_left;
-    line_left_offset -= start_padding.line_left - box_data.margin_line_left;
 
     DCHECK_GT(box_data.fragment_end, start);
     unsigned last = box_data.fragment_end - 1;
@@ -692,8 +693,16 @@
                                    last_child.margin_line_left +
                                    last_child.inline_size;
     LinePadding& last_padding = accumulated_padding[last];
-    last_padding.line_right += box_data.margin_border_padding_line_right;
-    line_right_offset += last_padding.line_right - box_data.margin_line_right;
+
+    if (!ignore_box_margin_border_padding) {
+      start_padding.line_left += box_data.margin_border_padding_line_left;
+      last_padding.line_right += box_data.margin_border_padding_line_right;
+      line_left_offset += box_data.margin_line_left;
+      line_right_offset -= box_data.margin_line_right;
+    }
+
+    line_left_offset -= start_padding.line_left;
+    line_right_offset += last_padding.line_right;
 
     box_data.rect.offset.inline_offset = line_left_offset;
     box_data.rect.size.inline_size = line_right_offset - line_left_offset;
@@ -748,6 +757,7 @@
 void NGInlineLayoutStateStack::CreateBoxFragments(
     const NGConstraintSpace& space,
     NGLogicalLineItems* line_box,
+    bool is_opaque,
     Vector<LogicalOffset, 32>* oof_relative_offsets) {
   DCHECK(!box_data_list_.IsEmpty());
   DCHECK(oof_relative_offsets);
@@ -761,7 +771,8 @@
     NGLogicalLineItem* child = &(*line_box)[start];
     DCHECK(box_data.item->ShouldCreateBoxFragment());
     scoped_refptr<const NGLayoutResult> box_fragment =
-        box_data.CreateBoxFragment(space, line_box, (*oof_relative_offsets)[i]);
+        box_data.CreateBoxFragment(space, line_box, is_opaque,
+                                   (*oof_relative_offsets)[i]);
     if (child->IsPlaceholder()) {
       child->layout_result = std::move(box_fragment);
       child->rect = box_data.rect;
@@ -783,6 +794,7 @@
 NGInlineLayoutStateStack::BoxData::CreateBoxFragment(
     const NGConstraintSpace& space,
     NGLogicalLineItems* line_box,
+    bool is_opaque,
     LogicalOffset oof_relative_offset) {
   DCHECK(item);
   DCHECK(item->Style());
@@ -802,10 +814,16 @@
   box.SetBoxType(NGPhysicalFragment::kInlineBox);
   box.SetStyleVariant(item->StyleVariant());
 
-  // Inline boxes have block start/end borders, even when its containing block
-  // was fragmented. Fragmenting a line box in block direction is not
-  // supported today.
-  box.SetSidesToInclude({true, has_line_right_edge, true, has_line_left_edge});
+  if (UNLIKELY(is_opaque)) {
+    box.SetIsOpaque();
+    box.SetSidesToInclude({false, false, false, false});
+  } else {
+    // Inline boxes have block start/end borders, even when its containing block
+    // was fragmented. Fragmenting a line box in block direction is not
+    // supported today.
+    box.SetSidesToInclude(
+        {true, has_line_right_edge, true, has_line_left_edge});
+  }
 
   for (unsigned i = fragment_start; i < fragment_end; i++) {
     NGLogicalLineItem& child = (*line_box)[i];
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h
index 901c6207..4eb00cc 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_box_state.h
@@ -190,7 +190,9 @@
   void UpdateAfterReorder(NGLogicalLineItems*);
 
   // Compute inline positions of fragments and boxes.
-  LayoutUnit ComputeInlinePositions(NGLogicalLineItems*, LayoutUnit position);
+  LayoutUnit ComputeInlinePositions(NGLogicalLineItems*,
+                                    LayoutUnit position,
+                                    bool ignore_box_margin_border_padding);
 
   // |oof_relative_offsets| is an output variable for the accumulated
   // relative positioning offsets to be applied to OOF positioned descendants.
@@ -202,6 +204,7 @@
   // a box tree.
   void CreateBoxFragments(const NGConstraintSpace&,
                           NGLogicalLineItems*,
+                          bool is_opaque,
                           Vector<LogicalOffset, 32>* oof_relative_offsets);
 
 #if DCHECK_IS_ON()
@@ -292,6 +295,7 @@
     scoped_refptr<const NGLayoutResult> CreateBoxFragment(
         const NGConstraintSpace&,
         NGLogicalLineItems*,
+        bool is_opaque = false,
         LogicalOffset oof_relative_offset = LogicalOffset());
   };
 
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc
index 2fdd10e..45c950b 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_cursor.cc
@@ -253,9 +253,11 @@
 
 void NGInlineCursor::ExpandRootToContainingBlock() {
   if (fragment_items_) {
-    const unsigned index_diff = items_.data() - fragment_items_->Items().data();
+    const unsigned index_diff = base::checked_cast<unsigned>(
+        items_.data() - fragment_items_->Items().data());
     DCHECK_LT(index_diff, fragment_items_->Items().size());
-    const unsigned item_index = current_.item_iter_ - items_.begin();
+    const unsigned item_index =
+        base::checked_cast<unsigned>(current_.item_iter_ - items_.begin());
     items_ = fragment_items_->Items();
     // Update the iterator to the one for the new span.
     MoveToItem(items_.begin() + item_index + index_diff);
@@ -881,7 +883,8 @@
   DCHECK(HasRoot());
   DCHECK(!items_.empty());
   DCHECK(fragment_items_->IsSubSpan(items_));
-  const wtf_size_t delta = items_.data() - fragment_items_->Items().data();
+  const wtf_size_t delta = base::checked_cast<wtf_size_t>(
+      items_.data() - fragment_items_->Items().data());
   DCHECK_LT(delta, fragment_items_->Items().size());
   return delta;
 }
@@ -892,8 +895,8 @@
   DCHECK(fragment_items_->IsSubSpan(items_));
   if (items_.data() == fragment_items_->Items().data())
     return index;
-  const wtf_size_t span_index =
-      fragment_items_->Items().data() - items_.data() + index;
+  const wtf_size_t span_index = base::checked_cast<wtf_size_t>(
+      fragment_items_->Items().data() - items_.data() + index);
   DCHECK_LT(span_index, items_.size());
   return span_index;
 }
@@ -1370,8 +1373,8 @@
           return;
         }
         if (cursor.fragment_items_ == fragment_items_) {
-          item_index =
-              cursor.Current().Item() - fragment_items_->Items().data();
+          item_index = base::checked_cast<wtf_size_t>(
+              cursor.Current().Item() - fragment_items_->Items().data());
           break;
         }
       }
@@ -1416,7 +1419,8 @@
   if (wtf_size_t delta = current_.item_->DeltaToNextForSameLayoutObject()) {
     while (true) {
       // Return if the next index is in the current range.
-      const wtf_size_t delta_to_end = items_.end() - current_.item_iter_;
+      const wtf_size_t delta_to_end =
+          base::checked_cast<wtf_size_t>(items_.end() - current_.item_iter_);
       if (delta < delta_to_end) {
         MoveToItem(current_.item_iter_ + delta);
         return;
@@ -1673,7 +1677,8 @@
   if (position.Item()) {
     DCHECK(HasRoot());
     DCHECK_EQ(position.item_, &*position.item_iter_);
-    const unsigned index = position.item_iter_ - items_.begin();
+    const unsigned index =
+        base::checked_cast<unsigned>(position.item_iter_ - items_.begin());
     DCHECK_LT(index, items_.size());
   }
 }
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
index 5a9cd8f..f661eb0 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
@@ -538,7 +538,7 @@
   EWhiteSpace whitespace = style.WhiteSpace();
   unsigned start = 0;
   for (unsigned offset : iter->value) {
-    DCHECK_LT(offset, string.length());
+    DCHECK_LE(offset, string.length());
     if (start < offset) {
       if (!ComputedStyle::CollapseWhiteSpace(whitespace)) {
         AppendPreserveWhitespace(string.Substring(start, offset - start),
@@ -551,6 +551,8 @@
     ExitAndEnterSvgTextChunk(layout_text);
     start = offset;
   }
+  if (start >= string.length())
+    return true;
   if (!ComputedStyle::CollapseWhiteSpace(whitespace)) {
     AppendPreserveWhitespace(string.Substring(start), &style, &layout_text);
   } else {
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
index f9bdf1e1..19035efa 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
@@ -334,9 +334,11 @@
   const LayoutUnit hang_width = line_info->HangWidth();
   LayoutUnit inline_size;
   if (IsLtr(line_info->BaseDirection())) {
-    inline_size = box_states_->ComputeInlinePositions(line_box, LayoutUnit());
+    inline_size = box_states_->ComputeInlinePositions(
+        line_box, LayoutUnit(), line_info->IsBlockInInline());
   } else {
-    inline_size = box_states_->ComputeInlinePositions(line_box, -hang_width);
+    inline_size = box_states_->ComputeInlinePositions(
+        line_box, -hang_width, line_info->IsBlockInInline());
     inline_size += hang_width;
   }
   if (UNLIKELY(hang_width)) {
@@ -419,6 +421,7 @@
   // creating a "hole" in the array.
   if (box_states_->HasBoxFragments()) {
     box_states_->CreateBoxFragments(ConstraintSpace(), line_box,
+                                    line_info->IsBlockInInline(),
                                     &oof_relative_offsets);
   }
 
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
index df939940..fa9c4d6 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.cc
@@ -270,9 +270,9 @@
     DCHECK_LT(start_offset, end_offset);
     const TextDirection direction = start_item.Direction();
     if (data_.segments) {
-      return data_.segments->ShapeText(&shaper_, &font, direction, start_offset,
-                                       end_offset,
-                                       &start_item - data_.items.begin());
+      return data_.segments->ShapeText(
+          &shaper_, &font, direction, start_offset, end_offset,
+          base::checked_cast<unsigned>(&start_item - data_.items.begin()));
     }
     RunSegmenter::RunSegmenterRange range =
         start_item.CreateRunSegmenterRange();
@@ -1086,6 +1086,14 @@
           ifc_text_view.NextCodePointOffset(text_content_offset);
     }
     const auto* unit = mapping->GetLastMappingUnit(text_content_offset);
+    // |text_content_offset| might point a control character not in any
+    // DOM nodes.
+    while (!unit) {
+      text_content_offset =
+          ifc_text_view.NextCodePointOffset(text_content_offset);
+      DCHECK_LT(text_content_offset, ifc_text_view.length());
+      unit = mapping->GetLastMappingUnit(text_content_offset);
+    }
     auto result = data.svg_node_data_->chunk_offsets.insert(
         To<LayoutText>(&unit->GetLayoutObject()), Vector<unsigned>());
     result.stored_value->value.push_back(
@@ -1734,7 +1742,8 @@
         if (item.Type() == NGInlineItem::kAtomicInline ||
             item.Type() == NGInlineItem::kBlockInInline) {
           // The max-size for atomic inlines are cached in |max_size_cache|.
-          unsigned item_index = &item - items_data.items.begin();
+          unsigned item_index =
+              base::checked_cast<unsigned>(&item - items_data.items.begin());
           position += max_size_cache[item_index];
           continue;
         }
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
index a21e698..b3982a5 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
@@ -21,6 +21,7 @@
 #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
+#include "third_party/blink/renderer/core/layout/ng/svg/layout_ng_svg_text.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/core/svg_names.h"
 
@@ -1602,4 +1603,38 @@
   EXPECT_EQ(0, font_description.WordSpacing());
 }
 
+// crbug.com/1034464 bad.svg
+TEST_F(NGInlineNodeTest, FindSvgTextChunksCrash1) {
+  ScopedSVGTextNGForTest enable_svg_text_ng(true);
+  SetBodyInnerHTML(
+      "<svg><text id='text' xml:space='preserve'>"
+      "<tspan unicode-bidi='embed' x='0'>(</tspan>"
+      "<tspan y='-2' unicode-bidi='embed' x='3'>)</tspan>"
+      "<tspan y='-2' x='6'>&#x05d2;</tspan>"
+      "<tspan y='-2' unicode-bidi='embed' x='10'>(</tspan>"
+      "</text></svg>");
+
+  auto* block_flow = To<LayoutNGSVGText>(GetLayoutObjectByElementId("text"));
+  const NGInlineNodeData* data = block_flow->GetNGInlineNodeData();
+  EXPECT_TRUE(data);
+  // Pass if no null pointer dereferences.
+}
+
+// crbug.com/1034464 good.svg
+TEST_F(NGInlineNodeTest, FindSvgTextChunksCrash2) {
+  ScopedSVGTextNGForTest enable_svg_text_ng(true);
+  SetBodyInnerHTML(
+      "<svg><text id='text' xml:space='preserve'>\n"
+      "<tspan unicode-bidi='embed' x='0'>(</tspan>\n"
+      "<tspan y='-2' unicode-bidi='embed' x='3'>)</tspan>\n"
+      "<tspan y='-2' x='6'>&#x05d2;</tspan>\n"
+      "<tspan y='-2' unicode-bidi='embed' x='10'>(</tspan>\n"
+      "</text></svg>");
+
+  auto* block_flow = To<LayoutNGSVGText>(GetLayoutObjectByElementId("text"));
+  const NGInlineNodeData* data = block_flow->GetNGInlineNodeData();
+  EXPECT_TRUE(data);
+  // Pass if no DCHECK() failures.
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
index 5972df5..cfddbb0 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.cc
@@ -419,8 +419,10 @@
 
 LayoutUnit NGLineBreaker::AddHyphen(NGInlineItemResults* item_results,
                                     NGInlineItemResult* item_result) {
-  return AddHyphen(item_results, item_result - item_results->begin(),
-                   item_result);
+  return AddHyphen(
+      item_results,
+      base::checked_cast<wtf_size_t>(item_result - item_results->begin()),
+      item_result);
 }
 
 // Remove the hyphen string from the |NGInlineItemResult|.
@@ -920,7 +922,8 @@
       state_ = LineBreakState::kTrailing;
       if (item_result->item->Style()->WhiteSpace() == EWhiteSpace::kPreWrap &&
           IsBreakableSpace(Text()[item_result->EndOffset() - 1])) {
-        unsigned end_index = item_result - line_info->Results().begin();
+        unsigned end_index = base::checked_cast<unsigned>(
+            item_result - line_info->Results().begin());
         Rewind(end_index, line_info);
       }
       return;
@@ -1403,7 +1406,7 @@
   } else {
     shape_result = items_data_.segments->ShapeText(
         &shaper_, &item.Style()->GetFont(), item.Direction(), start, end,
-        &item - items_data_.items.begin());
+        base::checked_cast<unsigned>(&item - items_data_.items.begin()));
   }
   if (UNLIKELY(spacing_.HasSpacing()))
     shape_result->ApplySpacing(spacing_);
@@ -1573,7 +1576,8 @@
   for (const NGInlineItemResult& item_result : base::Reversed(item_results)) {
     DCHECK(item_result.item);
     if (item_result.item->Type() != NGInlineItem::kOpenTag) {
-      unsigned end_index = &item_result - item_results.begin() + 1;
+      unsigned end_index =
+          base::checked_cast<unsigned>(&item_result - item_results.begin() + 1);
       if (end_index < item_results.size()) {
         const NGInlineItemResult& end_item_result = item_results[end_index];
         unsigned end_item_index = end_item_result.item_index;
@@ -1935,7 +1939,8 @@
   DCHECK(mode_ == NGLineBreakerMode::kMaxContent ||
          mode_ == NGLineBreakerMode::kMinContent);
   if (mode_ == NGLineBreakerMode::kMaxContent && max_size_cache_) {
-    const unsigned item_index = &item - Items().begin();
+    const unsigned item_index =
+        base::checked_cast<unsigned>(&item - Items().begin());
     item_result->inline_size = (*max_size_cache_)[item_index];
     return;
   }
@@ -1962,7 +1967,8 @@
     if (max_size_cache_) {
       if (max_size_cache_->IsEmpty())
         max_size_cache_->resize(Items().size());
-      const unsigned item_index = &item - Items().begin();
+      const unsigned item_index =
+          base::checked_cast<unsigned>(&item - Items().begin());
       (*max_size_cache_)[item_index] = result.sizes.max_size + inline_margins;
     }
     return;
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc
index 812dce2c..29e77ae5 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_line_truncator.cc
@@ -165,7 +165,8 @@
   if (truncated_child) {
     // In order to preserve layout information before truncated, hide the
     // original fragment and insert a truncated one.
-    size_t child_index_to_truncate = ellipsized_child - line_box->begin();
+    unsigned child_index_to_truncate =
+        base::checked_cast<unsigned>(ellipsized_child - line_box->begin());
     line_box->InsertChild(child_index_to_truncate + 1,
                           std::move(*truncated_child));
     box_states->ChildInserted(child_index_to_truncate + 1);
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc
index 084412b2..6d7d8bb 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.cc
@@ -332,7 +332,7 @@
                        });
 
   UnitVector result;
-  result.ReserveCapacity(result_end - result_begin);
+  result.ReserveCapacity(static_cast<unsigned>(result_end - result_begin));
   for (const auto& unit : base::make_span(result_begin, result_end)) {
     // If the unit isn't fully within the range, create a new unit that's
     // within the range.
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.cc
index 5482cc88..89377f9 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_builder.cc
@@ -160,10 +160,11 @@
   }
 
   // TODO(xiaochengh): Optimize if this becomes performance bottleneck.
-  unsigned position = std::distance(mapping_units_.begin(), container_unit);
+  wtf_size_t position = base::checked_cast<wtf_size_t>(
+      std::distance(mapping_units_.begin(), container_unit));
   mapping_units_.EraseAt(position);
   mapping_units_.InsertVector(position, new_units);
-  unsigned new_unit_end = position + new_units.size();
+  wtf_size_t new_unit_end = position + new_units.size();
   while (new_unit_end && new_unit_end < mapping_units_.size() &&
          mapping_units_[new_unit_end - 1].Concatenate(
              mapping_units_[new_unit_end])) {
@@ -201,7 +202,8 @@
       return;
     // When we collapsed multiple spaces, e.g. <b>   </b>.
     mapping_units_.insert(
-        std::distance(mapping_units_.begin(), &unit) + 1,
+        base::checked_cast<wtf_size_t>(
+            std::distance(mapping_units_.begin(), &unit) + 1),
         NGOffsetMappingUnit(NGOffsetMappingUnitType::kCollapsed, layout_text,
                             unit.dom_end_, original_dom_end,
                             unit.text_content_end_, unit.text_content_end_));
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc
index 7f49c910..7a9402e 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping_test.cc
@@ -140,7 +140,7 @@
   }
 
   Vector<NGOffsetMappingUnit> GetFirstLast(const std::string& caret_text) {
-    const auto offset = caret_text.find('|');
+    const unsigned offset = static_cast<unsigned>(caret_text.find('|'));
     return {*GetOffsetMapping().GetFirstMappingUnit(offset),
             *GetOffsetMapping().GetLastMappingUnit(offset)};
   }
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
index 31aebf7..ed30a13 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
@@ -181,7 +181,6 @@
              RuntimeEnabledFeatures::LayoutNGGridEnabled()) {
     CreateAlgorithmAndRun<NGGridLayoutAlgorithm>(params, callback);
   } else if (box.IsLayoutReplaced()) {
-    DCHECK(RuntimeEnabledFeatures::LayoutNGReplacedEnabled());
     CreateAlgorithmAndRun<NGReplacedLayoutAlgorithm>(params, callback);
   } else if (box.IsLayoutNGFieldset()) {
     CreateAlgorithmAndRun<NGFieldsetLayoutAlgorithm>(params, callback);
@@ -692,7 +691,6 @@
       To<NGPhysicalBoxFragment>(layout_result->PhysicalFragment());
 
   if (box_->IsLayoutReplaced()) {
-    DCHECK(RuntimeEnabledFeatures::LayoutNGReplacedEnabled());
     DCHECK(CanUseNewLayout());
     // NG replaced elements are painted with legacy painters. We need to force
     // a legacy "layout" so that paint invalidation flags are updated. But we
@@ -1027,9 +1025,7 @@
   DCHECK(RuntimeEnabledFeatures::LayoutNGEnabled());
   if (box.ForceLegacyLayout())
     return false;
-  return box.IsLayoutNGMixin() ||
-         (box.IsLayoutReplaced() &&
-          RuntimeEnabledFeatures::LayoutNGReplacedEnabled());
+  return box.IsLayoutNGMixin() || box.IsLayoutReplaced();
 }
 
 bool NGBlockNode::CanUseNewLayout() const {
diff --git a/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc
index d248781..27ade09 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_fieldset_layout_algorithm.cc
@@ -149,7 +149,8 @@
   bool has_seen_all_children = false;
   if (const auto* token = BreakToken()) {
     const auto child_tokens = token->ChildBreakTokens();
-    if (wtf_size_t break_token_count = child_tokens.size()) {
+    if (wtf_size_t break_token_count =
+            base::checked_cast<wtf_size_t>(child_tokens.size())) {
       scoped_refptr<const NGBlockBreakToken> child_token =
           To<NGBlockBreakToken>(child_tokens[0]);
       if (child_token) {
diff --git a/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h b/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h
index f86e751..5e83fb5 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h
@@ -59,6 +59,7 @@
   bool HasBlockSize() const { return size_.block_size != kIndefiniteSize; }
 
   void SetIsHiddenForPaint(bool value) { is_hidden_for_paint_ = value; }
+  void SetIsOpaque() { is_opaque_ = true; }
 
   void SetHasCollapsedBorders(bool value) { has_collapsed_borders_ = value; }
 
@@ -91,6 +92,7 @@
   LayoutObject* layout_object_ = nullptr;
   scoped_refptr<const NGBreakToken> break_token_;
   bool is_hidden_for_paint_ = false;
+  bool is_opaque_ = false;
   bool has_collapsed_borders_ = false;
   friend class NGPhysicalFragment;
 };
diff --git a/third_party/blink/renderer/core/layout/ng/ng_ink_overflow.cc b/third_party/blink/renderer/core/layout/ng/ng_ink_overflow.cc
index eb25ccc..6376a5f 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_ink_overflow.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_ink_overflow.cc
@@ -462,7 +462,7 @@
   }
 
   FloatRect accumulated_bound;
-  for (size_t applied_decoration_index = 0;
+  for (wtf_size_t applied_decoration_index = 0;
        applied_decoration_index < decorations.size();
        ++applied_decoration_index) {
     const AppliedTextDecoration& decoration =
diff --git a/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc b/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
index 359fba0..889dff23 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
@@ -371,41 +371,26 @@
     const NGBlockNode& child,
     const NGConstraintSpace& space) {
   const auto& child_style = child.Style();
+  const NGBoxStrut border_padding =
+      ComputeBorders(space, child) + ComputePadding(space, child_style);
+
   MinMaxSizes result;
+  result = ComputeReplacedSize(child, space, border_padding).inline_size;
 
-  if (RuntimeEnabledFeatures::LayoutNGReplacedEnabled()) {
-    const NGBoxStrut border_padding =
-        ComputeBorders(space, child) + ComputePadding(space, child_style);
-    result = ComputeReplacedSize(child, space, border_padding).inline_size;
-
-    if (child_style.LogicalWidth().IsPercentOrCalc() ||
-        child_style.LogicalMaxWidth().IsPercentOrCalc()) {
-      // TODO(ikilpatrick): No browser does this today, but we'd get slightly
-      // better results here if we also considered the min-block size, and
-      // transferred through the aspect-ratio (if available).
-      result.min_size = ResolveMinInlineLength(
-          space, child_style, border_padding,
-          [&](MinMaxSizesType) -> MinMaxSizesResult {
-            // Behave the same as if we couldn't resolve the min-inline size.
-            MinMaxSizes sizes;
-            sizes = border_padding.InlineSum();
-            return {sizes, /* depends_on_block_constraints */ false};
-          },
-          child_style.LogicalMinWidth());
-    }
-  } else {
-    LayoutBox* box = child.GetLayoutBox();
-    bool needs_size_reset = false;
-    if (!box->HasOverrideContainingBlockContentLogicalHeight()) {
-      box->SetOverrideContainingBlockContentLogicalHeight(
-          space.ReplacedPercentageResolutionBlockSize());
-      needs_size_reset = true;
-    }
-
-    result = box->PreferredLogicalWidths();
-
-    if (needs_size_reset)
-      box->ClearOverrideContainingBlockContentSize();
+  if (child_style.LogicalWidth().IsPercentOrCalc() ||
+      child_style.LogicalMaxWidth().IsPercentOrCalc()) {
+    // TODO(ikilpatrick): No browser does this today, but we'd get slightly
+    // better results here if we also considered the min-block size, and
+    // transferred through the aspect-ratio (if available).
+    result.min_size = ResolveMinInlineLength(
+        space, child_style, border_padding,
+        [&](MinMaxSizesType) -> MinMaxSizesResult {
+          // Behave the same as if we couldn't resolve the min-inline size.
+          MinMaxSizes sizes;
+          sizes = border_padding.InlineSum();
+          return {sizes, /* depends_on_block_constraints */ false};
+        },
+        child_style.LogicalMinWidth());
   }
 
   // Replaced elements which have a percentage block-size always depend on
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
index 434aef8..40e1e5b 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -1588,7 +1588,9 @@
         DCHECK_GT(parent_break_token->ChildBreakTokens().size(), 0u);
         parent_break_token->GetMutableForOutOfFlow().ReplaceChildBreakToken(
             new_fragment->BreakToken(),
-            parent_break_token->ChildBreakTokens().size() - 1);
+            base::checked_cast<wtf_size_t>(
+                parent_break_token->ChildBreakTokens().size()) -
+                1);
       }
       column_info.mutable_link->fragment->Release();
       new (&column_info.mutable_link->fragment)
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
index 99958e9e..12bff97 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
@@ -342,6 +342,7 @@
       sub_type_(sub_type),
       style_variant_((unsigned)builder->style_variant_),
       is_hidden_for_paint_(builder->is_hidden_for_paint_),
+      is_opaque_(builder->is_opaque_),
       is_fieldset_container_(false),
       is_table_ng_part_(false),
       is_legacy_layout_root_(false),
@@ -399,6 +400,7 @@
       sub_type_(other.sub_type_),
       style_variant_(other.style_variant_),
       is_hidden_for_paint_(other.is_hidden_for_paint_),
+      is_opaque_(other.is_opaque_),
       is_math_fraction_(other.is_math_fraction_),
       is_math_operator_(other.is_math_operator_),
       base_or_resolved_direction_(other.base_or_resolved_direction_),
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
index f8ca812..e6708ed 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.h
@@ -357,6 +357,11 @@
     return is_hidden_for_paint_ || layout_object_->IsTruncated();
   }
 
+  // This fragment is opaque for layout and paint, as if it does not exist and
+  // does not paint its backgrounds and borders, but it can have regular
+  // children and paint properties such as filters can apply.
+  bool IsOpaque() const { return is_opaque_; }
+
   // Return true if this fragment is monolithic, as far as block fragmentation
   // is concerned.
   bool IsMonolithic() const {
@@ -652,6 +657,7 @@
   const unsigned sub_type_ : 3;       // NGBoxType, NGTextType, or NGLineBoxType
   const unsigned style_variant_ : 2;  // NGStyleVariant
   const unsigned is_hidden_for_paint_ : 1;
+  unsigned is_opaque_ : 1;
   unsigned is_math_fraction_ : 1;
   unsigned is_math_operator_ : 1;
   // base (line box) or resolve (text) direction
diff --git a/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_algorithm.cc
index 8369e16..56633ae 100644
--- a/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/svg/ng_svg_text_layout_algorithm.cc
@@ -507,7 +507,8 @@
     auto* next_anchor =
         std::find_if(result_.begin() + i + 1, result_.end(),
                      [](const auto& info) { return info.anchored_chunk; });
-    wtf_size_t j = std::distance(result_.begin(), next_anchor) - 1;
+    wtf_size_t j = static_cast<wtf_size_t>(
+        std::distance(result_.begin(), next_anchor) - 1);
 
     const auto& text_path_ranges = inline_node_.SvgTextPathRangeList();
     const auto* text_path_iter =
diff --git a/third_party/blink/renderer/core/layout/ng/svg/resolved_text_layout_attributes_iterator.h b/third_party/blink/renderer/core/layout/ng/svg/resolved_text_layout_attributes_iterator.h
index b18dd08..c210a40 100644
--- a/third_party/blink/renderer/core/layout/ng/svg/resolved_text_layout_attributes_iterator.h
+++ b/third_party/blink/renderer/core/layout/ng/svg/resolved_text_layout_attributes_iterator.h
@@ -50,14 +50,14 @@
                             [addressable_index](const auto& pair) {
                               return addressable_index <= pair.first;
                             });
-    index_ = std::distance(resolved_.begin(), it);
+    index_ = static_cast<wtf_size_t>(std::distance(resolved_.begin(), it));
     return AdvanceTo(addressable_index);
   }
 
  private:
   const NGSvgCharacterData default_data_;
   const Vector<std::pair<unsigned, NGSvgCharacterData>>& resolved_;
-  unsigned index_ = 0u;
+  wtf_size_t index_ = 0u;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/loader/cookie_jar.cc b/third_party/blink/renderer/core/loader/cookie_jar.cc
index c260d0f..a7ddf0b 100644
--- a/third_party/blink/renderer/core/loader/cookie_jar.cc
+++ b/third_party/blink/renderer/core/loader/cookie_jar.cc
@@ -76,6 +76,14 @@
   return cookies_enabled;
 }
 
+void CookieJar::SetCookieManager(
+    mojo::PendingRemote<network::mojom::blink::RestrictedCookieManager>
+        cookie_manager) {
+  backend_.reset();
+  backend_.Bind(std::move(cookie_manager),
+                document_->GetTaskRunner(TaskType::kInternalDefault));
+}
+
 bool CookieJar::RequestRestrictedCookieManagerIfNeeded() {
   if (!backend_.is_bound() || !backend_.is_connected()) {
     backend_.reset();
diff --git a/third_party/blink/renderer/core/loader/cookie_jar.h b/third_party/blink/renderer/core/loader/cookie_jar.h
index 204c9152..4d1861d9 100644
--- a/third_party/blink/renderer/core/loader/cookie_jar.h
+++ b/third_party/blink/renderer/core/loader/cookie_jar.h
@@ -23,6 +23,9 @@
   void SetCookie(const String& value);
   String Cookies();
   bool CookiesEnabled();
+  void SetCookieManager(
+      mojo::PendingRemote<network::mojom::blink::RestrictedCookieManager>
+          cookie_manager);
 
  private:
   bool RequestRestrictedCookieManagerIfNeeded();
diff --git a/third_party/blink/renderer/core/mathml/mathml_operator_element.cc b/third_party/blink/renderer/core/mathml/mathml_operator_element.cc
index e9d9555..0ce2db4 100644
--- a/third_party/blink/renderer/core/mathml/mathml_operator_element.cc
+++ b/third_party/blink/renderer/core/mathml/mathml_operator_element.cc
@@ -29,7 +29,7 @@
     return kNonCharacter;
 
   UChar32 character;
-  size_t offset = 0;
+  unsigned offset = 0;
   U16_NEXT(text_content, offset, content_length, character);
   return character;
 }
diff --git a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc
index 2c27821..5e845ba 100644
--- a/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc
+++ b/third_party/blink/renderer/core/mobile_metrics/mobile_friendliness_checker.cc
@@ -63,10 +63,10 @@
 // targets which are too close.
 class FenwickTree {
  public:
-  explicit FenwickTree(size_t n) : tree(n + 1) {}
+  explicit FenwickTree(wtf_size_t n) : tree(n + 1) {}
 
   // Returns prefix sum of the array from 0 to |index|.
-  int sum(size_t index) const {
+  int sum(wtf_size_t index) const {
     int sum = 0;
     for (index += 1; 0 < index; index -= index & -index)
       sum += tree[index];
@@ -74,7 +74,7 @@
   }
 
   // Adds |val| at |index| of the array.
-  void add(size_t index, int val) {
+  void add(wtf_size_t index, int val) {
     for (index += 1; index <= tree.size() - 1; index += index & -index)
       tree[index] += val;
   }
@@ -92,37 +92,42 @@
 struct EdgeOrCenter {
   enum Type : int { kStartEdge = 0, kCenter = 1, kEndEdge = 2 } type;
 
+  union PositionOrIndexUnion {
+    int position;
+    wtf_size_t index;
+  };
+
   union EdgeOrCenterUnion {
     // Valid iff |type| is Edge.
     struct Edge {
-      int left;
-      int right;
+      PositionOrIndexUnion left;
+      PositionOrIndexUnion right;
     } edge;
 
     // Valid iff |type| is Center.
-    int center;
+    PositionOrIndexUnion center;
   } v;
 
   static EdgeOrCenter StartEdge(int left, int right) {
     EdgeOrCenter edge;
     edge.type = EdgeOrCenter::kStartEdge;
-    edge.v.edge.left = left;
-    edge.v.edge.right = right;
+    edge.v.edge.left.position = left;
+    edge.v.edge.right.position = right;
     return edge;
   }
 
   static EdgeOrCenter EndEdge(int left, int right) {
     EdgeOrCenter edge;
     edge.type = EdgeOrCenter::kEndEdge;
-    edge.v.edge.left = left;
-    edge.v.edge.right = right;
+    edge.v.edge.left.position = left;
+    edge.v.edge.right.position = right;
     return edge;
   }
 
   static EdgeOrCenter Center(int center) {
     EdgeOrCenter edge;
     edge.type = EdgeOrCenter::kCenter;
-    edge.v.center = center;
+    edge.v.center.position = center;
     return edge;
   }
 };
@@ -202,21 +207,21 @@
     switch (vertex.type) {
       case EdgeOrCenter::kStartEdge:
       case EdgeOrCenter::kEndEdge: {
-        vertex.v.edge.left =
+        vertex.v.edge.left.index = static_cast<wtf_size_t>(
             std::distance(positions.begin(),
                           std::lower_bound(positions.begin(), positions.end(),
-                                           vertex.v.edge.left));
-        vertex.v.edge.right =
+                                           vertex.v.edge.left.position)));
+        vertex.v.edge.right.index = static_cast<wtf_size_t>(
             std::distance(positions.begin(),
                           std::lower_bound(positions.begin(), positions.end(),
-                                           vertex.v.edge.right));
+                                           vertex.v.edge.right.position)));
         break;
       }
       case EdgeOrCenter::kCenter: {
-        vertex.v.center =
+        vertex.v.center.index = static_cast<wtf_size_t>(
             std::distance(positions.begin(),
                           std::lower_bound(positions.begin(), positions.end(),
-                                           vertex.v.center));
+                                           vertex.v.center.position)));
         break;
       }
     }
@@ -229,7 +234,7 @@
 // rightmost_position: Rightmost x position in all vertices.
 // Returns bad tap targets count.
 // Returns -1 if time limit exceeded.
-int CountBadTapTargets(int rightmost_position,
+int CountBadTapTargets(wtf_size_t rightmost_position,
                        const Vector<std::pair<int, EdgeOrCenter>>& vertices,
                        const base::Time& started) {
   FenwickTree tree(rightmost_position);
@@ -239,20 +244,20 @@
     switch (vertex.type) {
       case EdgeOrCenter::kStartEdge: {
         // Tap region begins.
-        tree.add(vertex.v.edge.left, 1);
-        tree.add(vertex.v.edge.right, -1);
+        tree.add(vertex.v.edge.left.index, 1);
+        tree.add(vertex.v.edge.right.index, -1);
         break;
       }
       case EdgeOrCenter::kEndEdge: {
         // Tap region ends.
-        tree.add(vertex.v.edge.left, -1);
-        tree.add(vertex.v.edge.right, 1);
+        tree.add(vertex.v.edge.left.index, -1);
+        tree.add(vertex.v.edge.right.index, 1);
         break;
       }
       case EdgeOrCenter::kCenter: {
         // Iff the center of a tap target is included other than itself, it is a
         // Bad Target.
-        if (tree.sum(vertex.v.center) > 1)
+        if (tree.sum(vertex.v.center.index) > 1)
           bad_tap_targets++;
         break;
       }
diff --git a/third_party/blink/renderer/core/origin_trials/origin_trial_context_test.cc b/third_party/blink/renderer/core/origin_trials/origin_trial_context_test.cc
index f004567..db146af 100644
--- a/third_party/blink/renderer/core/origin_trials/origin_trial_context_test.cc
+++ b/third_party/blink/renderer/core/origin_trials/origin_trial_context_test.cc
@@ -574,7 +574,7 @@
     EXPECT_EQ(trial_result->value.token_results.size(),
               expected_token_results.size());
 
-    for (size_t i = 0; i < expected_token_results.size(); i++) {
+    for (wtf_size_t i = 0; i < expected_token_results.size(); i++) {
       const auto& expected_token_result = expected_token_results[i];
       const auto& actual_token_result = trial_result->value.token_results[i];
 
diff --git a/third_party/blink/renderer/core/page/named_pages_mapper.cc b/third_party/blink/renderer/core/page/named_pages_mapper.cc
index 060d966..d77900d 100644
--- a/third_party/blink/renderer/core/page/named_pages_mapper.cc
+++ b/third_party/blink/renderer/core/page/named_pages_mapper.cc
@@ -20,7 +20,7 @@
         break;
     }
 
-    entries_.Shrink(entries_.rend() - prev_it);
+    entries_.Shrink(static_cast<wtf_size_t>(entries_.rend() - prev_it));
 
     // Terminate the previous entry (now that we know its last page index)
     // before adding the new entry.
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc
index a7f48f9a..984f9e6 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc
@@ -34,8 +34,8 @@
                         Vector<TextFragmentSelector>* out_selectors) {
   DCHECK(out_selectors);
 
-  size_t start_pos = 0;
-  size_t end_pos = 0;
+  wtf_size_t start_pos = 0;
+  wtf_size_t end_pos = 0;
   while (end_pos != kNotFound) {
     if (fragment_directive.Find(kTextFragmentIdentifierPrefix, start_pos) !=
         start_pos) {
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector.cc
index 787fb1b..4c6d208 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_selector.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_selector.cc
@@ -16,8 +16,8 @@
 // return an empty string to indicate no prefix/suffix was specified or it
 // was malformed and should be ignored.
 String ExtractPrefix(String* target_text) {
-  size_t comma_pos = target_text->find(',');
-  size_t hyphen_pos = target_text->find('-');
+  wtf_size_t comma_pos = target_text->find(',');
+  wtf_size_t hyphen_pos = target_text->find('-');
 
   if (hyphen_pos != kNotFound && hyphen_pos == comma_pos - 1) {
     String prefix = target_text->Substring(0, hyphen_pos);
@@ -28,8 +28,8 @@
 }
 
 String ExtractSuffix(String* target_text) {
-  size_t last_comma_pos = target_text->ReverseFind(',');
-  size_t last_hyphen_pos = target_text->ReverseFind('-');
+  wtf_size_t last_comma_pos = target_text->ReverseFind(',');
+  wtf_size_t last_hyphen_pos = target_text->ReverseFind('-');
 
   if (last_hyphen_pos != kNotFound && last_hyphen_pos == last_comma_pos + 1) {
     String suffix = target_text->Substring(last_hyphen_pos + 1);
@@ -49,10 +49,10 @@
   String prefix = ExtractPrefix(&target_text);
   String suffix = ExtractSuffix(&target_text);
 
-  size_t comma_pos = target_text.find(',');
+  wtf_size_t comma_pos = target_text.find(',');
 
   // If there are more commas, this is an invalid text fragment selector.
-  size_t next_comma_pos = target_text.find(',', comma_pos + 1);
+  wtf_size_t next_comma_pos = target_text.find(',', comma_pos + 1);
   if (next_comma_pos != kNotFound)
     return TextFragmentSelector(kInvalid);
 
diff --git a/third_party/blink/renderer/core/paint/link_highlight_impl.cc b/third_party/blink/renderer/core/paint/link_highlight_impl.cc
index 2f3de9d..cadd806a 100644
--- a/third_party/blink/renderer/core/paint/link_highlight_impl.cc
+++ b/third_party/blink/renderer/core/paint/link_highlight_impl.cc
@@ -244,7 +244,7 @@
     return;
   DCHECK(!object->GetFrameView()->ShouldThrottleRendering());
 
-  size_t fragment_count = 0;
+  wtf_size_t fragment_count = 0;
   for (const auto* fragment = &object->FirstFragment(); fragment;
        fragment = fragment->NextFragment())
     ++fragment_count;
diff --git a/third_party/blink/renderer/core/paint/link_highlight_impl.h b/third_party/blink/renderer/core/paint/link_highlight_impl.h
index 3f9b2da..34cd91fa 100644
--- a/third_party/blink/renderer/core/paint/link_highlight_impl.h
+++ b/third_party/blink/renderer/core/paint/link_highlight_impl.h
@@ -79,7 +79,7 @@
   void Paint(GraphicsContext&);
 
   wtf_size_t FragmentCountForTesting() const { return fragments_.size(); }
-  cc::PictureLayer* LayerForTesting(size_t index) const {
+  cc::PictureLayer* LayerForTesting(wtf_size_t index) const {
     return fragments_[index].Layer();
   }
 
diff --git a/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.cc
index acddffe..44fcc33 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_inline_box_fragment_painter.cc
@@ -68,7 +68,8 @@
     const PaintInfo& paint_info,
     const PhysicalOffset& paint_offset) {
   DCHECK(paint_info.phase == PaintPhase::kForeground);
-  if (inline_box_fragment_.Style().Visibility() != EVisibility::kVisible)
+  if (inline_box_fragment_.Style().Visibility() != EVisibility::kVisible ||
+      inline_box_fragment_.IsOpaque())
     return;
 
   // You can use p::first-line to specify a background. If so, the direct child
diff --git a/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.h b/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.h
index d1da3ab..a6d5c35 100644
--- a/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.h
+++ b/third_party/blink/renderer/core/paint/paint_and_raster_invalidation_test.h
@@ -19,7 +19,7 @@
             MakeGarbageCollected<SingleChildLocalFrameClient>()) {}
 
  protected:
-  ContentLayerClientImpl* GetContentLayerClient(size_t index = 0) const {
+  ContentLayerClientImpl* GetContentLayerClient(wtf_size_t index = 0) const {
     DCHECK(RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
     const auto& clients = GetDocument()
                               .View()
@@ -29,7 +29,7 @@
   }
 
   const RasterInvalidationTracking* GetRasterInvalidationTracking(
-      size_t index = 0) const {
+      wtf_size_t index = 0) const {
     if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
       if (auto* client = GetContentLayerClient(index))
         return client->GetRasterInvalidator().GetTracking();
diff --git a/third_party/blink/renderer/core/paint/paint_timing.cc b/third_party/blink/renderer/core/paint/paint_timing.cc
index 76d2c2d..1c5fddd 100644
--- a/third_party/blink/renderer/core/paint/paint_timing.cc
+++ b/third_party/blink/renderer/core/paint/paint_timing.cc
@@ -50,7 +50,7 @@
  public:
   RecodingTimeAfterBackForwardCacheRestoreFrameCallback(
       PaintTiming* paint_timing,
-      size_t record_index)
+      wtf_size_t record_index)
       : paint_timing_(paint_timing), record_index_(record_index) {}
   ~RecodingTimeAfterBackForwardCacheRestoreFrameCallback() override = default;
 
@@ -82,7 +82,7 @@
 
  private:
   Member<PaintTiming> paint_timing_;
-  const size_t record_index_;
+  const wtf_size_t record_index_;
   size_t count_ = 0;
 };
 
@@ -275,7 +275,7 @@
 
 void PaintTiming::
     RegisterNotifyFirstPaintAfterBackForwardCacheRestorePresentationTime(
-        size_t index) {
+        wtf_size_t index) {
   RegisterNotifyPresentationTime(CrossThreadBindOnce(
       &PaintTiming::
           ReportFirstPaintAfterBackForwardCacheRestorePresentationTime,
@@ -327,7 +327,7 @@
 }
 
 void PaintTiming::ReportFirstPaintAfterBackForwardCacheRestorePresentationTime(
-    size_t index,
+    wtf_size_t index,
     WebSwapResult result,
     base::TimeTicks timestamp) {
   DCHECK(IsMainThread());
@@ -389,7 +389,7 @@
 
 void PaintTiming::SetFirstPaintAfterBackForwardCacheRestorePresentation(
     base::TimeTicks stamp,
-    size_t index) {
+    wtf_size_t index) {
   // The elements are allocated when the page is restored from the cache.
   DCHECK_GE(first_paints_after_back_forward_cache_restore_presentation_.size(),
             index);
@@ -400,7 +400,7 @@
 }
 
 void PaintTiming::SetRequestAnimationFrameAfterBackForwardCacheRestore(
-    size_t index,
+    wtf_size_t index,
     size_t count) {
   auto now = clock_->NowTicks();
 
@@ -417,7 +417,7 @@
 void PaintTiming::OnRestoredFromBackForwardCache() {
   // Allocate the last element with 0, which indicates that the first paint
   // after this navigation doesn't happen yet.
-  size_t index =
+  wtf_size_t index =
       first_paints_after_back_forward_cache_restore_presentation_.size();
   DCHECK_EQ(index,
             request_animation_frames_after_back_forward_cache_restore_.size());
diff --git a/third_party/blink/renderer/core/paint/paint_timing.h b/third_party/blink/renderer/core/paint/paint_timing.h
index bd76380..7a44f1d7 100644
--- a/third_party/blink/renderer/core/paint/paint_timing.h
+++ b/third_party/blink/renderer/core/paint/paint_timing.h
@@ -146,7 +146,7 @@
                               WebSwapResult,
                               base::TimeTicks timestamp);
   void ReportFirstPaintAfterBackForwardCacheRestorePresentationTime(
-      size_t index,
+      wtf_size_t index,
       WebSwapResult,
       base::TimeTicks timestamp);
 
@@ -189,13 +189,13 @@
   // index to avoid confusing the data from different navigations.
   void SetFirstPaintAfterBackForwardCacheRestorePresentation(
       base::TimeTicks stamp,
-      size_t index);
-  void SetRequestAnimationFrameAfterBackForwardCacheRestore(size_t index,
+      wtf_size_t index);
+  void SetRequestAnimationFrameAfterBackForwardCacheRestore(wtf_size_t index,
                                                             size_t count);
 
   void RegisterNotifyPresentationTime(PaintEvent);
   void RegisterNotifyFirstPaintAfterBackForwardCacheRestorePresentationTime(
-      size_t index);
+      wtf_size_t index);
 
   base::TimeTicks FirstPaintRendered() const { return first_paint_; }
 
diff --git a/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc b/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc
index 3a5af3c8..14a8233b 100644
--- a/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc
+++ b/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc
@@ -85,7 +85,8 @@
 
   wtf_size_t CountRankingSetSize() {
     DCHECK(GetTextPaintTimingDetector());
-    return GetLargestTextPaintManager()->size_ordered_set_.size();
+    return static_cast<wtf_size_t>(
+        GetLargestTextPaintManager()->size_ordered_set_.size());
   }
 
   wtf_size_t CountInvisibleTexts() {
diff --git a/third_party/blink/renderer/core/paint/text_painter.cc b/third_party/blink/renderer/core/paint/text_painter.cc
index 6468ee1..77e220b7 100644
--- a/third_party/blink/renderer/core/paint/text_painter.cc
+++ b/third_party/blink/renderer/core/paint/text_painter.cc
@@ -82,7 +82,7 @@
     underline_position = ResolvedUnderlinePosition::kUnder;
   }
 
-  for (size_t applied_decoration_index = 0;
+  for (wtf_size_t applied_decoration_index = 0;
        applied_decoration_index < decorations.size();
        ++applied_decoration_index) {
     const AppliedTextDecoration& decoration =
@@ -158,7 +158,7 @@
   if (combined_text_)
     context.ConcatCTM(Rotation(text_frame_rect_, kClockwise));
 
-  for (size_t applied_decoration_index = 0;
+  for (wtf_size_t applied_decoration_index = 0;
        applied_decoration_index < decorations.size();
        ++applied_decoration_index) {
     const AppliedTextDecoration& decoration =
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 c9770c83..1810737 100644
--- a/third_party/blink/renderer/core/paint/text_painter_base.cc
+++ b/third_party/blink/renderer/core/paint/text_painter_base.cc
@@ -248,7 +248,7 @@
     underline_position = ResolvedUnderlinePosition::kUnder;
   }
 
-  for (size_t applied_decoration_index = 0;
+  for (wtf_size_t applied_decoration_index = 0;
        applied_decoration_index < decorations.size();
        ++applied_decoration_index) {
     const AppliedTextDecoration& decoration =
@@ -317,7 +317,7 @@
   GraphicsContextStateSaver state_saver(context);
   UpdateGraphicsContext(context, text_style, horizontal_, state_saver);
 
-  for (size_t applied_decoration_index = 0;
+  for (wtf_size_t applied_decoration_index = 0;
        applied_decoration_index < decorations.size();
        ++applied_decoration_index) {
     const AppliedTextDecoration& decoration =
diff --git a/third_party/blink/renderer/core/permissions_policy/document_policy_parser_test.cc b/third_party/blink/renderer/core/permissions_policy/document_policy_parser_test.cc
index 6a9e70e..ceb621c 100644
--- a/third_party/blink/renderer/core/permissions_policy/document_policy_parser_test.cc
+++ b/third_party/blink/renderer/core/permissions_policy/document_policy_parser_test.cc
@@ -476,7 +476,7 @@
 
   ASSERT_EQ(actual_messages.size(), expected_messages.size())
       << "message count should match";
-  for (size_t i = 0; i < expected_messages.size(); ++i) {
+  for (wtf_size_t i = 0; i < expected_messages.size(); ++i) {
     const auto& actual_message = actual_messages[i];
     const MessageForTest& expected_message = expected_messages[i];
 
diff --git a/third_party/blink/renderer/core/permissions_policy/permissions_policy_parser.cc b/third_party/blink/renderer/core/permissions_policy/permissions_policy_parser.cc
index 1ad8188..e30b290 100644
--- a/third_party/blink/renderer/core/permissions_policy/permissions_policy_parser.cc
+++ b/third_party/blink/renderer/core/permissions_policy/permissions_policy_parser.cc
@@ -50,7 +50,7 @@
                   1) {}
 
   bool Observed(mojom::blink::PermissionsPolicyFeature feature) {
-    size_t feature_index = static_cast<size_t>(feature);
+    wtf_size_t feature_index = static_cast<wtf_size_t>(feature);
     if (policies_[feature_index])
       return true;
     policies_[feature_index] = true;
diff --git a/third_party/blink/renderer/core/permissions_policy/permissions_policy_test.cc b/third_party/blink/renderer/core/permissions_policy/permissions_policy_test.cc
index fa60af1..617dbe8 100644
--- a/third_party/blink/renderer/core/permissions_policy/permissions_policy_test.cc
+++ b/third_party/blink/renderer/core/permissions_policy/permissions_policy_test.cc
@@ -200,7 +200,7 @@
       const Vector<PolicyParserMessageBuffer::Message>& actual,
       const std::vector<String> expected) {
     ASSERT_EQ(actual.size(), expected.size());
-    for (size_t i = 0; i < actual.size(); ++i) {
+    for (wtf_size_t i = 0; i < actual.size(); ++i) {
       EXPECT_EQ(actual[i].content, expected[i]);
     }
   }
diff --git a/third_party/blink/renderer/core/style/computed_style_diff_functions.json5 b/third_party/blink/renderer/core/style/computed_style_diff_functions.json5
index e49b726..76cfe67 100644
--- a/third_party/blink/renderer/core/style/computed_style_diff_functions.json5
+++ b/third_party/blink/renderer/core/style/computed_style_diff_functions.json5
@@ -281,8 +281,8 @@
             field_dependencies: ["color-scheme"]
           },
           {
-            method: "AccentColor()",
-            field_dependencies: ["accent-color"]
+            method: "AccentColorResolved()",
+            field_dependencies: ["accent-color", "color"]
           }
         ],
         predicates_to_test: [
diff --git a/third_party/blink/renderer/core/svg/animation/svg_smil_element.cc b/third_party/blink/renderer/core/svg/animation/svg_smil_element.cc
index 8a6f613..64624d6 100644
--- a/third_party/blink/renderer/core/svg/animation/svg_smil_element.cc
+++ b/third_party/blink/renderer/core/svg/animation/svg_smil_element.cc
@@ -85,7 +85,9 @@
     if (position->Origin() == origin)
       return;
   }
-  instance_times_.insert(position - instance_times_.begin(), time_with_origin);
+  instance_times_.insert(
+      static_cast<wtf_size_t>(position - instance_times_.begin()),
+      time_with_origin);
   AddOrigin(origin);
 }
 
@@ -97,7 +99,8 @@
                      [origin](const SMILTimeWithOrigin& instance_time) {
                        return instance_time.Origin() == origin;
                      });
-  instance_times_.Shrink(tail - instance_times_.begin());
+  instance_times_.Shrink(
+      static_cast<wtf_size_t>(tail - instance_times_.begin()));
   ClearOrigin(origin);
 }
 
diff --git a/third_party/blink/renderer/core/svg/svg_angle.cc b/third_party/blink/renderer/core/svg/svg_angle.cc
index d4711f3..51bd3a9 100644
--- a/third_party/blink/renderer/core/svg/svg_angle.cc
+++ b/third_party/blink/renderer/core/svg/svg_angle.cc
@@ -187,7 +187,7 @@
   }
   StringBuilder builder;
   builder.AppendNumber(value_in_specified_units_);
-  builder.Append(unit_string, strlen(unit_string));
+  builder.Append(unit_string, static_cast<unsigned>(strlen(unit_string)));
   return builder.ToString();
 }
 
diff --git a/third_party/blink/renderer/core/svg/svg_fe_convolve_matrix_element.cc b/third_party/blink/renderer/core/svg/svg_fe_convolve_matrix_element.cc
index ebc6145..12d4fed 100644
--- a/third_party/blink/renderer/core/svg/svg_fe_convolve_matrix_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_fe_convolve_matrix_element.cc
@@ -180,8 +180,8 @@
     return divisor_->CurrentValue()->Value();
   float divisor_value = 0;
   SVGNumberList* kernel_matrix = kernel_matrix_->CurrentValue();
-  size_t kernel_matrix_size = kernel_matrix->length();
-  for (size_t i = 0; i < kernel_matrix_size; ++i)
+  uint32_t kernel_matrix_size = kernel_matrix->length();
+  for (uint32_t i = 0; i < kernel_matrix_size; ++i)
     divisor_value += kernel_matrix->at(i)->Value();
   return divisor_value ? divisor_value : 1;
 }
diff --git a/third_party/blink/renderer/core/testing/sim/sim_request.cc b/third_party/blink/renderer/core/testing/sim/sim_request.cc
index b5a1bd0..2346cc36 100644
--- a/third_party/blink/renderer/core/testing/sim/sim_request.cc
+++ b/third_party/blink/renderer/core/testing/sim/sim_request.cc
@@ -82,7 +82,7 @@
   if (navigation_body_loader_)
     navigation_body_loader_->Write(data.data(), data.size());
   else
-    client_->DidReceiveData(data.data(), data.size());
+    client_->DidReceiveData(data.data(), base::checked_cast<int>(data.size()));
 }
 
 void SimRequestBase::Finish(bool body_loader_finished) {
diff --git a/third_party/blink/renderer/core/timing/performance_timing.cc b/third_party/blink/renderer/core/timing/performance_timing.cc
index d5acc15..b39ae6f 100644
--- a/third_party/blink/renderer/core/timing/performance_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_timing.cc
@@ -354,12 +354,12 @@
 
   WTF::Vector<BackForwardCacheRestoreTiming> restore_timings(
       navigation_starts.size());
-  for (size_t i = 0; i < restore_timings.size(); i++) {
+  for (wtf_size_t i = 0; i < restore_timings.size(); i++) {
     restore_timings[i].navigation_start =
         MonotonicTimeToIntegerMilliseconds(navigation_starts[i]);
     restore_timings[i].first_paint =
         MonotonicTimeToIntegerMilliseconds(first_paints[i]);
-    for (size_t j = 0; j < request_animation_frames[i].size(); j++) {
+    for (wtf_size_t j = 0; j < request_animation_frames[i].size(); j++) {
       restore_timings[i].request_animation_frames[j] =
           MonotonicTimeToIntegerMilliseconds(request_animation_frames[i][j]);
     }
diff --git a/third_party/blink/renderer/core/trustedtypes/trusted_types_util.cc b/third_party/blink/renderer/core/trustedtypes/trusted_types_util.cc
index 86a9530..4d33e88 100644
--- a/third_party/blink/renderer/core/trustedtypes/trusted_types_util.cc
+++ b/third_party/blink/renderer/core/trustedtypes/trusted_types_util.cc
@@ -188,7 +188,8 @@
       execution_context->GetContentSecurityPolicy()
           ->AllowTrustedTypeAssignmentFailure(
               GetMessage(kind),
-              prefix == "Function" ? value.Substring(strlen(kAnonymousPrefix))
+              prefix == "Function" ? value.Substring(static_cast<wtf_size_t>(
+                                         strlen(kAnonymousPrefix)))
                                    : value,
               prefix);
 
diff --git a/third_party/blink/renderer/core/url/url_search_params.cc b/third_party/blink/renderer/core/url/url_search_params.cc
index 30598f9..e3a1c16 100644
--- a/third_party/blink/renderer/core/url/url_search_params.cc
+++ b/third_party/blink/renderer/core/url/url_search_params.cc
@@ -44,7 +44,7 @@
 
  private:
   Member<URLSearchParams> params_;
-  size_t current_;
+  wtf_size_t current_;
 };
 
 bool CompareParams(const std::pair<String, String>& a,
@@ -143,15 +143,15 @@
 void URLSearchParams::SetInputWithoutUpdate(const String& query_string) {
   params_.clear();
 
-  size_t start = 0;
-  size_t query_string_length = query_string.length();
+  wtf_size_t start = 0;
+  wtf_size_t query_string_length = query_string.length();
   while (start < query_string_length) {
-    size_t name_start = start;
-    size_t name_value_end = query_string.find('&', start);
+    wtf_size_t name_start = start;
+    wtf_size_t name_value_end = query_string.find('&', start);
     if (name_value_end == kNotFound)
       name_value_end = query_string_length;
     if (name_value_end > start) {
-      size_t end_of_name = query_string.find('=', start);
+      wtf_size_t end_of_name = query_string.find('=', start);
       if (end_of_name == kNotFound || end_of_name > name_value_end)
         end_of_name = name_value_end;
       String name = DecodeString(
@@ -185,7 +185,7 @@
 }
 
 void URLSearchParams::deleteAllWithName(const String& name) {
-  for (size_t i = 0; i < params_.size();) {
+  for (wtf_size_t i = 0; i < params_.size();) {
     if (params_[i].first == name)
       params_.EraseAt(i);
     else
@@ -221,7 +221,7 @@
 
 void URLSearchParams::set(const String& name, const String& value) {
   bool found_match = false;
-  for (size_t i = 0; i < params_.size();) {
+  for (wtf_size_t i = 0; i < params_.size();) {
     // If there are any name-value whose name is 'name', set
     // the value of the first such name-value pair to 'value'
     // and remove the others.
diff --git a/third_party/blink/renderer/core/xml/parser/xml_document_parser.cc b/third_party/blink/renderer/core/xml/parser/xml_document_parser.cc
index 76324bb..23d27698 100644
--- a/third_party/blink/renderer/core/xml/parser/xml_document_parser.cc
+++ b/third_party/blink/renderer/core/xml/parser/xml_document_parser.cc
@@ -707,8 +707,8 @@
   InitializeLibXMLIfNecessary();
 
   // appendFragmentSource() checks that the length doesn't overflow an int.
-  xmlParserCtxtPtr parser =
-      xmlCreateMemoryParserCtxt(chunk.c_str(), chunk.length());
+  xmlParserCtxtPtr parser = xmlCreateMemoryParserCtxt(
+      chunk.c_str(), base::checked_cast<int>(chunk.length()));
 
   if (!parser)
     return nullptr;
diff --git a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
index 651f9c4f..16d8790 100644
--- a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
+++ b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
@@ -134,7 +134,7 @@
                             unsigned& charset_len) {
   charset_len = 0;
 
-  size_t pos = charset_pos;
+  unsigned pos = charset_pos;
   unsigned length = media_type.length();
 
   while (pos < length) {
@@ -944,7 +944,8 @@
   scoped_refptr<EncodedFormData> http_body;
 
   if (AreMethodAndURLValidForSend()) {
-    http_body = EncodedFormData::Create(data, length);
+    http_body =
+        EncodedFormData::Create(data, base::checked_cast<wtf_size_t>(length));
   }
 
   CreateRequest(std::move(http_body), exception_state);
diff --git a/third_party/blink/renderer/modules/BUILD.gn b/third_party/blink/renderer/modules/BUILD.gn
index 8c1ca06f..012442d 100644
--- a/third_party/blink/renderer/modules/BUILD.gn
+++ b/third_party/blink/renderer/modules/BUILD.gn
@@ -40,8 +40,6 @@
   configs += [
     ":modules_implementation",
 
-    # TODO(crbug.com/879657): Fix size_t to int truncations.
-    "//build/config/compiler:no_shorten_64_warnings",
     "//third_party/blink/renderer:config",
     "//third_party/blink/renderer:non_test_config",
     "//third_party/blink/renderer:inside_blink",
diff --git a/third_party/blink/renderer/modules/accessibility/testing/accessibility_selection_test.cc b/third_party/blink/renderer/modules/accessibility/testing/accessibility_selection_test.cc
index d85702c..402e1a0 100644
--- a/third_party/blink/renderer/modules/accessibility/testing/accessibility_selection_test.cc
+++ b/third_party/blink/renderer/modules/accessibility/testing/accessibility_selection_test.cc
@@ -235,7 +235,7 @@
       return ax_selections;
     }
 
-    for (size_t i = 0; i < foci_->size(); ++i) {
+    for (wtf_size_t i = 0; i < foci_->size(); ++i) {
       DCHECK(anchors_->at(i).first);
       const Position base(*anchors_->at(i).first, anchors_->at(i).second);
       const auto ax_base = AXPosition::FromPosition(base);
diff --git a/third_party/blink/renderer/modules/canvas/BUILD.gn b/third_party/blink/renderer/modules/canvas/BUILD.gn
index ea82c89..807fd66 100644
--- a/third_party/blink/renderer/modules/canvas/BUILD.gn
+++ b/third_party/blink/renderer/modules/canvas/BUILD.gn
@@ -37,6 +37,7 @@
     "canvas2d/hit_region.h",
     "canvas2d/identifiability_study_helper.cc",
     "canvas2d/identifiability_study_helper.h",
+    "canvas2d/path_2d.cc",
     "canvas2d/path_2d.h",
     "htmlcanvas/canvas_context_creation_attributes_helpers.cc",
     "htmlcanvas/canvas_context_creation_attributes_helpers.h",
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
index a6cdd8c..9224cb5 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
@@ -12,6 +12,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/numerics/checked_math.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/privacy_budget/identifiability_metrics.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_union_canvasfilter_string.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_union_csscolorvalue_canvasgradient_canvaspattern_string.h"
 #include "third_party/blink/renderer/core/css/cssom/css_color_value.h"
@@ -94,6 +95,9 @@
 void BaseRenderingContext2D::save() {
   if (isContextLost())
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kSave);
+  }
 
   ValidateStateStack();
 
@@ -118,6 +122,9 @@
   if (isContextLost())
     return;
 
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kRestore);
+  }
   ValidateStateStack();
 
   DCHECK_GT(state_stack_.size(), static_cast<WTF::wtf_size_t>(layer_count_));
@@ -141,6 +148,8 @@
 void BaseRenderingContext2D::beginLayer() {
   if (isContextLost())
     return;
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
 
   ValidateStateStack();
 
@@ -160,23 +169,48 @@
   GetState().FillStyle()->ApplyToFlags(flags);
   flags.setColor(GetState().FillStyle()->PaintColor());
   flags.setBlendMode(GetState().GlobalComposite());
-  flags.setAlpha(globalAlpha() * 255);
-  // TODO(crbug.com/1231277): Add filter and shadows to flags.
+  flags.setImageFilter(StateGetFilter());
+  // TODO(crbug.com/1235854): Add shadows to flags.
 
-  if (canvas)
-    canvas->saveLayer(nullptr, &flags);
+  if (canvas) {
+    if (StateGetFilter() && globalAlpha() != 1) {
+      // We have to save the filter before alpha.
+      GetState().setRestoreToCount(canvas->getSaveCount());
+      canvas->saveLayer(nullptr, &flags);
+
+      // Push to state stack to keep stack size up to date.
+      state_stack_.push_back(
+          MakeGarbageCollected<CanvasRenderingContext2DState>(
+              GetState(), CanvasRenderingContext2DState::kDontCopyClipList,
+              CanvasRenderingContext2DState::SaveType::kBeginEndLayer));
+
+      PaintFlags second_layer_flags;
+      GetState().FillStyle()->ApplyToFlags(second_layer_flags);
+      second_layer_flags.setColor(GetState().FillStyle()->PaintColor());
+      second_layer_flags.setAlpha(globalAlpha() * 255);
+      canvas->saveLayer(nullptr, &second_layer_flags);
+    } else {
+      GetState().setRestoreToCount(absl::nullopt);
+      flags.setAlpha(globalAlpha() * 255);
+      canvas->saveLayer(nullptr, &flags);
+    }
+  }
 
   ValidateStateStack();
 
   // Reset compositing attributes.
   setGlobalAlpha(1.0);
   setGlobalCompositeOperation("source-over");
-  // TODO(crbug.com/1231277): Reset filter.
+  V8UnionCanvasFilterOrString* filter =
+      MakeGarbageCollected<V8UnionCanvasFilterOrString>("none");
+  setFilter(GetCanvasRenderingContextHost()->GetTopExecutionContext(), filter);
 }
 
 void BaseRenderingContext2D::endLayer() {
   if (isContextLost())
     return;
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
 
   ValidateStateStack();
 
@@ -201,7 +235,10 @@
 }
 
 void BaseRenderingContext2D::PopAndRestore() {
-  DCHECK_GT(state_stack_.size(), 1u);
+  if (state_stack_.size() <= 1) {
+    NOTREACHED();
+    return;
+  }
 
   state_stack_.pop_back();
   state_stack_.back()->ClearResolvedFilter();
@@ -212,8 +249,20 @@
 
   cc::PaintCanvas* c = GetOrCreatePaintCanvas();
 
-  if (c)
-    c->restore();
+  if (c) {
+    const absl::optional<int> restore_to_count =
+        state_stack_.back()->getRestoreToCount();
+    if (restore_to_count.has_value()) {
+      c->restoreToCount(restore_to_count.value());
+      int pops = state_stack_.size() - restore_to_count.value() + 1;
+      for (int i = 0; i < pops; i++) {
+        state_stack_.pop_back();
+        state_stack_.back()->ClearResolvedFilter();
+      }
+    } else {
+      c->restore();
+    }
+  }
 
   ValidateStateStack();
 }
@@ -247,6 +296,9 @@
 }
 
 void BaseRenderingContext2D::reset() {
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kReset);
+  }
   ValidateStateStack();
   UnwindStateStack();
   state_stack_.resize(1);
@@ -287,7 +339,7 @@
       style->GetColorAsString());
 }
 
-void BaseRenderingContext2D::IdentifiabilityMaybeUpdateForStyleUnion(
+void BaseRenderingContext2D::IdentifiabilityUpdateForStyleUnion(
     const V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString* style) {
   switch (style->GetContentType()) {
     case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString::
@@ -295,19 +347,22 @@
       break;
     case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString::
         ContentType::kCanvasGradient:
-      identifiability_study_helper_.MaybeUpdateBuilder(
+      identifiability_study_helper_.UpdateBuilder(
           style->GetAsCanvasGradient()->GetIdentifiableToken());
       break;
     case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString::
         ContentType::kCanvasPattern:
-      identifiability_study_helper_.MaybeUpdateBuilder(
+      identifiability_study_helper_.UpdateBuilder(
           style->GetAsCanvasPattern()->GetIdentifiableToken());
       break;
     case V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString::
         ContentType::kString:
-      identifiability_study_helper_.MaybeUpdateBuilder(
+      identifiability_study_helper_.UpdateBuilder(
           IdentifiabilityBenignStringToken(style->GetAsString()));
       break;
+    default:
+      // TODO(crbug.com/1234113): Instrument new canvas APIs.
+      identifiability_study_helper_.set_encountered_skipped_ops();
   }
 }
 
@@ -329,8 +384,10 @@
     const V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString* style) {
   DCHECK(style);
 
-  identifiability_study_helper_.MaybeUpdateBuilder(CanvasOps::kSetStrokeStyle);
-  IdentifiabilityMaybeUpdateForStyleUnion(style);
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetStrokeStyle);
+    IdentifiabilityUpdateForStyleUnion(style);
+  }
 
   String color_string;
   CanvasStyle* canvas_style = nullptr;
@@ -388,8 +445,10 @@
   DCHECK(style);
 
   ValidateStateStack();
-  identifiability_study_helper_.MaybeUpdateBuilder(CanvasOps::kSetFillStyle);
-  IdentifiabilityMaybeUpdateForStyleUnion(style);
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetFillStyle);
+    IdentifiabilityUpdateForStyleUnion(style);
+  }
 
   String color_string;
   CanvasStyle* canvas_style = nullptr;
@@ -446,6 +505,10 @@
     return;
   if (GetState().LineWidth() == width)
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetLineWidth,
+                                                width);
+  }
   GetState().SetLineWidth(clampTo<float>(width));
 }
 
@@ -459,6 +522,9 @@
     return;
   if (GetState().GetLineCap() == cap)
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetLineCap, cap);
+  }
   GetState().SetLineCap(cap);
 }
 
@@ -472,6 +538,9 @@
     return;
   if (GetState().GetLineJoin() == join)
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetLineJoin, join);
+  }
   GetState().SetLineJoin(join);
 }
 
@@ -484,6 +553,10 @@
     return;
   if (GetState().MiterLimit() == limit)
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetMiterLimit,
+                                                limit);
+  }
   GetState().SetMiterLimit(clampTo<float>(limit));
 }
 
@@ -496,6 +569,10 @@
     return;
   if (GetState().ShadowOffset().Width() == x)
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetShadowOffsetX,
+                                                x);
+  }
   GetState().SetShadowOffsetX(clampTo<float>(x));
 }
 
@@ -508,6 +585,10 @@
     return;
   if (GetState().ShadowOffset().Height() == y)
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetShadowOffsetY,
+                                                y);
+  }
   GetState().SetShadowOffsetY(clampTo<float>(y));
 }
 
@@ -520,6 +601,10 @@
     return;
   if (GetState().ShadowBlur() == blur)
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetShadowBlur,
+                                                blur);
+  }
   GetState().SetShadowBlur(clampTo<float>(blur));
 }
 
@@ -533,6 +618,10 @@
     return;
   if (GetState().ShadowColor() == color)
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetShadowColor,
+                                                color.Rgb());
+  }
   GetState().SetShadowColor(color.Rgb());
 }
 
@@ -548,6 +637,10 @@
 void BaseRenderingContext2D::setLineDash(const Vector<double>& dash) {
   if (!LineDashSequenceIsValid(dash))
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetLineDash,
+                                                base::make_span(dash));
+  }
   GetState().SetLineDash(dash);
 }
 
@@ -558,6 +651,10 @@
 void BaseRenderingContext2D::setLineDashOffset(double offset) {
   if (!std::isfinite(offset) || GetState().LineDashOffset() == offset)
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetLineDashOffset,
+                                                offset);
+  }
   GetState().SetLineDashOffset(clampTo<float>(offset));
 }
 
@@ -570,6 +667,10 @@
     return;
   if (GetState().GlobalAlpha() == alpha)
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kSetGlobalAlpha,
+                                                alpha);
+  }
   GetState().SetGlobalAlpha(alpha);
 }
 
@@ -588,6 +689,10 @@
   SkBlendMode sk_blend_mode = WebCoreCompositeToSkiaComposite(op, blend_mode);
   if (GetState().GlobalComposite() == sk_blend_mode)
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kSetGlobalCompositeOpertion, sk_blend_mode);
+  }
   GetState().SetGlobalComposite(sk_blend_mode);
 }
 
@@ -610,10 +715,17 @@
       if (RuntimeEnabledFeatures::NewCanvas2DAPIEnabled()) {
         GetState().SetCanvasFilter(input->GetAsCanvasFilter());
         SnapshotStateForFilter();
+        // TODO(crbug.com/1234113): Instrument new canvas APIs.
+        identifiability_study_helper_.set_encountered_skipped_ops();
       }
       break;
     case V8UnionCanvasFilterOrString::ContentType::kString: {
       const String& filter_string = input->GetAsString();
+      if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+        identifiability_study_helper_.UpdateBuilder(
+            CanvasOps::kSetFilter,
+            IdentifiabilitySensitiveStringToken(filter_string));
+      }
       if (!GetState().GetCanvasFilter() &&
           filter_string == GetState().UnparsedCSSFilter()) {
         return;
@@ -641,6 +753,9 @@
 
   if (!std::isfinite(sx) || !std::isfinite(sy))
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kScale, sx, sy);
+  }
 
   TransformationMatrix new_transform = GetState().GetTransform();
   float fsx = clampTo<float>(sx);
@@ -691,6 +806,10 @@
 
   if (!std::isfinite(angle_in_radians))
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kRotate,
+                                                angle_in_radians);
+  }
 
   TransformationMatrix new_transform = GetState().GetTransform();
   new_transform.Rotate(rad2deg(angle_in_radians));
@@ -712,6 +831,8 @@
 
   if (!std::isfinite(rx) || !std::isfinite(ry) || !std::isfinite(rz))
     return;
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
 
   TransformationMatrix rotation_matrix =
       TransformationMatrix().Rotate3d(rad2deg(rx), rad2deg(ry), rad2deg(rz));
@@ -742,6 +863,8 @@
   if (!std::isfinite(axisX) || !std::isfinite(axisY) || !std::isfinite(axisZ) ||
       !std::isfinite(angle_in_radians))
     return;
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
 
   TransformationMatrix rotation_matrix = TransformationMatrix().Rotate3d(
       axisX, axisY, axisZ, rad2deg(angle_in_radians));
@@ -773,6 +896,9 @@
 
   if (!std::isfinite(tx) || !std::isfinite(ty))
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kTranslate, tx, ty);
+  }
 
   TransformationMatrix new_transform = GetState().GetTransform();
   // clamp to float to avoid float cast overflow when used as SkScalar
@@ -828,6 +954,8 @@
 
   if (length == 0 || !std::isfinite(length))
     return;
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
 
   float flength = clampTo<float>(length);
 
@@ -876,6 +1004,8 @@
       !std::isfinite(m41) || !std::isfinite(m42) || !std::isfinite(m43) ||
       !std::isfinite(m44))
     return;
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
 
   // clamp to float to avoid float cast overflow when used as SkScalar
   float fm11 = clampTo<float>(m11);
@@ -937,6 +1067,10 @@
   float fm22 = clampTo<float>(m22);
   float fdx = clampTo<float>(dx);
   float fdy = clampTo<float>(dy);
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kTransform, fm11,
+                                                fm12, fm21, fm22, fdx, fdy);
+  }
 
   TransformationMatrix transform(fm11, fm12, fm21, fm22, fdx, fdy);
   TransformationMatrix new_transform = GetState().GetTransform() * transform;
@@ -955,6 +1089,9 @@
   cc::PaintCanvas* c = GetOrCreatePaintCanvas();
   if (!c)
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kResetTransform);
+  }
 
   TransformationMatrix ctm = GetState().GetTransform();
   bool invertible_ctm = IsTransformInvertible();
@@ -1068,6 +1205,9 @@
 
 void BaseRenderingContext2D::beginPath() {
   path_.Clear();
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kBeginPath);
+  }
 }
 
 void BaseRenderingContext2D::DrawPathInternal(
@@ -1114,23 +1254,39 @@
 }
 
 void BaseRenderingContext2D::fill(const String& winding_rule_string) {
+  const SkPathFillType winding_rule = ParseWinding(winding_rule_string);
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kFill, winding_rule);
+  }
   DrawPathInternal(path_, CanvasRenderingContext2DState::kFillPaintType,
-                   ParseWinding(winding_rule_string), UsePaintCache::kDisabled);
+                   winding_rule, UsePaintCache::kDisabled);
 }
 
 void BaseRenderingContext2D::fill(Path2D* dom_path,
                                   const String& winding_rule_string) {
+  const SkPathFillType winding_rule = ParseWinding(winding_rule_string);
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kFill__Path, dom_path->GetIdentifiableToken(), winding_rule);
+  }
   DrawPathInternal(dom_path->GetPath(),
-                   CanvasRenderingContext2DState::kFillPaintType,
-                   ParseWinding(winding_rule_string), UsePaintCache::kEnabled);
+                   CanvasRenderingContext2DState::kFillPaintType, winding_rule,
+                   UsePaintCache::kEnabled);
 }
 
 void BaseRenderingContext2D::stroke() {
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kStroke);
+  }
   DrawPathInternal(path_, CanvasRenderingContext2DState::kStrokePaintType,
                    SkPathFillType::kWinding, UsePaintCache::kDisabled);
 }
 
 void BaseRenderingContext2D::stroke(Path2D* dom_path) {
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kStroke__Path, dom_path->GetIdentifiableToken());
+  }
   DrawPathInternal(dom_path->GetPath(),
                    CanvasRenderingContext2DState::kStrokePaintType,
                    SkPathFillType::kWinding, UsePaintCache::kEnabled);
@@ -1145,6 +1301,10 @@
 
   if (!GetOrCreatePaintCanvas())
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kFillRect, x, y,
+                                                width, height);
+  }
 
   // clamp to float to avoid float cast overflow when used as SkScalar
   AdjustRectForCanvas(x, y, width, height);
@@ -1206,6 +1366,10 @@
 
   if (!GetOrCreatePaintCanvas())
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kStrokeRect, x, y,
+                                                width, height);
+  }
 
   // clamp to float to avoid float cast overflow when used as SkScalar
   AdjustRectForCanvas(x, y, width, height);
@@ -1252,11 +1416,21 @@
 }
 
 void BaseRenderingContext2D::clip(const String& winding_rule_string) {
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kClip,
+        IdentifiabilitySensitiveStringToken(winding_rule_string));
+  }
   ClipInternal(path_, winding_rule_string, UsePaintCache::kDisabled);
 }
 
 void BaseRenderingContext2D::clip(Path2D* dom_path,
                                   const String& winding_rule_string) {
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kClip__Path, dom_path->GetIdentifiableToken(),
+        IdentifiabilitySensitiveStringToken(winding_rule_string));
+  }
   ClipInternal(dom_path->GetPath(), winding_rule_string,
                UsePaintCache::kEnabled);
 }
@@ -1348,6 +1522,10 @@
   SkIRect clip_bounds;
   if (!c->getDeviceClipBounds(&clip_bounds))
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kClearRect, x, y,
+                                                width, height);
+  }
 
   PaintFlags clear_flags;
   clear_flags.setBlendMode(SkBlendMode::kClear);
@@ -1671,6 +1849,12 @@
 
   if (src_rect.IsEmpty())
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kDrawImage, fsx, fsy, fsw, fsh, fdx, fdy, fdw, fdh,
+        image ? image->width() : 0, image ? image->height() : 0);
+    identifiability_study_helper_.set_encountered_partially_digested_image();
+  }
 
   ValidateStateStack();
 
@@ -1789,6 +1973,8 @@
 
   auto* gradient = MakeGarbageCollected<CanvasGradient>(FloatPoint(fx0, fy0),
                                                         FloatPoint(fx1, fy1));
+  gradient->SetExecutionContext(
+      identifiability_study_helper_.execution_context());
   return gradient;
 }
 
@@ -1822,6 +2008,8 @@
 
   auto* gradient = MakeGarbageCollected<CanvasGradient>(
       FloatPoint(fx0, fy0), fr0, FloatPoint(fx1, fy1), fr1);
+  gradient->SetExecutionContext(
+      identifiability_study_helper_.execution_context());
   return gradient;
 }
 
@@ -1831,6 +2019,8 @@
   if (!std::isfinite(startAngle) || !std::isfinite(centerX) ||
       !std::isfinite(centerY))
     return nullptr;
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
 
   // clamp to float to avoid float cast overflow
   float a = clampTo<float>(startAngle);
@@ -1842,6 +2032,8 @@
   a = rad2deg(a) + 90;
 
   auto* gradient = MakeGarbageCollected<CanvasGradient>(a, FloatPoint(x, y));
+  gradient->SetExecutionContext(
+      identifiability_study_helper_.execution_context());
   return gradient;
 }
 
@@ -1913,8 +2105,11 @@
 
   bool origin_clean = !WouldTaintOrigin(image_source);
 
-  return MakeGarbageCollected<CanvasPattern>(std::move(image_for_rendering),
-                                             repeat_mode, origin_clean);
+  auto* pattern = MakeGarbageCollected<CanvasPattern>(
+      std::move(image_for_rendering), repeat_mode, origin_clean);
+  pattern->SetExecutionContext(
+      identifiability_study_helper_.execution_context());
+  return pattern;
 }
 
 bool BaseRenderingContext2D::ComputeDirtyRect(const FloatRect& local_rect,
@@ -2160,6 +2355,14 @@
   if (!hasResourceProvider)
     return;
 
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kPutImageData, data->width(), data->height(),
+        data->GetCanvasColorSpace(), data->GetImageDataStorageFormat(), dx, dy,
+        dirty_x, dirty_y, dirty_width, dirty_height);
+    identifiability_study_helper_.set_encountered_partially_digested_image();
+  }
+
   if (dirty_width < 0) {
     if (dirty_x < 0) {
       dirty_x = dirty_width = 0;
@@ -2304,6 +2507,10 @@
 void BaseRenderingContext2D::setImageSmoothingEnabled(bool enabled) {
   if (enabled == GetState().ImageSmoothingEnabled())
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kSetImageSmoothingEnabled, enabled);
+  }
 
   GetState().SetImageSmoothingEnabled(enabled);
 }
@@ -2316,6 +2523,11 @@
   if (quality == GetState().ImageSmoothingQuality())
     return;
 
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kSetImageSmoothingQuality,
+        IdentifiabilitySensitiveStringToken(quality));
+  }
   GetState().SetImageSmoothingQuality(quality);
 }
 
@@ -2405,8 +2617,10 @@
 }
 
 void BaseRenderingContext2D::setTextAlign(const String& s) {
-  identifiability_study_helper_.MaybeUpdateBuilder(
-      CanvasOps::kSetTextAlign, IdentifiabilityBenignStringToken(s));
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kSetTextAlign, IdentifiabilityBenignStringToken(s));
+  }
   TextAlign align;
   if (!ParseTextAlign(s, align))
     return;
@@ -2420,8 +2634,10 @@
 }
 
 void BaseRenderingContext2D::setTextBaseline(const String& s) {
-  identifiability_study_helper_.MaybeUpdateBuilder(
-      CanvasOps::kSetTextBaseline, IdentifiabilityBenignStringToken(s));
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kSetTextBaseline, IdentifiabilityBenignStringToken(s));
+  }
   TextBaseline baseline;
   if (!ParseTextBaseline(s, baseline))
     return;
@@ -2447,6 +2663,7 @@
   visitor->Trace(dispatch_context_lost_event_timer_);
   visitor->Trace(dispatch_context_restored_event_timer_);
   visitor->Trace(try_restore_context_event_timer_);
+  CanvasPath::Trace(visitor);
 }
 
 BaseRenderingContext2D::UsageCounters::UsageCounters()
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h
index 744d9c6d..b3226c8 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h
@@ -30,8 +30,7 @@
 class V8UnionCanvasFilterOrString;
 using cc::UsePaintCache;
 
-class MODULES_EXPORT BaseRenderingContext2D : public GarbageCollectedMixin,
-                                              public CanvasPath {
+class MODULES_EXPORT BaseRenderingContext2D : public CanvasPath {
  public:
   ~BaseRenderingContext2D() override;
 
@@ -498,7 +497,6 @@
   bool context_restorable_{true};
   CanvasRenderingContext::LostContextMode context_lost_mode_{
       CanvasRenderingContext::kNotLostContext};
-  IdentifiabilityStudyHelper identifiability_study_helper_;
 
  private:
   // Pops from the top of the state stack, inverts transform, restores the
@@ -590,7 +588,9 @@
                            float height,
                            base::TimeTicks start_time);
 
-  void IdentifiabilityMaybeUpdateForStyleUnion(
+  // Only call if identifiability_study_helper_.ShouldUpdateBuilder() returns
+  // true.
+  void IdentifiabilityUpdateForStyleUnion(
       const V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString* style);
 
   RespectImageOrientationEnum RespectImageOrientationInternal(
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_gradient.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_gradient.cc
index 7510eb5..bf61fda 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_gradient.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_gradient.cc
@@ -39,8 +39,10 @@
                                  kSpreadMethodPad,
                                  Gradient::ColorInterpolation::kUnpremultiplied,
                                  Gradient::DegenerateHandling::kDisallow)) {
-  identifiability_study_helper_.MaybeUpdateBuilder(
-      CanvasOps::kCreateLinearGradient, p0.X(), p0.Y(), p1.X(), p1.Y());
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kCreateLinearGradient, p0.X(), p0.Y(), p1.X(), p1.Y());
+  }
 }
 
 CanvasGradient::CanvasGradient(const FloatPoint& p0,
@@ -56,8 +58,11 @@
                                  kSpreadMethodPad,
                                  Gradient::ColorInterpolation::kUnpremultiplied,
                                  Gradient::DegenerateHandling::kDisallow)) {
-  identifiability_study_helper_.MaybeUpdateBuilder(
-      CanvasOps::kCreateRadialGradient, p0.X(), p0.Y(), r0, p1.X(), p1.Y(), r1);
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kCreateRadialGradient, p0.X(), p0.Y(), r0, p1.X(), p1.Y(),
+        r1);
+  }
 }
 
 // CanvasRenderingContext2D.createConicGradient only takes one angle argument
@@ -90,8 +95,10 @@
                                           "') could not be parsed as a color.");
     return;
   }
-  identifiability_study_helper_.MaybeUpdateBuilder(CanvasOps::kAddColorStop,
-                                                   value, color.Rgb());
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kAddColorStop, value,
+                                                color.Rgb());
+  }
 
   gradient_->AddColorStop(value, color);
 }
@@ -100,4 +107,13 @@
   return identifiability_study_helper_.GetToken();
 }
 
+void CanvasGradient::SetExecutionContext(ExecutionContext* context) {
+  identifiability_study_helper_.SetExecutionContext(context);
+}
+
+void CanvasGradient::Trace(Visitor* visitor) const {
+  visitor->Trace(identifiability_study_helper_);
+  ScriptWrappable::Trace(visitor);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_gradient.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_gradient.h
index 6da6796..7d8c53b 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_gradient.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_gradient.h
@@ -58,6 +58,11 @@
 
   IdentifiableToken GetIdentifiableToken() const;
 
+  // Sets on internal IdentifiabilityStudyHelper.
+  void SetExecutionContext(ExecutionContext*);
+
+  void Trace(Visitor* visitor) const override;
+
  private:
   scoped_refptr<Gradient> gradient_;
   IdentifiabilityStudyHelper identifiability_study_helper_;
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.cc
index ea6c949a..91a3900 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.cc
@@ -36,7 +36,9 @@
 #include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.h"
 
 #include "base/numerics/safe_conversions.h"
+#include "third_party/blink/public/common/privacy_budget/identifiability_metrics.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_point_init.h"
+#include "third_party/blink/renderer/modules/canvas/canvas2d/identifiability_study_helper.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
 #include "third_party/blink/renderer/platform/transforms/affine_transform.h"
@@ -50,6 +52,9 @@
 void CanvasPath::closePath() {
   if (UNLIKELY(path_.IsEmpty()))
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kClosePath);
+  }
   path_.CloseSubpath();
 }
 
@@ -58,6 +63,10 @@
   float y = base::saturated_cast<float>(double_y);
   if (UNLIKELY(!std::isfinite(x) || !std::isfinite(y)))
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kMoveTo, double_x,
+                                                double_y);
+  }
   if (UNLIKELY(!IsTransformInvertible())) {
     path_.MoveTo(GetTransform().MapPoint(FloatPoint(x, y)));
     return;
@@ -70,6 +79,10 @@
   float y = base::saturated_cast<float>(double_y);
   if (UNLIKELY(!std::isfinite(x) || !std::isfinite(y)))
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kLineTo, double_x,
+                                                double_y);
+  }
   FloatPoint p1 = FloatPoint(x, y);
 
   if (UNLIKELY(!IsTransformInvertible())) {
@@ -94,6 +107,11 @@
   if (UNLIKELY(!std::isfinite(cpx) || !std::isfinite(cpy) ||
                !std::isfinite(x) || !std::isfinite(y)))
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kQuadradicCurveTo,
+                                                double_cpx, double_cpy,
+                                                double_x, double_y);
+  }
   FloatPoint p1 = FloatPoint(x, y);
   FloatPoint cp = FloatPoint(cpx, cpy);
 
@@ -124,6 +142,11 @@
                !std::isfinite(cp2x) || !std::isfinite(cp2y) ||
                !std::isfinite(x) || !std::isfinite(y)))
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kBezierCurveTo, double_cp1x, double_cp1y, double_cp2x,
+        double_cp2y, double_x, double_y);
+  }
 
   FloatPoint p1 = FloatPoint(x, y);
   FloatPoint cp1 = FloatPoint(cp1x, cp1y);
@@ -161,6 +184,11 @@
         "The radius provided (" + String::Number(r) + ") is negative.");
     return;
   }
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(CanvasOps::kArcTo, double_x1,
+                                                double_y1, double_x2, double_y2,
+                                                double_r);
+  }
 
   FloatPoint p1 = FloatPoint(x1, y1);
   FloatPoint p2 = FloatPoint(x2, y2);
@@ -365,6 +393,11 @@
 
   if (UNLIKELY(!IsTransformInvertible()))
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kArc, double_x, double_y, double_radius, double_start_angle,
+        double_end_angle, anticlockwise);
+  }
 
   if (UNLIKELY(!radius || start_angle == end_angle)) {
     // The arc is empty but we still need to draw the connecting line.
@@ -416,6 +449,12 @@
 
   if (UNLIKELY(!IsTransformInvertible()))
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kEllipse, double_x, double_y, double_radius_x,
+        double_radius_y, double_rotation, double_start_angle, double_end_angle,
+        anticlockwise);
+  }
 
   CanonicalizeAngle(&start_angle, &end_angle);
   float adjusted_end_angle =
@@ -446,6 +485,10 @@
   if (UNLIKELY(!std::isfinite(x) || !std::isfinite(y) ||
                !std::isfinite(width) || !std::isfinite(height)))
     return;
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kRect, double_x, double_y, double_width, double_height);
+  }
 
   path_.AddRect(FloatRect(x, y, width, height));
 }
@@ -474,6 +517,8 @@
   if (UNLIKELY(!std::isfinite(x) || !std::isfinite(y) ||
                !std::isfinite(width) || !std::isfinite(height)))
     return;
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
 
   FloatSize r[num_radii];
   for (int i = 0; i < num_radii; ++i) {
@@ -575,4 +620,7 @@
   path_.MoveTo(FloatPoint(x, y));
 }
 
+void CanvasPath::Trace(Visitor* visitor) const {
+  visitor->Trace(identifiability_study_helper_);
+}
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.h
index d834a09..cf9d2e6 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.h
@@ -31,6 +31,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_CANVAS_CANVAS2D_CANVAS_PATH_H_
 
 #include "third_party/blink/renderer/bindings/core/v8/v8_union_dompointinit_unrestricteddouble.h"
+#include "third_party/blink/renderer/modules/canvas/canvas2d/identifiability_study_helper.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/bindings/no_alloc_direct_call_host.h"
 #include "third_party/blink/renderer/platform/graphics/path.h"
@@ -42,7 +43,8 @@
 
 class ExceptionState;
 
-class MODULES_EXPORT CanvasPath : public NoAllocDirectCallHost {
+class MODULES_EXPORT CanvasPath : public GarbageCollectedMixin,
+                                  public NoAllocDirectCallHost {
   DISALLOW_NEW();
 
  public:
@@ -102,6 +104,12 @@
     return TransformationMatrix();
   }
 
+  IdentifiableToken GetIdentifiableToken() const {
+    return identifiability_study_helper_.GetToken();
+  }
+
+  void Trace(Visitor*) const override;
+
  protected:
   CanvasPath() { path_.SetIsVolatile(true); }
   explicit CanvasPath(const Path& path) : path_(path) {
@@ -118,6 +126,8 @@
   // GetTransform() remains virtual, which is okay because it is only called in
   // code paths that handle non-invertible transforms.
   bool is_transform_invertible_ = true;
+
+  IdentifiabilityStudyHelper identifiability_study_helper_;
 };
 
 ALWAYS_INLINE bool CanvasPath::IsTransformInvertible() const {
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_pattern.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_pattern.cc
index 6b14386..c06e40d 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_pattern.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_pattern.cc
@@ -58,9 +58,11 @@
                              bool origin_clean)
     : pattern_(Pattern::CreateImagePattern(std::move(image), repeat)),
       origin_clean_(origin_clean) {
-  identifiability_study_helper_.MaybeUpdateBuilder(
-      CanvasOps::kCreatePattern, image ? image->width() : 0,
-      image ? image->height() : 0, repeat);
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kCreatePattern, image ? image->width() : 0,
+        image ? image->height() : 0, repeat);
+  }
 }
 
 void CanvasPattern::setTransform(DOMMatrix2DInit* transform,
@@ -71,8 +73,10 @@
   if (!m) {
     return;
   }
-  identifiability_study_helper_.MaybeUpdateBuilder(
-      m->m11(), m->m12(), m->m21(), m->m22(), m->m41(), m->m42());
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(m->m11(), m->m12(), m->m21(),
+                                                m->m22(), m->m41(), m->m42());
+  }
 
   pattern_transform_ = m->GetAffineTransform();
 }
@@ -81,4 +85,13 @@
   return identifiability_study_helper_.GetToken();
 }
 
+void CanvasPattern::SetExecutionContext(ExecutionContext* context) {
+  identifiability_study_helper_.SetExecutionContext(context);
+}
+
+void CanvasPattern::Trace(Visitor* visitor) const {
+  visitor->Trace(identifiability_study_helper_);
+  ScriptWrappable::Trace(visitor);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_pattern.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_pattern.h
index 6bd134a..0e347ce 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_pattern.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_pattern.h
@@ -56,6 +56,11 @@
 
   IdentifiableToken GetIdentifiableToken() const;
 
+  // Sets on internal IdentifiabilityStudyHelper.
+  void SetExecutionContext(ExecutionContext*);
+
+  void Trace(Visitor* visitor) const override;
+
  private:
   scoped_refptr<Pattern> pattern_;
   AffineTransform pattern_transform_;
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
index 6486b4ef..fa3db49f 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
@@ -132,6 +132,8 @@
       random_generator_((uint32_t)base::RandUint64()),
       bernoulli_distribution_(kRasterMetricProbability),
       color_params_(attrs.color_space, attrs.pixel_format, attrs.alpha) {
+  identifiability_study_helper_.SetExecutionContext(
+      canvas->GetTopExecutionContext());
   if (canvas->GetDocument().GetSettings() &&
       canvas->GetDocument().GetSettings()->GetAntialiasedClips2dCanvasEnabled())
     clip_antialiasing_ = kAntiAliased;
@@ -488,8 +490,10 @@
   // documents.
   if (!canvas()->GetDocument().GetFrame())
     return;
-  identifiability_study_helper_.MaybeUpdateBuilder(
-      CanvasOps::kSetFont, IdentifiabilityBenignStringToken(new_font));
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kSetFont, IdentifiabilityBenignStringToken(new_font));
+  }
 
   base::TimeTicks start_time = base::TimeTicks::Now();
   canvas()->GetDocument().UpdateStyleAndLayoutTreeForNode(canvas());
@@ -776,6 +780,8 @@
 void CanvasRenderingContext2D::setLetterSpacing(const double letter_spacing) {
   if (UNLIKELY(!std::isfinite(letter_spacing)))
     return;
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
 
   if (!GetState().HasRealizedFont())
     setFont(font());
@@ -787,6 +793,8 @@
 void CanvasRenderingContext2D::setWordSpacing(const double word_spacing) {
   if (UNLIKELY(!std::isfinite(word_spacing)))
     return;
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
 
   if (!GetState().HasRealizedFont())
     setFont(font());
@@ -797,6 +805,8 @@
 
 void CanvasRenderingContext2D::setTextRendering(
     const String& text_rendering_string) {
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
   if (!GetState().HasRealizedFont())
     setFont(font());
 
@@ -822,6 +832,8 @@
 
 void CanvasRenderingContext2D::setFontKerning(
     const String& font_kerning_string) {
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
   if (!GetState().HasRealizedFont())
     setFont(font());
   FontDescription::Kerning kerning;
@@ -842,6 +854,8 @@
 }
 
 void CanvasRenderingContext2D::setFontStretch(const String& font_stretch) {
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
   if (!GetState().HasRealizedFont())
     setFont(font());
 
@@ -876,6 +890,8 @@
 
 void CanvasRenderingContext2D::setFontVariantCaps(
     const String& font_variant_caps_string) {
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
   if (!GetState().HasRealizedFont())
     setFont(font());
   FontDescription::FontVariantCaps variant_caps;
@@ -956,6 +972,8 @@
     double wrap_width) {
   if (!formatted_text)
     return;
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
 
   if (!GetState().HasRealizedFont())
     setFont(font());
@@ -997,13 +1015,15 @@
   if (max_width && (!std::isfinite(*max_width) || *max_width <= 0))
     return;
 
-  identifiability_study_helper_.MaybeUpdateBuilder(
-      paint_type == CanvasRenderingContext2DState::kFillPaintType
-          ? CanvasOps::kFillText
-          : CanvasOps::kStrokeText,
-      IdentifiabilitySensitiveStringToken(text), x, y,
-      max_width ? *max_width : -1);
-  identifiability_study_helper_.set_encountered_sensitive_ops();
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        paint_type == CanvasRenderingContext2DState::kFillPaintType
+            ? CanvasOps::kFillText
+            : CanvasOps::kStrokeText,
+        IdentifiabilitySensitiveStringToken(text), x, y,
+        max_width ? *max_width : -1);
+    identifiability_study_helper_.set_encountered_sensitive_ops();
+  }
 
   const Font& font = AccessFont();
   const SimpleFontData* font_data = font.PrimaryFont();
@@ -1130,11 +1150,14 @@
 
 void CanvasRenderingContext2D::drawFocusIfNeeded(Path2D* path2d,
                                                  Element* element) {
-  DrawFocusIfNeededInternal(path2d->GetPath(), element);
+  DrawFocusIfNeededInternal(path2d->GetPath(), element,
+                            path2d->GetIdentifiableToken());
 }
 
-void CanvasRenderingContext2D::DrawFocusIfNeededInternal(const Path& path,
-                                                         Element* element) {
+void CanvasRenderingContext2D::DrawFocusIfNeededInternal(
+    const Path& path,
+    Element* element,
+    IdentifiableToken path_token) {
   if (!FocusRingCallIsValid(path, element))
     return;
 
@@ -1142,6 +1165,10 @@
   // element->focused(), because element->focused() isn't updated until after
   // focus events fire.
   if (element->GetDocument().FocusedElement() == element) {
+    if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+      identifiability_study_helper_.UpdateBuilder(CanvasOps::kDrawFocusIfNeeded,
+                                                  path_token);
+    }
     ScrollPathIntoViewInternal(path);
     DrawFocusRing(path, element);
   }
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h
index b70b44e..cdbb9633 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h
@@ -241,6 +241,10 @@
 
   void SendContextLostEventIfNeeded() override;
 
+  bool IdentifiabilityEncounteredPartiallyDigestedImage() const override {
+    return identifiability_study_helper_.encountered_partially_digested_image();
+  }
+
  protected:
   // This reports CanvasColorParams to the CanvasRenderingContext interface.
   CanvasColorParams CanvasRenderingContextColorParams() const override {
@@ -273,7 +277,10 @@
 
   const Font& AccessFont();
 
-  void DrawFocusIfNeededInternal(const Path&, Element*);
+  void DrawFocusIfNeededInternal(
+      const Path&,
+      Element*,
+      IdentifiableToken path_hash = IdentifiableToken());
   bool FocusRingCallIsValid(const Path&, Element*);
   void DrawFocusRing(const Path&, Element*);
   void UpdateElementAccessibility(const Path&, Element*);
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_api_test.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_api_test.cc
index 4254db33..4f08b2afd 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_api_test.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_api_test.cc
@@ -13,6 +13,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_union_float32array_uint16array_uint8clampedarray.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_hit_region_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_union_csscolorvalue_canvasgradient_canvaspattern_string.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_union_cssimagevalue_htmlcanvaselement_htmlimageelement_htmlvideoelement_imagebitmap_offscreencanvas_svgimageelement_videoframe.h"
 #include "third_party/blink/renderer/core/accessibility/ax_context.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
@@ -403,7 +404,8 @@
 // participation.
 class ActiveSettingsProvider : public IdentifiabilityStudySettingsProvider {
  public:
-  bool IsActive() const override { return true; }
+  explicit ActiveSettingsProvider(bool enabled) : enabled_(enabled) {}
+  bool IsActive() const override { return enabled_; }
   bool IsAnyTypeOrSurfaceBlocked() const override { return false; }
   bool IsSurfaceAllowed(IdentifiableSurface surface) const override {
     return true;
@@ -411,15 +413,18 @@
   bool IsTypeAllowed(IdentifiableSurface::Type type) const override {
     return true;
   }
+
+ private:
+  const bool enabled_ = true;
 };
 
 // An RAII class that opts into study participation using
 // ActiveSettingsProvider.
 class StudyParticipationRaii {
  public:
-  StudyParticipationRaii() {
+  explicit StudyParticipationRaii(bool enabled = true) {
     IdentifiabilityStudySettings::SetGlobalProvider(
-        std::make_unique<ActiveSettingsProvider>());
+        std::make_unique<ActiveSettingsProvider>(enabled));
   }
   ~StudyParticipationRaii() {
     IdentifiabilityStudySettings::ResetStateForTesting();
@@ -448,6 +453,7 @@
 
   EXPECT_TRUE(Context2D()->IdentifiabilityEncounteredSkippedOps());
   EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredPartiallyDigestedImage());
 }
 
 TEST_F(CanvasRenderingContext2DAPITest, IdentifiabilityStudyDigest_Font) {
@@ -455,11 +461,27 @@
   CreateContext(kNonOpaque);
 
   Context2D()->setFont("Arial");
-  EXPECT_EQ(INT64_C(4260982106376580867),
+  EXPECT_EQ(INT64_C(-7111871220951205888),
             Context2D()->IdentifiableTextToken().ToUkmMetricValue());
 
   EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
   EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredPartiallyDigestedImage());
+}
+
+TEST_F(CanvasRenderingContext2DAPITest, IdentifiabilityStudyDisabled) {
+  StudyParticipationRaii study_participation_raii(/*enabled=*/false);
+  constexpr int64_t kTokenBuilderInitialDigest = INT64_C(6544625333304541877);
+
+  CreateContext(kNonOpaque);
+
+  Context2D()->setFont("Arial");
+  EXPECT_EQ(kTokenBuilderInitialDigest,
+            Context2D()->IdentifiableTextToken().ToUkmMetricValue());
+
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredPartiallyDigestedImage());
 }
 
 TEST_F(CanvasRenderingContext2DAPITest, IdentifiabilityStudyDigest_StrokeText) {
@@ -467,11 +489,12 @@
   CreateContext(kNonOpaque);
 
   Context2D()->strokeText("Sensitive message", 1.0, 1.0);
-  EXPECT_EQ(INT64_C(-2943272460643878232),
+  EXPECT_EQ(INT64_C(2232415440872807707),
             Context2D()->IdentifiableTextToken().ToUkmMetricValue());
 
   EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
   EXPECT_TRUE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredPartiallyDigestedImage());
 }
 
 TEST_F(CanvasRenderingContext2DAPITest, IdentifiabilityStudyDigest_FillText) {
@@ -479,11 +502,12 @@
   CreateContext(kNonOpaque);
 
   Context2D()->fillText("Sensitive message", 1.0, 1.0);
-  EXPECT_EQ(INT64_C(8733208206881150098),
+  EXPECT_EQ(INT64_C(6317349156921019980),
             Context2D()->IdentifiableTextToken().ToUkmMetricValue());
 
   EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
   EXPECT_TRUE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredPartiallyDigestedImage());
 }
 
 TEST_F(CanvasRenderingContext2DAPITest, IdentifiabilityStudyDigest_TextAlign) {
@@ -491,11 +515,12 @@
   CreateContext(kNonOpaque);
 
   Context2D()->setTextAlign("center");
-  EXPECT_EQ(INT64_C(-4778938416456134710),
+  EXPECT_EQ(INT64_C(-1799394612814265049),
             Context2D()->IdentifiableTextToken().ToUkmMetricValue());
 
   EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
   EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredPartiallyDigestedImage());
 }
 
 TEST_F(CanvasRenderingContext2DAPITest,
@@ -504,11 +529,12 @@
   CreateContext(kNonOpaque);
 
   Context2D()->setTextBaseline("top");
-  EXPECT_EQ(INT64_C(-3065573128425485855),
+  EXPECT_EQ(INT64_C(-7620161594820691651),
             Context2D()->IdentifiableTextToken().ToUkmMetricValue());
 
   EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
   EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredPartiallyDigestedImage());
 }
 
 TEST_F(CanvasRenderingContext2DAPITest,
@@ -519,11 +545,12 @@
   auto* style = MakeGarbageCollected<
       V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString>("blue");
   Context2D()->setStrokeStyle(style);
-  EXPECT_EQ(INT64_C(2059186787917525779),
+  EXPECT_EQ(INT64_C(-1964835352532316734),
             Context2D()->IdentifiableTextToken().ToUkmMetricValue());
 
   EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
   EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredPartiallyDigestedImage());
 }
 
 TEST_F(CanvasRenderingContext2DAPITest, IdentifiabilityStudyDigest_FillStyle) {
@@ -533,11 +560,12 @@
   auto* style = MakeGarbageCollected<
       V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString>("blue");
   Context2D()->setFillStyle(style);
-  EXPECT_EQ(INT64_C(-6322980727372024031),
+  EXPECT_EQ(INT64_C(-4860826471555317536),
             Context2D()->IdentifiableTextToken().ToUkmMetricValue());
 
   EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
   EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredPartiallyDigestedImage());
 }
 
 TEST_F(CanvasRenderingContext2DAPITest, IdentifiabilityStudyDigest_Combo) {
@@ -545,7 +573,7 @@
   CreateContext(kNonOpaque);
 
   Context2D()->fillText("Sensitive message", 1.0, 1.0);
-  EXPECT_EQ(INT64_C(8733208206881150098),
+  EXPECT_EQ(INT64_C(6317349156921019980),
             Context2D()->IdentifiableTextToken().ToUkmMetricValue());
   Context2D()->setFont("Helvetica");
   Context2D()->setTextBaseline("bottom");
@@ -554,11 +582,48 @@
       V8UnionCSSColorValueOrCanvasGradientOrCanvasPatternOrString>("red");
   Context2D()->setFillStyle(style);
   Context2D()->fillText("Bye", 4.0, 3.0);
-  EXPECT_EQ(INT64_C(2368400155273386771),
+  EXPECT_EQ(INT64_C(5574475585707445774),
             Context2D()->IdentifiableTextToken().ToUkmMetricValue());
 
   EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
   EXPECT_TRUE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredPartiallyDigestedImage());
+}
+
+TEST_F(CanvasRenderingContext2DAPITest,
+       IdentifiabilityStudyDigest_putImageData) {
+  StudyParticipationRaii study_participation_raii;
+  CreateContext(kNonOpaque);
+  NonThrowableExceptionState exception_state;
+
+  ImageData* image_data =
+      Context2D()->createImageData(/*sw=*/1, /*sh=*/1, exception_state);
+  EXPECT_FALSE(exception_state.HadException());
+  Context2D()->putImageData(image_data, /*dx=*/1, /*dy=*/1, exception_state);
+  EXPECT_EQ(INT64_C(2821795876044191773),
+            Context2D()->IdentifiableTextToken().ToUkmMetricValue());
+
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
+  EXPECT_TRUE(Context2D()->IdentifiabilityEncounteredPartiallyDigestedImage());
+}
+
+TEST_F(CanvasRenderingContext2DAPITest, IdentifiabilityStudyDigest_drawImage) {
+  StudyParticipationRaii study_participation_raii;
+  CreateContext(kNonOpaque);
+  NonThrowableExceptionState exception_state;
+
+  // We can use our own canvas as the image source!
+  auto* image_source =
+      MakeGarbageCollected<V8CanvasImageSource>(&CanvasElement());
+  Context2D()->drawImage(/*script_state=*/nullptr, image_source, /*x=*/1,
+                         /*y=*/1, exception_state);
+  EXPECT_EQ(INT64_C(-4851825694092845811),
+            Context2D()->IdentifiableTextToken().ToUkmMetricValue());
+
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSkippedOps());
+  EXPECT_FALSE(Context2D()->IdentifiabilityEncounteredSensitiveOps());
+  EXPECT_TRUE(Context2D()->IdentifiabilityEncounteredPartiallyDigestedImage());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h
index 52caaab..7c12c40 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_state.h
@@ -40,7 +40,7 @@
   enum ClipListCopyMode { kCopyClipList, kDontCopyClipList };
   // SaveType indicates whether the state was pushed to the state stack by Save
   // or by BeginLayer. The first state on the state stack, which is created in 
-  // the canvas constructor and not by Save or BeginLayer, has SaveType kIntiial.
+  // the canvas constructor and not by Save or BeginLayer, has SaveType kInitial.
   enum class SaveType { kSaveRestore, kBeginEndLayer, kInitial };
 
   CanvasRenderingContext2DState();
@@ -232,6 +232,12 @@
 
   SaveType GetSaveType() const { return save_type_; }
 
+  void setRestoreToCount(absl::optional<int> count) {
+    restore_to_count_ = count;
+  }
+
+  absl::optional<int> getRestoreToCount() { return restore_to_count_; }
+
  private:
   void UpdateLineDash() const;
   void UpdateStrokeStyle() const;
@@ -312,6 +318,10 @@
   const SaveType save_type_ = SaveType::kInitial;
 
   DISALLOW_COPY_AND_ASSIGN(CanvasRenderingContext2DState);
+
+  // Some endlayer calls need to restore to a specific save count.
+  // If no such restore is needed, restore_to_count_ is set to nullopt.
+  absl::optional<int> restore_to_count_ = absl::nullopt;
 };
 
 ALWAYS_INLINE bool CanvasRenderingContext2DState::ShouldDrawShadows() const {
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_test.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_test.cc
index 0781d56..67bfce9 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_test.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_test.cc
@@ -961,7 +961,7 @@
   EXPECT_EQ(data_length, data_f32->length());
 
   ImageData* image_data = nullptr;
-  int num_pixels = data_length / 4;
+  size_t num_pixels = data_length / 4;
 
   // At most four bytes are needed for Float32 output per color component.
   std::unique_ptr<uint8_t[]> pixels_converted_manually(
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/identifiability_study_helper.cc b/third_party/blink/renderer/modules/canvas/canvas2d/identifiability_study_helper.cc
index 480d3eb..75a93059 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/identifiability_study_helper.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/identifiability_study_helper.cc
@@ -11,4 +11,8 @@
 // IdentifiabilityStudyHelper::ScopedMaxOperationsSetter.
 /*static*/ int IdentifiabilityStudyHelper::max_operations_ = 1 << 20;
 
+void IdentifiabilityStudyHelper::Trace(Visitor* visitor) const {
+  visitor->Trace(execution_context_);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/identifiability_study_helper.h b/third_party/blink/renderer/modules/canvas/canvas2d/identifiability_study_helper.h
index c45827a2..ca03d57 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/identifiability_study_helper.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/identifiability_study_helper.h
@@ -10,6 +10,7 @@
 #include "third_party/blink/public/common/privacy_budget/identifiability_metrics.h"
 #include "third_party/blink/public/common/privacy_budget/identifiability_study_settings.h"
 #include "third_party/blink/public/common/privacy_budget/identifiable_token_builder.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/privacy_budget/identifiability_digest_helpers.h"
 
@@ -28,18 +29,66 @@
 // **Don't renumber after the privacy budget study has started to ensure
 // consistency.**
 enum class CanvasOps {
+  // CanvasPath operations.
+  kClosePath = 0,
+  kMoveTo,
+  kLineTo,
+  kQuadradicCurveTo,
+  kBezierCurveTo,
+  kArcTo,
+  kArc,
+  kEllipse,
+  kRect,
+  // Path2D operations.
+  kAddPath,
   // BaseRenderingContext2D methods.
   kSetStrokeStyle,
   kSetFillStyle,
+  kSetLineWidth,
+  kSetLineCap,
+  kSetLineJoin,
+  kSetMiterLimit,
+  kSetLineDash,
+  kSetLineDashOffset,
+  kSetShadowOffsetX,
+  kSetShadowOffsetY,
+  kSetShadowBlur,
+  kSetShadowColor,
+  kSetGlobalAlpha,
+  kSetGlobalCompositeOpertion,
+  kSetFilter,
+  kSave,
+  kRestore,
+  kScale,
+  kRotate,
+  kTranslate,
+  kTransform,
+  kResetTransform,
+  kBeginPath,
+  kFill,
+  kFill__Path,
+  kStroke,
+  kStroke__Path,
+  kClip,
+  kClip__Path,
+  kClearRect,
+  kFillRect,
+  kStrokeRect,
+  kDrawImage,
   kCreateLinearGradient,
   kCreateRadialGradient,
   kCreatePattern,
+  kPutImageData,
+  kSetImageSmoothingEnabled,
+  kSetImageSmoothingQuality,
   kSetTextAlign,
   kSetTextBaseline,
+  kReset,
   // CanvasRenderingContext2D / OffscreenCanvasRenderingContext2D methods.
   kSetFont,
   kFillText,
   kStrokeText,
+  kDrawFocusIfNeeded,  // CanvasRenderingContext2D only.
   // CanvasGradient methods.
   kAddColorStop,
 };
@@ -47,30 +96,62 @@
 // A helper class to simplify maintaining the current text digest for the canvas
 // context. An operation count is also maintained to limit the performance
 // impact of the study.
-class IdentifiabilityStudyHelper {
+class IdentifiabilityStudyHelper final {
+  DISALLOW_NEW();
+
  public:
-  template <typename... Ts>
-  void MaybeUpdateBuilder(Ts... tokens) {
-    if (!IdentifiabilityStudySettings::Get()->IsTypeAllowed(
-            blink::IdentifiableSurface::Type::kCanvasReadback)) {
-      return;
-    }
-    if (operation_count_ >= max_operations_) {
+  // UpdateBuilder() should be called iff ShouldUpdateBuilder() is true, to
+  // avoid unnecessary copies of parameters and hashing when GetToken() won't be
+  // called.
+  bool ShouldUpdateBuilder() {
+    if (!is_canvas_type_allowed_)
+      return false;
+    if (!execution_context_ ||
+        execution_context_->IsInRequestAnimationFrame() ||
+        operation_count_ >= max_operations_) {
       encountered_skipped_ops_ = true;
-      return;
+      return false;
     }
+    return true;
+  }
+
+  // Do *not* call this method if ShouldUpdateBuilder() is false -- updates
+  // the internal digest based on the series of digestable parameters.
+  template <typename... Ts>
+  void UpdateBuilder(Ts... tokens) {
     AddTokens(tokens...);
     operation_count_++;
   }
 
+  // Returns an IdentifiableToken representing the internal computed digest.
   IdentifiableToken GetToken() const { return builder_.GetToken(); }
 
-  bool encountered_skipped_ops() const { return encountered_skipped_ops_; }
+  bool encountered_skipped_ops() const WARN_UNUSED_RESULT {
+    return encountered_skipped_ops_;
+  }
 
-  bool encountered_sensitive_ops() const { return encountered_sensitive_ops_; }
+  bool encountered_sensitive_ops() const WARN_UNUSED_RESULT {
+    return encountered_sensitive_ops_;
+  }
+
+  bool encountered_partially_digested_image() const WARN_UNUSED_RESULT {
+    return encountered_partially_digested_image_;
+  }
+
+  void set_encountered_skipped_ops() { encountered_skipped_ops_ = true; }
 
   void set_encountered_sensitive_ops() { encountered_sensitive_ops_ = true; }
 
+  void set_encountered_partially_digested_image() {
+    encountered_partially_digested_image_ = true;
+  }
+
+  void SetExecutionContext(ExecutionContext* context) {
+    execution_context_ = context;
+  }
+
+  ExecutionContext* execution_context() const { return execution_context_; }
+
   // For testing, allows scoped changing the max number of operations for all
   // IdentifiabilityStudyHelper instances.
   class ScopedMaxOperationsSetter {
@@ -87,6 +168,8 @@
     const int old_max_operations_;
   };
 
+  void Trace(Visitor* visitor) const;
+
  private:
   // Note that primitives are implicitly converted to IdentifiableTokens
   template <typename... Ts>
@@ -96,6 +179,12 @@
   }
   void AddTokens() {}
 
+  const bool is_canvas_type_allowed_ =
+      IdentifiabilityStudySettings::Get()->IsTypeAllowed(
+          blink::IdentifiableSurface::Type::kCanvasReadback);
+
+  Member<ExecutionContext> execution_context_;
+
   static MODULES_EXPORT int max_operations_;
 
   IdentifiableTokenBuilder builder_;
@@ -109,6 +198,11 @@
   //
   // This must be set manually by calling set_encountered_sensitive_ops().
   bool encountered_sensitive_ops_ = false;
+
+  // If true, at least one op was partially-digested -- for instance, images
+  // drawn to the canvas have their width, height, etc. digested, but not the
+  // image contents, for performance and complexity reasons.
+  bool encountered_partially_digested_image_ = false;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/path_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/path_2d.cc
new file mode 100644
index 0000000..42065a4
--- /dev/null
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/path_2d.cc
@@ -0,0 +1,15 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/canvas/canvas2d/path_2d.h"
+
+namespace blink {
+
+void Path2D::Trace(Visitor* visitor) const {
+  visitor->Trace(identifiability_study_helper_);
+  ScriptWrappable::Trace(visitor);
+  CanvasPath::Trace(visitor);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/path_2d.h b/third_party/blink/renderer/modules/canvas/canvas2d/path_2d.h
index a3990859..aa38648f 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/path_2d.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/path_2d.h
@@ -29,11 +29,13 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_CANVAS_CANVAS2D_PATH_2D_H_
 
 #include "base/macros.h"
+#include "third_party/blink/public/common/privacy_budget/identifiability_metrics.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_matrix_2d_init.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_union_path2d_string.h"
 #include "third_party/blink/renderer/core/geometry/dom_matrix.h"
 #include "third_party/blink/renderer/core/svg/svg_path_utilities.h"
 #include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.h"
+#include "third_party/blink/renderer/modules/canvas/canvas2d/identifiability_study_helper.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/transforms/affine_transform.h"
@@ -46,20 +48,23 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  static Path2D* Create(const V8UnionPath2DOrString* path) {
+  static Path2D* Create(ExecutionContext* context,
+                        const V8UnionPath2DOrString* path) {
     DCHECK(path);
     switch (path->GetContentType()) {
       case V8UnionPath2DOrString::ContentType::kPath2D:
-        return MakeGarbageCollected<Path2D>(path->GetAsPath2D());
+        return MakeGarbageCollected<Path2D>(context, path->GetAsPath2D());
       case V8UnionPath2DOrString::ContentType::kString:
-        return MakeGarbageCollected<Path2D>(path->GetAsString());
+        return MakeGarbageCollected<Path2D>(context, path->GetAsString());
     }
     NOTREACHED();
     return nullptr;
   }
-  static Path2D* Create() { return MakeGarbageCollected<Path2D>(); }
-  static Path2D* Create(const Path& path) {
-    return MakeGarbageCollected<Path2D>(path);
+  static Path2D* Create(ExecutionContext* context) {
+    return MakeGarbageCollected<Path2D>(context);
+  }
+  static Path2D* Create(ExecutionContext* context, const Path& path) {
+    return MakeGarbageCollected<Path2D>(context, path);
   }
 
   const Path& GetPath() const { return path_; }
@@ -75,12 +80,26 @@
         !std::isfinite(matrix->m42()))
       return;
     path_.AddPath(path->GetPath(), matrix->GetAffineTransform());
+    if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+      identifiability_study_helper_.UpdateBuilder(CanvasOps::kAddPath,
+                                                  path->GetIdentifiableToken());
+    }
   }
 
-  Path2D() : CanvasPath() {}
-  Path2D(const Path& path) : CanvasPath(path) {}
-  Path2D(Path2D* path) : CanvasPath(path->GetPath()) {}
-  Path2D(const String& path_data) : CanvasPath() {
+  void Trace(Visitor*) const override;
+
+  explicit Path2D(ExecutionContext* context) {
+    identifiability_study_helper_.SetExecutionContext(context);
+  }
+  Path2D(ExecutionContext* context, const Path& path) : CanvasPath(path) {
+    identifiability_study_helper_.SetExecutionContext(context);
+  }
+  Path2D(ExecutionContext* context, Path2D* path)
+      : CanvasPath(path->GetPath()) {
+    identifiability_study_helper_.SetExecutionContext(context);
+  }
+  Path2D(ExecutionContext* context, const String& path_data) {
+    identifiability_study_helper_.SetExecutionContext(context);
     BuildPathFromString(path_data, path_);
   }
   ~Path2D() override = default;
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/path_2d.idl b/third_party/blink/renderer/modules/canvas/canvas2d/path_2d.idl
index c9049fc..d9d38f3 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/path_2d.idl
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/path_2d.idl
@@ -31,7 +31,7 @@
 [
     Exposed=(PaintWorklet,Window,Worker)
 ] interface Path2D {
-    constructor(optional (Path2D or DOMString) path);
+    [CallWith=ExecutionContext] constructor(optional (Path2D or DOMString) path);
     [RaisesException] void addPath(Path2D path, optional DOMMatrix2DInit transform = {});
 };
 
diff --git a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
index 1d8c6da..2af7de9 100644
--- a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
@@ -99,6 +99,8 @@
       random_generator_(static_cast<uint32_t>(base::RandUint64())),
       bernoulli_distribution_(kUMASampleProbability),
       color_params_(attrs.color_space, attrs.pixel_format, attrs.alpha) {
+  identifiability_study_helper_.SetExecutionContext(
+      canvas->GetTopExecutionContext());
   is_valid_size_ = IsValidImageSize(Host()->Size());
 
   // Clear the background transparent or opaque.
@@ -404,8 +406,10 @@
 void OffscreenCanvasRenderingContext2D::setFont(const String& new_font) {
   if (GetState().HasRealizedFont() && new_font == GetState().UnparsedFont())
     return;
-  identifiability_study_helper_.MaybeUpdateBuilder(
-      CanvasOps::kSetFont, IdentifiabilityBenignStringToken(new_font));
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        CanvasOps::kSetFont, IdentifiabilityBenignStringToken(new_font));
+  }
 
   base::TimeTicks start_time = base::TimeTicks::Now();
   OffscreenFontCache& font_cache = GetOffscreenFontCache();
@@ -456,6 +460,8 @@
     const double letter_spacing) {
   if (UNLIKELY(!std::isfinite(letter_spacing)))
     return;
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
 
   if (!GetState().HasRealizedFont())
     setFont(font());
@@ -468,6 +474,8 @@
     const double word_spacing) {
   if (UNLIKELY(!std::isfinite(word_spacing)))
     return;
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
 
   if (!GetState().HasRealizedFont())
     setFont(font());
@@ -478,6 +486,8 @@
 
 void OffscreenCanvasRenderingContext2D::setTextRendering(
     const String& text_rendering_string) {
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
   if (!GetState().HasRealizedFont())
     setFont(font());
 
@@ -519,6 +529,8 @@
 
 void OffscreenCanvasRenderingContext2D::setFontKerning(
     const String& font_kerning_string) {
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
   if (!GetState().HasRealizedFont())
     setFont(font());
   FontDescription::Kerning kerning;
@@ -540,6 +552,8 @@
 
 void OffscreenCanvasRenderingContext2D::setFontStretch(
     const String& font_stretch) {
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
   if (!GetState().HasRealizedFont())
     setFont(font());
 
@@ -574,6 +588,8 @@
 
 void OffscreenCanvasRenderingContext2D::setFontVariantCaps(
     const String& font_variant_caps_string) {
+  // TODO(crbug.com/1234113): Instrument new canvas APIs.
+  identifiability_study_helper_.set_encountered_skipped_ops();
   if (!GetState().HasRealizedFont())
     setFont(font());
   FontDescription::FontVariantCaps variant_caps;
@@ -644,13 +660,15 @@
   if (max_width && (!std::isfinite(*max_width) || *max_width <= 0))
     return;
 
-  identifiability_study_helper_.MaybeUpdateBuilder(
-      paint_type == CanvasRenderingContext2DState::kFillPaintType
-          ? CanvasOps::kFillText
-          : CanvasOps::kStrokeText,
-      IdentifiabilitySensitiveStringToken(text), x, y,
-      max_width ? *max_width : -1);
-  identifiability_study_helper_.set_encountered_sensitive_ops();
+  if (identifiability_study_helper_.ShouldUpdateBuilder()) {
+    identifiability_study_helper_.UpdateBuilder(
+        paint_type == CanvasRenderingContext2DState::kFillPaintType
+            ? CanvasOps::kFillText
+            : CanvasOps::kStrokeText,
+        IdentifiabilitySensitiveStringToken(text), x, y,
+        max_width ? *max_width : -1);
+    identifiability_study_helper_.set_encountered_sensitive_ops();
+  }
 
   const Font& font = AccessFont();
   const SimpleFontData* font_data = font.PrimaryFont();
diff --git a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.h b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.h
index 44a038e..4d7270f 100644
--- a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.h
+++ b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.h
@@ -154,6 +154,10 @@
     return identifiability_study_helper_.encountered_sensitive_ops();
   }
 
+  bool IdentifiabilityEncounteredPartiallyDigestedImage() const override {
+    return identifiability_study_helper_.encountered_partially_digested_image();
+  }
+
  protected:
   // This reports CanvasColorParams to the CanvasRenderingContext interface.
   CanvasColorParams CanvasRenderingContextColorParams() const override {
diff --git a/third_party/blink/renderer/modules/csspaint/document_paint_definition_test.cc b/third_party/blink/renderer/modules/csspaint/document_paint_definition_test.cc
index 99c36c8..5249c810 100644
--- a/third_party/blink/renderer/modules/csspaint/document_paint_definition_test.cc
+++ b/third_party/blink/renderer/modules/csspaint/document_paint_definition_test.cc
@@ -30,7 +30,7 @@
                                               custom_invalidation_properties,
                                               input_argument_types, true);
   EXPECT_EQ(document_definition.NativeInvalidationProperties().size(), 3u);
-  for (size_t i = 0; i < 3; i++) {
+  for (wtf_size_t i = 0; i < 3; i++) {
     EXPECT_EQ(native_invalidation_properties[i],
               document_definition.NativeInvalidationProperties()[i]);
   }
@@ -48,7 +48,7 @@
                                               custom_invalidation_properties,
                                               input_argument_types, true);
   EXPECT_EQ(document_definition.CustomInvalidationProperties().size(), 2u);
-  for (size_t i = 0; i < 2; i++) {
+  for (wtf_size_t i = 0; i < 2; i++) {
     EXPECT_EQ(custom_invalidation_properties[i],
               document_definition.CustomInvalidationProperties()[i]);
   }
@@ -82,7 +82,7 @@
                                               input_argument_types, true);
 
   EXPECT_EQ(document_definition.InputArgumentTypes().size(), 2u);
-  for (size_t i = 0; i < 2; i++) {
+  for (wtf_size_t i = 0; i < 2; i++) {
     EXPECT_EQ(input_argument_types[i],
               document_definition.InputArgumentTypes()[i]);
   }
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client_test.cc b/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client_test.cc
index d1e4d1be..c1a9d85 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client_test.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_proxy_client_test.cc
@@ -89,13 +89,13 @@
     // create multiple WorkerThread for this. Note that the underlying thread
     // may be shared even though they are unique WorkerThread instances!
     Vector<std::unique_ptr<WorkerThread>> worklet_threads;
-    for (size_t i = 0; i < PaintWorklet::kNumGlobalScopesPerThread; i++) {
+    for (wtf_size_t i = 0; i < PaintWorklet::kNumGlobalScopesPerThread; i++) {
       worklet_threads.push_back(CreateThreadAndProvidePaintWorkletProxyClient(
           &GetDocument(), reporting_proxy_.get(), proxy_client_));
     }
 
     // Add the global scopes. This must happen on the worklet thread.
-    for (size_t i = 0; i < PaintWorklet::kNumGlobalScopesPerThread; i++) {
+    for (wtf_size_t i = 0; i < PaintWorklet::kNumGlobalScopesPerThread; i++) {
       base::WaitableEvent waitable_event;
       PostCrossThreadTask(
           *worklet_threads[i]->GetTaskRunner(TaskType::kInternalTest),
@@ -121,7 +121,7 @@
     waitable_event.Wait();
 
     // And finally clean up.
-    for (size_t i = 0; i < PaintWorklet::kNumGlobalScopesPerThread; i++) {
+    for (wtf_size_t i = 0; i < PaintWorklet::kNumGlobalScopesPerThread; i++) {
       worklet_threads[i]->Terminate();
       worklet_threads[i]->WaitForShutdownForTesting();
     }
@@ -356,7 +356,7 @@
   // end up posting a task to the PaintWorklet.
   const Vector<CrossThreadPersistent<PaintWorkletGlobalScope>>& global_scopes =
       proxy_client->GetGlobalScopesForTesting();
-  for (size_t i = 0; i < global_scopes.size() - 1; i++) {
+  for (wtf_size_t i = 0; i < global_scopes.size() - 1; i++) {
     ClassicScript::CreateUnspecifiedScript(
         ScriptSourceCode("registerPaint('foo', class { paint() { } });"))
         ->RunScriptOnWorkerOrWorklet(*global_scopes[i]);
diff --git a/third_party/blink/renderer/modules/csspaint/paint_worklet_test.cc b/third_party/blink/renderer/modules/csspaint/paint_worklet_test.cc
index 5de58c9..c8e5230 100644
--- a/third_party/blink/renderer/modules/csspaint/paint_worklet_test.cc
+++ b/third_party/blink/renderer/modules/csspaint/paint_worklet_test.cc
@@ -252,7 +252,7 @@
   EXPECT_CALL(*observer, PaintImageGeneratorReady).Times(0);
 
   Vector<Persistent<PaintWorkletGlobalScope>> global_scopes;
-  for (size_t i = 0; i < PaintWorklet::kNumGlobalScopesPerThread; ++i) {
+  for (wtf_size_t i = 0; i < PaintWorklet::kNumGlobalScopesPerThread; ++i) {
     paint_worklet_to_test->AddGlobalScopeForTesting();
     global_scopes.push_back(
         PaintWorkletGlobalScopeProxy::From(
@@ -360,7 +360,7 @@
   EXPECT_CALL(*observer, PaintImageGeneratorReady).Times(0);
 
   Vector<Persistent<PaintWorkletGlobalScope>> global_scopes;
-  for (size_t i = 0; i < PaintWorklet::kNumGlobalScopesPerThread; ++i) {
+  for (wtf_size_t i = 0; i < PaintWorklet::kNumGlobalScopesPerThread; ++i) {
     paint_worklet_to_test->AddGlobalScopeForTesting();
     global_scopes.push_back(
         PaintWorkletGlobalScopeProxy::From(
@@ -517,7 +517,7 @@
   EXPECT_CALL(*observer, PaintImageGeneratorReady).Times(0);
 
   Vector<Persistent<PaintWorkletGlobalScope>> global_scopes;
-  for (size_t i = 0; i < PaintWorklet::kNumGlobalScopesPerThread; ++i) {
+  for (wtf_size_t i = 0; i < PaintWorklet::kNumGlobalScopesPerThread; ++i) {
     paint_worklet_to_test->AddGlobalScopeForTesting();
     global_scopes.push_back(
         PaintWorkletGlobalScopeProxy::From(
diff --git a/third_party/blink/renderer/modules/indexeddb/indexed_db_blink_mojom_traits_test.cc b/third_party/blink/renderer/modules/indexeddb/indexed_db_blink_mojom_traits_test.cc
index 3654f6d..49ac837 100644
--- a/third_party/blink/renderer/modules/indexeddb/indexed_db_blink_mojom_traits_test.cc
+++ b/third_party/blink/renderer/modules/indexeddb/indexed_db_blink_mojom_traits_test.cc
@@ -25,7 +25,7 @@
 TEST(IDBMojomTraitsTest, IDBKeyBinary) {
   // Generate test data.
   std::mt19937 rng(5);
-  size_t test_data_size = 10000;
+  wtf_size_t test_data_size = 10000;
   Vector<char> test_data(test_data_size);
   std::generate(test_data.begin(), test_data.end(), rng);
   scoped_refptr<SharedBuffer> input_data =
@@ -56,7 +56,7 @@
 TEST(IDBMojomTraitsTest, IDBValue) {
   // Generate test data.
   std::mt19937 rng(5);
-  size_t test_data_size = 10000;
+  wtf_size_t test_data_size = 10000;
   Vector<char> test_data(test_data_size);
   std::generate(test_data.begin(), test_data.end(), rng);
   scoped_refptr<SharedBuffer> input_data =
diff --git a/third_party/blink/renderer/modules/installedapp/installed_app_controller.cc b/third_party/blink/renderer/modules/installedapp/installed_app_controller.cc
index cc97909..0e37d959 100644
--- a/third_party/blink/renderer/modules/installedapp/installed_app_controller.cc
+++ b/third_party/blink/renderer/modules/installedapp/installed_app_controller.cc
@@ -8,7 +8,6 @@
 
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
-#include "third_party/blink/public/common/manifest/manifest.h"
 #include "third_party/blink/public/mojom/installedapp/related_application.mojom-blink.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom-blink.h"
 #include "third_party/blink/public/platform/web_string.h"
diff --git a/third_party/blink/renderer/modules/manifest/image_resource_type_converters.h b/third_party/blink/renderer/modules/manifest/image_resource_type_converters.h
index 20c7b45c..ba7ed28c 100644
--- a/third_party/blink/renderer/modules/manifest/image_resource_type_converters.h
+++ b/third_party/blink/renderer/modules/manifest/image_resource_type_converters.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_MANIFEST_IMAGE_RESOURCE_TYPE_CONVERTERS_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_MANIFEST_IMAGE_RESOURCE_TYPE_CONVERTERS_H_
 
+#include "mojo/public/cpp/bindings/type_converter.h"
 #include "third_party/blink/public/common/manifest/manifest.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom-blink-forward.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
diff --git a/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager_test.cc b/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager_test.cc
index 4d13e66..aeca5c9 100644
--- a/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager_test.cc
+++ b/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager_test.cc
@@ -110,7 +110,7 @@
   }
 
   // Number of instantiated mixers.
-  int mixer_count() { return manager_->mixers_.size(); }
+  size_t mixer_count() { return manager_->mixers_.size(); }
 
  protected:
   scoped_refptr<media::MockAudioRendererSink> GetSink(
@@ -154,7 +154,7 @@
 // respect to the explicit ref counting done.
 TEST_F(AudioRendererMixerManagerTest, GetReturnMixer) {
   // There should be no mixers outstanding to start with.
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 
   media::AudioParameters params1(media::AudioParameters::AUDIO_PCM_LINEAR,
                                  kChannelLayout, kSampleRate, kBufferSize);
@@ -163,17 +163,17 @@
       GetMixer(kFrameToken, params1, AudioLatency::LATENCY_PLAYBACK,
                kDefaultDeviceId, SinkUseState::kNewSink);
   ASSERT_TRUE(mixer1);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
 
   // The same parameters should return the same mixer1.
   EXPECT_EQ(mixer1,
             GetMixer(kFrameToken, params1, AudioLatency::LATENCY_PLAYBACK,
                      kDefaultDeviceId, SinkUseState::kExistingSink));
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
 
   // Return the extra mixer we just acquired.
   ReturnMixer(mixer1);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
 
   media::AudioParameters params2(AudioParameters::AUDIO_PCM_LINEAR,
                                  kAnotherChannelLayout, kSampleRate * 2,
@@ -182,22 +182,22 @@
       GetMixer(kFrameToken, params2, AudioLatency::LATENCY_PLAYBACK,
                kDefaultDeviceId, SinkUseState::kNewSink);
   ASSERT_TRUE(mixer2);
-  EXPECT_EQ(2, mixer_count());
+  EXPECT_EQ(2u, mixer_count());
 
   // Different parameters should result in a different mixer1.
   EXPECT_NE(mixer1, mixer2);
 
   // Return both outstanding mixers.
   ReturnMixer(mixer1);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
   ReturnMixer(mixer2);
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 }
 
 // Verify GetMixer() correctly deduplicates mixer with irrelevant AudioParameter
 // differences.
 TEST_F(AudioRendererMixerManagerTest, MixerReuse) {
-  EXPECT_EQ(mixer_count(), 0);
+  EXPECT_EQ(0u, mixer_count());
 
   media::AudioParameters params1(AudioParameters::AUDIO_PCM_LINEAR,
                                  kChannelLayout, kSampleRate, kBufferSize);
@@ -205,7 +205,7 @@
       GetMixer(kFrameToken, params1, AudioLatency::LATENCY_PLAYBACK,
                kDefaultDeviceId, SinkUseState::kNewSink);
   ASSERT_TRUE(mixer1);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
 
   // Different sample rates, formats, bit depths, and buffer sizes should not
   // result in a different mixer.
@@ -216,9 +216,9 @@
       GetMixer(kFrameToken, params2, AudioLatency::LATENCY_PLAYBACK,
                kDefaultDeviceId, SinkUseState::kExistingSink);
   EXPECT_EQ(mixer1, mixer2);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
   ReturnMixer(mixer2);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
 
   // Modify some parameters that do matter: channel layout
   media::AudioParameters params3(AudioParameters::AUDIO_PCM_LOW_LATENCY,
@@ -229,13 +229,13 @@
       GetMixer(kFrameToken, params3, AudioLatency::LATENCY_PLAYBACK,
                kDefaultDeviceId, SinkUseState::kNewSink);
   EXPECT_NE(mixer1, mixer3);
-  EXPECT_EQ(2, mixer_count());
+  EXPECT_EQ(2u, mixer_count());
   ReturnMixer(mixer3);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
 
   // Return final mixer.
   ReturnMixer(mixer1);
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 }
 
 // Verify CreateInput() provides AudioRendererMixerInput with the appropriate
@@ -247,14 +247,14 @@
                                 kChannelLayout, kSampleRate, kBufferSize);
 
   // Create two mixer inputs and ensure this doesn't instantiate any mixers yet.
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
   media::FakeAudioRenderCallback callback(0, kSampleRate);
   mock_sink_ = CreateNormalSink();
   EXPECT_CALL(*mock_sink_, Start()).Times(1);
   auto input =
       CreateInputHelper(kFrameToken, base::UnguessableToken(), kDefaultDeviceId,
                         AudioLatency::LATENCY_PLAYBACK, params, &callback);
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
   media::FakeAudioRenderCallback another_callback(1, kSampleRate);
 
   EXPECT_FALSE(!!mock_sink_);
@@ -263,22 +263,22 @@
   auto another_input = CreateInputHelper(
       kAnotherFrameToken, base::UnguessableToken(), kDefaultDeviceId,
       AudioLatency::LATENCY_PLAYBACK, params, &another_callback);
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 
   // Implicitly test that AudioRendererMixerInput was provided with the expected
   // callbacks needed to acquire an AudioRendererMixer and return it.
   input->Start();
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
   another_input->Start();
-  EXPECT_EQ(2, mixer_count());
+  EXPECT_EQ(2u, mixer_count());
 
   // Destroying the inputs should destroy the mixers.
   input->Stop();
   input = nullptr;
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
   another_input->Stop();
   another_input = nullptr;
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 }
 
 // Verify CreateInput() provided with session id creates AudioRendererMixerInput
@@ -291,68 +291,68 @@
   media::AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR,
                                 kChannelLayout, kSampleRate, kBufferSize);
   media::FakeAudioRenderCallback callback(0, kSampleRate);
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 
   // Empty device id, zero session id;
   auto input_to_default_device = CreateInputHelper(
       kFrameToken, base::UnguessableToken(),  // session_id
       std::string(), AudioLatency::LATENCY_PLAYBACK, params, &callback);
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 
   // Specific device id, zero session id;
   auto input_to_another_device = CreateInputHelper(
       kFrameToken, base::UnguessableToken(),  // session_id
       kMatchedDeviceId, AudioLatency::LATENCY_PLAYBACK, params, &callback);
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 
   // Specific device id, non-zero session id (to be ignored);
   auto input_to_matched_device = CreateInputHelper(
       kFrameToken,
       base::UnguessableToken::Create(),  // session id
       kAnotherDeviceId, AudioLatency::LATENCY_PLAYBACK, params, &callback);
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 
   // Empty device id, non-zero session id;
   auto input_to_matched_device_with_session_id = CreateInputHelper(
       kFrameToken,
       base::UnguessableToken::Create(),  // session id
       std::string(), AudioLatency::LATENCY_PLAYBACK, params, &callback);
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 
   // Implicitly test that AudioRendererMixerInput was provided with the expected
   // callbacks needed to acquire an AudioRendererMixer and return it.
   input_to_default_device->Start();
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
 
   input_to_another_device->Start();
-  EXPECT_EQ(2, mixer_count());
+  EXPECT_EQ(2u, mixer_count());
 
   input_to_matched_device->Start();
-  EXPECT_EQ(3, mixer_count());
+  EXPECT_EQ(3u, mixer_count());
 
   // Should go to the same device as the input above.
   input_to_matched_device_with_session_id->Start();
-  EXPECT_EQ(3, mixer_count());
+  EXPECT_EQ(3u, mixer_count());
 
   // Destroying the inputs should destroy the mixers.
   input_to_default_device->Stop();
   input_to_default_device = nullptr;
-  EXPECT_EQ(2, mixer_count());
+  EXPECT_EQ(2u, mixer_count());
   input_to_another_device->Stop();
   input_to_another_device = nullptr;
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
   input_to_matched_device->Stop();
   input_to_matched_device = nullptr;
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
   input_to_matched_device_with_session_id->Stop();
   input_to_matched_device_with_session_id = nullptr;
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 }
 
 // Verify GetMixer() correctly creates different mixers with the same
 // parameters, but different device ID.
 TEST_F(AudioRendererMixerManagerTest, MixerDevices) {
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 
   media::AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR,
                                 kChannelLayout, kSampleRate, kBufferSize);
@@ -360,25 +360,25 @@
       GetMixer(kFrameToken, params, AudioLatency::LATENCY_PLAYBACK,
                kDefaultDeviceId, SinkUseState::kNewSink);
   ASSERT_TRUE(mixer1);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
 
   media::AudioRendererMixer* mixer2 =
       GetMixer(kFrameToken, params, AudioLatency::LATENCY_PLAYBACK,
                kAnotherDeviceId, SinkUseState::kNewSink);
   ASSERT_TRUE(mixer2);
-  EXPECT_EQ(2, mixer_count());
+  EXPECT_EQ(2u, mixer_count());
   EXPECT_NE(mixer1, mixer2);
 
   ReturnMixer(mixer1);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
   ReturnMixer(mixer2);
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 }
 
 // Verify GetMixer() correctly deduplicate mixers with the same
 // parameters and default device ID, even if one is "" and one is "default".
 TEST_F(AudioRendererMixerManagerTest, OneMixerDifferentDefaultDeviceIDs) {
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 
   media::AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR,
                                 kChannelLayout, kSampleRate, kBufferSize);
@@ -386,25 +386,25 @@
       GetMixer(kFrameToken, params, AudioLatency::LATENCY_PLAYBACK,
                kDefaultDeviceId, SinkUseState::kNewSink);
   ASSERT_TRUE(mixer1);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
 
   media::AudioRendererMixer* mixer2 =
       GetMixer(kFrameToken, params, AudioLatency::LATENCY_PLAYBACK,
                std::string(), SinkUseState::kExistingSink);
   ASSERT_TRUE(mixer2);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
   EXPECT_EQ(mixer1, mixer2);
 
   ReturnMixer(mixer1);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
   ReturnMixer(mixer2);
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 }
 
 // Verify that GetMixer() correctly returns a null mixer and an appropriate
 // status code when a nonexistent device is requested.
 TEST_F(AudioRendererMixerManagerTest, NonexistentDevice) {
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 
   media::AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR,
                                 kChannelLayout, kSampleRate, kBufferSize);
@@ -416,13 +416,13 @@
 
   EXPECT_EQ(media::OUTPUT_DEVICE_STATUS_ERROR_NOT_FOUND,
             device_info.device_status());
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 }
 
 // Verify GetMixer() correctly deduplicate mixers basing on latency
 // requirements.
 TEST_F(AudioRendererMixerManagerTest, LatencyMixing) {
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 
   media::AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR,
                                 kChannelLayout, kSampleRate, kBufferSize);
@@ -430,58 +430,58 @@
       GetMixer(kFrameToken, params, AudioLatency::LATENCY_PLAYBACK,
                kDefaultDeviceId, SinkUseState::kNewSink);
   ASSERT_TRUE(mixer1);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
 
   media::AudioRendererMixer* mixer2 =
       GetMixer(kFrameToken, params, AudioLatency::LATENCY_PLAYBACK,
                kDefaultDeviceId, SinkUseState::kExistingSink);
   ASSERT_TRUE(mixer2);
   EXPECT_EQ(mixer1, mixer2);  // Same latency => same mixer.
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
 
   media::AudioRendererMixer* mixer3 =
       GetMixer(kFrameToken, params, AudioLatency::LATENCY_RTC, kDefaultDeviceId,
                SinkUseState::kNewSink);
   ASSERT_TRUE(mixer3);
   EXPECT_NE(mixer1, mixer3);
-  EXPECT_EQ(2, mixer_count());  // Another latency => another mixer.
+  EXPECT_EQ(2u, mixer_count());  // Another latency => another mixer.
 
   media::AudioRendererMixer* mixer4 =
       GetMixer(kFrameToken, params, AudioLatency::LATENCY_RTC, kDefaultDeviceId,
                SinkUseState::kExistingSink);
   EXPECT_EQ(mixer3, mixer4);
-  EXPECT_EQ(2, mixer_count());  // Same latency => same mixer.
+  EXPECT_EQ(2u, mixer_count());  // Same latency => same mixer.
 
   media::AudioRendererMixer* mixer5 =
       GetMixer(kFrameToken, params, AudioLatency::LATENCY_INTERACTIVE,
                kDefaultDeviceId, SinkUseState::kNewSink);
   ASSERT_TRUE(mixer5);
-  EXPECT_EQ(3, mixer_count());  // Another latency => another mixer.
+  EXPECT_EQ(3u, mixer_count());  // Another latency => another mixer.
 
   media::AudioRendererMixer* mixer6 =
       GetMixer(kFrameToken, params, AudioLatency::LATENCY_INTERACTIVE,
                kDefaultDeviceId, SinkUseState::kExistingSink);
   EXPECT_EQ(mixer5, mixer6);
-  EXPECT_EQ(3, mixer_count());  // Same latency => same mixer.
+  EXPECT_EQ(3u, mixer_count());  // Same latency => same mixer.
 
   ReturnMixer(mixer1);
-  EXPECT_EQ(3, mixer_count());
+  EXPECT_EQ(3u, mixer_count());
   ReturnMixer(mixer2);
-  EXPECT_EQ(2, mixer_count());
+  EXPECT_EQ(2u, mixer_count());
   ReturnMixer(mixer3);
-  EXPECT_EQ(2, mixer_count());
+  EXPECT_EQ(2u, mixer_count());
   ReturnMixer(mixer4);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
   ReturnMixer(mixer5);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
   ReturnMixer(mixer6);
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 }
 
 // Verify GetMixer() correctly deduplicate mixers basing on the effects
 // requirements.
 TEST_F(AudioRendererMixerManagerTest, EffectsMixing) {
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 
   media::AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR,
                                 kChannelLayout, kSampleRate, kBufferSize);
@@ -490,14 +490,14 @@
       GetMixer(kFrameToken, params, AudioLatency::LATENCY_PLAYBACK,
                kDefaultDeviceId, SinkUseState::kNewSink);
   ASSERT_TRUE(mixer1);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
 
   media::AudioRendererMixer* mixer2 =
       GetMixer(kFrameToken, params, AudioLatency::LATENCY_PLAYBACK,
                kDefaultDeviceId, SinkUseState::kExistingSink);
   ASSERT_TRUE(mixer2);
   EXPECT_EQ(mixer1, mixer2);  // Same effects => same mixer.
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
 
   params.set_effects(2);
   media::AudioRendererMixer* mixer3 =
@@ -505,39 +505,39 @@
                kDefaultDeviceId, SinkUseState::kNewSink);
   ASSERT_TRUE(mixer3);
   EXPECT_NE(mixer1, mixer3);
-  EXPECT_EQ(2, mixer_count());  // Another effects => another mixer.
+  EXPECT_EQ(2u, mixer_count());  // Another effects => another mixer.
 
   media::AudioRendererMixer* mixer4 =
       GetMixer(kFrameToken, params, AudioLatency::LATENCY_PLAYBACK,
                kDefaultDeviceId, SinkUseState::kExistingSink);
   EXPECT_EQ(mixer3, mixer4);
-  EXPECT_EQ(2, mixer_count());  // Same effects => same mixer.
+  EXPECT_EQ(2u, mixer_count());  // Same effects => same mixer.
 
   params.set_effects(3);
   media::AudioRendererMixer* mixer5 =
       GetMixer(kFrameToken, params, AudioLatency::LATENCY_PLAYBACK,
                kDefaultDeviceId, SinkUseState::kNewSink);
   ASSERT_TRUE(mixer5);
-  EXPECT_EQ(3, mixer_count());  // Another effects => another mixer.
+  EXPECT_EQ(3u, mixer_count());  // Another effects => another mixer.
 
   media::AudioRendererMixer* mixer6 =
       GetMixer(kFrameToken, params, AudioLatency::LATENCY_PLAYBACK,
                kDefaultDeviceId, SinkUseState::kExistingSink);
   EXPECT_EQ(mixer5, mixer6);
-  EXPECT_EQ(3, mixer_count());  // Same effects => same mixer.
+  EXPECT_EQ(3u, mixer_count());  // Same effects => same mixer.
 
   ReturnMixer(mixer1);
-  EXPECT_EQ(3, mixer_count());
+  EXPECT_EQ(3u, mixer_count());
   ReturnMixer(mixer2);
-  EXPECT_EQ(2, mixer_count());
+  EXPECT_EQ(2u, mixer_count());
   ReturnMixer(mixer3);
-  EXPECT_EQ(2, mixer_count());
+  EXPECT_EQ(2u, mixer_count());
   ReturnMixer(mixer4);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
   ReturnMixer(mixer5);
-  EXPECT_EQ(1, mixer_count());
+  EXPECT_EQ(1u, mixer_count());
   ReturnMixer(mixer6);
-  EXPECT_EQ(0, mixer_count());
+  EXPECT_EQ(0u, mixer_count());
 }
 
 // Verify output bufer size of the mixer is correctly adjusted for Playback
diff --git a/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache_test.cc b/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache_test.cc
index 522049e02..cdbe7d0 100644
--- a/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache_test.cc
+++ b/third_party/blink/renderer/modules/media/audio/audio_renderer_sink_cache_test.cc
@@ -54,7 +54,7 @@
   }
 
  protected:
-  int sink_count() {
+  size_t sink_count() {
     DCHECK(task_runner_->BelongsToCurrentThread());
     return cache_->GetCacheSizeForTesting();
   }
@@ -108,19 +108,19 @@
 // Verify that normal get/release sink sequence works.
 TEST_F(AudioRendererSinkCacheTest, GetReleaseSink) {
   // Verify that a new sink is successfully created.
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
   scoped_refptr<media::AudioRendererSink> sink =
       cache_->GetSink(kFrameToken, kDefaultDeviceId).get();
   ExpectNotToStop(sink.get());  // Cache should not stop sinks marked as used.
   EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
 
   // Verify that another sink with the same key is successfully created
   scoped_refptr<media::AudioRendererSink> another_sink =
       cache_->GetSink(kFrameToken, kDefaultDeviceId).get();
   ExpectNotToStop(another_sink.get());
   EXPECT_EQ(kDefaultDeviceId, another_sink->GetOutputDeviceInfo().device_id());
-  EXPECT_EQ(2, sink_count());
+  EXPECT_EQ(2u, sink_count());
   EXPECT_NE(sink, another_sink);
 
   // Verify that another sink with a different kay is successfully created.
@@ -129,13 +129,13 @@
   ExpectNotToStop(yet_another_sink.get());
   EXPECT_EQ(kAnotherDeviceId,
             yet_another_sink->GetOutputDeviceInfo().device_id());
-  EXPECT_EQ(3, sink_count());
+  EXPECT_EQ(3u, sink_count());
   EXPECT_NE(sink, yet_another_sink);
   EXPECT_NE(another_sink, yet_another_sink);
 
   // Verify that the first sink is successfully deleted.
   cache_->ReleaseSink(sink.get());
-  EXPECT_EQ(2, sink_count());
+  EXPECT_EQ(2u, sink_count());
   sink = nullptr;
 
   // Make sure we deleted the right sink, and the memory for the rest is not
@@ -146,77 +146,77 @@
 
   // Verify that the second sink is successfully deleted.
   cache_->ReleaseSink(another_sink.get());
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
   EXPECT_EQ(kAnotherDeviceId,
             yet_another_sink->GetOutputDeviceInfo().device_id());
 
   cache_->ReleaseSink(yet_another_sink.get());
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
 }
 
 // Verify that the sink created with GetSinkInfo() is reused when possible.
 TEST_F(AudioRendererSinkCacheTest, GetDeviceInfo) {
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
   media::OutputDeviceInfo device_info = cache_->GetSinkInfo(
       kFrameToken, base::UnguessableToken(), kDefaultDeviceId);
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
 
   // The info on the same device is requested, so no new sink is created.
   media::OutputDeviceInfo one_more_device_info = cache_->GetSinkInfo(
       kFrameToken, base::UnguessableToken(), kDefaultDeviceId);
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
   EXPECT_EQ(device_info.device_id(), one_more_device_info.device_id());
 
   // Aquire the sink that was created on GetSinkInfo().
   scoped_refptr<media::AudioRendererSink> sink =
       cache_->GetSink(kFrameToken, kDefaultDeviceId).get();
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
   EXPECT_EQ(device_info.device_id(), sink->GetOutputDeviceInfo().device_id());
 
   // Now the sink is in used, but we can still get the device info out of it, no
   // new sink is created.
   one_more_device_info = cache_->GetSinkInfo(
       kFrameToken, base::UnguessableToken(), kDefaultDeviceId);
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
   EXPECT_EQ(device_info.device_id(), one_more_device_info.device_id());
 
   // Request sink for the same device. The first sink is in use, so a new one
   // should be created.
   scoped_refptr<media::AudioRendererSink> another_sink =
       cache_->GetSink(kFrameToken, kDefaultDeviceId).get();
-  EXPECT_EQ(2, sink_count());
+  EXPECT_EQ(2u, sink_count());
   EXPECT_EQ(device_info.device_id(),
             another_sink->GetOutputDeviceInfo().device_id());
 }
 
 // Verify that the sink created with GetSinkInfo() is deleted if unused.
 TEST_F(AudioRendererSinkCacheTest, GarbageCollection) {
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
 
   media::OutputDeviceInfo device_info = cache_->GetSinkInfo(
       kFrameToken, base::UnguessableToken(), kDefaultDeviceId);
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
 
   media::OutputDeviceInfo another_device_info = cache_->GetSinkInfo(
       kFrameToken, base::UnguessableToken(), kAnotherDeviceId);
-  EXPECT_EQ(2, sink_count());
+  EXPECT_EQ(2u, sink_count());
 
   // Wait for garbage collection. Doesn't actually sleep, just advances the mock
   // clock.
   task_runner_->FastForwardBy(kDeleteTimeout);
 
   // All the sinks should be garbage-collected by now.
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
 }
 
 // Verify that the sink created with GetSinkInfo() is not deleted if used within
 // the timeout.
 TEST_F(AudioRendererSinkCacheTest, NoGarbageCollectionForUsedSink) {
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
 
   media::OutputDeviceInfo device_info = cache_->GetSinkInfo(
       kFrameToken, base::UnguessableToken(), kDefaultDeviceId);
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
 
   // Wait less than garbage collection timeout.
   base::TimeDelta wait_a_bit =
@@ -224,31 +224,31 @@
   task_runner_->FastForwardBy(wait_a_bit);
 
   // Sink is not deleted yet.
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
 
   // Request it:
   scoped_refptr<media::AudioRendererSink> sink =
       cache_->GetSink(kFrameToken, kDefaultDeviceId).get();
   EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
 
   // Wait more to hit garbage collection timeout.
   task_runner_->FastForwardBy(kDeleteTimeout);
 
   // The sink is still in place.
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
 }
 
 // Verify that the sink created with GetSinkInfo() is not cached if it is
 // unhealthy.
 TEST_F(AudioRendererSinkCacheTest, UnhealthySinkIsNotCached) {
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
   media::OutputDeviceInfo device_info = cache_->GetSinkInfo(
       kFrameToken, base::UnguessableToken(), kUnhealthyDeviceId);
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
   scoped_refptr<media::AudioRendererSink> sink =
       cache_->GetSink(kFrameToken, kUnhealthyDeviceId).get();
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
 }
 
 // Verify that a sink created with GetSinkInfo() is stopped even if it's
@@ -310,39 +310,39 @@
 // Verify that cache works fine if a sink scheduled for deletion is acquired and
 // released before deletion timeout elapses.
 TEST_F(AudioRendererSinkCacheTest, ReleaseSinkBeforeScheduledDeletion) {
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
 
   base::Thread thread("timeout_thread");
   thread.Start();
 
   media::OutputDeviceInfo device_info = cache_->GetSinkInfo(
       kFrameToken, base::UnguessableToken(), kDefaultDeviceId);
-  EXPECT_EQ(1, sink_count());  // This sink is scheduled for deletion now.
+  EXPECT_EQ(1u, sink_count());  // This sink is scheduled for deletion now.
 
   // Request it:
   scoped_refptr<media::AudioRendererSink> sink =
       cache_->GetSink(kFrameToken, kDefaultDeviceId).get();
   ExpectNotToStop(sink.get());
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
 
   // Release it:
   cache_->ReleaseSink(sink.get());
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
 
   media::OutputDeviceInfo another_device_info = cache_->GetSinkInfo(
       kFrameToken, base::UnguessableToken(), kAnotherDeviceId);
-  EXPECT_EQ(1, sink_count());  // This sink is scheduled for deletion now.
+  EXPECT_EQ(1u, sink_count());  // This sink is scheduled for deletion now.
 
   task_runner_->FastForwardBy(kDeleteTimeout);
 
   // Nothing crashed and the second sink deleted on schedule.
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
 }
 
 // Check that a sink created on one thread in response to GetSinkInfo can be
 // used on another thread.
 TEST_F(AudioRendererSinkCacheTest, MultithreadedAccess) {
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
 
   base::Thread thread1("thread1");
   thread1.Start();
@@ -357,7 +357,7 @@
                      base::Unretained(cache_.get()), kFrameToken,
                      base::UnguessableToken(), kDefaultDeviceId));
 
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
 
   // Request the sink on the second thread.
   media::AudioRendererSink* sink;
@@ -368,7 +368,7 @@
                                       kDefaultDeviceId, &sink));
 
   EXPECT_EQ(kDefaultDeviceId, sink->GetOutputDeviceInfo().device_id());
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
 
   // Request device information on the first thread again.
   PostAndWaitUntilDone(
@@ -376,7 +376,7 @@
       base::BindOnce(base::IgnoreResult(&AudioRendererSinkCache::GetSinkInfo),
                      base::Unretained(cache_.get()), kFrameToken,
                      base::UnguessableToken(), kDefaultDeviceId));
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
 
   // Release the sink on the second thread.
   PostAndWaitUntilDone(
@@ -384,37 +384,37 @@
       base::BindOnce(&AudioRendererSinkCache::ReleaseSink,
                      base::Unretained(cache_.get()), base::RetainedRef(sink)));
 
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
 }
 
 TEST_F(AudioRendererSinkCacheTest, StopsAndDropsSinks) {
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
   scoped_refptr<media::AudioRendererSink> sink1 =
       cache_->GetSink(kFrameToken, "device1").get();
   scoped_refptr<media::AudioRendererSink> sink2 =
       cache_->GetSink(kFrameToken, "device2").get();
-  EXPECT_EQ(2, sink_count());
+  EXPECT_EQ(2u, sink_count());
 
   EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink1.get()), Stop());
   EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink2.get()), Stop());
   DropSinksForFrame(kFrameToken);
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
 }
 
 TEST_F(AudioRendererSinkCacheTest, StopsAndDropsCorrectSinks) {
-  EXPECT_EQ(0, sink_count());
+  EXPECT_EQ(0u, sink_count());
   scoped_refptr<media::AudioRendererSink> sink1 =
       cache_->GetSink(kFrameToken, "device1").get();
   scoped_refptr<media::AudioRendererSink> another_sink =
       cache_->GetSink(LocalFrameToken(), "device1").get();
   scoped_refptr<media::AudioRendererSink> sink2 =
       cache_->GetSink(kFrameToken, "device2").get();
-  EXPECT_EQ(3, sink_count());
+  EXPECT_EQ(3u, sink_count());
 
   EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink1.get()), Stop());
   EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(sink2.get()), Stop());
   DropSinksForFrame(kFrameToken);
-  EXPECT_EQ(1, sink_count());
+  EXPECT_EQ(1u, sink_count());
   EXPECT_CALL(*static_cast<media::MockAudioRendererSink*>(another_sink.get()),
               Stop());
 }
diff --git a/third_party/blink/renderer/modules/media/audio/web_audio_output_ipc_factory_test.cc b/third_party/blink/renderer/modules/media/audio/web_audio_output_ipc_factory_test.cc
index 66884af..23cd436 100644
--- a/third_party/blink/renderer/modules/media/audio/web_audio_output_ipc_factory_test.cc
+++ b/third_party/blink/renderer/modules/media/audio/web_audio_output_ipc_factory_test.cc
@@ -179,7 +179,7 @@
 
   WebAudioOutputIPCFactory ipc_factory(io_thread->task_runner());
 
-  for (size_t i = 0; i < n_factories; i++) {
+  for (int i = 0; i < n_factories; i++) {
     ipc_factory.RegisterRemoteFactory(TokenFromInt(kRenderFrameId + i),
                                       &interface_broker);
   }
@@ -207,7 +207,7 @@
           ipc_factory.CreateAudioOutputIPC(TokenFromInt(kRenderFrameId + 2))));
   run_loop2.Run();
 
-  for (size_t i = 0; i < n_factories; i++) {
+  for (int i = 0; i < n_factories; i++) {
     if (i == 1)
       continue;
     ipc_factory.MaybeDeregisterRemoteFactory(TokenFromInt(i));
diff --git a/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder_unittest.cc b/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder_unittest.cc
index 8517758..ab3cc9a 100644
--- a/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder_unittest.cc
+++ b/third_party/blink/renderer/modules/mediarecorder/audio_track_recorder_unittest.cc
@@ -202,12 +202,12 @@
     if (codec_ == AudioTrackRecorder::CodecId::OPUS) {
       // Decode |encoded_data| and check we get the expected number of frames
       // per buffer.
-      EXPECT_EQ(
-          kDefaultSampleRate * kOpusBufferDurationMs / 1000,
-          opus_decode_float(
-              opus_decoder_,
-              reinterpret_cast<uint8_t*>(base::data(encoded_data)),
-              encoded_data.size(), opus_buffer_.get(), kFramesPerBuffer, 0));
+      EXPECT_EQ(kDefaultSampleRate * kOpusBufferDurationMs / 1000,
+                opus_decode_float(
+                    opus_decoder_,
+                    reinterpret_cast<uint8_t*>(base::data(encoded_data)),
+                    static_cast<wtf_size_t>(encoded_data.size()),
+                    opus_buffer_.get(), kFramesPerBuffer, 0));
     } else if (codec_ == AudioTrackRecorder::CodecId::PCM) {
       // Manually confirm that we're getting the same data out as what we
       // generated from the sine wave.
@@ -246,7 +246,7 @@
   // Save the data we generate from the first source so that we might compare it
   // later if we happen to be using the PCM encoder.
   Vector<float> first_source_cache_;
-  size_t first_source_cache_pos_;
+  wtf_size_t first_source_cache_pos_;
 
  private:
   // Prepares a blink track of a given MediaStreamType and attaches the native
diff --git a/third_party/blink/renderer/modules/mediastream/media_devices_test.cc b/third_party/blink/renderer/modules/mediastream/media_devices_test.cc
index 69a69a2..47a6e41 100644
--- a/third_party/blink/renderer/modules/mediastream/media_devices_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_devices_test.cc
@@ -193,7 +193,7 @@
               expected_config->all_origins_permitted);
     ASSERT_EQ(config->permitted_origins.size(),
               expected_config->permitted_origins.size());
-    for (size_t i = 0; i < config->permitted_origins.size(); ++i) {
+    for (wtf_size_t i = 0; i < config->permitted_origins.size(); ++i) {
       EXPECT_TRUE(config->permitted_origins[i]->IsSameOriginWith(
           expected_config->permitted_origins[i].get()));
     }
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc
index 4e12390..f8875aa2 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc
@@ -88,7 +88,7 @@
     const int packet_size = params.frames_per_buffer() * 2 * params.channels();
     const size_t length = packet_size * kNumberOfPacketsForTest;
     std::unique_ptr<char[]> capture_data(new char[length]);
-    ReadDataFromSpeechFile(capture_data.get(), length);
+    ReadDataFromSpeechFile(capture_data.get(), static_cast<int>(length));
     const int16_t* data_ptr =
         reinterpret_cast<const int16_t*>(capture_data.get());
     std::unique_ptr<media::AudioBus> data_bus =
diff --git a/third_party/blink/renderer/modules/mediastream/webaudio_media_stream_audio_sink.cc b/third_party/blink/renderer/modules/mediastream/webaudio_media_stream_audio_sink.cc
index 99d93b48..e6db745 100644
--- a/third_party/blink/renderer/modules/mediastream/webaudio_media_stream_audio_sink.cc
+++ b/third_party/blink/renderer/modules/mediastream/webaudio_media_stream_audio_sink.cc
@@ -23,7 +23,7 @@
 // Size of the buffer that WebAudio processes each time, it is the same value
 // as AudioNode::ProcessingSizeInFrames in WebKit.
 // static
-const size_t WebAudioMediaStreamAudioSink::kWebAudioRenderBufferSize = 128;
+const int WebAudioMediaStreamAudioSink::kWebAudioRenderBufferSize = 128;
 
 WebAudioMediaStreamAudioSink::WebAudioMediaStreamAudioSink(
     MediaStreamComponent* component,
@@ -120,7 +120,7 @@
 
 void WebAudioMediaStreamAudioSink::ProvideInput(
     const WebVector<float*>& audio_data,
-    size_t number_of_frames) {
+    int number_of_frames) {
   NON_REENTRANT_SCOPE(provide_input_reentrancy_checker_);
   DCHECK_EQ(number_of_frames, kWebAudioRenderBufferSize);
 
@@ -134,7 +134,7 @@
         media::AudioBus::CreateWrapper(static_cast<int>(audio_data.size()));
   }
 
-  output_wrapper_->set_frames(static_cast<int>(number_of_frames));
+  output_wrapper_->set_frames(number_of_frames);
   for (size_t i = 0; i < audio_data.size(); ++i)
     output_wrapper_->SetChannelData(static_cast<int>(i), audio_data[i]);
 
diff --git a/third_party/blink/renderer/modules/mediastream/webaudio_media_stream_audio_sink.h b/third_party/blink/renderer/modules/mediastream/webaudio_media_stream_audio_sink.h
index ef1f148..c12e1b1 100644
--- a/third_party/blink/renderer/modules/mediastream/webaudio_media_stream_audio_sink.h
+++ b/third_party/blink/renderer/modules/mediastream/webaudio_media_stream_audio_sink.h
@@ -46,7 +46,7 @@
       public media::AudioConverter::InputCallback,
       public WebMediaStreamAudioSink {
  public:
-  static const size_t kWebAudioRenderBufferSize;
+  static const int kWebAudioRenderBufferSize;
 
   explicit WebAudioMediaStreamAudioSink(MediaStreamComponent* component,
                                         int context_sample_rate);
@@ -61,7 +61,7 @@
   // WebAudioSourceProvider implementation.
   void SetClient(WebAudioSourceProviderClient* client) override;
   void ProvideInput(const WebVector<float*>& audio_data,
-                    size_t number_of_frames) override;
+                    int number_of_frames) override;
 
   // Method to allow the unittests to inject its own sink parameters to avoid
   // query the hardware.
diff --git a/third_party/blink/renderer/modules/mediastream/webaudio_media_stream_audio_sink_test.cc b/third_party/blink/renderer/modules/mediastream/webaudio_media_stream_audio_sink_test.cc
index dd14e81e..dfc4b6eb 100644
--- a/third_party/blink/renderer/modules/mediastream/webaudio_media_stream_audio_sink_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/webaudio_media_stream_audio_sink_test.cc
@@ -60,7 +60,7 @@
 
   // Point the WebVector into memory owned by |sink_bus_|.
   WebVector<float*> audio_data(static_cast<size_t>(sink_bus_->channels()));
-  for (size_t i = 0; i < audio_data.size(); ++i)
+  for (int i = 0; i < sink_bus_->channels(); ++i)
     audio_data[i] = sink_bus_->channel(i);
 
   // Enable the |source_provider_| by asking for data. This will inject
diff --git a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_test.cc b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_test.cc
index 36ab8dc..3c885eca 100644
--- a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_test.cc
@@ -294,7 +294,7 @@
     int double_size_index,
     media::VideoRotation rotation) {
   gfx::Size standard_size = standard_size_;
-  for (size_t i = 0; i < timestamp_or_frame_type.size(); i++) {
+  for (wtf_size_t i = 0; i < timestamp_or_frame_type.size(); i++) {
     const int token = timestamp_or_frame_type[i];
     if (static_cast<int>(i) == double_size_index) {
       standard_size =
diff --git a/third_party/blink/renderer/modules/modules.gni b/third_party/blink/renderer/modules/modules.gni
index 843aac2..fec3fa3 100644
--- a/third_party/blink/renderer/modules/modules.gni
+++ b/third_party/blink/renderer/modules/modules.gni
@@ -49,8 +49,6 @@
   # is the list normally applied to static libraries and source sets.
   configs = default_compiler_configs
   configs += [
-    # TODO(crbug.com/879657): Fix size_t to int truncations.
-    "//build/config/compiler:no_shorten_64_warnings",
     "//third_party/blink/renderer:config",
     "//third_party/blink/renderer/modules:modules_implementation",
     "//third_party/blink/renderer:non_test_config",
diff --git a/third_party/blink/renderer/modules/nfc/nfc_proxy_test.cc b/third_party/blink/renderer/modules/nfc/nfc_proxy_test.cc
index cbcd1ec..0fbe495 100644
--- a/third_party/blink/renderer/modules/nfc/nfc_proxy_test.cc
+++ b/third_party/blink/renderer/modules/nfc/nfc_proxy_test.cc
@@ -117,7 +117,7 @@
     std::move(callback).Run(nullptr);
   }
   void CancelWatch(uint32_t id) override {
-    size_t index = watchIDs_.Find(id);
+    wtf_size_t index = watchIDs_.Find(id);
     if (index != kNotFound)
       watchIDs_.EraseAt(index);
   }
diff --git a/third_party/blink/renderer/modules/peerconnection/fake_rtc_rtp_transceiver_impl.cc b/third_party/blink/renderer/modules/peerconnection/fake_rtc_rtp_transceiver_impl.cc
index 8b5c0ce7..83aaed0 100644
--- a/third_party/blink/renderer/modules/peerconnection/fake_rtc_rtp_transceiver_impl.cc
+++ b/third_party/blink/renderer/modules/peerconnection/fake_rtc_rtp_transceiver_impl.cc
@@ -158,8 +158,9 @@
 }
 
 Vector<String> FakeRTCRtpReceiverImpl::StreamIds() const {
-  Vector<String> wtf_stream_ids(stream_ids_.size());
-  for (size_t i = 0; i < stream_ids_.size(); ++i) {
+  Vector<String> wtf_stream_ids(
+      base::checked_cast<wtf_size_t>(stream_ids_.size()));
+  for (wtf_size_t i = 0; i < wtf_stream_ids.size(); ++i) {
     wtf_stream_ids[i] = String::FromUTF8(stream_ids_[i]);
   }
   return wtf_stream_ids;
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_configuration.idl b/third_party/blink/renderer/modules/peerconnection/rtc_configuration.idl
index e3a6550..fbba82e 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_configuration.idl
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_configuration.idl
@@ -48,9 +48,6 @@
     [EnforceRange] octet iceCandidatePoolSize = 0;
     // Nonstandard, added for Unified Plan migration
     [RuntimeEnabled=RTCUnifiedPlan] SdpSemantics sdpSemantics;
-    // Nonstandard, added for backward compatibility reasons during a
-    // transitional phase. https://crbug.com/908377
-    boolean offerExtmapAllowMixed;
     [RuntimeEnabled=RtcAudioJitterBufferMaxPackets] long rtcAudioJitterBufferMaxPackets;
     [RuntimeEnabled=RtcAudioJitterBufferMaxPackets] boolean rtcAudioJitterBufferFastAccelerate;
     [RuntimeEnabled=RtcAudioJitterBufferMaxPackets] long rtcAudioJitterBufferMinDelayMs;
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
index 35df8de..73f8554 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
@@ -249,24 +249,6 @@
   return kSdpSemanticRequestedDefault;
 }
 
-enum class OfferExtmapAllowMixedSetting {
-  kDefault,
-  kEnabled,
-  kDisabled,
-  kMaxValue = kDisabled
-};
-
-OfferExtmapAllowMixedSetting GetOfferExtmapAllowMixedSetting(
-    const blink::RTCConfiguration* configuration) {
-  if (!configuration->hasOfferExtmapAllowMixed()) {
-    return OfferExtmapAllowMixedSetting::kDefault;
-  }
-
-  return configuration->offerExtmapAllowMixed()
-             ? OfferExtmapAllowMixedSetting::kEnabled
-             : OfferExtmapAllowMixedSetting::kDisabled;
-}
-
 webrtc::PeerConnectionInterface::IceTransportsType IceTransportPolicyFromString(
     const String& policy) {
   if (policy == "relay")
@@ -369,20 +351,6 @@
     }
   }
 
-  if (configuration->hasOfferExtmapAllowMixed()) {
-    web_configuration.offer_extmap_allow_mixed =
-        configuration->offerExtmapAllowMixed();
-    if (!web_configuration.offer_extmap_allow_mixed) {
-      // Only show a deprecation warning when set to false. The default
-      // is "true" as of M91.
-      Deprecation::CountDeprecation(
-          context, WebFeature::kRTCPeerConnectionOfferAllowExtmapMixedFalse);
-    }
-  } else {
-    web_configuration.offer_extmap_allow_mixed =
-        base::FeatureList::IsEnabled(features::kRTCOfferExtmapAllowMixed);
-  }
-
   if (configuration->hasIceServers()) {
     WebVector<webrtc::PeerConnectionInterface::IceServer> ice_servers;
     for (const RTCIceServer* ice_server : configuration->iceServers()) {
@@ -790,9 +758,6 @@
                       WebFeature::kRTCPeerConnectionConstructedWithUnifiedPlan);
   }
 
-  UMA_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.OfferExtmapAllowMixed",
-                            GetOfferExtmapAllowMixedSetting(rtc_configuration));
-
   return peer_connection;
 }
 
diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_installed_scripts_manager_test.cc b/third_party/blink/renderer/modules/service_worker/service_worker_installed_scripts_manager_test.cc
index a4ee314..e6225c9 100644
--- a/third_party/blink/renderer/modules/service_worker/service_worker_installed_scripts_manager_test.cc
+++ b/third_party/blink/renderer/modules/service_worker/service_worker_installed_scripts_manager_test.cc
@@ -94,7 +94,7 @@
                     const mojo::DataPipeProducerHandle& handle) {
     // Send |data| with null terminator.
     ASSERT_TRUE(handle.is_valid());
-    uint32_t written_bytes = data.size() + 1;
+    uint32_t written_bytes = static_cast<uint32_t>(data.size() + 1);
     MojoResult rv = handle.WriteData(data.c_str(), &written_bytes,
                                      MOJO_WRITE_DATA_FLAG_NONE);
     ASSERT_EQ(MOJO_RESULT_OK, rv);
diff --git a/third_party/blink/renderer/modules/webaudio/media_element_audio_source_node.cc b/third_party/blink/renderer/modules/webaudio/media_element_audio_source_node.cc
index 15169c6..5cdd8fcd 100644
--- a/third_party/blink/renderer/modules/webaudio/media_element_audio_source_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/media_element_audio_source_node.cc
@@ -186,7 +186,8 @@
   DCHECK(Context()->IsAudioThread());
   DCHECK(MediaElement());
   DCHECK(dest);
-  MediaElement()->GetAudioSourceProvider().ProvideInput(dest, dest->length());
+  MediaElement()->GetAudioSourceProvider().ProvideInput(
+      dest, base::checked_cast<int>(dest->length()));
 }
 
 void MediaElementAudioSourceHandler::Process(uint32_t number_of_frames) {
@@ -214,14 +215,15 @@
     AudioSourceProvider& provider = MediaElement()->GetAudioSourceProvider();
     // Grab data from the provider so that the element continues to make
     // progress, even if we're going to output silence anyway.
+    const int frames_int = base::checked_cast<int>(number_of_frames);
     if (multi_channel_resampler_.get()) {
       DCHECK_NE(source_sample_rate_, Context()->sampleRate());
-      multi_channel_resampler_->Resample(number_of_frames, output_bus);
+      multi_channel_resampler_->Resample(frames_int, output_bus);
     } else {
       // Bypass the resampler completely if the source is at the context's
       // sample-rate.
       DCHECK_EQ(source_sample_rate_, Context()->sampleRate());
-      provider.ProvideInput(output_bus, number_of_frames);
+      provider.ProvideInput(output_bus, frames_int);
     }
     // Output silence if we don't have access to the element.
     if (is_origin_tainted_) {
diff --git a/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.cc b/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.cc
index ad2f683..63d13e2 100644
--- a/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.cc
@@ -118,7 +118,8 @@
       output_bus->Zero();
       return;
     }
-    audio_source_provider_.get()->ProvideInput(output_bus, number_of_frames);
+    audio_source_provider_.get()->ProvideInput(
+        output_bus, base::checked_cast<int>(number_of_frames));
     if (!is_processing_) {
       SendLogMessage(String::Format("%s({number_of_frames=%u})", __func__,
                                     number_of_frames));
diff --git a/third_party/blink/renderer/modules/websockets/websocket_channel_impl_test.cc b/third_party/blink/renderer/modules/websockets/websocket_channel_impl_test.cc
index 9984ddee..62fdb0ce 100644
--- a/third_party/blink/renderer/modules/websockets/websocket_channel_impl_test.cc
+++ b/third_party/blink/renderer/modules/websockets/websocket_channel_impl_test.cc
@@ -311,7 +311,8 @@
 
   static Vector<uint8_t> AsVector(const char* data, size_t size) {
     Vector<uint8_t> v;
-    v.Append(reinterpret_cast<const uint8_t*>(data), size);
+    v.Append(reinterpret_cast<const uint8_t*>(data),
+             static_cast<wtf_size_t>(size));
     return v;
   }
   static Vector<uint8_t> AsVector(const char* data) {
diff --git a/third_party/blink/renderer/modules/websockets/websocket_message_chunk_accumulator_test.cc b/third_party/blink/renderer/modules/websockets/websocket_message_chunk_accumulator_test.cc
index f0677310..958727e 100644
--- a/third_party/blink/renderer/modules/websockets/websocket_message_chunk_accumulator_test.cc
+++ b/third_party/blink/renderer/modules/websockets/websocket_message_chunk_accumulator_test.cc
@@ -18,7 +18,7 @@
   static Vector<char> Flatten(const Vector<base::span<const char>>& chunks) {
     Vector<char> v;
     for (const auto& chunk : chunks) {
-      v.Append(chunk.data(), chunk.size());
+      v.Append(chunk.data(), base::checked_cast<wtf_size_t>(chunk.size()));
     }
     return v;
   }
diff --git a/third_party/blink/renderer/modules/webtransport/web_transport_test.cc b/third_party/blink/renderer/modules/webtransport/web_transport_test.cc
index 4077b68..ce6966de 100644
--- a/third_party/blink/renderer/modules/webtransport/web_transport_test.cc
+++ b/third_party/blink/renderer/modules/webtransport/web_transport_test.cc
@@ -772,7 +772,7 @@
   }
 
   Vector<uint8_t> result;
-  result.Append(array->Data(), array->length());
+  result.Append(array->Data(), base::checked_cast<wtf_size_t>(array->length()));
   return result;
 }
 
diff --git a/third_party/blink/renderer/platform/audio/audio_resampler.cc b/third_party/blink/renderer/platform/audio/audio_resampler.cc
index 764547e..c14430f 100644
--- a/third_party/blink/renderer/platform/audio/audio_resampler.cc
+++ b/third_party/blink/renderer/platform/audio/audio_resampler.cc
@@ -84,7 +84,8 @@
   }
 
   // Ask the provider to supply the desired number of source frames.
-  provider->ProvideInput(source_bus_.get(), source_bus_->length());
+  provider->ProvideInput(source_bus_.get(),
+                         base::checked_cast<int>(source_bus_->length()));
 
   // Now that we have the source data, resample each channel into the
   // destination bus.
diff --git a/third_party/blink/renderer/platform/audio/audio_source_provider.h b/third_party/blink/renderer/platform/audio/audio_source_provider.h
index 9131a8e46..3feeadf 100644
--- a/third_party/blink/renderer/platform/audio/audio_source_provider.h
+++ b/third_party/blink/renderer/platform/audio/audio_source_provider.h
@@ -49,7 +49,7 @@
 
   // provideInput() gets called repeatedly to render time-slices of a continuous
   // audio stream.
-  virtual void ProvideInput(AudioBus* bus, uint32_t frames_to_process) = 0;
+  virtual void ProvideInput(AudioBus* bus, int frames_to_process) = 0;
 
   // If a client is set, we call it back when the audio format is available or
   // changes.
diff --git a/third_party/blink/renderer/platform/audio/sinc_resampler.cc b/third_party/blink/renderer/platform/audio/sinc_resampler.cc
index 924db3f2..a84673f 100644
--- a/third_party/blink/renderer/platform/audio/sinc_resampler.cc
+++ b/third_party/blink/renderer/platform/audio/sinc_resampler.cc
@@ -144,7 +144,8 @@
   // FIXME: Find a way to make the following const-correct:
   bus->SetChannelMemory(0, buffer, number_of_source_frames);
 
-  source_provider_->ProvideInput(bus.get(), number_of_source_frames);
+  source_provider_->ProvideInput(
+      bus.get(), base::checked_cast<int>(number_of_source_frames));
 }
 
 namespace {
@@ -153,11 +154,11 @@
 
 class BufferSourceProvider final : public AudioSourceProvider {
  public:
-  BufferSourceProvider(const float* source, uint32_t number_of_source_frames)
+  BufferSourceProvider(const float* source, int number_of_source_frames)
       : source_(source), source_frames_available_(number_of_source_frames) {}
 
   // Consumes samples from the in-memory buffer.
-  void ProvideInput(AudioBus* bus, uint32_t frames_to_process) override {
+  void ProvideInput(AudioBus* bus, int frames_to_process) override {
     DCHECK(source_);
     DCHECK(bus);
     if (!source_ || !bus)
@@ -166,8 +167,7 @@
     float* buffer = bus->Channel(0)->MutableData();
 
     // Clamp to number of frames available and zero-pad.
-    uint32_t frames_to_copy =
-        std::min(source_frames_available_, frames_to_process);
+    int frames_to_copy = std::min(source_frames_available_, frames_to_process);
     memcpy(buffer, source_, sizeof(float) * frames_to_copy);
 
     // Zero-pad if necessary.
@@ -181,14 +181,14 @@
 
  private:
   const float* source_;
-  uint32_t source_frames_available_;
+  int source_frames_available_;
 };
 
 }  // namespace
 
 void SincResampler::Process(const float* source,
                             float* destination,
-                            unsigned number_of_source_frames) {
+                            int number_of_source_frames) {
   // Resample an in-memory buffer using an AudioSourceProvider.
   BufferSourceProvider source_provider(source, number_of_source_frames);
 
diff --git a/third_party/blink/renderer/platform/audio/sinc_resampler.h b/third_party/blink/renderer/platform/audio/sinc_resampler.h
index 2242fb5..65f8905 100644
--- a/third_party/blink/renderer/platform/audio/sinc_resampler.h
+++ b/third_party/blink/renderer/platform/audio/sinc_resampler.h
@@ -57,7 +57,7 @@
   // / scaleFactor frames in destination.
   void Process(const float* source,
                float* destination,
-               unsigned number_of_source_frames);
+               int number_of_source_frames);
 
   // Process with input source callback function for streaming applications.
   void Process(AudioSourceProvider*,
diff --git a/third_party/blink/renderer/platform/graphics/color_correction_test_utils.cc b/third_party/blink/renderer/platform/graphics/color_correction_test_utils.cc
index ca97bde..95b307d 100644
--- a/third_party/blink/renderer/platform/graphics/color_correction_test_utils.cc
+++ b/third_party/blink/renderer/platform/graphics/color_correction_test_utils.cc
@@ -71,7 +71,7 @@
 void ColorCorrectionTestUtils::CompareColorCorrectedPixels(
     const void* actual_pixels,
     const void* expected_pixels,
-    int num_pixels,
+    size_t num_pixels,
     PixelFormat pixel_format,
     PixelsAlphaMultiply alpha_multiplied,
     UnpremulRoundTripTolerance premul_unpremul_tolerance) {
@@ -94,12 +94,12 @@
             static_cast<const uint8_t*>(actual_pixels);
         const uint8_t* expected_pixels_u8 =
             static_cast<const uint8_t*>(expected_pixels);
-        for (int i = 0; test_passed && i < num_pixels; i++) {
+        for (size_t i = 0; test_passed && i < num_pixels; i++) {
           test_passed &=
               (actual_pixels_u8[i * 4 + 3] == expected_pixels_u8[i * 4 + 3]);
           int alpha_multiplier =
               alpha_multiplied ? 1 : expected_pixels_u8[i * 4 + 3];
-          for (int j = 0; j < 3; j++) {
+          for (size_t j = 0; j < 3; j++) {
             test_passed &= IsNearlyTheSame(
                 actual_pixels_u8[i * 4 + j] * alpha_multiplier,
                 expected_pixels_u8[i * 4 + j] * alpha_multiplier,
@@ -118,7 +118,7 @@
           static_cast<const uint16_t*>(actual_pixels);
       const uint16_t* expected_pixels_u16 =
           static_cast<const uint16_t*>(expected_pixels);
-      for (int i = 0; test_passed && i < num_pixels * 4; i++) {
+      for (size_t i = 0; test_passed && i < num_pixels * 4; i++) {
         test_passed &=
             IsNearlyTheSame(actual_pixels_u16[i], expected_pixels_u16[i],
                             _16161616_color_correction_tolerance);
@@ -140,7 +140,7 @@
                           expected_pixels_f32, skcms_PixelFormat_BGRA_ffff,
                           skcms_AlphaFormat_Unpremul, nullptr, num_pixels));
 
-      for (int i = 0; test_passed && i < num_pixels * 4; i++) {
+      for (size_t i = 0; test_passed && i < num_pixels * 4; i++) {
         test_passed &=
             IsNearlyTheSame(actual_pixels_f32[i], expected_pixels_f32[i],
                             floating_point_color_correction_tolerance);
@@ -152,7 +152,7 @@
       const float* actual_pixels_f32 = static_cast<const float*>(actual_pixels);
       const float* expected_pixels_f32 =
           static_cast<const float*>(expected_pixels);
-      for (int i = 0; test_passed && i < num_pixels * 4; i++) {
+      for (size_t i = 0; test_passed && i < num_pixels * 4; i++) {
         test_passed &=
             IsNearlyTheSame(actual_pixels_f32[i], expected_pixels_f32[i],
                             floating_point_color_correction_tolerance);
diff --git a/third_party/blink/renderer/platform/graphics/color_correction_test_utils.h b/third_party/blink/renderer/platform/graphics/color_correction_test_utils.h
index f935655d..88a7aa6 100644
--- a/third_party/blink/renderer/platform/graphics/color_correction_test_utils.h
+++ b/third_party/blink/renderer/platform/graphics/color_correction_test_utils.h
@@ -41,7 +41,7 @@
   static void CompareColorCorrectedPixels(
       const void* actual_pixels,
       const void* expected_pixels,
-      int num_pixels,
+      size_t num_pixels,
       PixelFormat pixel_format,
       PixelsAlphaMultiply alpha_multiplied = kAlphaUnmultiplied,
       UnpremulRoundTripTolerance premul_unpremul_tolerance =
diff --git a/third_party/blink/renderer/platform/graphics/compositing_reasons.h b/third_party/blink/renderer/platform/graphics/compositing_reasons.h
index e85c6bd..2bc6d04 100644
--- a/third_party/blink/renderer/platform/graphics/compositing_reasons.h
+++ b/third_party/blink/renderer/platform/graphics/compositing_reasons.h
@@ -201,11 +201,11 @@
 }
 
 struct CompositingReasonsStats {
-  size_t overlap_layers = 0;
-  size_t active_animation_layers = 0;
-  size_t assumed_overlap_layers = 0;
-  size_t indirect_composited_layers = 0;
-  size_t total_composited_layers = 0;
+  int overlap_layers = 0;
+  int active_animation_layers = 0;
+  int assumed_overlap_layers = 0;
+  int indirect_composited_layers = 0;
+  int total_composited_layers = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc
index fcdd420..79bfec5 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc
@@ -200,17 +200,13 @@
     viz::TransferableResource* out_resource,
     viz::ReleaseCallback* out_release_callback) {
   DCHECK(!neutered_);
-  if (!current_swap_buffer_ || neutered_) {
+  if (!current_swap_buffer_ || neutered_ || !GetContextProviderWeakPtr()) {
     return false;
   }
 
   DCHECK(client_);
   client_->OnTextureTransferred();
 
-  if (!GetContextProviderWeakPtr()) {
-    return false;
-  }
-
   // Make Dawn relinquish access to the texture so it can be used by the
   // compositor. This will call wgpu::Texture::Destroy so that further accesses
   // to the texture are errors.
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc
index 5980266..868726d 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider_test.cc
@@ -58,14 +58,19 @@
 
 class FakeProviderClient : public WebGPUSwapBufferProvider::Client {
  public:
-  void OnTextureTransferred() override {}
+  void OnTextureTransferred() override {
+    DCHECK(texture);
+    texture = nullptr;
+  }
+
+  WGPUTexture texture;
 };
 
 class WebGPUSwapBufferProviderForTests : public WebGPUSwapBufferProvider {
  public:
   WebGPUSwapBufferProviderForTests(
       bool* alive,
-      Client* client,
+      FakeProviderClient* client,
       WGPUDevice device,
       scoped_refptr<DawnControlClientHolder> dawn_control_client,
       WGPUTextureUsage usage,
@@ -75,11 +80,18 @@
                                  device,
                                  usage,
                                  format),
-        alive_(alive) {}
+        alive_(alive),
+        client_(client) {}
   ~WebGPUSwapBufferProviderForTests() override { *alive_ = false; }
 
+  WGPUTexture GetNewTexture(const IntSize& size) {
+    client_->texture = WebGPUSwapBufferProvider::GetNewTexture(size);
+    return client_->texture;
+  }
+
  private:
   bool* alive_;
+  FakeProviderClient* client_;
 };
 
 }  // anonymous namespace
@@ -364,4 +376,28 @@
                                                      &release_callback_4));
 }
 
+// Regression test for crbug.com/1236418 where calling
+// PrepareTransferableResource twice after the context is destroyed would hit a
+// DCHECK.
+TEST_F(WebGPUSwapBufferProviderTest,
+       PrepareTransferableResourceTwiceAfterDestroy) {
+  viz::TransferableResource resource;
+  gpu::webgpu::ReservedTexture reservation = {
+      reinterpret_cast<WGPUTexture>(&resource), 1, 1};
+
+  EXPECT_CALL(*webgpu_, ReserveTexture(fake_device_))
+      .WillOnce(Return(reservation));
+  provider_->GetNewTexture(IntSize(10, 10));
+
+  dawn_control_client_->Destroy();
+
+  viz::ReleaseCallback release_callback_1;
+  EXPECT_FALSE(provider_->PrepareTransferableResource(nullptr, &resource,
+                                                      &release_callback_1));
+
+  viz::ReleaseCallback release_callback_2;
+  EXPECT_FALSE(provider_->PrepareTransferableResource(nullptr, &resource,
+                                                      &release_callback_2));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
index 0916745..db6acc9 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
@@ -1247,7 +1247,9 @@
     Restart(resource_->GetResourceRequest());
     return;
   }
-  if (error.CorsErrorStatus()) {
+  if (error.CorsErrorStatus() &&
+      !base::FeatureList::IsEnabled(blink::features::kCORSErrorsIssueOnly)) {
+    // CORS issues are reported via network service instrumentation.
     fetcher_->GetConsoleLogger().AddConsoleMessage(
         mojom::ConsoleMessageSource::kJavaScript,
         mojom::ConsoleMessageLevel::kError,
diff --git a/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc b/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc
index b2dc7ae4..6d073b4f 100644
--- a/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc
+++ b/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc
@@ -204,15 +204,14 @@
 
 void WebAudioSourceProviderImpl::ProvideInput(
     const WebVector<float*>& audio_data,
-    size_t number_of_frames) {
+    int number_of_frames) {
   if (!bus_wrapper_ ||
       static_cast<size_t>(bus_wrapper_->channels()) != audio_data.size()) {
     bus_wrapper_ =
         media::AudioBus::CreateWrapper(static_cast<int>(audio_data.size()));
   }
 
-  const int incoming_number_of_frames = static_cast<int>(number_of_frames);
-  bus_wrapper_->set_frames(incoming_number_of_frames);
+  bus_wrapper_->set_frames(number_of_frames);
   for (size_t i = 0; i < audio_data.size(); ++i)
     bus_wrapper_->SetChannelData(static_cast<int>(i), audio_data[i]);
 
@@ -245,8 +244,8 @@
     return;
   }
 
-  if (frames < incoming_number_of_frames)
-    bus_wrapper_->ZeroFramesPartial(frames, incoming_number_of_frames - frames);
+  if (frames < number_of_frames)
+    bus_wrapper_->ZeroFramesPartial(frames, number_of_frames - frames);
 
   bus_wrapper_->Scale(volume_);
 }
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_component.cc b/third_party/blink/renderer/platform/mediastream/media_stream_component.cc
index c956282..8f8d7c4 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_component.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_component.cc
@@ -117,7 +117,7 @@
 
 void MediaStreamComponent::AudioSourceProviderImpl::ProvideInput(
     AudioBus* bus,
-    uint32_t frames_to_process) {
+    int frames_to_process) {
   DCHECK(bus);
   if (!bus)
     return;
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_component.h b/third_party/blink/renderer/platform/mediastream/media_stream_component.h
index 6138a1cf..ea94de0 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_component.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_component.h
@@ -124,7 +124,7 @@
     void Wrap(WebAudioSourceProvider*);
 
     // blink::AudioSourceProvider
-    void ProvideInput(AudioBus*, uint32_t frames_to_process) override;
+    void ProvideInput(AudioBus*, int frames_to_process) override;
 
    private:
     WebAudioSourceProvider* web_audio_source_provider_;
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_web_audio_source.cc b/third_party/blink/renderer/platform/mediastream/media_stream_web_audio_source.cc
index 5034ab2..6b911d4 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_web_audio_source.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_web_audio_source.cc
@@ -42,7 +42,7 @@
 MediaStreamWebAudioSource::~MediaStreamWebAudioSource() = default;
 
 void MediaStreamWebAudioSource::ProvideInput(AudioBus* bus,
-                                             uint32_t frames_to_process) {
+                                             int frames_to_process) {
   DCHECK(bus);
   if (!bus)
     return;
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_web_audio_source.h b/third_party/blink/renderer/platform/mediastream/media_stream_web_audio_source.h
index 4e573402..efcbe3d 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_web_audio_source.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_web_audio_source.h
@@ -53,7 +53,7 @@
 
  private:
   // blink::AudioSourceProvider implementation.
-  void ProvideInput(AudioBus*, uint32_t frames_to_process) override;
+  void ProvideInput(AudioBus*, int frames_to_process) override;
 
   std::unique_ptr<WebAudioSourceProvider> web_audio_source_provider_;
   WebVector<float*> web_audio_data_;
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 0f5c331..5fbe9748 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
@@ -1057,7 +1057,7 @@
             current_spatial_layer_resolutions_[spatial_index].height();
 
         vp9.first_frame_in_picture = spatial_index == 0;
-        vp9.inter_pic_predicted = metadata.vp9->has_reference;
+        vp9.inter_pic_predicted = metadata.vp9->inter_pic_predicted;
         vp9.non_ref_for_inter_layer_pred =
             !metadata.vp9->referenced_by_upper_spatial_layers;
         vp9.temporal_idx = metadata.vp9->temporal_idx;
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc
index eb4a7f3..84dc9b4 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder_test.cc
@@ -270,7 +270,7 @@
       // TL1:     /_____[#2]
       // TL0: [#0]-----------------[#4]
       media::Vp9Metadata vp9;
-      vp9.has_reference = frame_num != 0 && !force_keyframe;
+      vp9.inter_pic_predicted = frame_num != 0 && !force_keyframe;
       constexpr int kNumTemporalLayers = 3;
       vp9.temporal_up_switch = frame_num != kNumTemporalLayers;
       switch (frame_num) {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index c72615e..f3e2283 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1253,11 +1253,6 @@
       name: "LayoutNGPrinting",
     },
     {
-      name: "LayoutNGReplaced",
-      depends_on: ["LayoutNG"],
-      status: "stable",
-    },
-    {
       name: "LayoutNGTable",
       depends_on: ["LayoutNG"],
       status: "stable"
@@ -2282,6 +2277,10 @@
       status: "experimental",
     },
     {
+      name: "WebAppLaunchHandler",
+      status: "experimental",
+    },
+    {
       name: "WebAppLinkCapturing",
       origin_trial_feature_name: "WebAppLinkCapturing",
       origin_trial_os: ["chromeos"],
diff --git a/third_party/blink/renderer/platform/wtf/text/wtf_string.h b/third_party/blink/renderer/platform/wtf/text/wtf_string.h
index dd160cc..b545918 100644
--- a/third_party/blink/renderer/platform/wtf/text/wtf_string.h
+++ b/third_party/blink/renderer/platform/wtf/text/wtf_string.h
@@ -181,7 +181,7 @@
   std::string Utf8(UTF8ConversionMode = kLenientUTF8Conversion) const
       WARN_UNUSED_RESULT;
 
-  UChar operator[](unsigned index) const {
+  UChar operator[](wtf_size_t index) const {
     if (!impl_ || index >= impl_->length())
       return 0;
     return (*impl_)[index];
@@ -203,17 +203,17 @@
       WARN_UNUSED_RESULT;
 
   // Find characters.
-  wtf_size_t find(UChar c, unsigned start = 0) const {
+  wtf_size_t find(UChar c, wtf_size_t start = 0) const {
     return impl_ ? impl_->Find(c, start) : kNotFound;
   }
-  wtf_size_t find(LChar c, unsigned start = 0) const {
+  wtf_size_t find(LChar c, wtf_size_t start = 0) const {
     return impl_ ? impl_->Find(c, start) : kNotFound;
   }
-  wtf_size_t find(char c, unsigned start = 0) const {
+  wtf_size_t find(char c, wtf_size_t start = 0) const {
     return find(static_cast<LChar>(c), start);
   }
   wtf_size_t Find(CharacterMatchFunctionPtr match_function,
-                  unsigned start = 0) const {
+                  wtf_size_t start = 0) const {
     return impl_ ? impl_->Find(match_function, start) : kNotFound;
   }
   wtf_size_t Find(base::RepeatingCallback<bool(UChar)> match_callback,
@@ -222,7 +222,7 @@
   // Find substrings.
   wtf_size_t Find(
       const StringView& value,
-      unsigned start = 0,
+      wtf_size_t start = 0,
       TextCaseSensitivity case_sensitivity = kTextCaseSensitive) const {
     return impl_
                ? DISPATCH_CASE_OP(case_sensitivity, impl_->Find, (value, start))
diff --git a/third_party/blink/tools/blinkpy/common/checkout/baseline_optimizer_unittest.py b/third_party/blink/tools/blinkpy/common/checkout/baseline_optimizer_unittest.py
index 725ff1a..ec97f6a 100644
--- a/third_party/blink/tools/blinkpy/common/checkout/baseline_optimizer_unittest.py
+++ b/third_party/blink/tools/blinkpy/common/checkout/baseline_optimizer_unittest.py
@@ -60,8 +60,12 @@
         # MockPortFactory and use it.
         self.host.builders = BuilderList({
             'Fake Test Win10': {
-                'port_name': 'win-win10',
-                'specifiers': ['Win10', 'Release']
+                'port_name': 'win-win10.1909',
+                'specifiers': ['Win10.1909', 'Release']
+            },
+            'Fake Test Win10.20h2': {
+                'port_name': 'win-win10.20h2',
+                'specifiers': ['Win10.20h2', 'Release']
             },
             'Fake Test Linux': {
                 'port_name': 'linux-trusty',
@@ -91,11 +95,10 @@
         # Note: this is a pre-assumption of the tests in this file. If this
         # assertion fails, port configurations are likely changed, and the
         # tests need to be adjusted accordingly.
-        self.assertEqual(
-            sorted(self.host.port_factory.all_port_names()), [
-                'linux-trusty', 'mac-mac10.12', 'mac-mac10.13', 'mac-mac10.14',
-                'mac-mac10.15', 'mac-mac11.0', 'win-win10'
-            ])
+        self.assertEqual(sorted(self.host.port_factory.all_port_names()), [
+            'linux-trusty', 'mac-mac10.12', 'mac-mac10.13', 'mac-mac10.14',
+            'mac-mac10.15', 'mac-mac11.0', 'win-win10.1909', 'win-win10.20h2'
+        ])
 
     def _assert_optimization(self,
                              results_by_directory,
diff --git a/third_party/blink/tools/blinkpy/common/config/builders.json b/third_party/blink/tools/blinkpy/common/config/builders.json
index e22e3d6..2d80e1f 100644
--- a/third_party/blink/tools/blinkpy/common/config/builders.json
+++ b/third_party/blink/tools/blinkpy/common/config/builders.json
@@ -56,8 +56,8 @@
     },
     "Win10 Tests x64": {
         "master": "chromium.win",
-        "port_name": "win-win10",
-        "specifiers": ["Win10", "Release"]
+        "port_name": "win-win10.1909",
+        "specifiers": ["Win10.1909", "Release"]
     },
     "Win7 Tests (dbg)(1)": {
         "master": "chromium.win",
@@ -127,8 +127,14 @@
     },
     "win10-blink-rel": {
         "master": "tryserver.blink",
-        "port_name": "win-win10",
-        "specifiers": ["Win10", "Release"],
+        "port_name": "win-win10.1909",
+        "specifiers": ["Win10.1909", "Release"],
+        "is_try_builder": true
+    },
+    "win10.20h2-blink-rel": {
+        "master": "tryserver.blink",
+        "port_name": "win-win10.20h2",
+        "specifiers": ["Win10.20h2", "Release"],
         "is_try_builder": true
     },
     "linux-rel": {
@@ -148,8 +154,8 @@
     },
     "win10_chromium_x64_rel_ng": {
         "master": "tryserver.chromium.win",
-        "port_name": "win-win10",
-        "specifiers": ["Win10", "Release"],
+        "port_name": "win-win10.1909",
+        "specifiers": ["Win10.1909", "Release"],
         "is_try_builder": true,
         "is_cq_builder":true
     },
diff --git a/third_party/blink/tools/blinkpy/common/system/platform_info.py b/third_party/blink/tools/blinkpy/common/system/platform_info.py
index afd887e..8cc9943c 100644
--- a/third_party/blink/tools/blinkpy/common/system/platform_info.py
+++ b/third_party/blink/tools/blinkpy/common/system/platform_info.py
@@ -191,7 +191,12 @@
 
     def _determine_win_version(self, win_version_tuple):
         if win_version_tuple[:2] == (10, 0):
-            return '10'
+            # came across instances where build number was 15063.
+            # Treat those as 1909.
+            if win_version_tuple[2] > 19000:
+                return '10.20h2'
+            else:
+                return '10.1909'
         if win_version_tuple[:2] == (6, 3):
             return '8.1'
         if win_version_tuple[:2] == (6, 2):
diff --git a/third_party/blink/tools/blinkpy/common/system/platform_info_unittest.py b/third_party/blink/tools/blinkpy/common/system/platform_info_unittest.py
index ea6a933..8acd17b 100644
--- a/third_party/blink/tools/blinkpy/common/system/platform_info_unittest.py
+++ b/third_party/blink/tools/blinkpy/common/system/platform_info_unittest.py
@@ -201,7 +201,12 @@
             self.make_info(
                 fake_sys('win32', tuple([10, 0, 1234])),
                 fake_platform(win_version_string="10.0.1234")).os_version,
-            '10')
+            '10.1909')
+        self.assertEqual(
+            self.make_info(
+                fake_sys('win32', tuple([10, 0, 19042])),
+                fake_platform(win_version_string="10.0.19042")).os_version,
+            '10.20h2')
         self.assertEqual(
             self.make_info(
                 fake_sys('win32', tuple([6, 3, 1234])),
diff --git a/third_party/blink/tools/blinkpy/web_tests/models/test_expectations.py b/third_party/blink/tools/blinkpy/web_tests/models/test_expectations.py
index a9d19cd..dc7a461 100644
--- a/third_party/blink/tools/blinkpy/web_tests/models/test_expectations.py
+++ b/third_party/blink/tools/blinkpy/web_tests/models/test_expectations.py
@@ -47,7 +47,7 @@
 
 _PLATFORM_TOKENS_LIST = [
     'Android', 'Fuchsia', 'Linux', 'Mac', 'Mac10.12', 'Mac10.13', 'Mac10.14',
-    'Win', 'Win7', 'Win10'
+    'Win', 'Win7', 'Win10.1909', 'Win10.20h2'
 ]
 
 _BUILD_TYPE_TOKEN_LIST = [
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/base.py b/third_party/blink/tools/blinkpy/web_tests/port/base.py
index eab40e0..1e9ce4f 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/base.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/base.py
@@ -143,15 +143,18 @@
         ('mac11.0', 'x86'),
         ('mac-arm11.0', 'arm64'),
         ('win7', 'x86'),
-        ('win10', 'x86'),
+        ('win10.1909', 'x86'),
+        ('win10.20h2', 'x86'),
         ('trusty', 'x86_64'),
         ('fuchsia', 'x86_64'),
     )
 
     CONFIGURATION_SPECIFIER_MACROS = {
-        'mac': ['mac10.12', 'mac10.13', 'mac10.14', 'mac10.15', 'mac11.0',
-                'mac-arm11.0'],
-        'win': ['win7', 'win10'],
+        'mac': [
+            'mac10.12', 'mac10.13', 'mac10.14', 'mac10.15', 'mac11.0',
+            'mac-arm11.0'
+        ],
+        'win': ['win7', 'win10.1909', 'win10.20h2'],
         'linux': ['trusty'],
         'fuchsia': ['fuchsia'],
     }
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/factory_unittest.py b/third_party/blink/tools/blinkpy/web_tests/port/factory_unittest.py
index 849d042c..eaba6ea 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/factory_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/factory_unittest.py
@@ -77,7 +77,8 @@
 
     def test_win(self):
         self.assert_port(port_name='win-win7', cls=win.WinPort)
-        self.assert_port(port_name='win-win10', cls=win.WinPort)
+        self.assert_port(port_name='win-win10.1909', cls=win.WinPort)
+        self.assert_port(port_name='win-win10.20h2', cls=win.WinPort)
         self.assert_port(
             port_name='win', os_name='win', os_version='win7', cls=win.WinPort)
 
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/win.py b/third_party/blink/tools/blinkpy/web_tests/port/win.py
index 4ec9a32..0185895 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/win.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/win.py
@@ -51,10 +51,13 @@
 class WinPort(base.Port):
     port_name = 'win'
 
-    SUPPORTED_VERSIONS = ('win7', 'win10')
+    SUPPORTED_VERSIONS = ('win7', 'win10.1909', 'win10.20h2')
 
-    FALLBACK_PATHS = {'win10': ['win']}
-    FALLBACK_PATHS['win7'] = ['win7'] + FALLBACK_PATHS['win10']
+    FALLBACK_PATHS = {}
+    FALLBACK_PATHS['win10.20h2'] = ['win']
+    FALLBACK_PATHS['win10.1909'] = ['win10.1909'
+                                    ] + FALLBACK_PATHS['win10.20h2']
+    FALLBACK_PATHS['win7'] = ['win7'] + FALLBACK_PATHS['win10.1909']
 
     BUILD_REQUIREMENTS_URL = 'https://chromium.googlesource.com/chromium/src/+/master/docs/windows_build_instructions.md'
 
@@ -66,8 +69,10 @@
             if host.platform.os_version in ('vista', '7sp0', '7sp1'):
                 version = 'win7'
             # Same for win8, we treat it as win10.
-            elif host.platform.os_version in ('8', '8.1', '10', 'future'):
-                version = 'win10'
+            elif host.platform.os_version in ('8', '8.1', '10.1909'):
+                version = 'win10.1909'
+            elif host.platform.os_version in ('10.20h2', 'future'):
+                version = 'win10.20h2'
             else:
                 version = host.platform.os_version
             port_name = port_name + '-' + version
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/win_unittest.py b/third_party/blink/tools/blinkpy/web_tests/port/win_unittest.py
index 8ee08bb..5298c3e78 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/win_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/win_unittest.py
@@ -70,15 +70,20 @@
         self.assert_name(None, 'win7', 'win-win7')
         self.assert_name('win', 'win7', 'win-win7')
 
-        self.assert_name(None, '10', 'win-win10')
-        self.assert_name('win', '10', 'win-win10')
-        self.assert_name('win-win10', '10', 'win-win10')
-        self.assert_name('win-win10', 'win7', 'win-win10')
+        self.assert_name(None, '10.1909', 'win-win10.1909')
+        self.assert_name('win', '10.1909', 'win-win10.1909')
+        self.assert_name('win-win10.1909', '10.1909', 'win-win10.1909')
+        self.assert_name('win-win10.1909', 'win7', 'win-win10.1909')
 
-        self.assert_name(None, '8', 'win-win10')
-        self.assert_name(None, '8.1', 'win-win10')
-        self.assert_name('win', '8', 'win-win10')
-        self.assert_name('win', '8.1', 'win-win10')
+        self.assert_name(None, '10.20h2', 'win-win10.20h2')
+        self.assert_name('win', '10.20h2', 'win-win10.20h2')
+        self.assert_name('win-win10.20h2', '10.20h2', 'win-win10.20h2')
+        self.assert_name('win-win10.20h2', 'win7', 'win-win10.20h2')
+
+        self.assert_name(None, '8', 'win-win10.1909')
+        self.assert_name(None, '8.1', 'win-win10.1909')
+        self.assert_name('win', '8', 'win-win10.1909')
+        self.assert_name('win', '8.1', 'win-win10.1909')
 
         self.assert_name(None, '7sp1', 'win-win7')
         self.assert_name(None, '7sp0', 'win-win7')
@@ -90,9 +95,9 @@
         self.assert_name('win-win7', '7sp0', 'win-win7')
         self.assert_name('win-win7', 'vista', 'win-win7')
 
-        self.assert_name(None, 'future', 'win-win10')
-        self.assert_name('win', 'future', 'win-win10')
-        self.assert_name('win-win10', 'future', 'win-win10')
+        self.assert_name(None, 'future', 'win-win10.20h2')
+        self.assert_name('win', 'future', 'win-win10.20h2')
+        self.assert_name('win-win10.20h2', 'future', 'win-win10.20h2')
 
         with self.assertRaises(AssertionError):
             self.assert_name(None, 'w2k', 'win-win7')
@@ -106,8 +111,9 @@
             self.assertTrue(port.baseline_search_path()[i].endswith(path))
 
     def test_baseline_path(self):
-        self.assert_baseline_paths('win-win7', 'win7', '/win')
-        self.assert_baseline_paths('win-win10', 'win')
+        self.assert_baseline_paths('win-win7', 'win7', 'win10.1909', '/win')
+        self.assert_baseline_paths('win-win10.1909', 'win10.1909', 'win')
+        self.assert_baseline_paths('win-win10.20h2', 'win')
 
     def test_operating_system(self):
         self.assertEqual('win', self.make_port().operating_system())
diff --git a/third_party/blink/web_tests/ASANExpectations b/third_party/blink/web_tests/ASANExpectations
index 75bf5e4..1562c2f 100644
--- a/third_party/blink/web_tests/ASANExpectations
+++ b/third_party/blink/web_tests/ASANExpectations
@@ -1,4 +1,4 @@
-# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Win Win7 Win10 ]
+# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Win Win7 Win10.1909 Win10.20h2 ]
 # tags: [ Release Debug ]
 # results: [ Timeout Crash Pass Failure Slow Skip ]
 
@@ -75,6 +75,3 @@
 crbug.com/989365 [ Linux ] external/wpt/cookie-store/serviceworker_cookiechange_eventhandler_single_subscription.tentative.https.any.serviceworker.html [ Pass Timeout ]
 
 crbug.com/1048597 [ Linux ] virtual/android/fullscreen/video-scrolled-iframe.html [ Pass Timeout ]
-
-# Sheriff 2020-08-25
-crbug.com/1121429 fast/selectors/placeholder-shown-style-update.html [ Pass Crash ]
diff --git a/third_party/blink/web_tests/LeakExpectations b/third_party/blink/web_tests/LeakExpectations
index f6b3c35..fc235c0 100644
--- a/third_party/blink/web_tests/LeakExpectations
+++ b/third_party/blink/web_tests/LeakExpectations
@@ -1,4 +1,4 @@
-# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Win Win7 Win10 ]
+# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Win Win7 Win10.1909 Win10.20h2 ]
 # tags: [ Release Debug ]
 # results: [ Timeout Crash Pass Failure Slow Skip ]
 
diff --git a/third_party/blink/web_tests/MSANExpectations b/third_party/blink/web_tests/MSANExpectations
index 7ac1312e..0ec5282b 100644
--- a/third_party/blink/web_tests/MSANExpectations
+++ b/third_party/blink/web_tests/MSANExpectations
@@ -1,4 +1,4 @@
-# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Win Win7 Win10 ]
+# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Win Win7 Win10.1909 Win10.20h2 ]
 # tags: [ Release Debug ]
 # results: [ Timeout Crash Pass Failure Slow Skip ]
 
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index 19dcfac9..f72d212 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -1,5 +1,5 @@
 # tags: [ Android Fuchsia IOS IOS12.2 IOS13.0 Linux Mac Mac10.10 Mac10.11 Mac10.12
-#         Mac10.13 Win Win7 Win10 ]
+#         Mac10.13 Win Win7 Win10.1909 Win10.20h2 ]
 # tags: [ Release Debug ]
 # results: [ Skip Pass ]
 
diff --git a/third_party/blink/web_tests/SlowTests b/third_party/blink/web_tests/SlowTests
index 0e752bf..ca63b9a 100644
--- a/third_party/blink/web_tests/SlowTests
+++ b/third_party/blink/web_tests/SlowTests
@@ -1,4 +1,4 @@
-# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Mac10.14 Mac10.15 Mac11.0 Win Win7 Win10 ]
+# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Mac10.14 Mac10.15 Mac11.0 Win Win7 Win10.1909 Win10.20h2 ]
 # tags: [ Release Debug ]
 # results: [ Slow ]
 
@@ -555,13 +555,20 @@
 crbug.com/1145716 fast/forms/calendar-picker/datetimelocal-picker-open-to-focused-field.html [ Slow ]
 
 # These began to hit timeouts when moving from Windows-10-15063 to Windows-10-18363
-crbug.com/1142023 [ Win10 ] external/wpt/web-animations/animation-model/animation-types/accumulation-per-property-002.html [ Slow ]
-crbug.com/1142023 [ Win10 ] external/wpt/web-animations/animation-model/animation-types/addition-per-property-002.html [ Slow ]
-crbug.com/1142023 [ Win10 ] external/wpt/web-animations/animation-model/animation-types/interpolation-per-property-002.html [ Slow ]
-crbug.com/1142023 [ Win10 ] fast/dom/HTMLScriptElement/script-sync-load-failure.html [ Slow ]
-crbug.com/1142023 [ Win10 ] http/tests/eventsource/eventsource-cors-no-server.html [ Slow ]
-crbug.com/1142023 [ Win10 ] http/tests/eventsource/workers/eventsource-cors-no-server.html [ Slow ]
-crbug.com/1142023 [ Win10 ] http/tests/security/document-domain-canonicalizes-iframe.html [ Slow ]
+crbug.com/1142023 [ Win10.1909 ] external/wpt/web-animations/animation-model/animation-types/accumulation-per-property-002.html [ Slow ]
+crbug.com/1142023 [ Win10.20h2 ] external/wpt/web-animations/animation-model/animation-types/accumulation-per-property-002.html [ Slow ]
+crbug.com/1142023 [ Win10.1909 ] external/wpt/web-animations/animation-model/animation-types/addition-per-property-002.html [ Slow ]
+crbug.com/1142023 [ Win10.20h2 ] external/wpt/web-animations/animation-model/animation-types/addition-per-property-002.html [ Slow ]
+crbug.com/1142023 [ Win10.1909 ] external/wpt/web-animations/animation-model/animation-types/interpolation-per-property-002.html [ Slow ]
+crbug.com/1142023 [ Win10.20h2 ] external/wpt/web-animations/animation-model/animation-types/interpolation-per-property-002.html [ Slow ]
+crbug.com/1142023 [ Win10.1909 ] fast/dom/HTMLScriptElement/script-sync-load-failure.html [ Slow ]
+crbug.com/1142023 [ Win10.20h2 ] fast/dom/HTMLScriptElement/script-sync-load-failure.html [ Slow ]
+crbug.com/1142023 [ Win10.1909 ] http/tests/eventsource/eventsource-cors-no-server.html [ Slow ]
+crbug.com/1142023 [ Win10.20h2 ] http/tests/eventsource/eventsource-cors-no-server.html [ Slow ]
+crbug.com/1142023 [ Win10.1909 ] http/tests/eventsource/workers/eventsource-cors-no-server.html [ Slow ]
+crbug.com/1142023 [ Win10.20h2 ] http/tests/eventsource/workers/eventsource-cors-no-server.html [ Slow ]
+crbug.com/1142023 [ Win10.1909 ] http/tests/security/document-domain-canonicalizes-iframe.html [ Slow ]
+crbug.com/1142023 [ Win10.20h2 ] http/tests/security/document-domain-canonicalizes-iframe.html [ Slow ]
 
 # crbug.com/1095379: These were all added here in an attempt to reduce flakiness.
 crbug.com/983788 http/tests/cookies/same-site/popup-cross-site.https.html [ Slow ]
@@ -590,7 +597,7 @@
 crbug.com/1064522 external/wpt/html/user-activation/propagation-crossorigin.sub.tentative.html [ Slow ]
 crbug.com/863599 [ Debug ] external/wpt/requestidlecallback/callback-iframe.html [ Slow ]
 crbug.com/869364 http/tests/devtools/console/console-correct-suggestions.js [ Slow ]
-crbug.com/886566 [ Win10 ] http/tests/csspaint/invalidation-background-image.html [ Slow ]
+crbug.com/886566 [ Win10.1909 ] http/tests/csspaint/invalidation-background-image.html [ Slow ]
 crbug.com/886566 [ Mac ] http/tests/csspaint/invalidation-background-image.html [ Slow ]
 crbug.com/886566 http/tests/csspaint/invalidation-content-image.html [ Slow ]
 crbug.com/888609 http/tests/devtools/coverage/gutter-css.js [ Slow ]
@@ -737,7 +744,7 @@
 crbug.com/1046784 http/tests/devtools/a11y-axe-core/sources/scope-pane-a11y-test.js [ Slow ]
 crbug.com/1046784 http/tests/inspector-protocol/network/navigate-iframe-out2in.js [ Slow ]
 crbug.com/1041847 virtual/gpu/fast/canvas/color-space/canvas-colorManaged-convertToBlob-roundtrip.html [ Slow ]
-crbug.com/848799 [ Win10 ] http/tests/devtools/coverage/multiple-instances-merge.js [ Slow ]
+crbug.com/848799 [ Win10.1909 ] http/tests/devtools/coverage/multiple-instances-merge.js [ Slow ]
 crbug.com/1048597 [ Linux ] virtual/android/fullscreen/video-scrolled-iframe.html [ Slow ]
 crbug.com/1057807 http/tests/misc/destroy-middle-click-locked-target-crash.html [ Slow ]
 crbug.com/1042694 fast/forms/form-control-with-state-eager-tracing-crashTest.html [ Slow ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 72a462a..01730fe 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1,4 +1,4 @@
-# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Mac10.14 Mac10.15 Mac11.0 Win Win7 Win10 ]
+# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Mac10.14 Mac10.15 Mac11.0 Win Win7 Win10.1909 Win10.20h2 ]
 # tags: [ Release Debug ]
 # results: [ Timeout Crash Pass Failure Skip ]
 
@@ -896,12 +896,15 @@
 crbug.com/1067242 [ Mac10.12 ] external/wpt/css/css-text-decor/text-decoration-thickness-from-font-variable.html [ Failure ]
 crbug.com/1067242 [ Mac10.13 ] external/wpt/css/css-text-decor/text-decoration-thickness-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 ]
-crbug.com/1068947 [ Win10 ] external/wpt/css/css-text-decor/text-decoration-thickness-fixed.html [ Failure ]
-crbug.com/1068947 [ Win10 ] external/wpt/css/css-text-decor/text-decoration-thickness-from-font-variable.html [ Failure ]
+crbug.com/1068947 [ Win10.1909 ] external/wpt/css/css-text-decor/text-underline-position-from-font-variable.html [ Failure ]
+crbug.com/1068947 [ Win10.1909 ] external/wpt/css/css-text-decor/text-decoration-thickness-fixed.html [ Failure ]
+crbug.com/1068947 [ Win10.20h2 ] external/wpt/css/css-text-decor/text-decoration-thickness-fixed.html [ Failure ]
+crbug.com/1068947 [ Win10.1909 ] external/wpt/css/css-text-decor/text-decoration-thickness-from-font-variable.html [ Failure ]
 # Windows 10 bot upgrade now causes these failures on Windows-10-18363
-crbug.com/1140324 [ Win10 ] external/wpt/css/css-text-decor/text-underline-offset-variable.html [ Failure ]
-crbug.com/1143005 [ Win10 ] media/track/track-cue-rendering-vertical.html [ Failure ]
+crbug.com/1140324 [ Win10.1909 ] external/wpt/css/css-text-decor/text-underline-offset-variable.html [ Failure ]
+crbug.com/1140324 [ Win10.20h2 ] external/wpt/css/css-text-decor/text-underline-offset-variable.html [ Failure ]
+crbug.com/1143005 [ Win10.1909 ] media/track/track-cue-rendering-vertical.html [ Failure ]
+crbug.com/1143005 [ Win10.20h2 ] media/track/track-cue-rendering-vertical.html [ Failure ]
 
 # Tentative mansonry tests
 crbug.com/1076027 external/wpt/css/css-grid/masonry/* [ Skip ]
@@ -1015,9 +1018,10 @@
 
 ### With LayoutNGFragmentItem enabled
 crbug.com/982194 [ Win7 ] external/wpt/css/css-text/white-space/seg-break-transformation-018.tentative.html [ Failure ]
-crbug.com/982194 [ Win10 ] fast/inline/emptyInlinesWithinLists.html [ Failure Pass ]
-crbug.com/982194 [ Win10 ] fast/writing-mode/background-vertical-rl.html [ Failure Pass ]
-crbug.com/982194 [ Win10 ] fast/writing-mode/border-image-vertical-lr.html [ Failure Pass ]
+crbug.com/982194 [ Win10.1909 ] fast/inline/emptyInlinesWithinLists.html [ Failure Pass ]
+crbug.com/982194 [ Win10.1909 ] fast/writing-mode/background-vertical-rl.html [ Failure Pass ]
+crbug.com/982194 [ Win10.1909 ] fast/writing-mode/border-image-vertical-lr.html [ Failure Pass ]
+crbug.com/982194 [ Win10.20h2 ] fast/writing-mode/border-image-vertical-lr.html [ Failure Pass ]
 
 ### Tests passing with LayoutNGBlockFragmentation enabled:
 virtual/layout_ng_block_frag/external/wpt/css/css-break/abspos-in-opacity-001.html [ Pass ]
@@ -1458,7 +1462,7 @@
 
 crbug.com/767269 [ Win ] inspector-protocol/layout-fonts/cjk-ideograph-fallback-by-lang.js [ Failure Pass ]
 
-crbug.com/788110 [ Win10 ] inspector-protocol/layout-fonts/unicode-range-combining-chars-fallback.js [ Failure Pass ]
+crbug.com/788110 [ Win10.1909 ] inspector-protocol/layout-fonts/unicode-range-combining-chars-fallback.js [ Failure Pass ]
 
 crbug.com/803276 [ Mac ] inspector-protocol/memory/sampling-native-profile.js [ Skip ]
 crbug.com/803276 [ Win ] inspector-protocol/memory/sampling-native-profile.js [ Skip ]
@@ -1479,7 +1483,7 @@
 crbug.com/280342 http/tests/media/progress-events-generated-correctly.html [ Failure Pass ]
 
 crbug.com/520736 [ Win7 ] media/W3C/video/networkState/networkState_during_progress.html [ Failure Pass ]
-crbug.com/520736 [ Win10 ] media/W3C/video/networkState/networkState_during_progress.html [ Failure Pass ]
+crbug.com/520736 [ Win10.1909 ] media/W3C/video/networkState/networkState_during_progress.html [ Failure Pass ]
 crbug.com/520736 [ Linux ] media/W3C/video/networkState/networkState_during_progress.html [ Failure Pass ]
 crbug.com/909095 [ Mac ] media/W3C/video/networkState/networkState_during_progress.html [ Failure Pass ]
 
@@ -1982,15 +1986,22 @@
 crbug.com/501659 fast/css/stylesheet-candidate-nodes-crash.xhtml [ Failure ]
 
 # TODO(chrishtr) uncomment ones marked crbug.com/591500 after fixing crbug.com/665259.
-crbug.com/591500 [ Win10 ] virtual/threaded/printing/fixed-positioned-headers-and-footers-absolute-covering-some-pages.html [ Failure ]
-crbug.com/591500 [ Win10 ] printing/fixed-positioned-but-static-headers-and-footers.html [ Failure Pass ]
-crbug.com/591500 [ Win10 ] printing/fixed-positioned-headers-and-footers-inside-transform.html [ Failure Pass ]
-crbug.com/591500 [ Win10 ] printing/fixed-positioned-headers-and-footers-larger-than-page.html [ Failure Pass ]
-crbug.com/591500 [ Win10 ] printing/fixed-positioned-headers-and-footers.html [ Failure Pass ]
-crbug.com/591500 [ Win10 ] virtual/threaded/printing/list-item-with-empty-first-line.html [ Failure ]
+crbug.com/591500 [ Win10.1909 ] virtual/threaded/printing/fixed-positioned-headers-and-footers-absolute-covering-some-pages.html [ Failure ]
+crbug.com/591500 [ Win10.20h2 ] virtual/threaded/printing/fixed-positioned-headers-and-footers-absolute-covering-some-pages.html [ Failure ]
+crbug.com/591500 [ Win10.1909 ] printing/fixed-positioned-but-static-headers-and-footers.html [ Failure Pass ]
+crbug.com/591500 [ Win10.20h2 ] virtual/threaded/printing/fixed-positioned-but-static-headers-and-footers.html [ Failure Pass ]
+crbug.com/591500 [ Win10.1909 ] printing/fixed-positioned-headers-and-footers-inside-transform.html [ Failure Pass ]
+crbug.com/591500 [ Win10.1909 ] printing/fixed-positioned-headers-and-footers-larger-than-page.html [ Failure Pass ]
+crbug.com/591500 [ Win10.20h2 ] virtual/threaded/printing/fixed-positioned-headers-and-footers-larger-than-page.html [ Failure Pass ]
 
-crbug.com/591500 [ Win10 ] printing/fixed-positioned-headers-and-footers-clipped.html [ Failure Pass ]
-crbug.com/591500 [ Win10 ] printing/ellipsis-printing-style.html [ Failure Pass ]
+crbug.com/591500 [ Win10.1909 ] printing/fixed-positioned-headers-and-footers.html [ Failure Pass ]
+crbug.com/591500 [ Win10.20h2 ] virtual/threaded/printing/fixed-positioned-headers-and-footers.html [ Failure Pass ]
+crbug.com/591500 [ Win10.1909 ] virtual/threaded/printing/list-item-with-empty-first-line.html [ Failure ]
+crbug.com/591500 [ Win10.20h2 ] virtual/threaded/printing/list-item-with-empty-first-line.html [ Failure ]
+
+crbug.com/591500 [ Win10.1909 ] printing/fixed-positioned-headers-and-footers-clipped.html [ Failure Pass ]
+crbug.com/591500 [ Win10.20h2 ] virtual/threaded/printing/fixed-positioned-headers-and-footers-clipped.html [ Failure Pass ]
+crbug.com/591500 [ Win10.1909 ] printing/ellipsis-printing-style.html [ Failure Pass ]
 
 crbug.com/353746 virtual/android/fullscreen/video-specified-size.html [ Failure Pass ]
 
@@ -2025,7 +2036,8 @@
 # Note: this test was previously marked as slow on Debug builds. Skipping until crash is fixed
 crbug.com/619978 fast/css/giant-stylesheet-crash.html [ Skip ]
 
-crbug.com/624430 [ Win10 ] virtual/text-antialias/font-features/caps-casemapping.html [ Failure ]
+crbug.com/624430 [ Win10.1909 ] virtual/text-antialias/font-features/caps-casemapping.html [ Failure ]
+crbug.com/624430 [ Win10.20h2 ] virtual/text-antialias/font-features/caps-casemapping.html [ Failure ]
 
 crbug.com/399507 virtual/threaded/http/tests/devtools/tracing/timeline-paint/layer-tree.js [ Skip ]
 
@@ -2653,7 +2665,7 @@
 crbug.com/626703 [ Mac10.15 ] external/wpt/selection/textcontrols/selectionchange-bubble.html [ Timeout ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/selection/textcontrols/selectionchange-bubble.html [ Timeout ]
 crbug.com/626703 [ Win ] external/wpt/selection/textcontrols/selectionchange-bubble.html [ Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/wasm/jsapi/exception/basic.tentative.any.html [ Failure Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/wasm/jsapi/exception/basic.tentative.any.html [ Failure Timeout ]
 crbug.com/626703 external/wpt/density-size-correction/density-corrected-image-svg-aspect-ratio-cross-origin.sub.html [ Failure ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-click.html [ Timeout ]
 crbug.com/626703 [ Mac10.13 ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-click.html [ Timeout ]
@@ -2662,7 +2674,8 @@
 crbug.com/626703 [ Win7 ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-click.html [ Timeout ]
 crbug.com/626703 [ Linux ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-click.html [ Failure Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-click.html [ Failure Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-click.html [ Failure Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-click.html [ Failure Timeout ]
+crbug.com/626703 [ Win10.20h2 ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-click.html [ Failure Timeout ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-assign-user-click.html [ Failure Timeout ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-mouseup.html [ Timeout ]
 crbug.com/626703 [ Mac10.13 ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-mouseup.html [ Timeout ]
@@ -2671,7 +2684,8 @@
 crbug.com/626703 [ Win7 ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-mouseup.html [ Timeout ]
 crbug.com/626703 [ Linux ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-mouseup.html [ Failure Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-mouseup.html [ Failure Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-mouseup.html [ Failure Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-mouseup.html [ Failure Timeout ]
+crbug.com/626703 [ Win10.20h2 ] external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/location-setter-user-mouseup.html [ Failure Timeout ]
 crbug.com/626703 external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-user-click-during-pageshow.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-lists/marker-webkit-text-fill-color.html [ Failure ]
 crbug.com/626703 external/wpt/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-user-click-during-load.html [ Timeout ]
@@ -2816,12 +2830,13 @@
 crbug.com/626703 [ Linux ] external/wpt/streams/readable-byte-streams/non-transferable-buffers.any.serviceworker.html [ Crash ]
 crbug.com/626703 [ Mac ] external/wpt/streams/readable-byte-streams/non-transferable-buffers.any.serviceworker.html [ Crash ]
 crbug.com/626703 [ Win7 ] external/wpt/streams/readable-byte-streams/non-transferable-buffers.any.serviceworker.html [ Crash ]
-crbug.com/626703 [ Win10 ] external/wpt/streams/readable-byte-streams/non-transferable-buffers.any.serviceworker.html [ Crash Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/streams/readable-byte-streams/non-transferable-buffers.any.serviceworker.html [ Crash Timeout ]
+crbug.com/626703 [ Win10.20h2 ] external/wpt/streams/readable-byte-streams/non-transferable-buffers.any.serviceworker.html [ Crash Timeout ]
 crbug.com/626703 [ Mac11.0 ] external/wpt/css/css-counter-styles/counter-style-name-syntax.html [ Failure Timeout ]
 crbug.com/626703 external/wpt/streams/readable-byte-streams/general.any.sharedworker.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-sizing/fit-content-length-percentage-015.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-sizing/fit-content-length-percentage-014.html [ Failure ]
-crbug.com/626703 [ Win10 ] virtual/prerender/wpt_internal/prerender/session-storage.html [ Timeout ]
+crbug.com/626703 [ Win10.1909 ] virtual/prerender/wpt_internal/prerender/session-storage.html [ Timeout ]
 crbug.com/626703 [ Mac10.14 ] external/wpt/css/CSS2/backgrounds/background-233.xht [ Failure Timeout ]
 crbug.com/626703 external/wpt/streams/readable-byte-streams/general.any.html [ Timeout ]
 crbug.com/626703 external/wpt/css/CSS2/backgrounds/background-intrinsic-004.xht [ Failure ]
@@ -2865,7 +2880,8 @@
 crbug.com/626703 [ Linux ] external/wpt/streams/readable-byte-streams/non-transferable-buffers.any.sharedworker.html [ Crash ]
 crbug.com/626703 [ Mac ] external/wpt/streams/readable-byte-streams/non-transferable-buffers.any.sharedworker.html [ Crash ]
 crbug.com/626703 [ Win7 ] external/wpt/streams/readable-byte-streams/non-transferable-buffers.any.sharedworker.html [ Crash ]
-crbug.com/626703 [ Win10 ] external/wpt/streams/readable-byte-streams/non-transferable-buffers.any.sharedworker.html [ Crash Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/streams/readable-byte-streams/non-transferable-buffers.any.sharedworker.html [ Crash Timeout ]
+crbug.com/626703 [ Win10.20h2 ] external/wpt/streams/readable-byte-streams/non-transferable-buffers.any.sharedworker.html [ Crash Timeout ]
 crbug.com/626703 external/wpt/css/css-sizing/fit-content-length-percentage-008.html [ Failure ]
 crbug.com/626703 external/wpt/streams/readable-byte-streams/general.any.serviceworker.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-sizing/fit-content-length-percentage-011.html [ Failure ]
@@ -3076,69 +3092,70 @@
 crbug.com/626703 [ Mac ] external/wpt/websockets/bufferedAmount-unchanged-by-sync-xhr.any.worker.html?wpt_flags=h2 [ Failure Pass ]
 crbug.com/626703 [ Mac ] external/wpt/websockets/basic-auth.any.html?wpt_flags=h2 [ Failure Pass ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/worklets/audio-worklet-credentials.https.html [ Crash Failure ]
-crbug.com/626703 [ Win10 ] external/wpt/worklets/audio-worklet-credentials.https.html [ Crash Failure ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/worklets/audio-worklet-credentials.https.html [ Crash Failure ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/websockets/stream/tentative/constructor.any.html?wpt_flags=h2 [ Failure Timeout ]
 crbug.com/626703 [ Win ] external/wpt/websockets/stream/tentative/constructor.any.html?wpt_flags=h2 [ Failure Timeout ]
 crbug.com/626703 [ Win ] external/wpt/websockets/stream/tentative/backpressure-send.any.html?wpt_flags=h2 [ Failure Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/websockets/basic-auth.any.sharedworker.html?wpt_flags=h2 [ Crash Failure Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/websockets/basic-auth.any.sharedworker.html?wpt_flags=h2 [ Crash Failure Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/websockets/basic-auth.any.sharedworker.html?wpt_flags=h2 [ Crash Failure Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/webrtc-encoded-transform/script-metadata-transform.https.html [ Failure Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/webrtc-encoded-transform/script-metadata-transform.https.html [ Failure Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/webrtc-encoded-transform/script-metadata-transform.https.html [ Failure Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/fetch/api/basic/request-upload.any.serviceworker.html [ Crash Failure Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/fetch/api/basic/request-upload.any.serviceworker.html [ Crash Failure Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/fetch/api/basic/request-upload.any.serviceworker.html [ Crash Failure Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/editing/other/editing-around-select-element.tentative.html?insertText [ Crash Failure Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/editing/other/editing-around-select-element.tentative.html?insertText [ Crash Failure Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/editing/other/editing-around-select-element.tentative.html?insertText [ Crash Failure Timeout ]
 crbug.com/626703 [ Mac ] external/wpt/custom-elements/form-associated/ElementInternals-setFormValue.html [ Crash Failure Pass Timeout ]
 crbug.com/626703 [ Win ] external/wpt/custom-elements/form-associated/ElementInternals-setFormValue.html [ Failure Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/html/browsers/sandboxing/window-open-blank-from-different-initiator.html [ Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/html/browsers/sandboxing/window-open-blank-from-different-initiator.html [ Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/html/browsers/sandboxing/window-open-blank-from-different-initiator.html [ Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/fetch/api/basic/request-upload.any.sharedworker.html [ Failure Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/fetch/api/basic/request-upload.any.sharedworker.html [ Failure Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/fetch/api/basic/request-upload.any.sharedworker.html [ Failure Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/mediacapture-image/MediaStreamTrack-getConstraints.https.html [ Crash Failure Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/mediacapture-image/MediaStreamTrack-getConstraints.https.html [ Crash Failure Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/mediacapture-image/MediaStreamTrack-getConstraints.https.html [ Crash Failure Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/webvtt/api/VTTCue/constructor.html [ Crash Failure ]
-crbug.com/626703 [ Win10 ] external/wpt/webvtt/api/VTTCue/constructor.html [ Crash Failure ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/webvtt/api/VTTCue/constructor.html [ Crash Failure ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/webrtc-encoded-transform/sframe-keys.https.html [ Failure Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/dom/xslt/transformToFragment.tentative.window.html [ Crash Failure ]
-crbug.com/626703 [ Win10 ] external/wpt/dom/xslt/transformToFragment.tentative.window.html [ Crash Failure ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/dom/xslt/transformToFragment.tentative.window.html [ Crash Failure ]
 crbug.com/626703 external/wpt/webrtc-encoded-transform/script-transform.https.html [ Crash Failure Pass Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/shadow-dom/accesskey.tentative.html [ Failure Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/websockets/stream/tentative/backpressure-send.any.sharedworker.html?wpt_flags=h2 [ Failure Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/websockets/stream/tentative/backpressure-send.any.sharedworker.html?wpt_flags=h2 [ Failure Timeout ]
+crbug.com/626703 [ Win10.20h2 ] external/wpt/websockets/stream/tentative/backpressure-send.any.sharedworker.html?wpt_flags=h2 [ Failure Timeout ]
 crbug.com/1167095 [ Linux ] virtual/synchronous_html_parser/external/wpt/html/semantics/forms/form-submission-0/multipart-formdata.window.html [ Failure Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/webrtc-encoded-transform/sframe-keys.https.html [ Failure Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/webrtc-encoded-transform/sframe-keys.https.html [ Failure Timeout ]
 crbug.com/626703 [ Mac10.14 ] external/wpt/websockets/stream/tentative/backpressure-send.any.html?wpt_flags=h2 [ Failure Timeout ]
 crbug.com/626703 [ Mac10.15 ] virtual/plz-dedicated-worker/external/wpt/html/cross-origin-embedder-policy/shared-workers.html [ Failure Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/html/cross-origin-embedder-policy/cross-origin-isolated-permission.https.html [ Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/html/cross-origin-embedder-policy/cross-origin-isolated-permission.https.html [ Timeout ]
 crbug.com/626703 [ Mac10.15 ] virtual/composite-bgcolor-animation/external/wpt/css/css-backgrounds/parsing/background-image-computed.sub.html [ Crash Pass ]
 crbug.com/1167095 [ Mac ] external/wpt/html/semantics/forms/form-submission-0/multipart-formdata.window.html [ Failure Pass Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/webrtc-encoded-transform/sframe-transform-buffer-source.html [ Crash Failure Timeout ]
 crbug.com/626703 external/wpt/websockets/stream/tentative/close.any.worker.html?wpt_flags=h2 [ Failure Timeout ]
 crbug.com/626703 virtual/plz-dedicated-worker/external/wpt/html/cross-origin-embedder-policy/multi-globals/workers-coep-report.https.html [ Failure Pass ]
 crbug.com/626703 [ Mac10.15 ] virtual/plz-dedicated-worker/external/wpt/fetch/api/basic/http-response-code.any.html [ Crash Failure ]
-crbug.com/626703 [ Win10 ] virtual/plz-dedicated-worker/external/wpt/fetch/api/basic/http-response-code.any.html [ Crash Failure ]
+crbug.com/626703 [ Win10.1909 ] virtual/plz-dedicated-worker/external/wpt/fetch/api/basic/http-response-code.any.html [ Crash Failure ]
 crbug.com/626703 [ Mac10.15 ] virtual/synchronous_html_parser/external/wpt/html/semantics/forms/form-submission-0/text-plain.window.html [ Crash Failure ]
-crbug.com/626703 [ Win10 ] virtual/synchronous_html_parser/external/wpt/html/semantics/forms/form-submission-0/text-plain.window.html [ Crash Failure ]
+crbug.com/626703 [ Win10.1909 ] virtual/synchronous_html_parser/external/wpt/html/semantics/forms/form-submission-0/text-plain.window.html [ Crash Failure ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/fetch/api/basic/http-response-code.any.sharedworker.html [ Crash Failure ]
-crbug.com/626703 [ Win10 ] external/wpt/fetch/api/basic/http-response-code.any.sharedworker.html [ Crash Failure ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/fetch/api/basic/http-response-code.any.sharedworker.html [ Crash Failure ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/html/semantics/forms/form-submission-0/text-plain.window.html [ Failure Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/html/semantics/forms/form-submission-0/text-plain.window.html [ Failure Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/html/semantics/forms/form-submission-0/text-plain.window.html [ Failure Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/websockets/stream/tentative/backpressure-send.any.serviceworker.html?wpt_flags=h2 [ Crash Failure ]
-crbug.com/626703 [ Win10 ] external/wpt/websockets/stream/tentative/backpressure-send.any.serviceworker.html?wpt_flags=h2 [ Crash Failure ]
-crbug.com/626703 [ Win10 ] external/wpt/url/a-element.html [ Crash Failure ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/websockets/stream/tentative/backpressure-send.any.serviceworker.html?wpt_flags=h2 [ Crash Failure ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/url/a-element.html [ Crash Failure ]
 crbug.com/1167095 [ Mac ] virtual/synchronous_html_parser/external/wpt/html/semantics/forms/form-submission-0/multipart-formdata.window.html [ Crash Failure Pass ]
-crbug.com/1167095 [ Win10 ] virtual/synchronous_html_parser/external/wpt/html/semantics/forms/form-submission-0/multipart-formdata.window.html [ Crash Failure ]
+crbug.com/1167095 [ Win10.1909 ] virtual/synchronous_html_parser/external/wpt/html/semantics/forms/form-submission-0/multipart-formdata.window.html [ Crash Failure ]
 crbug.com/626703 [ Mac ] external/wpt/FileAPI/file/send-file-formdata-controls.html [ Crash Failure ]
 crbug.com/626703 [ Win ] external/wpt/FileAPI/file/send-file-formdata-controls.html [ Crash Failure ]
 crbug.com/626703 [ Mac ] external/wpt/websockets/stream/tentative/abort.any.serviceworker.html?wpt_flags=h2 [ Crash Failure Pass ]
-crbug.com/626703 [ Win10 ] external/wpt/websockets/stream/tentative/abort.any.serviceworker.html?wpt_flags=h2 [ Crash Failure ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/websockets/stream/tentative/abort.any.serviceworker.html?wpt_flags=h2 [ Crash Failure ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/websockets/stream/tentative/abort.any.sharedworker.html?wpt_flags=h2 [ Crash Failure ]
-crbug.com/626703 [ Win10 ] external/wpt/websockets/stream/tentative/abort.any.sharedworker.html?wpt_flags=h2 [ Crash Failure ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/websockets/stream/tentative/abort.any.sharedworker.html?wpt_flags=h2 [ Crash Failure ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/websockets/stream/tentative/backpressure-send.any.html?wpt_flags=h2 [ Failure Timeout ]
 crbug.com/626703 [ Mac10.13 ] external/wpt/websockets/stream/tentative/backpressure-send.any.html?wpt_flags=h2 [ Failure Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/websockets/stream/tentative/backpressure-send.any.worker.html?wpt_flags=h2 [ Crash Failure ]
-crbug.com/626703 [ Win10 ] external/wpt/websockets/stream/tentative/backpressure-send.any.worker.html?wpt_flags=h2 [ Crash Failure ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/websockets/stream/tentative/backpressure-send.any.worker.html?wpt_flags=h2 [ Crash Failure ]
 crbug.com/626703 [ Mac10.15 ] virtual/offsetparent-old-behavior/external/wpt/shadow-dom/accesskey.tentative.html [ Crash Failure ]
-crbug.com/626703 [ Win10 ] virtual/offsetparent-old-behavior/external/wpt/shadow-dom/accesskey.tentative.html [ Crash Failure ]
+crbug.com/626703 [ Win10.1909 ] virtual/offsetparent-old-behavior/external/wpt/shadow-dom/accesskey.tentative.html [ Crash Failure ]
 crbug.com/626703 external/wpt/websockets/stream/tentative/close.any.html?wpt_flags=h2 [ Failure Timeout ]
 crbug.com/626703 external/wpt/websockets/stream/tentative/close.any.sharedworker.html?wpt_flags=h2 [ Failure Timeout ]
 crbug.com/626703 external/wpt/websockets/stream/tentative/close.any.serviceworker.html?wpt_flags=h2 [ Failure Timeout ]
@@ -3185,7 +3202,7 @@
 crbug.com/1170337 [ Linux ] external/wpt/html/canvas/element/drawing-text-to-the-canvas/2d.text.draw.fill.maxWidth.zero.html [ Pass Timeout ]
 crbug.com/626703 [ Linux ] external/wpt/html/canvas/offscreen/text/2d.text.draw.fill.maxWidth.NaN.worker.html [ Timeout ]
 crbug.com/626703 [ Linux ] external/wpt/webmessaging/with-ports/011.html [ Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/html/canvas/offscreen/text/2d.text.draw.fill.maxWidth.NaN.worker.html [ Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/html/canvas/offscreen/text/2d.text.draw.fill.maxWidth.NaN.worker.html [ Timeout ]
 crbug.com/626703 [ Mac10.13 ] external/wpt/html/canvas/offscreen/text/2d.text.draw.fill.maxWidth.NaN.worker.html [ Timeout ]
 crbug.com/626703 [ Mac10.13 ] external/wpt/webmessaging/without-ports/011.html [ Timeout ]
 crbug.com/626703 [ Mac10.13 ] external/wpt/html/canvas/offscreen/text/2d.text.draw.fill.maxWidth.zero.worker.html [ Timeout ]
@@ -3241,7 +3258,7 @@
 crbug.com/626703 [ Mac ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-pause-immediately.https.html [ Failure Pass ]
 crbug.com/626703 [ Mac ] external/wpt/web-locks/query-ordering.tentative.https.html [ Failure Pass ]
 crbug.com/626703 external/wpt/fetch/connection-pool/network-partition-key.html [ Failure Timeout ]
-crbug.com/626703 [ Win10 ] virtual/plz-dedicated-worker/external/wpt/fetch/connection-pool/network-partition-key.html [ Failure Pass Timeout ]
+crbug.com/626703 [ Win10.1909 ] virtual/plz-dedicated-worker/external/wpt/fetch/connection-pool/network-partition-key.html [ Failure Pass Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/open-features-tokenization-top-left.html [ Timeout ]
 crbug.com/626703 virtual/off-main-thread-css-paint/external/wpt/css/css-paint-api/valid-image-before-load.https.html [ Failure Pass ]
 crbug.com/626703 [ Win7 ] external/wpt/html/cross-origin-embedder-policy/reporting-subresource-corp.https.html [ Failure Timeout ]
@@ -3327,7 +3344,7 @@
 crbug.com/626703 [ Win7 ] external/wpt/pointerevents/pointerevent_touch-action-inherit_child-pan-x-child-pan-y_touch.html [ Timeout ]
 crbug.com/626703 [ Win7 ] external/wpt/pointerevents/pointerevent_touch-action-inherit_child-none_touch.html [ Timeout ]
 crbug.com/626703 [ Win7 ] external/wpt/pointerevents/pointerevent_touch-action-pan-y-css_touch.html [ Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/payment-request/payment-request-hasenrolledinstrument-method.tentative.https.html [ Failure Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/payment-request/payment-request-hasenrolledinstrument-method.tentative.https.html [ Failure Timeout ]
 crbug.com/626703 [ Win ] external/wpt/web-animations/timing-model/animation-effects/phases-and-states.html [ Crash ]
 crbug.com/626703 external/wpt/webvtt/rendering/cues-with-video/processing-model/snap-to-line.html [ Failure ]
 crbug.com/626703 [ Mac10.13 ] external/wpt/webaudio/the-audio-api/the-mediastreamaudiosourcenode-interface/mediastreamaudiosourcenode-routing.html [ Timeout ]
@@ -3337,7 +3354,7 @@
 crbug.com/626703 [ Win7 ] external/wpt/pointerevents/extension/pointerevent_touch-action-pan-right-css_touch.html [ Timeout ]
 crbug.com/626703 external/wpt/webrtc/RTCPeerConnection-setRemoteDescription-offer.html [ Timeout ]
 crbug.com/626703 external/wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success.https.html [ Timeout ]
-crbug.com/626703 [ Win10 ] virtual/plz-dedicated-worker/external/wpt/fetch/api/request/destination/fetch-destination-worker.https.html [ Crash ]
+crbug.com/626703 [ Win10.1909 ] virtual/plz-dedicated-worker/external/wpt/fetch/api/request/destination/fetch-destination-worker.https.html [ Crash ]
 crbug.com/626703 external/wpt/webauthn/idlharness-manual.https.window.js [ Skip ]
 crbug.com/983503 [ Mac ] external/wpt/css/css-contain/contain-size-select-elem-002.html [ Failure ]
 crbug.com/626703 [ Mac ] external/wpt/css/css-fonts/font-family-name-025.html [ Failure ]
@@ -3408,7 +3425,7 @@
 crbug.com/626703 external/wpt/css/css-text/overflow-wrap/overflow-wrap-normal-keep-all-001.html [ Failure ]
 crbug.com/626703 external/wpt/html/semantics/embedded-content/media-elements/src_object_blob.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-lists/list-item-definition.html [ Failure ]
-crbug.com/626703 [ Win10 ] external/wpt/content-security-policy/frame-ancestors/frame-ancestors-nested-cross-in-same-url-allow.html [ Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/content-security-policy/frame-ancestors/frame-ancestors-nested-cross-in-same-url-allow.html [ Timeout ]
 crbug.com/626703 external/wpt/media-source/mediasource-correct-frames-after-reappend.html [ Failure Pass Timeout ]
 crbug.com/626703 external/wpt/media-source/mediasource-correct-frames.html [ Failure Pass Timeout ]
 crbug.com/626703 external/wpt/payment-method-basic-card/steps_for_selecting_the_payment_handler.html [ Timeout ]
@@ -3650,8 +3667,8 @@
 crbug.com/981970 external/wpt/fetch/http-cache/split-cache.html [ Skip ]
 crbug.com/626703 external/wpt/fetch/http-cache/basic-auth-cache-test.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-fonts/font-feature-settings-descriptor-01.html [ Failure ]
-crbug.com/626703 [ Win10 ] external/wpt/fetch/api/redirect/redirect-count.any.worker.html [ Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/fetch/api/redirect/redirect-count.any.html [ Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/fetch/api/redirect/redirect-count.any.worker.html [ Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/fetch/api/redirect/redirect-count.any.html [ Timeout ]
 crbug.com/626703 external/wpt/css/cssom-view/scroll-behavior-smooth.html [ Crash Timeout ]
 crbug.com/626703 external/wpt/html/browsers/history/joint-session-history/joint-session-history-remove-iframe.html [ Timeout ]
 crbug.com/626703 external/wpt/css/css-fonts/font-variant-descriptor-01.html [ Failure ]
@@ -3753,7 +3770,7 @@
 crbug.com/626703 external/wpt/screen-orientation/onchange-event-subframe.html [ Timeout ]
 crbug.com/626703 external/wpt/svg/linking/reftests/href-filter-element.html [ Failure ]
 crbug.com/626703 virtual/plz-dedicated-worker/external/wpt/xhr/event-readystatechange-loaded.htm [ Failure Timeout ]
-crbug.com/626703 [ Win10 ] external/wpt/preload/delaying-onload-link-preload-after-discovery.html [ Timeout ]
+crbug.com/626703 [ Win10.1909 ] external/wpt/preload/delaying-onload-link-preload-after-discovery.html [ Timeout ]
 crbug.com/626703 external/wpt/html/dom/elements/global-attributes/dir_auto-N-EN-ref.html [ Failure ]
 
 # Synthetic modules report the wrong location in errors
@@ -4457,7 +4474,8 @@
 crbug.com/769056 virtual/text-antialias/emoji-web-font.html [ Failure ]
 crbug.com/1159689 [ Mac ] virtual/text-antialias/emoticons.html [ Failure Pass ]
 
-crbug.com/770232 [ Win10 ] virtual/text-antialias/hyphenate-character.html [ Failure ]
+crbug.com/770232 [ Win10.1909 ] virtual/text-antialias/hyphenate-character.html [ Failure ]
+crbug.com/770232 [ Win10.20h2 ] virtual/text-antialias/hyphenate-character.html [ Failure ]
 
 # Editing commands incorrectly assume no plain text length change after formatting text.
 crbug.com/764489 editing/execCommand/format-block-multiple-paragraphs.html [ Failure ]
@@ -4919,7 +4937,7 @@
 crbug.com/833658 media/video-controls-focus-movement-on-hide.html [ Failure Pass ]
 
 # Sheriff 2018-04-23
-crbug.com/833331 [ Win10 ] inspector-protocol/page/pageNavigateToFragment.js [ Failure Pass ]
+crbug.com/833331 [ Win10.1909 ] inspector-protocol/page/pageNavigateToFragment.js [ Failure Pass ]
 
 # Sheriff 2018-05-22
 crbug.com/845610 [ Win ] http/tests/inspector-protocol/target/target-browser-context.js [ Failure Pass ]
@@ -5400,10 +5418,12 @@
 crbug.com/1005128 crypto/subtle/abandon-crypto-operation2.html [ Crash Pass ]
 
 # Sheriff 2019-09-30
-crbug.com/1003715 [ Win10 ] http/tests/notifications/serviceworker-notification-properties.html [ Failure Pass Timeout ]
+crbug.com/1003715 [ Win10.1909 ] http/tests/notifications/serviceworker-notification-properties.html [ Failure Pass Timeout ]
+crbug.com/1003715 [ Win10.20h2 ] http/tests/notifications/serviceworker-notification-properties.html [ Failure Pass Timeout ]
+
 
 # Sheriff 2019-10-01
-crbug.com/1010013 [ Win10 ] virtual/threaded/http/tests/devtools/tracing/timeline-style/timeline-style-recalc-all-invalidator-types.js [ Failure ]
+crbug.com/1010013 [ Win10.1909 ] virtual/threaded/http/tests/devtools/tracing/timeline-style/timeline-style-recalc-all-invalidator-types.js [ Failure ]
 crbug.com/1010032 [ Win7 ] virtual/text-antialias/fallback-traits-fixup.html [ Failure ]
 crbug.com/1010032 [ Win7 ] virtual/text-antialias/international/bold-bengali.html [ Failure ]
 crbug.com/1010032 [ Win7 ] virtual/text-antialias/selection/khmer-selection.html [ Failure ]
@@ -5922,7 +5942,7 @@
 
 # Sheriff 2020-06-11
 crbug.com/1093026 [ Linux ] http/tests/security/offscreencanvas-placeholder-read-blocked-no-crossorigin.html [ Failure Pass ]
-crbug.com/1093026 [ Win10 ] http/tests/security/offscreencanvas-placeholder-read-blocked-no-crossorigin.html [ Failure Pass ]
+crbug.com/1093026 [ Win10.1909 ] http/tests/security/offscreencanvas-placeholder-read-blocked-no-crossorigin.html [ Failure Pass ]
 
 # Ecosystem-Infra Rotation 2020-06-15
 crbug.com/924472 external/wpt/css/css-transforms/transform-box/cssbox-content-box.html [ Failure ]
@@ -7232,4 +7252,4 @@
 crbug.com/1215390 [ Linux ] external/wpt/pointerevents/pointerevent_pointerId_scope.html [ Failure Pass ]
 
 # Sheriff 2021-08-04
-crbug.com/1236466 inspector-protocol/runtime/runtime-execution-contexts-events.js [ Failure Pass Crash ]
+crbug.com/1236466 inspector-protocol/runtime/runtime-execution-contexts-events.js [ Failure Pass Crash Timeout ]
diff --git a/third_party/blink/web_tests/W3CImportExpectations b/third_party/blink/web_tests/W3CImportExpectations
index 4e4fac6..36667cd 100644
--- a/third_party/blink/web_tests/W3CImportExpectations
+++ b/third_party/blink/web_tests/W3CImportExpectations
@@ -1,4 +1,4 @@
-# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Win Win7 Win10 ]
+# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Win Win7 Win10.1909 ]
 # tags: [ Release Debug ]
 # results: [ Skip ]
 
diff --git a/third_party/blink/web_tests/WPTOverrideExpectations b/third_party/blink/web_tests/WPTOverrideExpectations
index 3afc434..e4c91a7b 100644
--- a/third_party/blink/web_tests/WPTOverrideExpectations
+++ b/third_party/blink/web_tests/WPTOverrideExpectations
@@ -1,4 +1,4 @@
-# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Win Win7 Win10 ]
+# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Win Win7 Win10.1909 ]
 # tags: [ Release Debug ]
 # results: [ Timeout Crash Pass Failure Slow Skip ]
 
diff --git a/third_party/blink/web_tests/WebDriverExpectations b/third_party/blink/web_tests/WebDriverExpectations
index 18dd887..fbdf89a 100644
--- a/third_party/blink/web_tests/WebDriverExpectations
+++ b/third_party/blink/web_tests/WebDriverExpectations
@@ -1,4 +1,4 @@
-# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Mac10.14 Mac10.15 Mac11.0 Win Win7 Win10 ]
+# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Mac10.14 Mac10.15 Mac11.0 Win Win7 Win10.1909 ]
 # tags: [ Release Debug ]
 # results: [ Timeout Crash Pass Failure Slow Skip ]
 
diff --git a/third_party/blink/web_tests/WebGPUExpectations b/third_party/blink/web_tests/WebGPUExpectations
index 20cf373..8d9557b 100644
--- a/third_party/blink/web_tests/WebGPUExpectations
+++ b/third_party/blink/web_tests/WebGPUExpectations
@@ -1,4 +1,4 @@
-# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Win Win7 Win10 ]
+# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.13 Win Win7 Win10.1909 ]
 # tags: [ Release Debug ]
 # results: [ Timeout Crash Pass Failure Slow Skip RetryOnFailure ]
 
@@ -257,12 +257,6 @@
 ### Linux (Vulkan) specific
 ###
 
-# Very flaky on Windows/Linux, especially (but not exclusively!) with backend validation
-crbug.com/1087130 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createView:* [ RetryOnFailure ]
-
-# Nvidia only, worker only, very flaky
-[ Linux ] wpt_internal/webgpu/cts.html?worker=1&q=webgpu:api,operation,render_pass,storeop2:* [ Failure ]
-
 # and possibly crbug.com/1231840
 crbug.com/1213657 [ Linux ] wpt_internal/webgpu/cts.html?q=webgpu:web_platform,copyToTexture,canvas:copy_contents_from_gl_context_canvas:* [ Slow Crash Failure Timeout ]
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/fallback-remote-to-data-url.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/fallback-remote-to-data-url.html
index 2f63312..eb3df63 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/fallback-remote-to-data-url.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/fallback-remote-to-data-url.html
@@ -21,14 +21,15 @@
   font-family: remote-font;
   /* Set a long delay to make sure it doesn't finish loading */
   src: url('/fonts/Revalia.woff?pipe=trickle(d1)') format('woff');
+  font-display: swap;
 }
 
-#target {
+#target, #not-covered {
   font: 25px/1 remote-font, data-font, monospace;
 }
 </style>
 <span id="target">0123456789</span>
-<span id="non-target">ABCDEFGHIJ</span>
+<span id="not-covered">ABCDEFGHIJ</span>
 <script>
 promise_test(async () => {
   // The loading of data-font may still be asynchronous. Wait to allow it to load.
@@ -40,7 +41,7 @@
     target.offsetWidth, 250,
     'Digits are in the unicode range of data-font and hence shaped with it');
   assert_not_equals(
-    document.getElementById('non-target').offsetWidth, 250,
+    document.getElementById('not-covered').offsetWidth, 250,
     'Letters are out of the unicode range of data-font and hence shaped with fallback');
 }, 'We should use the inline custom font to render the page when the primary remote font is loading');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-invalidation-currentcolor-ref.html b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-invalidation-currentcolor-ref.html
new file mode 100644
index 0000000..7138dcc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-invalidation-currentcolor-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<div style="accent-color: red">
+  The following checkbox should be red: <input type=checkbox checked>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-invalidation-currentcolor.html b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-invalidation-currentcolor.html
new file mode 100644
index 0000000..804e854
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-invalidation-currentcolor.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1225661">
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=match href="accent-color-invalidation-currentcolor-ref.html">
+
+<div style="accent-color: currentColor">
+  The following checkbox should be red: <input type=checkbox checked>
+</div>
+
+<script>
+async function rAF() {
+  return new Promise(resolve => requestAnimationFrame(resolve));
+}
+
+(async () => {
+  await rAF();
+  await rAF();
+  document.querySelector('input').style = 'color: red';
+  await rAF();
+  await rAF();
+  document.documentElement.classList.remove('reftest-wait');
+})();
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-parent-currentcolor-ref.html b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-parent-currentcolor-ref.html
new file mode 100644
index 0000000..3930038a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-parent-currentcolor-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<div style="accent-color: red; color: red">
+  The following checkbox should be red: <input type=checkbox checked>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-parent-currentcolor.html b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-parent-currentcolor.html
new file mode 100644
index 0000000..32240cf
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/accent-color-parent-currentcolor.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1225661">
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=match href="accent-color-parent-currentcolor-ref.html">
+
+<div style="accent-color: currentColor; color: red">
+  The following checkbox should be red: <input type=checkbox checked style="color:unset">
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/mediaqueries/prefers-contrast.html b/third_party/blink/web_tests/external/wpt/css/mediaqueries/prefers-contrast.html
index 304660b..daba852 100644
--- a/third_party/blink/web_tests/external/wpt/css/mediaqueries/prefers-contrast.html
+++ b/third_party/blink/web_tests/external/wpt/css/mediaqueries/prefers-contrast.html
@@ -9,6 +9,7 @@
 query_should_be_css_parseable("(prefers-contrast: no-preference)");
 query_should_be_css_parseable("(prefers-contrast: more)");
 query_should_be_css_parseable("(prefers-contrast: less)");
+query_should_be_css_parseable("(prefers-contrast: custom)");
 
 query_should_not_be_css_parseable("(prefers-contrast: increase)");
 query_should_not_be_css_parseable("(prefers-contrast: none)");
@@ -25,6 +26,7 @@
 query_should_be_js_parseable("(prefers-contrast: no-preference)");
 query_should_be_js_parseable("(prefers-contrast: more)");
 query_should_be_js_parseable("(prefers-contrast: less)");
+query_should_be_js_parseable("(prefers-contrast: custom)");
 
 query_should_not_be_js_parseable("(prefers-contrast: increase)");
 query_should_not_be_js_parseable("(prefers-contrast: none)");
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-parts-structure.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-parts-structure.tentative.html
index 32c7a83..6b50beba 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-parts-structure.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-parts-structure.tentative.html
@@ -61,6 +61,15 @@
   </div>
 </selectmenu>
 
+<selectmenu id="selectMenu5">
+  <div slot="button" id="selectMenu5-button-slot">
+    <div part="button" id="selectMenu5-button0">button0</div>
+    <div part="selected-value" id="selectMenu5-selectedValue0"></div>
+  </div>
+  <option>one</option>
+  <option id="selectMenu5-option0">two</option>
+</selectmenu>
+
 <script>
   function clickOn(element) {
     const actions = new test_driver.Actions();
@@ -175,4 +184,25 @@
     await clickOn(selectMenu4Option2);
     assert_equals(selectMenu4.value, "two", "An option in a listbox should get controller code if its listbox is first in document order, even if another listbox was added dynamically");
 }, "Listbox controller code should be applied in flat tree traversal order regardless of dynamic insertion order");
+
+promise_test(async () => {
+    const selectMenu5 = document.getElementById("selectMenu5");
+    const selectMenu5ButtonSlot = document.getElementById("selectMenu5-button-slot");
+    const selectMenu5Button0 = document.getElementById("selectMenu5-button0");
+    const selectMenu5SelectedValue0 = document.getElementById("selectMenu5-selectedValue0");
+
+    assert_false(selectMenu3.open);
+    assert_equals(selectMenu5SelectedValue0.innerText, "one");
+
+    let selectedValue1 = document.createElement("div");
+    selectMenu5ButtonSlot.appendChild(selectedValue1);
+
+    await clickOn(selectMenu5Button0);
+    assert_true(selectMenu5.open);
+
+    await clickOn(document.getElementById("selectMenu5-option0"));
+    assert_false(selectMenu5.open);
+    assert_equals(selectMenu5SelectedValue0.innerText, "two", "first selected-value part in flat tree order should get controller code");
+    assert_equals(selectedValue1.innerText, "", "Dynamically inserted selected-value part shouldn't get controller code if it's not first in flat tree order");
+  }, "selected-value controller code should be applied in flat tree traversal order regardless of dynamic insertion order");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-popup.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-popup.tentative-expected.txt
deleted file mode 100644
index 056f1af..0000000
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-popup.tentative-expected.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This is a testharness.js-based test.
-PASS Opening the popup and clicking an option should change the selectmenu's value
-PASS With custom button and popup: opening the popup and clicking an option should change the selectmenu's value
-FAIL Clicking a popup with no listbox part does nothing assert_equals: expected false but got true
-PASS Clicking a popup with a listbox that was removed does nothing
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-popup.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-popup.tentative.html
index 156725d..b43a8fe 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-popup.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-popup.tentative.html
@@ -90,8 +90,6 @@
     const selectMenu2 = document.getElementById("selectMenu2");
     await clickOn(selectMenu2);
     assert_equals(selectMenu2.value, "");
-
-    // TODO(crbug.com/1234899) Fails because listbox pointer isn't cleared when default listbox is swapped out due to initial <slot> replacement
     assert_equals(selectMenu2.open, false);
 
   }, "Clicking a popup with no listbox part does nothing");
diff --git a/third_party/blink/web_tests/fast/canvas/layers-alpha-filter-expected.html b/third_party/blink/web_tests/fast/canvas/layers-alpha-filter-expected.html
new file mode 100644
index 0000000..dda152f
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/layers-alpha-filter-expected.html
@@ -0,0 +1,25 @@
+<body>
+<script>
+    var canvas, ctx;
+    canvas = document.createElement("canvas");
+    canvas.width = 200;
+    canvas.height = 200;
+    document.body.appendChild(canvas);
+
+    ctx = canvas.getContext("2d");
+    ctx.fillStyle = 'rgba(0,0,255,1)';
+    ctx.fillRect(60,60,75,50);
+    ctx.filter = 'drop-shadow(-9px 9px 0px #e81)';
+    ctx.globalAlpha = 0.6;
+
+    canvas2 = document.createElement("canvas");
+    ctx2 = canvas2.getContext("2d");
+
+    ctx2.fillStyle = 'rgba(225,0,0,1)';
+    ctx2.fillRect(50,50,75,50);
+    ctx2.fillStyle = 'rgba(0,255,0,1)';
+    ctx2.fillRect(70,70,75,50);
+
+    ctx.drawImage(canvas2,0,0);
+</script>
+</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/canvas/layers-alpha-filter-globalcompositeoperation-expected.html b/third_party/blink/web_tests/fast/canvas/layers-alpha-filter-globalcompositeoperation-expected.html
new file mode 100644
index 0000000..b602ddd
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/layers-alpha-filter-globalcompositeoperation-expected.html
@@ -0,0 +1,29 @@
+<body>
+<script>
+    var canvas, ctx;
+    canvas = document.createElement("canvas");
+    canvas.width = 200;
+    canvas.height = 200;
+    document.body.appendChild(canvas);
+
+    ctx = canvas.getContext("2d");
+    ctx.fillStyle = 'rgba(0,0,255,1)';
+    var circle = new Path2D();
+    circle.arc(90, 90, 40, 0, 2 * Math.PI);
+    ctx.fill(circle);
+    ctx.filter = 'drop-shadow(-9px 9px 0px #e81)';
+    ctx.globalAlpha = 0.6;
+    ctx.globalCompositeOperation = 'source-in';
+
+    canvas2 = document.createElement("canvas");
+    ctx2 = canvas2.getContext("2d");
+
+    ctx2.fillStyle = 'rgba(225,0,0,1)';
+    ctx2.fillRect(50,50,75,50);
+    ctx2.fillStyle = 'rgba(0,255,0,1)';
+    ctx2.fillRect(70,70,75,50);
+
+    ctx.drawImage(canvas2,0,0);
+
+</script>
+</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/canvas/layers-alpha-filter-globalcompositeoperation.html b/third_party/blink/web_tests/fast/canvas/layers-alpha-filter-globalcompositeoperation.html
new file mode 100644
index 0000000..ee7edb7
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/layers-alpha-filter-globalcompositeoperation.html
@@ -0,0 +1,30 @@
+<body>
+<script>
+    // Test to ensure beginlayer works when global alpha, filter, and 
+    // global composite operation are applied.
+    var canvas, ctx;
+    canvas = document.createElement("canvas");
+    canvas.width = 200;
+    canvas.height = 200;
+    document.body.appendChild(canvas);
+
+    ctx = canvas.getContext("2d");
+    ctx.fillStyle = 'rgba(0,0,255,1)';
+    var circle = new Path2D();
+    circle.arc(90, 90, 40, 0, 2 * Math.PI);
+    ctx.fill(circle);
+    ctx.filter = 'drop-shadow(-9px 9px 0px #e81)';
+    ctx.globalAlpha = 0.6;
+
+    ctx.globalCompositeOperation = 'source-in';
+
+    ctx.beginLayer();
+
+    ctx.fillStyle = 'rgba(225,0,0,1)';
+    ctx.fillRect(50,50,75,50);
+    ctx.fillStyle = 'rgba(0,255,0,1)';
+    ctx.fillRect(70,70,75,50);
+
+    ctx.endLayer();
+</script>
+</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/canvas/layers-alpha-filter.html b/third_party/blink/web_tests/fast/canvas/layers-alpha-filter.html
new file mode 100644
index 0000000..42d8968
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/layers-alpha-filter.html
@@ -0,0 +1,25 @@
+<body>
+<script>
+    // Test to ensure beginlayer works when both alpha and filter are applied.
+    var canvas, ctx;
+    canvas = document.createElement("canvas");
+    canvas.width = 200;
+    canvas.height = 200;
+    document.body.appendChild(canvas);
+
+    ctx = canvas.getContext("2d");
+    ctx.fillStyle = 'rgba(0,0,255,1)';
+    ctx.fillRect(60,60,75,50);
+    ctx.filter = 'drop-shadow(-9px 9px 0px #e81)';
+    ctx.globalAlpha = 0.6;
+
+    ctx.beginLayer();
+
+    ctx.fillStyle = 'rgba(225,0,0,1)';
+    ctx.fillRect(50,50,75,50);
+    ctx.fillStyle = 'rgba(0,255,0,1)';
+    ctx.fillRect(70,70,75,50);
+
+    ctx.endLayer();
+</script>
+</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/canvas/layers-filter-expected.html b/third_party/blink/web_tests/fast/canvas/layers-filter-expected.html
new file mode 100644
index 0000000..2c69d20
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/layers-filter-expected.html
@@ -0,0 +1,24 @@
+<body>
+<script>
+    var canvas, ctx;
+    canvas = document.createElement("canvas");
+    canvas.width = 200;
+    canvas.height = 200;
+    document.body.appendChild(canvas);
+
+    ctx = canvas.getContext("2d");
+    ctx.fillStyle = 'rgba(0,0,255,1)';
+    ctx.fillRect(60,60,75,50);
+    ctx.filter = 'sepia(1) opacity(30%)';
+
+    canvas2 = document.createElement("canvas");
+    ctx2 = canvas2.getContext("2d");
+
+    ctx2.fillStyle = 'rgba(225,0,0,1)';
+    ctx2.fillRect(50,50,75,50);
+    ctx2.fillStyle = 'rgba(0,255,0,1)';
+    ctx2.fillRect(70,70,75,50);
+
+    ctx.drawImage(canvas2,0,0);
+</script>
+</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/canvas/layers-filter-globalcompositeoperation-expected.html b/third_party/blink/web_tests/fast/canvas/layers-filter-globalcompositeoperation-expected.html
new file mode 100644
index 0000000..5346e71
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/layers-filter-globalcompositeoperation-expected.html
@@ -0,0 +1,29 @@
+<body>
+<script>
+    var canvas, ctx;
+    canvas = document.createElement("canvas");
+    canvas.width = 200;
+    canvas.height = 200;
+    document.body.appendChild(canvas);
+
+    ctx = canvas.getContext("2d");
+    ctx.fillStyle = 'rgba(0,0,255,1)';
+
+    var circle = new Path2D();
+    circle.arc(90, 90, 40, 0, 2 * Math.PI);
+    ctx.fill(circle);
+
+    ctx.globalCompositeOperation = 'source-in';
+    ctx.filter = 'drop-shadow(-9px 9px 0px #e81)';
+
+    canvas2 = document.createElement("canvas");
+    ctx2 = canvas2.getContext("2d");
+
+    ctx2.fillStyle = 'rgba(225,0,0,1)';
+    ctx2.fillRect(50,50,75,50);
+    ctx2.fillStyle = 'rgba(0,255,0,1)';
+    ctx2.fillRect(70,70,75,50);
+
+    ctx.drawImage(canvas2,0,0);
+</script>
+</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/canvas/layers-filter-globalcompositeoperation.html b/third_party/blink/web_tests/fast/canvas/layers-filter-globalcompositeoperation.html
new file mode 100644
index 0000000..d62d0c43
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/layers-filter-globalcompositeoperation.html
@@ -0,0 +1,28 @@
+<body>
+<script>
+    var canvas, ctx;
+    canvas = document.createElement("canvas");
+    canvas.width = 200;
+    canvas.height = 200;
+    document.body.appendChild(canvas);
+
+    ctx = canvas.getContext("2d");
+    ctx.fillStyle = 'rgba(0,0,255,1)';
+
+    var circle = new Path2D();
+    circle.arc(90, 90, 40, 0, 2 * Math.PI);
+    ctx.fill(circle);
+
+    ctx.globalCompositeOperation = 'source-in';
+    ctx.filter = 'drop-shadow(-9px 9px 0px #e81)';
+
+    ctx.beginLayer();
+
+    ctx.fillStyle = 'rgba(225,0,0,1)';
+    ctx.fillRect(50,50,75,50);
+    ctx.fillStyle = 'rgba(0,255,0,1)';
+    ctx.fillRect(70,70,75,50);
+
+    ctx.endLayer();
+</script>
+</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/canvas/layers-filter.html b/third_party/blink/web_tests/fast/canvas/layers-filter.html
new file mode 100644
index 0000000..fdbb86e65
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/layers-filter.html
@@ -0,0 +1,24 @@
+<body>
+<script>
+    // Test to ensure beginlayer works for filter.
+    var canvas, ctx;
+    canvas = document.createElement("canvas");
+    canvas.width = 200;
+    canvas.height = 200;
+    document.body.appendChild(canvas);
+
+    ctx = canvas.getContext("2d");
+    ctx.fillStyle = 'rgba(0,0,255,1)';
+    ctx.fillRect(60,60,75,50);
+    ctx.filter = 'sepia(1) opacity(30%)';
+
+    ctx.beginLayer();
+
+    ctx.fillStyle = 'rgba(225,0,0,1)';
+    ctx.fillRect(50,50,75,50);
+    ctx.fillStyle = 'rgba(0,255,0,1)';
+    ctx.fillRect(70,70,75,50);
+
+    ctx.endLayer();
+</script>
+</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/css/focus-ring-multiline-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/css/focus-ring-multiline-expected.png
index b7f621b..fc2315dc 100644
--- a/third_party/blink/web_tests/platform/fuchsia/fast/css/focus-ring-multiline-expected.png
+++ b/third_party/blink/web_tests/platform/fuchsia/fast/css/focus-ring-multiline-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-expected.png
index 67b553b..e103866b 100644
--- a/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-expected.png
+++ b/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-expected.png
index 3ec0aa9..6a99d77 100644
--- a/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-expected.png
+++ b/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-links-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-links-expected.png
index a431968..72174737 100644
--- a/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-links-expected.png
+++ b/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-links-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-not-propagated-by-out-of-flow-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-not-propagated-by-out-of-flow-expected.png
index 57a16d8a..438f25d 100644
--- a/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-not-propagated-by-out-of-flow-expected.png
+++ b/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-not-propagated-by-out-of-flow-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-simple-underlines-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-simple-underlines-expected.png
index fc70a3b..7d109259 100644
--- a/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-simple-underlines-expected.png
+++ b/third_party/blink/web_tests/platform/fuchsia/fast/css3-text/css3-text-decoration/text-decoration-style-inherit-simple-underlines-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/fast/replaced/selection-rect-transform-expected.png b/third_party/blink/web_tests/platform/fuchsia/fast/replaced/selection-rect-transform-expected.png
index 8b5c715..16a6ab87 100644
--- a/third_party/blink/web_tests/platform/fuchsia/fast/replaced/selection-rect-transform-expected.png
+++ b/third_party/blink/web_tests/platform/fuchsia/fast/replaced/selection-rect-transform-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/paint/invalidation/outline/focus-layers-expected.png b/third_party/blink/web_tests/platform/fuchsia/paint/invalidation/outline/focus-layers-expected.png
index d809984..ca92d13 100644
--- a/third_party/blink/web_tests/platform/fuchsia/paint/invalidation/outline/focus-layers-expected.png
+++ b/third_party/blink/web_tests/platform/fuchsia/paint/invalidation/outline/focus-layers-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug2479-3-expected.png b/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug2479-3-expected.png
index bf4d50c..53c1a2ed 100644
--- a/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug2479-3-expected.png
+++ b/third_party/blink/web_tests/platform/fuchsia/tables/mozilla/bugs/bug2479-3-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
index 335aa2f1..2159bd6 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
index 9bac769..70dfb9d8 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac-mac-arm11.0/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
new file mode 100644
index 0000000..c366218
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac-mac-arm11.0/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
new file mode 100644
index 0000000..be3771d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
index c366218..570ce3d7 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
index be3771d..6fa2a8e 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
index 807abd7..0833ca8 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/imagemap-overflowing-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
index c698a08..9aa08f7 100644
--- a/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/gpu-rasterization/images/imagemap-polygon-focus-ring-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/resources/unload-on-prerender-main-frame-navigation.html b/third_party/blink/web_tests/wpt_internal/prerender/resources/unload-on-prerender-main-frame-navigation.html
new file mode 100644
index 0000000..f1c647d
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/prerender/resources/unload-on-prerender-main-frame-navigation.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="unload-utils.js"></script>
+<script src="utils.js"></script>
+<body>
+<script type="module">
+const params = new URLSearchParams(location.search);
+const state = params.get('state') || 'start';
+
+const bc = openChannel();
+
+// Test steps:
+//   1. `start` page starts prerendering `prerendering` page.
+//   2. `prerendering` page navigates to `another-page`.
+//   3. `prerendering` page asks the `start` page to navigate.
+//   4. ``pagehide` and unload` may not be handled as the prerendering is
+//      cancelled.
+//   5. `start` does a fallback navigation as the prerendering is cancelled.
+//   6. `prerendering` page loads again with document.prerendering == false.
+//   7. `prerendering` page navigates to `another-page` again.
+//   8. `pagehide`, `visibilitychange`, and `unload` are handled this time.
+//   9. `another-page` finishes the test.
+bc.postMessage('load ' + state +
+               (document.prerendering ? ' in prerendering' : ''));
+if (state === 'start') {
+  const url = createTestUrl('prerendering');
+  startPrerendering(url);
+
+  // Wait for a message from `prerendering` to navigate that results in
+  // fallback network navigation as the prerendering is cancelled.
+  await waitChannelMessage('request fallback');
+
+  // Navigate.
+  document.location = url;
+} else if (state === 'prerendering') {
+  // unload handler may not be triggered for cancellation.
+  addEventListeners('main-frame');
+
+  // Trigger a main frame navigation that will cancel the prerendering.
+  // This navigates successfully if the page isn't prerendered.
+  document.location = createTestUrl('another-page');
+
+  // Ask the `start` page to navigate if on prerendering. Otherwise, finish
+  // the test.
+  if (document.prerendering)
+    bc.postMessage('request fallback');
+} else if (state === 'another-page') {
+  // Reach here after the prerendering is cancelled and fallback request loads
+  // the `prerendering` page with document.prerendering == false. The main
+  // frame navigation will succeed this time.
+  bc.postMessage('Done');
+}
+
+bc.close();
+</script>
+</body>
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/resources/unload-utils.js b/third_party/blink/web_tests/wpt_internal/prerender/resources/unload-utils.js
index a93c959..1a4a4c04 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/resources/unload-utils.js
+++ b/third_party/blink/web_tests/wpt_internal/prerender/resources/unload-utils.js
@@ -1,5 +1,5 @@
 // Note: Following utility functions are expected to be used from
-// upload-on-prerender-* test files.
+// unload-on-prerender-* test files.
 
 function createTestUrl(nextState) {
   const params = new URLSearchParams();
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/unload-on-prerender-main-frame-navigation.html b/third_party/blink/web_tests/wpt_internal/prerender/unload-on-prerender-main-frame-navigation.html
new file mode 100644
index 0000000..3055a0f
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/prerender/unload-on-prerender-main-frame-navigation.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<!--
+This file cannot be upstreamed to WPT until:
+* The test changes to use a method to trigger cancellation of prerendering that
+  is guaranteed by the specification. Currently the test cancels prerendering
+  by performing a main frame navigation after the initial prerendering
+  navigation.
+* `unload` event handling matches what the specification expects. The current
+  specification expects that the unload event handler is never fired, and the
+  test passes whether or not it is fired.
+* `pageshow` and `pagehide` behaviors during prerendering is clearly defined
+  in the specification. This test just checks current behaviors.
+  (https://crbug.com/1222551)
+-->
+<title>unload event handlers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/unload-utils.js"></script>
+<script>
+  promise_test(async t => {
+    const gotMessages = waitChannelMessage('Done');
+    const url = 'resources/unload-on-prerender-main-frame-navigation.html';
+    window.open(url, '_blank', 'noopener');
+    const result = await gotMessages;
+
+    const expected = [
+      // If `optional` is specified as true, that entry may not appear.
+      { message: 'load start' },
+      { message: 'load prerendering in prerendering' },
+      { message: 'request fallback' },
+      // TODO(https://crbug.com/1200241): `pagehide` and `unload` may not run
+      // sometimes for unknown reason. Clarify the reason and fix it.
+      { message: 'pagehide main-frame in prerendering', optional: true },
+      { message: 'unload main-frame in prerendering', optional: true },
+      { message: 'load prerendering' },
+      { message: 'pagehide main-frame' },
+      { message: 'visibilitychange main-frame' },
+      { message: 'unload main-frame' },
+      { message: 'load another-page' },
+      { message: 'Done' },
+    ];
+    let offset = 0;
+    assert_less_than_equal(result.length, expected.length);
+    for (let i = 0; i < expected.length; ++i) {
+      assert_less_than(offset, result.length);
+      if (expected[i].optional && expected[i].message != result[offset])
+        continue;
+      assert_equals(result[offset], expected[i].message, `messages${offset}`);
+      offset++;
+    }
+    assert_equals(offset, result.length);
+  }, 'unload on main frame navigation to cancel prerendering');
+</script>
\ No newline at end of file
diff --git a/tools/bisect-builds.py b/tools/bisect-builds.py
index 674fd09..3727afd 100755
--- a/tools/bisect-builds.py
+++ b/tools/bisect-builds.py
@@ -62,7 +62,7 @@
 CHROMIUM_SEARCH_PATTERN_OLD = (
     r'.*git-svn-id: svn://svn.chromium.org/chrome/trunk/src@(\d+) ')
 CHROMIUM_SEARCH_PATTERN = (
-    r'Cr-Commit-Position: refs/heads/master@{#(\d+)}')
+    r'Cr-Commit-Position: refs/heads/(?:master|main)@{#(\d+)}')
 
 # Search pattern to be matched in the json output from
 # BLINK_GITHASH_TO_SVN_URL to get the blink revision (svn revision).
diff --git a/tools/clang/scripts/build.py b/tools/clang/scripts/build.py
index 56c6fb93..f421cbc 100755
--- a/tools/clang/scripts/build.py
+++ b/tools/clang/scripts/build.py
@@ -471,15 +471,6 @@
 
   global CLANG_REVISION, PACKAGE_VERSION, LLVM_BUILD_DIR
 
-  # TODO(crbug.com/1233845): Remove in the next Clang roll.
-  if args.llvm_force_head_revision:
-    global RELEASE_VERSION
-    RELEASE_VERSION = '14.0.0'
-    old_lib_dir = os.path.join(LLVM_BUILD_DIR, 'lib', 'clang', '13.0.0')
-    if (os.path.isdir(old_lib_dir)):
-      print('Removing old lib dir: ' + old_lib_dir)
-      RmTree(old_lib_dir)
-
   if (args.pgo or args.thinlto) and not args.bootstrap:
     print('--pgo/--thinlto requires --bootstrap')
     return 1
diff --git a/tools/clang/scripts/update.py b/tools/clang/scripts/update.py
index b8f44d2..e592ae2 100755
--- a/tools/clang/scripts/update.py
+++ b/tools/clang/scripts/update.py
@@ -39,12 +39,11 @@
 # https://chromium.googlesource.com/chromium/src/+/main/docs/updating_clang.md
 # Reverting problematic clang rolls is safe, though.
 # This is the output of `git describe` and is usable as a commit-ish.
-CLANG_REVISION = 'llvmorg-13-init-15561-gf98ed74f'
+CLANG_REVISION = 'llvmorg-14-init-591-g7d9d926a'
 CLANG_SUB_REVISION = 1
 
 PACKAGE_VERSION = '%s-%s' % (CLANG_REVISION, CLANG_SUB_REVISION)
-RELEASE_VERSION = '13.0.0'
-# TODO(crbug.com/1233845): Bump to 14.0.0 in the next Clang roll.
+RELEASE_VERSION = '14.0.0'
 
 CDS_URL = os.environ.get('CDS_CLANG_BUCKET_OVERRIDE',
     'https://commondatastorage.googleapis.com/chromium-browser-clang')
@@ -312,11 +311,6 @@
                       help='Verify that clang has the passed-in version.')
   args = parser.parse_args()
 
-  # TODO(crbug.com/1233845): Remove in the next Clang roll.
-  if args.llvm_force_head_revision:
-    global RELEASE_VERSION
-    RELEASE_VERSION = '14.0.0'
-
   if args.verify_version and args.verify_version != RELEASE_VERSION:
     print('RELEASE_VERSION is %s but --verify-version argument was %s.' % (
         RELEASE_VERSION, args.verify_version))
diff --git a/tools/ipc_fuzzer/message_lib/BUILD.gn b/tools/ipc_fuzzer/message_lib/BUILD.gn
index 7ee2596..6aca6eb 100644
--- a/tools/ipc_fuzzer/message_lib/BUILD.gn
+++ b/tools/ipc_fuzzer/message_lib/BUILD.gn
@@ -47,9 +47,6 @@
     public_deps += [ "//components/nacl/common" ]
   }
   if (enable_remoting) {
-    public_deps += [
-      "//remoting/host",
-      "//remoting/host:headers",
-    ]
+    public_deps += [ "//remoting/host" ]
   }
 }
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 01a4e783..b777526c 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -101,13 +101,13 @@
       'android-weblayer-with-aosp-webview-x86-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_resource_allowlisting_disable_proguard_chrome_google',
       'android-weblayer-x86-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_disable_proguard_webview_google',
       'android-10-arm64-rel': 'android_release_bot_minimal_symbols_arm64_fastbuild_webview_google',
+      'android-11-x86-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_webview_google',
     },
 
     'chromium.android.fyi': {
       'Android ASAN (dbg) (reclient)': 'android_clang_asan_debug_bot_reclient',
       'Android arm64 Builder (dbg) (reclient)': 'android_webview_google_debug_static_bot_arm64_reclient',
       'Android WebView P FYI (rel)': 'android_release_bot_minimal_symbols_arm64_webview_google',
-      'android-11-x86-fyi-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_webview_google',
       'android-12-x64-fyi-rel': 'android_release_bot_minimal_symbols_x64_fastbuild_webview_google',
       'android-pie-arm64-wpt-rel-non-cq': 'android_release_bot_minimal_symbols_arm64_webview_google',
       'android-web-platform-pie-x86-fyi-rel': 'android_release_bot_minimal_symbols_x86_fastbuild_webview_google',
@@ -824,7 +824,7 @@
       'android-weblayer-pie-x86-wpt-smoketest': 'android_release_trybot_x86_fastbuild_webview_google',
       'android-webview-pie-arm64-fyi-rel': 'android_release_trybot_arm64_webview_google',
       'android-10-arm64-rel': 'android_release_trybot_arm64_fastbuild_webview_google',
-      'android-11-x86-fyi-rel': 'android_release_trybot_x86_fastbuild_webview_google',
+      'android-11-x86-rel': 'android_release_trybot_x86_fastbuild_webview_google',
       'android-12-x64-fyi-rel': 'android_release_trybot_x64_fastbuild_webview_google',
       'android-webview-marshmallow-arm64-dbg': 'android_release_trybot_arm64_webview_google',
       'android-webview-nougat-arm64-dbg': 'android_release_trybot_arm64_webview_google',
diff --git a/tools/mb/mb_config_expectations/chromium.android.fyi.json b/tools/mb/mb_config_expectations/chromium.android.fyi.json
index 506d6dc2..6c06eae 100644
--- a/tools/mb/mb_config_expectations/chromium.android.fyi.json
+++ b/tools/mb/mb_config_expectations/chromium.android.fyi.json
@@ -41,23 +41,6 @@
       "use_rbe": true
     }
   },
-  "android-11-x86-fyi-rel": {
-    "gn_args": {
-      "dcheck_always_on": false,
-      "disable_android_lint": true,
-      "ffmpeg_branding": "Chrome",
-      "is_component_build": false,
-      "is_debug": false,
-      "proprietary_codecs": true,
-      "strip_debug_info": true,
-      "symbol_level": 1,
-      "system_webview_package_name": "com.google.android.webview",
-      "target_cpu": "x86",
-      "target_os": "android",
-      "use_errorprone_java_compiler": false,
-      "use_goma": true
-    }
-  },
   "android-12-x64-fyi-rel": {
     "gn_args": {
       "dcheck_always_on": false,
diff --git a/tools/mb/mb_config_expectations/chromium.android.json b/tools/mb/mb_config_expectations/chromium.android.json
index e841bf1..59931af0 100644
--- a/tools/mb/mb_config_expectations/chromium.android.json
+++ b/tools/mb/mb_config_expectations/chromium.android.json
@@ -114,6 +114,23 @@
       "use_goma": true
     }
   },
+  "android-11-x86-rel": {
+    "gn_args": {
+      "dcheck_always_on": false,
+      "disable_android_lint": true,
+      "ffmpeg_branding": "Chrome",
+      "is_component_build": false,
+      "is_debug": false,
+      "proprietary_codecs": true,
+      "strip_debug_info": true,
+      "symbol_level": 1,
+      "system_webview_package_name": "com.google.android.webview",
+      "target_cpu": "x86",
+      "target_os": "android",
+      "use_errorprone_java_compiler": false,
+      "use_goma": true
+    }
+  },
   "android-arm64-proguard-rel": {
     "gn_args": {
       "dcheck_always_on": false,
diff --git a/tools/mb/mb_config_expectations/tryserver.chromium.android.json b/tools/mb/mb_config_expectations/tryserver.chromium.android.json
index d9f0c98..9f75201 100644
--- a/tools/mb/mb_config_expectations/tryserver.chromium.android.json
+++ b/tools/mb/mb_config_expectations/tryserver.chromium.android.json
@@ -17,7 +17,7 @@
       "use_goma": true
     }
   },
-  "android-11-x86-fyi-rel": {
+  "android-11-x86-rel": {
     "gn_args": {
       "blink_enable_generated_code_formatting": false,
       "dcheck_always_on": true,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index c02a6bd..40e2ba5 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -33802,8 +33802,8 @@
   <int value="3711" label="BarcodeDetectorDetect"/>
   <int value="3712" label="FaceDetectorDetect"/>
   <int value="3713" label="TextDetectorDetect"/>
-  <int value="3714" label="LocalStorageFirstUsedBeforeFcp"/>
-  <int value="3715" label="LocalStorageFirstUsedAfterFcp"/>
+  <int value="3714" label="OBSOLETE_LocalStorageFirstUsedBeforeFcp"/>
+  <int value="3715" label="OBSOLETE_LocalStorageFirstUsedAfterFcp"/>
   <int value="3716" label="OBSOLETE_CSSPseudoHostCompoundList"/>
   <int value="3717" label="OBSOLETE_CSSPseudoHostContextCompoundList"/>
   <int value="3718" label="OBSOLETE_CSSPseudoHostDynamicSpecificity"/>
@@ -33885,8 +33885,8 @@
   <int value="3790" label="WindowScreenChange"/>
   <int value="3791" label="XRWebGLDepthInformationTextureAttribute"/>
   <int value="3792" label="XRWebGLBindingGetDepthInformation"/>
-  <int value="3793" label="SessionStorageFirstUsedBeforeFcp"/>
-  <int value="3794" label="SessionStorageFirstUsedAfterFcp"/>
+  <int value="3793" label="OBSOLETE_SessionStorageFirstUsedBeforeFcp"/>
+  <int value="3794" label="OBSOLETE_SessionStorageFirstUsedAfterFcp"/>
   <int value="3795" label="GravitySensorConstructor"/>
   <int value="3796" label="ElementInternalsStates"/>
   <int value="3797" label="WebPImage"/>
@@ -45184,7 +45184,7 @@
 </enum>
 
 <enum name="LaunchMode">
-  <int value="1" label="LM_AS_WEBAPP_WINDOW">
+  <int value="1" label="(DEPRECATED) LM_AS_WEBAPP_WINDOW">
     Launched as an installed web application in a standalone window
   </int>
   <int value="2" label="LM_WITH_URLS">Launched with urls in the cmd line</int>
@@ -45249,6 +45249,18 @@
   <int value="22" label="LM_UNKNOWN_WEBAPP">
     The requested web application was not installed
   </int>
+  <int value="23" label="LM_AS_WEBAPP_WINDOW_BY_URL">
+    Launched installed web app in standalone window from command line, using
+    --app flag.
+  </int>
+  <int value="24" label="LM_AS_WEBAPP_WINDOW_BY_APP_ID">
+    Launched installed web app in standalone window from command line, using
+    --app-id flag.
+  </int>
+  <int value="25" label="LM_AS_WEBAPP_WINDOW_OTHER">
+    Launched installed web app in standalone window by any method other than
+    command line or a platform shortcut.
+  </int>
 </enum>
 
 <enum name="LaunchSource">
@@ -49302,6 +49314,7 @@
       label="ChromeHomePersonalizedOmniboxSuggestions:enabled"/>
   <int value="84911198" label="ScanCardsInWebPayments:disabled"/>
   <int value="87886288" label="DiagnosticsApp:enabled"/>
+  <int value="88249612" label="CalendarView:enabled"/>
   <int value="88437020" label="FeaturePolicy:enabled"/>
   <int value="89357752" label="PdfXfaSupport:enabled"/>
   <int value="89758831" label="FontAccessChooser:enabled"/>
@@ -50223,6 +50236,7 @@
   <int value="835726069" label="NtpWebUI:disabled"/>
   <int value="836406476" label="EnableTouchableAppContextMenu:enabled"/>
   <int value="837350465" label="SystemProxyForSystemServices:enabled"/>
+  <int value="838300788" label="CalendarView:disabled"/>
   <int value="838887742" label="manual-enhanced-bookmarks"/>
   <int value="839230937" label="AutofillEnableCardNicknameUpstream:disabled"/>
   <int value="839798268" label="SafeBrowsingTelemetryForApkDownloads:disabled"/>
@@ -64387,6 +64401,9 @@
 </enum>
 
 <enum name="PeerConnectionOfferExtmapAllowMixed">
+  <obsolete>
+    Removed as of 2021-07-30.
+  </obsolete>
   <int value="0" label="Default"/>
   <int value="1" label="Enabled"/>
   <int value="2" label="Disabled"/>
@@ -66308,6 +66325,7 @@
   <int value="24" label="kStop"/>
   <int value="25" label="kSslCertificateError"/>
   <int value="26" label="kLoginAuthRequested"/>
+  <int value="27" label="kUaChangeRequiresReload"/>
 </enum>
 
 <enum name="PrerenderHoverEvent">
@@ -81716,7 +81734,7 @@
   <int value="28" label="kSettingsResetPromptLastTriggeredForHomepage"/>
   <int value="29" label="kMediaStorageIdSalt"/>
   <int value="30" label="kModuleBlocklistCacheMD5Digest"/>
-  <int value="32" label="kMediaCdmOrigin"/>
+  <int value="32" label="kMediaCdmOriginData"/>
 </enum>
 
 <enum name="TranslateAssistContentResult">
diff --git a/tools/metrics/histograms/extract_histograms.py b/tools/metrics/histograms/extract_histograms.py
index e996d42..c224700 100644
--- a/tools/metrics/histograms/extract_histograms.py
+++ b/tools/metrics/histograms/extract_histograms.py
@@ -21,7 +21,6 @@
   <owner>person@chromium.org</owner>
   <owner>some-team@chromium.org</owner>
   <summary>A brief description.</summary>
-  <details>This is a more thorough description of this histogram.</details>
 </histogram>
 
 <histogram name="HistogramEnum" enum="MyEnumType">
@@ -630,11 +629,6 @@
     if histogram.hasAttribute('units'):
       histogram_entry['units'] = histogram.getAttribute('units')
 
-    # Find <details> tag.
-    for node in IterElementsWithTag(histogram, 'details'):
-      histogram_entry['details'] = _GetTextFromChildNodes(node)
-      break
-
     # Handle enum types.
     if histogram.hasAttribute('enum'):
       enum_name = histogram.getAttribute('enum')
diff --git a/tools/metrics/histograms/histogram_configuration_model.py b/tools/metrics/histograms/histogram_configuration_model.py
index 0e7cda01..68dbd9e 100644
--- a/tools/metrics/histograms/histogram_configuration_model.py
+++ b/tools/metrics/histograms/histogram_configuration_model.py
@@ -14,7 +14,6 @@
 _OWNER_TYPE = models.TextNodeType('owner', single_line=True)
 _COMPONENT_TYPE = models.TextNodeType('component', single_line=True)
 _SUMMARY_TYPE = models.TextNodeType('summary', single_line=True)
-_DETAILS_TYPE = models.TextNodeType('details')
 
 # A key for sorting XML nodes by the lower case of the value of |attribute|.
 _LOWERCASE_FN = lambda attribute: (lambda node: node.get(attribute).lower())
@@ -145,7 +144,6 @@
         (_OWNER_TYPE.tag, _KEEP_ORDER),
         (_COMPONENT_TYPE.tag, _KEEP_ORDER),
         (_SUMMARY_TYPE.tag, _KEEP_ORDER),
-        (_DETAILS_TYPE.tag, _KEEP_ORDER),
         (_TOKEN_TYPE.tag, _KEEP_ORDER),
     ],
     extra_newlines=(1, 1, 1),
@@ -154,7 +152,6 @@
         models.ChildType(_OWNER_TYPE.tag, _OWNER_TYPE, multiple=True),
         models.ChildType(_COMPONENT_TYPE.tag, _COMPONENT_TYPE, multiple=True),
         models.ChildType(_SUMMARY_TYPE.tag, _SUMMARY_TYPE, multiple=False),
-        models.ChildType(_DETAILS_TYPE.tag, _DETAILS_TYPE, multiple=False),
         models.ChildType(_TOKEN_TYPE.tag, _TOKEN_TYPE, multiple=True),
     ])
 
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml
index a8bccce..ba54c53a 100644
--- a/tools/metrics/histograms/metadata/autofill/histograms.xml
+++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -1037,8 +1037,7 @@
   <summary>
     Autofill form events for address forms. These are recorded when the user
     interacts with a form requesting an address. {AutofillDataAvailability}
-  </summary>
-  <details>
+
     Important caveat about submission metrics: - Submission using autofill data
     is determined by simply evaluating if there was a fill operation in this
     page. So, if the user filled with local data, completed erased or modified
@@ -1048,7 +1047,7 @@
     the page load. So, if a user initially filled with local data and after that
     filled with server, we will only emit &quot;Submitted with server suggestion
     filled (once)&quot;.
-  </details>
+  </summary>
   <token key="AutofillDataAvailability" variants="AutofillDataAvailability">
     <variant name=""/>
   </token>
@@ -1060,8 +1059,7 @@
   <summary>
     Autofill form events for credit card forms on nonsecure pages. These are
     recorded when the user interacts with a form requesting a credit card.
-  </summary>
-  <details>
+
     Important caveat about submission metrics: - Submission using autofill data
     is determined by simply evaluating if there was a fill operation in this
     page. So, if the user filled with local data, completed erased or modified
@@ -1071,7 +1069,7 @@
     the page load. So, if a user initially filled with local data and after that
     filled with server, we will only emit &quot;Submitted with server suggestion
     filled (once)&quot;.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Autofill.FormEvents.CreditCard.WithOffer"
@@ -1096,15 +1094,14 @@
     Autofill form events for credit card forms. These are recorded when the user
     interacts with a form requesting a credit card, a dropdown of suggestions is
     shown and at least one of the suggestions has a server nickname available.
-  </summary>
-  <details>
+
     These metrics are used to measure the impact of the server card nickname
     experiment. They are used to calculate the suggestion selection rate with
     server nickname available. We will run the experiment on the same group of
     user who has server cards with nicknames to compare the selection rate. For
     enabled user, we will show nickname and log. For the other, we won't show
     nickname in suggestion but we still log to this histogram.
-  </details>
+  </summary>
 </histogram>
 
 <histogram
@@ -1133,8 +1130,7 @@
   <summary>
     Autofill form events for credit card forms. These are recorded when the user
     interacts with a form requesting a credit card. {AutofillDataAvailability}
-  </summary>
-  <details>
+
     Important caveat about submission metrics: - Submission using autofill data
     is determined by simply evaluating if there was a fill operation in this
     page. So, if the user filled with local data, completed erased or modified
@@ -1144,7 +1140,7 @@
     the page load. So, if a user initially filled with local data and after that
     filled with server, we will only emit &quot;Submitted with server suggestion
     filled (once)&quot;.
-  </details>
+  </summary>
   <token key="AutofillDataAvailability" variants="AutofillDataAvailability">
     <variant name=""/>
   </token>
diff --git a/tools/metrics/histograms/metadata/background/histograms.xml b/tools/metrics/histograms/metadata/background/histograms.xml
index 2fe7364..d825056 100644
--- a/tools/metrics/histograms/metadata/background/histograms.xml
+++ b/tools/metrics/histograms/metadata/background/histograms.xml
@@ -319,11 +319,10 @@
   <summary>
     Records whether a `sync` event succeeded or failed and whether the sync
     event finished in the foreground or background.
-  </summary>
-  <details>
+
     A `sync` event finished in the foreground if the associated Service Worker
     Registration has a client at the time that the event finished.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="BackgroundSync.Event.OneShotStartedInForeground"
@@ -344,11 +343,10 @@
   <summary>
     Records whether a periodic sync event succeeded or failed and whether the
     sync event finished in the foreground or background.
-  </summary>
-  <details>
+
     A sync event finished in the foreground if the associated Service Worker
     Registration has a client at the time that the event finished.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="BackgroundSync.Event.PeriodicStartedInForeground"
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index 24dd6825..21275cea 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -2910,14 +2910,13 @@
     or dynamically, from the time the page is initialised to when it is closed
     or navigated away from. Each property is counted at most once per page per
     view via PageLoadMetricsObserver.
-  </summary>
-  <details>
+
     This histogram counts CSS properties only when they are animated by a CSS
     Animation. Refer to Blink.UseCounter.CSSProperties for more details.
 
     As of M69, Blink.UseCounter.AnimatedCSSProperties is moved from the blink
     side to the browser side.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Blink.UseCounter.CSSProperties" enum="MappedCSSProperties"
@@ -2933,8 +2932,7 @@
     dynamically, from the time the page is initialised to when it is closed or
     navigated away from. Each property is counted at most once per page per view
     via PageLoadMetricsObserver.
-  </summary>
-  <details>
+
     The first time a CSS property is parsed on a page, the histogram is updated
     to increment the counter. Each histogram bucket corresponds to a CSS
     property (eg. width, border-radius). The exception is the 'Total pages
@@ -2956,7 +2954,7 @@
 
     As of M69, Blink.UseCounter.AnimatedCSSProperties is moved from the blink
     side to the browser side.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Blink.UseCounter.DocumentPolicy.Enforced"
@@ -3006,11 +3004,10 @@
     chrome-extension:// URL only. The PageVisits bucket is incremented for each
     page load, and the other buckets incremented at most once per PageVisit via
     the WebCore::UserCounter class.
-  </summary>
-  <details>
+
     Warning: This histogram represents pre-renderer metrics and so is flawed
     under OOPIF.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Blink.UseCounter.FeaturePolicy.Allow"
@@ -3101,13 +3098,12 @@
     page. The PageVisits bucket is incremented for each page load, and the other
     buckets incremented at most once per PageVisit via blink::UseCounter and
     UseCounterPageLoadMetricsObserver.
-  </summary>
-  <details>
+
     Note that this histogram only counts page with HTTP/HTTPS URL scheme.
     Feature used in pages with other URL schemes might be counted in other
     histograms: &quot;extension://&quot; : Blink.UseCounter.Extensions.Features
     &quot;file://&quot; : Blink.UseCounter.File.Features
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Blink.UseCounter.File.Features" enum="FeatureObserver"
@@ -3119,10 +3115,9 @@
     frames in the page. This histogram has the same semantics as
     &quot;Blink.UseCounter.Features&quot;, but applies only to `file:` URLs
     (which that histogram does not include).
-  </summary>
-  <details>
+
     Refer to &quot;Blink.UseCounter.Features&quot; for more details.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Blink.UseCounter.MainFrame.Features" enum="FeatureObserver"
@@ -3137,11 +3132,10 @@
     page load and the other buckets incremented at most once per PageVisit via
     blink::UseCounter and UseCounterPageLoadMetricsObserver throughout the
     lifetime of a page load metrics observer.
-  </summary>
-  <details>
+
     This histogram counts usage of web features in main frame only. Refer to
     Blink.UseCounter.Features for more details.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Blink.UseCounter.PermissionsPolicy.Allow"
diff --git a/tools/metrics/histograms/metadata/browser/histograms.xml b/tools/metrics/histograms/metadata/browser/histograms.xml
index 2948b7b4..0d9bd5f 100644
--- a/tools/metrics/histograms/metadata/browser/histograms.xml
+++ b/tools/metrics/histograms/metadata/browser/histograms.xml
@@ -89,10 +89,26 @@
   </summary>
 </histogram>
 
-<histogram name="Browser.ChromeOS.HatsStatus" enum="HatsStatus"
-    expires_after="2021-12-05">
+<histogram name="Browser.ChromeOS.HatsSatisfaction{Survey}" units="score"
+    expires_after="2022-08-01">
   <owner>aalsum@chromium.org</owner>
-  <owner>malaykeshav@chromium.org</owner>
+  <owner>cros-telemetry@google.com</owner>
+  <summary>
+    Records the overall satisfaction score for HaTS, to be analyzed against
+    other OS metrics like performance or number of crashes. There is one score
+    per survey type.
+  </summary>
+  <token key="Survey">
+    <variant name=".General"/>
+    <variant name=".OnboardingExperience"/>
+    <variant name=".Unlock"/>
+  </token>
+</histogram>
+
+<histogram name="Browser.ChromeOS.HatsStatus" enum="HatsStatus"
+    expires_after="2022-08-01">
+  <owner>aalsum@chromium.org</owner>
+  <owner>cros-telemetry@google.com</owner>
   <summary>
     Records the stage up to which the user interacted with the HaTS Chrome OS
     survey. This is needed in addition to the Hats metrics data since Chrome OS
@@ -699,7 +715,7 @@
 </histogram>
 
 <histogram name="Browser.Tabs.TotalIncompleteSwitchDuration{TabSwitchingType}"
-    units="ms" expires_after="2021-08-09">
+    units="ms" expires_after="2022-01-02">
   <owner>fdoray@chromium.org</owner>
   <owner>jonross@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/content/histograms.xml b/tools/metrics/histograms/metadata/content/histograms.xml
index 05cdf74b..97a05d1 100644
--- a/tools/metrics/histograms/metadata/content/histograms.xml
+++ b/tools/metrics/histograms/metadata/content/histograms.xml
@@ -28,12 +28,11 @@
   <summary>
     The time between a change in content and when the new capture is sent to the
     browser process.
-  </summary>
-  <details>
+
     A specific content change is hard to track. This is roughly calculated as
     the interval from the first content change after a prior capturing content
     to the first content being sent after the next capture of content.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="ContentCapture.CaptureContentTime" units="microseconds"
diff --git a/tools/metrics/histograms/metadata/cras/OWNER b/tools/metrics/histograms/metadata/cras/OWNER
deleted file mode 100644
index a1252a8..0000000
--- a/tools/metrics/histograms/metadata/cras/OWNER
+++ /dev/null
@@ -1,5 +0,0 @@
-per-file OWNERS=file://tools/metrics/histograms/histograms_xml/METRIC_REVIEWER_OWNERS
-
-# Prefer sending CLs to the owners listed below.
-# Use chromium-metrics-reviews@google.com as a backup.
-yuhsuan@chromium.org
diff --git a/tools/metrics/histograms/metadata/cras/OWNERS b/tools/metrics/histograms/metadata/cras/OWNERS
new file mode 100644
index 0000000..20475d6
--- /dev/null
+++ b/tools/metrics/histograms/metadata/cras/OWNERS
@@ -0,0 +1,5 @@
+per-file OWNERS=file://tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
+
+# Prefer sending CLs to the owners listed below.
+# Use chromium-metrics-reviews@google.com as a backup.
+yuhsuan@chromium.org
diff --git a/tools/metrics/histograms/metadata/cross_device/histograms.xml b/tools/metrics/histograms/metadata/cross_device/histograms.xml
index e1e78f6..2bb7c82 100644
--- a/tools/metrics/histograms/metadata/cross_device/histograms.xml
+++ b/tools/metrics/histograms/metadata/cross_device/histograms.xml
@@ -1172,11 +1172,10 @@
   <summary>
     The hash of the phone model used to successfully sign in or unlock using
     Smart Lock.
-  </summary>
-  <details>
+
     This hash is calculated by taking the first 4 bytes of the MD5 hash of the
     device model.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="EasyUnlock.AuthProximity.RollingRssi" units="dBm"
@@ -1187,8 +1186,7 @@
     Measures the exponentially weighted rolling average of the received signal
     strength indicator (RSSI) of the phone when the user successfully unlocks or
     signs in using Smart Lock.
-  </summary>
-  <details>
+
     The exponentially weighted averaging formula is:
 
     rollingRssi = (1 - weight) * rollingRssi + weight * currentRssi;
@@ -1197,7 +1195,7 @@
     value to work with as a heuristic for proximity.
 
     If no RSSI was read, then a sentinel value of 127 will be recorded.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="EasyUnlock.RemoteLockScreenState"
diff --git a/tools/metrics/histograms/metadata/disk/histograms.xml b/tools/metrics/histograms/metadata/disk/histograms.xml
index 2719ecd..cb35f13 100644
--- a/tools/metrics/histograms/metadata/disk/histograms.xml
+++ b/tools/metrics/histograms/metadata/disk/histograms.xml
@@ -342,11 +342,12 @@
 
 <histogram name="DiskCache.0.NumberOfReferences" units="?" expires_after="M85">
   <owner>rvargas@chromium.org</owner>
-  <summary>The number of open entry references at any given moment.</summary>
-  <details>
+  <summary>
+    The number of open entry references at any given moment.
+
     Closely related to AverageOpenEntries, but this one is not the average per
     client but instead a direct histogram updated every 30 secs.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="DiskCache.0.OpenTime" units="ms" expires_after="M85">
diff --git a/tools/metrics/histograms/metadata/enterprise/histograms.xml b/tools/metrics/histograms/metadata/enterprise/histograms.xml
index 3a48f0f2..d2e36e7 100644
--- a/tools/metrics/histograms/metadata/enterprise/histograms.xml
+++ b/tools/metrics/histograms/metadata/enterprise/histograms.xml
@@ -1144,7 +1144,7 @@
 </histogram>
 
 <histogram name="Enterprise.EnrollmentTime.Success" units="ms"
-    expires_after="2021-08-09">
+    expires_after="2022-02-01">
   <owner>raleksandrov@google.com</owner>
   <owner>cros-oac@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml
index 44ea23c..35ceeea 100644
--- a/tools/metrics/histograms/metadata/ios/histograms.xml
+++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -667,7 +667,7 @@
 </histogram>
 
 <histogram name="IOS.MetricKit.ApplicationHangTime" units="ms"
-    expires_after="2021-09-15">
+    expires_after="2022-09-12">
   <owner>justincohen@chromium.org</owner>
   <owner>olivierrobin@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/memory/histograms.xml b/tools/metrics/histograms/metadata/memory/histograms.xml
index 9ad1142..7a26d2c 100644
--- a/tools/metrics/histograms/metadata/memory/histograms.xml
+++ b/tools/metrics/histograms/metadata/memory/histograms.xml
@@ -292,7 +292,7 @@
 </histogram>
 
 <histogram name="Memory.Browser.MemoryFootprint.Active.Over200MBWatermark"
-    enum="BooleanGreaterOrEqualThan200MB" expires_after="2021-08-27">
+    enum="BooleanGreaterOrEqualThan200MB" expires_after="2022-09-12">
   <owner>justincohen@chromium.org</owner>
   <owner>olivierrobin@chromium.org</owner>
   <summary>
@@ -308,7 +308,7 @@
 </histogram>
 
 <histogram name="Memory.Browser.MemoryFootprint.Background" units="MB"
-    expires_after="2021-08-27">
+    expires_after="2021-09-12">
   <owner>justincohen@chromium.org</owner>
   <owner>olivierrobin@chromium.org</owner>
   <summary>
@@ -324,7 +324,7 @@
 </histogram>
 
 <histogram name="Memory.Browser.MemoryFootprint.Inactive" units="MB"
-    expires_after="2021-08-27">
+    expires_after="2022-09-12">
   <owner>justincohen@chromium.org</owner>
   <owner>olivierrobin@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index 25e9800..86de09c 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -568,6 +568,20 @@
   </summary>
 </histogram>
 
+<histogram name="Net.DNS.DnsConfig.AdditionalDnsQueryTypesEnabled"
+    enum="BooleanEnabled" expires_after="2022-08-14">
+  <owner>ericorth@chromium.org</owner>
+  <owner>dmcardle@chromium.org</owner>
+  <owner>src/net/OWNERS</owner>
+  <summary>
+    Whether or not `prefs::kAdditionalDnsQueryTypesEnabled` is enabled. Recorded
+    at startup if disabled or whenever Chrome is believed to be
+    enterprise-managed (per the same detection as the default disabling of DoH).
+    Measures whether or not enterprise admins are setting the disable,
+    potentially due to issues encoutered with HTTPS/INTEGRITY query experiments.
+  </summary>
+</histogram>
+
 <histogram name="Net.DNS.DnsConfig.Nsswitch.Compatible" enum="BooleanValid"
     expires_after="2022-01-15">
   <owner>ericorth@chromium.org</owner>
@@ -2389,10 +2403,9 @@
   <summary>
     Number of bytes read from the network on behalf of prefetch requests. This
     is prefilter, so before any decompression.
-  </summary>
-  <details>
+
     This applies to requests with RESOURCE_TYPE_PREFETCH.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Net.ProxyScriptFetcher.FirstByteDuration" units="ms"
diff --git a/tools/metrics/histograms/metadata/network/histograms.xml b/tools/metrics/histograms/metadata/network/histograms.xml
index 5dac9643..a33d3c96 100644
--- a/tools/metrics/histograms/metadata/network/histograms.xml
+++ b/tools/metrics/histograms/metadata/network/histograms.xml
@@ -791,6 +791,16 @@
   </token>
 </histogram>
 
+<histogram name="Network.Radio.PossibleWakeupTrigger"
+    units="TrafficAnnotationUniqueIdHash" expires_after="2022-02-01">
+  <owner>bashi@chromium.org</owner>
+  <owner>blink-network-stack@google.com</owner>
+  <summary>
+    Records a traffic annotation ID hash when a URLLoader is created and the
+    radio state is dormant. Only recorded on Android.
+  </summary>
+</histogram>
+
 <histogram name="Network.Shill.Cellular.3GPPRegistrationDelayedDrop"
     enum="NetworkCellular3GPPRegistrationDelayedDrop"
     expires_after="2022-01-30">
diff --git a/tools/metrics/histograms/metadata/obsolete_histograms.xml b/tools/metrics/histograms/metadata/obsolete_histograms.xml
index e34edf4..2ac44e4 100644
--- a/tools/metrics/histograms/metadata/obsolete_histograms.xml
+++ b/tools/metrics/histograms/metadata/obsolete_histograms.xml
@@ -4780,8 +4780,7 @@
     field's signature changed, or a field was newly created. The reference
     period starts when Autofill parses the form for the first time, and ends
     when a navigation is committed or the frame is destructed.
-  </summary>
-  <details>
+
     Every form is observed for dynamic changes from the time it was first parsed
     by Autofill until either a navigation has been committed or the frame is
     destructed or the form's observation had to be flushed prematurely (the form
@@ -4791,7 +4790,7 @@
     the form's lifetime (its signature changed); the second bit is 1 iff a field
     was added (its renderer ID is new to the form); the highest bit is 1 iff the
     form was changed (its signature changed).
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Autofill.FormEvents.CreditCard.BankNameDisplayed"
@@ -4806,15 +4805,14 @@
     interacts with a form requesting a credit card, a dropdown of suggestions is
     shown and at least one of the suggestions has a bank name. Form events are
     logged at most once per page load.
-  </summary>
-  <details>
+
     These metrics are used to measure the impact of the bank name experiment.
     They are used to calculate the CTR of the autofill UI with bank names
     available. Not all credit cards will have bank names even if we launch the
     experiment. With these metrics we can run the experiment on 2 groups. For
     one group, we will show bank names if available. For the other, we won't
     shown.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Autofill.HasModifiedProfile.CreditCardFormSubmission"
@@ -5439,10 +5437,9 @@
   <summary>
     The number of Wallet addresses that were added to Chrome via Sync. Recorded
     when receiving an AUTOFILL_WALLET_DATA update from the Sync server.
-  </summary>
-  <details>
+
     Can probably be removed around Q1 2019, after project Dice has rolled out.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Autofill.WalletAddressesAddedOrRemoved" units="addresses"
@@ -5456,10 +5453,9 @@
     The total number of Wallet addresses that were added to or removed from
     Chrome via Sync. Recorded when receiving an AUTOFILL_WALLET_DATA update from
     the Sync server.
-  </summary>
-  <details>
+
     Can probably be removed around Q1 2019, after project Dice has rolled out.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Autofill.WalletAddressesRemoved" units="addresses"
@@ -5472,10 +5468,9 @@
   <summary>
     The number of Wallet addresses that were removed from Chrome via Sync.
     Recorded when receiving an AUTOFILL_WALLET_DATA update from the Sync server.
-  </summary>
-  <details>
+
     Can probably be removed around Q1 2019, after project Dice has rolled out.
-  </details>
+  </summary>
 </histogram>
 
 <histogram base="true" name="Autofill.WalletCards" units="credit cards"
@@ -5526,10 +5521,9 @@
   <summary>
     The number of Wallet credit cards that were added to Chrome via Sync.
     Recorded when receiving an AUTOFILL_WALLET_DATA update from the Sync server.
-  </summary>
-  <details>
+
     Can probably be removed around Q1 2019, after project Dice has rolled out.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Autofill.WalletCardsAddedOrRemoved" units="credit cards"
@@ -5543,10 +5537,9 @@
     The total number of Wallet credit cards that were added to or removed from
     Chrome via Sync. Recorded when receiving an AUTOFILL_WALLET_DATA update from
     the Sync server.
-  </summary>
-  <details>
+
     Can probably be removed around Q1 2019, after project Dice has rolled out.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Autofill.WalletCardsRemoved" units="credit cards"
@@ -5559,10 +5552,9 @@
   <summary>
     The number of Wallet credit cards that were removed from Chrome via Sync.
     Recorded when receiving an AUTOFILL_WALLET_DATA update from the Sync server.
-  </summary>
-  <details>
+
     Can probably be removed around Q1 2019, after project Dice has rolled out.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Autofill.WalletUseDate.Address" units="ms" expires_after="M76">
@@ -7118,13 +7110,12 @@
     Count of how many page loads use various features. The PageVisits bucket is
     incremented for each page load, and the other buckets incremented at most
     once per PageVisit via the blink::UseCounter class.
-  </summary>
-  <details>
+
     This histogram recorded values in the renderer process. With the addition of
     OOPIF, the counts could be inaccurate due to multiple renderer processes for
     a page. It was replaced by an implementation that records values in the
     browser process.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Blink.UseCounter.Features_TestBrowserProcessLogging"
@@ -7154,14 +7145,13 @@
   <summary>
     Like Blink.UseCounter.AnimatedCSSProperties but specifically for the case of
     CSS properties used inside of an SVG image.
-  </summary>
-  <details>
+
     Warning: This histogram represents pre-renderer metrics and so is flawed
     under OOPIF.
 
     This histogram counts usage of animated CSS properties only. Refer to
     Blink.UseCounter.SVGImage.CSSProperties for details.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Blink.UseCounter.SVGImage.CSSProperties"
@@ -7175,8 +7165,7 @@
   <summary>
     Like Blink.UseCounter.CSSProperties but specifically for the case of CSS
     properties used inside of an SVG image.
-  </summary>
-  <details>
+
     Warning: This histogram represents pre-renderer metrics and so is flawed
     under OOPIF.
 
@@ -7184,7 +7173,7 @@
     is created. Note that the same SVG image can be used across multiple tabs in
     a single renderer but this counts as a single usage. See
     http://crbug.com/236262.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Blink.UseCounter.SVGImage.Features" enum="FeatureObserver"
@@ -7201,8 +7190,7 @@
   <summary>
     Like Blink.UseCounter.Features except specifically for the case of SVG
     Images.
-  </summary>
-  <details>
+
     Warning: This histogram represents pre-renderer metrics and so is flawed
     under OOPIF.
 
@@ -7210,7 +7198,7 @@
     incremented each time a new SVG image is created. Note that the same SVG
     image can be used across multiple tabs in a single renderer but this counts
     as a single usage. See http://crbug.com/236262.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Blink.XHR.setRequestHeader.HeaderValueCategoryInRFC7230"
@@ -10505,14 +10493,15 @@
     Removed 04/2020. No longer used.
   </obsolete>
   <owner>ebeach@google.com</owner>
-  <summary>HTTP latency seen by the Connectivity Diagnostics.</summary>
-  <details>
+  <summary>
+    HTTP latency seen by the Connectivity Diagnostics.
+
     HTTP latency is computed using the chrome.socket API to make an HTTP GET
     request to the /generate_204 page of three randomly generated Google
     hostnames (*-ccd-testing-v4.metric.gstatic.com). The time taken from issuing
     the HTTP request to receiving a response is clocked in JavaScript and the
     arithmetic mean of the three times is used as the HTTP latency.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="ConnectivityDiagnostics.TestVerdict"
@@ -17907,15 +17896,14 @@
   <summary>
     Measures the time delta in milliseconds since the last zero RSSI value was
     read to when the user successfully unlocks or signs in using Smart Lock.
-  </summary>
-  <details>
+
     A zero RSSI value is special because both Bluetooth devices in a connection
     attempt to maintain by adjusting their transmit power levels. This time
     delta can be used as a possible heuristic to determine that the phone is
     close to the local device.
 
     If no RSSI was read, then an overflow value will be recorded.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="EasyUnlock.AuthProximity.TransmitPowerDelta" units="dBm"
@@ -17929,8 +17917,7 @@
     Measures the difference between the current transmit power and the maximum
     transmit power of the local device when the user successfully unlocks or
     signs in using Smart Lock.
-  </summary>
-  <details>
+
     Devices connected using classic Bluetooth adjust their transmit power
     dynamically to optimize power and signal strength. The difference between
     the current transmit power and maximum transmit power can be used as a
@@ -17941,7 +17928,7 @@
 
     If no transmit power was read, then a sentinel value of 127 will be
     recorded.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="EasyUnlock.BluetoothAvailability"
@@ -17952,15 +17939,14 @@
   <owner>bcwhite@chromium.org</owner>
   <summary>
     Reports the type of Bluetooth adapter present in the device.
-  </summary>
-  <details>
+
     Statistics about what Bluetooth capabilities are available will determine
     how the EasyUnlock feature gets developed and deployed across platforms.
 
     This value is logged only once during the lifetime of the Chrome process,
     shortly after it starts up. If a Bluetooth USB adapter is inserted after
     that point, the change will not be registered until Chrome restarts.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="EasyUnlock.ClickedButton" enum="EasyUnlockButton"
@@ -17994,12 +17980,11 @@
   <summary>
     The number of eligible devices that the CryptAuth server returns during the
     Smart Lock setup flow.
-  </summary>
-  <details>
+
     Note that a single user might report multiple values, for example if they
     try to complete the setup flow with a device in airplane mode, and then try
     again taking the device out of airplane mode.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="EasyUnlock.Setup.Devices.Count.Ineligible" units="units"
@@ -18011,12 +17996,11 @@
   <summary>
     The number of ineligible devices that the CryptAuth server returns during
     the Smart Lock setup flow.
-  </summary>
-  <details>
+
     Note that a single user might report multiple values, for example if they
     try to complete the setup flow with a device in airplane mode, and then try
     again taking the device out of airplane mode.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="EasyUnlock.Setup.Devices.HasSecureScreenLock"
@@ -18041,14 +18025,13 @@
     Whether the user's phone has a trust agent -- e.g. Smart Lock for Android --
     enabled. Recorded during Smart Lock (for Chrome) setup, when the user's
     phone first connects to the Chromebook.
-  </summary>
-  <details>
+
     Note that this histogram tracks whether the setting under Settings ~&gt;
     Security ~&gt; Trust agents ~&gt; Smart Lock (Google) (or any other trust
     agent) is enabled. The Smart Lock trust agent is enabled by default for
     users who have ascure lock screen. This metric does _not_ measure whether
     users have any Smart Lock trustlets enabled.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="EasyUnlock.Setup.Devices.IneligibilityReason"
@@ -18062,15 +18045,14 @@
     eligible as unlock keys for Smart Lock. This is recorded during the Smart
     Lock setup flow, only if the CryptAuth server returns no eligible devices
     for the user.
-  </summary>
-  <details>
+
     To be precise, this metric tracks the _least_ actionable _reason_ why the
     _most_ actionable _device_ is not eligible as an unlock key. For example,
     suppose that the user has 10 ineligible devices returned. This metric tries
     to identify the most likely candidate device for use as a Smart Lock key,
     and then records the most confounding reason why that device is still not
     eligible to be used as an unlock key.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="EasyUnlock.Setup.Progress.SansPromo"
@@ -18107,14 +18089,13 @@
   <owner>hansberry@chromium.org</owner>
   <summary>
     Measures user interactions with the Smart Lock promo notification.
-  </summary>
-  <details>
+
     Due to technical limitations of the implementation, it is hard to track
     precisely which users opened the setup app as a result of interacting with
     the promo notification. This metric measures setup app interactions from all
     users who click on the promo notification, and subsequently launch the setup
     app.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="EasyUnlock.SetupStateOnClose" enum="EasyUnlockSetupState"
@@ -18152,15 +18133,14 @@
   <summary>
     Records when the Easy Unlock trial run is launched, and when the user
     attempts to click on the lock icon during the trial run.
-  </summary>
-  <details>
+
     If a user clicks on the lock icon more than once, then the &quot;clicked
     lock icon&quot; event counter will be incremented more than once as well.
     Hence, the &quot;user count&quot; data shows how many users ever clicked on
     the lock icon during the trial run. From the raw (non-&quot;user
     count&quot;) data, we can also see whether users click on the icon multiple
     times.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="EasyUnlock.UnlockEvent" enum="EasyUnlockUnlockEvent"
@@ -40649,8 +40629,7 @@
   <owner>pasko@chromium.org</owner>
   <summary>
     The completion status of prefetches that have finished loading.
-  </summary>
-  <details>
+
     Measurement occurs at ResourceLoader::ResponseCompleted so requests canceled
     before that point are not registered.
 
@@ -40663,7 +40642,7 @@
     &quot;success already prefetched&quot; means that both was_cached() and
     unused_since_prefetch were true. Validated results are considered cached,
     even though a conditional network request is made.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Net.MTPR_GetProxyForUrl_Thread_Wait_Time" units="ms"
@@ -40978,8 +40957,7 @@
   <owner>csharrison@chromium.org</owner>
   <summary>
     When a preconnection is made, indicate what the motivation was.
-  </summary>
-  <details>
+
     Currently, the most common (only?) motivations are SELF_REFERAL,
     LEARNED_REFERAL and OMNIBOX. The SELF_REFERAL indicates that we made sure a
     second connection was available for a resource that either was never before
@@ -40989,7 +40967,7 @@
     search is being suggested, and we preconnect to the search provider.
     (WARNING: Prior to version 7.517.*, enums 7, 8, and 9 may be confused, as
     EARLY_LOAD_MOTIVATED was inserted new 6 value.)
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Net.PreconnectProxyStatus" enum="ProxyStatus"
@@ -41024,13 +41002,12 @@
   <summary>
     What did we decide to do about a predicted resource, based on the historical
     expected number of connection that this subresource will require.
-  </summary>
-  <details>
+
     This is basically the current thresholding of the SubresourceExpectation,
     relative to current static thresholds, and taking into account whether
     preconnection is enabled (i.e., if preconnection is disabled, we'll never
     decide to preconnect).
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Net.PreconnectSubresourceExpectation" units="units"
@@ -41042,12 +41019,11 @@
   <summary>
     The expected number of connections, times 100, that we'll make to a given
     subresource, based on learned history.
-  </summary>
-  <details>
+
     By comparing this to thresholds, we decide if we will preconnect,
     preresolve, or do nothing. This histogram can be used to select those static
     thresholds.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Net.PreconnectTriggerUsed" enum="PreconnectTriggerUsed"
@@ -41071,12 +41047,11 @@
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <summary>
     Indicate final utilization for each attempted socket connection.
-  </summary>
-  <details>
+
     We also include stats for non-speculative sockets. Some socket connections
     may never connect, and others may never be used (as the user may abort
     before then).
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Net.PreconnectUtilization2" enum="NetPreconnectUtilization"
@@ -41087,12 +41062,11 @@
   <owner>csharrison@chromium.org</owner>
   <summary>
     Indicate final utilization for each attempted socket connection.
-  </summary>
-  <details>
+
     We also include stats for non-speculative sockets. Some socket connections
     may never connect, and others may never be used (as the user may abort
     before then).
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Net.Predictor.MRUIndex" units="units"
@@ -41127,8 +41101,7 @@
   <owner>jkarlin@chromium.org</owner>
   <summary>
     The completion status of prefetches that have finished loading.
-  </summary>
-  <details>
+
     Measurement occurs at ResourceLoader::ResponseCompleted so requests canceled
     before that point are not registered.
 
@@ -41140,7 +41113,7 @@
     &quot;success already prefetched&quot; means that both was_cached() and
     unused_since_prefetch were true. Validated results are considered cached,
     even though a conditional network request is made.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Net.Prefetch.TimeBeforeCancel" units="ms"
@@ -41151,10 +41124,9 @@
   <owner>jkarlin@chromium.org</owner>
   <summary>
     Time spent on prefetch requests before the request was canceled.
-  </summary>
-  <details>
+
     This applies to requests with RESOURCE_TYPE_PREFETCH.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Net.Prefetch.TimeSpentOnPrefetchHit" units="ms"
@@ -41175,10 +41147,11 @@
     Removed July 2018
   </obsolete>
   <owner>jkarlin@chromium.org</owner>
-  <summary>Time spent on prefetch requests when fetched from cache.</summary>
-  <details>
+  <summary>
+    Time spent on prefetch requests when fetched from cache.
+
     This applies to requests with RESOURCE_TYPE_PREFETCH.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Net.Prefetch.TimeSpentPrefetchingFromNetwork" units="ms"
@@ -51244,13 +51217,14 @@
   </obsolete>
   <owner>rbyers@chromium.org</owner>
   <owner>mfomitchev@chromium.org</owner>
-  <summary>Completed overscroll gestures.</summary>
-  <details>
+  <summary>
+    Completed overscroll gestures.
+
     An overscroll gesture starts when user scrolls past the edge of the web page
     and continues scrolling in the same direction. An overscroll gesture is
     completed when user stops scrolling (e.g. by lifting the fingers from the
     touchscreen or touchpad).
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Overscroll.Navigated" enum="OverscrollMode"
@@ -74087,11 +74061,10 @@
   <summary>
     Amount of time between subsequent SessionService Save() operations (aka
     updates to session data).
-  </summary>
-  <details>
+
     Periods longer than 10 minutes are grouped together; see SaveLongPeriod for
     resolution.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="SessionRestore.SubFrameUniqueNameChangedBeforeFirstCommit"
@@ -85603,12 +85576,13 @@
     histograms.
   </obsolete>
   <owner>asvitkine@chromium.org</owner>
-  <summary>The server returned a 400 code, and we discarded a log.</summary>
-  <details>
+  <summary>
+    The server returned a 400 code, and we discarded a log.
+
     This tends to indicate that a syntax error is present in a log, such as
     would appear when a bogus XML tag is included, or the XML is not balanced
     and well structured.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="UMA.UploadCreation" enum="BooleanSuccess"
diff --git a/tools/metrics/histograms/metadata/oobe/histograms.xml b/tools/metrics/histograms/metadata/oobe/histograms.xml
index c1aa606..261f874 100644
--- a/tools/metrics/histograms/metadata/oobe/histograms.xml
+++ b/tools/metrics/histograms/metadata/oobe/histograms.xml
@@ -344,7 +344,7 @@
 </histogram>
 
 <histogram name="OOBE.SyncConsentScreen.ReviewFollowingSetup"
-    enum="BooleanChecked" expires_after="2021-08-09">
+    enum="BooleanChecked" expires_after="2022-02-01">
   <owner>raleksandrov@google.com</owner>
   <owner>cros-oac@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index a432a33..d86c858 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -3389,26 +3389,26 @@
   <summary>
     Connectivity Diagnostics App: WiFi signal strength recorded during
     NIC_SIGNAL_STRENGTH test.
-  </summary>
-  <details>
+
     The &quot;Strength&quot; property of a WiFi signal is a partially-reversible
     function that linearly maps the RSSI range -120dBm to -20dBm to Strength
     values from 0 to 100.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="ConnectivityDiagnostics.RESOLVER_LATENCY" units="ms"
     expires_after="M83">
   <owner>ebeach@google.com</owner>
-  <summary>Resolution latency seen by the Connectivity Diagnostics.</summary>
-  <details>
+  <summary>
+    Resolution latency seen by the Connectivity Diagnostics.
+
     Resolver latency is computed by using the chrome.dns API to query three
     randomly generated Google hostnames (*-ccd-testing-v4.metric.gstatic.com).
     The random hostnames guarantees that there will be no caching of DNS
     hostnames. The time taken from issuing the DNS request to receiving a
     response is clocked in JavaScript and the arithmetic mean of the three times
     is used as the resolver latency.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="ContextMenu.CancelSystemTouches" enum="BooleanHit"
@@ -4030,7 +4030,7 @@
 </histogram>
 
 <histogram name="CrashReport.BreakpadIOSUploadOutcome"
-    enum="FoundationPopularErrorCode" expires_after="2021-09-01">
+    enum="FoundationPopularErrorCode" expires_after="2022-09-12">
   <owner>justincohen@chromium.org</owner>
   <owner>olivierrobin@chromium.org</owner>
   <summary>
@@ -6472,7 +6472,7 @@
 </histogram>
 
 <histogram name="FirstUserAction.BackgroundTime" units="minutes"
-    expires_after="2021-08-22">
+    expires_after="2022-09-12">
 <!-- Name completed by histogram_suffixes name="FirstUserActionType" and name="FirstUserActionTypeDevice" -->
 
   <owner>justincohen@chromium.org</owner>
@@ -6504,7 +6504,7 @@
 </histogram>
 
 <histogram name="FirstUserAction.TabletUserActionType"
-    enum="FirstUserActionType" expires_after="2021-08-22">
+    enum="FirstUserActionType" expires_after="2022-09-12">
   <owner>justincohen@chromium.org</owner>
   <owner>olivierrobin@chromium.org</owner>
   <summary>
@@ -17547,14 +17547,13 @@
     the touch events enabled flag and the presence of a touchscreen.
 
     Team: input-dev@chromium.org.
-  </summary>
-  <details>
+
     NOTE: This metric was incorrectly recorded on Chrome OS for versions 42
     through 45, see http://crbug.com/499476 for more details. As of
     http://crbug.com/644318, we changed the internal name to
     TouchEventFeatureDetection, but we still keep the old histogram name here to
     keep consistensy.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="TPM.EarlyResetDuringCommand" units="count"
diff --git a/tools/metrics/histograms/metadata/tab/histograms.xml b/tools/metrics/histograms/metadata/tab/histograms.xml
index 7a08a71..ddc74f0 100644
--- a/tools/metrics/histograms/metadata/tab/histograms.xml
+++ b/tools/metrics/histograms/metadata/tab/histograms.xml
@@ -341,14 +341,13 @@
   <summary>
     Record the action executed when the user performs a pull down gesture. This
     feature is currently iOS only.
-  </summary>
-  <details>
+
     A pull down gesture is an action completed when the user scrolls past the
     edge of the web page and continues scrolling in the same direction revealing
     a specific UI on the header with multiple actions icons. The user can then
     choose an action by scrolling left or right and lift the finger or cancel by
     scrolling back up. This is currently an iOS specific feature.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Tab.RendererCrashStatus" enum="TabRendererCrashStatus"
@@ -1478,13 +1477,12 @@
   <summary>
     The offset between the tab currently selected tab and the newly selected
     tab.
-  </summary>
-  <details>
+
     If the user switches to a tab to the left of their currently selected tab,
     we log a positive offset value indicating how many tabs to the left they
     moved. If the user switches to a tab to the right of their currently
     selected tab, we log a negative offset value.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Tabs.FirstSwitchedToForegroundCreationRank" units="units"
@@ -1534,11 +1532,12 @@
     expires_after="2020-07-31">
   <owner>mattsimmons@chromium.org</owner>
   <owner>memex-team@google.com</owner>
-  <summary>The number of entries/cards shown in the grid tab switcher.</summary>
-  <details>
+  <summary>
+    The number of entries/cards shown in the grid tab switcher.
+
     When the user is shown the grid tab switcher, logs the number of entries
     (tabs or tab groups) open in the grid tab switcher.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Tabs.MaxTabsInADay{BatteryState}" units="tabs"
@@ -1977,12 +1976,11 @@
   <summary>
     Recorded once per tab activation. The number of tabs that were activated
     while the tab was inactive.
-  </summary>
-  <details>
+
     This metric is to be removed after M46 hits stable. We don't expect this
     metric to change over release, so we will remove this once we have the
     numbers from stable.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Tabs.StateTransfer.TabDistanceInactiveToActive" units="tabs"
@@ -1991,12 +1989,11 @@
   <owner>tzik@chromium.org</owner>
   <summary>
     Number of tabs between the previously active tab and the new active tab.
-  </summary>
-  <details>
+
     This metric is to be removed after M46 hits stable. We don't expect this
     metric to change over release, so we will remove this once we have the
     numbers from stable.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Tabs.StateTransfer.Target" enum="TabStripState"
@@ -2006,10 +2003,9 @@
   <summary>
     The state to which a tab transitioned. Recorded when a tab transitions from
     the state in the histogram name to a new state.
-  </summary>
-  <details>
+
     kouhei@ and tzik@ will remove some variation of these once M46 hits stable.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Tabs.Suggestions.NumSuggestionsChanged{TabSuggestionType}"
@@ -2124,11 +2120,10 @@
   <owner>memex-team@google.com</owner>
   <summary>
     The number of tabs open when the grid tab switcher is shown.
-  </summary>
-  <details>
+
     When the user is shown the tab switcher, logs the total number of individual
     open tabs. Android only.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Tabs.TabCountOnStartScreenShown" units="tabs"
@@ -2138,11 +2133,10 @@
   <summary>
     The number of tabs open when the user is returned to the tab switcher on
     start.
-  </summary>
-  <details>
+
     When the user is returned to the tab switcher on starting Chrome due to
     inactivity, logs the overall number of tabs open. Android only.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Tabs.TabCountPerDomainPerLoad.{TotalTabCountBucket}"
@@ -2181,13 +2175,12 @@
   <summary>
     The number of tabs open in all browsers (counting app-mode windows) when a
     load completes.
-  </summary>
-  <details>
+
     This is basically the average number of tabs over time.
 
     See also MPArch.RPHCountPerLoad for the number of processes used by these
     tabs.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Tabs.TabCountPerWindow" units="tabs"
@@ -2197,11 +2190,10 @@
   <summary>
     The number of tabs open per window (counting app-mode windows) when a load
     completes.
-  </summary>
-  <details>
+
     This value will be recorded multiple times per load if more than one window
     is open.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="Tabs.TabCount{BatteryState}" units="tabs"
@@ -2223,8 +2215,7 @@
   <summary>
     How many tabs a user moved forward or backward in the Android tab switcher.
     {TabOffsetOfSwitchComponentList}
-  </summary>
-  <details>
+
     Logged by the Android tab switcher when a user switches tabs (other than by
     opening a new tab). If the user switches to an earlier tab in the stack, we
     log a positive offset value indicating how many tabs back they moved. If the
@@ -2232,7 +2223,7 @@
     stays on the same tab, we log 0. If the user switches to a tab in a
     different stack (i.e. they switch from normal to incognito or vice-versa),
     we don't log a value.
-  </details>
+  </summary>
   <token key="TabOffsetOfSwitchComponentList">
     <variant name="">
       <obsolete>
@@ -2906,13 +2897,12 @@
   <summary>
     The amount of time from cursor entering the tabstrip to first click on a tab
     to switch.
-  </summary>
-  <details>
+
     This will exclude some kinds of switching (hotkeys, between windows) where
     it would be difficult to define a start time. Entering the tabstrip is an
     approximation which will vary between users, but should still be useful for
     A/B comparison.
-  </details>
+  </summary>
 </histogram>
 
 <histogram name="TabStrip.UserStatus" enum="TabStripUserStatus"
diff --git a/tools/metrics/histograms/metadata/web_rtc/histograms.xml b/tools/metrics/histograms/metadata/web_rtc/histograms.xml
index f5fe8da..5ec451b8 100644
--- a/tools/metrics/histograms/metadata/web_rtc/histograms.xml
+++ b/tools/metrics/histograms/metadata/web_rtc/histograms.xml
@@ -1592,6 +1592,9 @@
 
 <histogram name="WebRTC.PeerConnection.OfferExtmapAllowMixed"
     enum="PeerConnectionOfferExtmapAllowMixed" expires_after="2022-01-02">
+  <obsolete>
+    Removed as of 2021-08-03.
+  </obsolete>
   <owner>kron@chromium.org</owner>
   <summary>
     What setting for the SDP attribute extmap-allow-mixed has been asked for by
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index e1b8b1f..7360b4c 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,20 +5,20 @@
             "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "win": {
-            "hash": "f0284bbb5f297b609fef13bf60ef912d879c8831",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/b846a827ab0b47d0299bc3776d1fb5c067c50d94/trace_processor_shell.exe"
+            "hash": "2d32f8ca491ef981f1d4c4b512613e76ba1d1bee",
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/00e6f338d036b5d1ad547b979b612ec008fe3165/trace_processor_shell.exe"
         },
         "mac": {
-            "hash": "2703fb07b6378e32d6d3ad287c4b65a1024547d7",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/53ba4116486cf1a72004102abefd3f0f0c82b0e3/trace_processor_shell"
+            "hash": "e353596db4eeb16778697e6a9cdae30adf2842dd",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/00e6f338d036b5d1ad547b979b612ec008fe3165/trace_processor_shell"
         },
         "linux_arm64": {
             "hash": "5074025a2898ec41a872e70a5719e417acb0a380",
             "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm64/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "linux": {
-            "hash": "518702cc772f7966e269023cd566080e3506a6c3",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/53ba4116486cf1a72004102abefd3f0f0c82b0e3/trace_processor_shell"
+            "hash": "6d8243db1d936c6f26a12d5f59b9460f4ef8c43d",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/00e6f338d036b5d1ad547b979b612ec008fe3165/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/diagnose_test_failure b/tools/perf/diagnose_test_failure
index fa96e40..58aad53 100755
--- a/tools/perf/diagnose_test_failure
+++ b/tools/perf/diagnose_test_failure
@@ -12,7 +12,7 @@
 
 
 _BUILD_REGEX = r'builds/(\d+)'
-_REVISION_REGEX = r'\nCr-Commit-Position: refs/heads/master@{#(\d+)}'
+_REVISION_REGEX = r'\nCr-Commit-Position: refs/heads/(?:master|main)@{#(\d+)}'
 
 
 class _Color(object):
diff --git a/ui/android/java/src/org/chromium/ui/base/Clipboard.java b/ui/android/java/src/org/chromium/ui/base/Clipboard.java
index 79edebc..ad97cf1 100644
--- a/ui/android/java/src/org/chromium/ui/base/Clipboard.java
+++ b/ui/android/java/src/org/chromium/ui/base/Clipboard.java
@@ -31,7 +31,6 @@
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.base.BuildInfo;
 import org.chromium.base.Callback;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.StreamUtil;
@@ -260,7 +259,7 @@
     boolean hasUrl() {
         // ClipDescription#getConfidenceScore is only available on Android S+, so before Android S,
         // we will access the clipboard content and valid by URLUtil#isValidUrl.
-        if (BuildInfo.isAtLeastS()) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
             ClipDescription description = mClipboardManager.getPrimaryClipDescription();
             // If getClassificationStatus() is not CLASSIFICATION_COMPLETE,
             // ClipDescription#getConfidenceScore will trows exception.
@@ -288,7 +287,7 @@
     String getUrl() {
         if (!hasUrl()) return null;
 
-        if (!BuildInfo.isAtLeastS()) return getCoercedText();
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return getCoercedText();
 
         try {
             ClipData.Item item = mClipboardManager.getPrimaryClip().getItemAt(0);
@@ -765,7 +764,7 @@
      * @return True if the system clipboard contain a styled text, otherwise, false.
      */
     private boolean hasStyledText(ClipDescription description) {
-        if (BuildInfo.isAtLeastS()) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
             return ApiHelperForS.isStyleText(description);
         } else {
             return hasStyledTextOnPreS();
diff --git a/ui/android/java/src/org/chromium/ui/gfx/AdpfRenderingStageScheduler.java b/ui/android/java/src/org/chromium/ui/gfx/AdpfRenderingStageScheduler.java
index d3e37b1..14c391b1 100644
--- a/ui/android/java/src/org/chromium/ui/gfx/AdpfRenderingStageScheduler.java
+++ b/ui/android/java/src/org/chromium/ui/gfx/AdpfRenderingStageScheduler.java
@@ -5,10 +5,10 @@
 package org.chromium.ui.gfx;
 
 import android.annotation.SuppressLint;
+import android.os.Build;
 
 import androidx.annotation.Nullable;
 
-import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.annotations.CalledByNative;
@@ -30,7 +30,7 @@
 
     static {
         boolean enabled = false;
-        if (BuildInfo.isAtLeastS()) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
             try {
                 Class hintManagerClazz = Class.forName("android.os.PerformanceHintManager");
                 sHintManagerCreateHintSession =
diff --git a/ui/android/javatests/src/org/chromium/ui/test/util/DisableAnimationsTestRule.java b/ui/android/javatests/src/org/chromium/ui/test/util/DisableAnimationsTestRule.java
index 80c198d..0791102 100644
--- a/ui/android/javatests/src/org/chromium/ui/test/util/DisableAnimationsTestRule.java
+++ b/ui/android/javatests/src/org/chromium/ui/test/util/DisableAnimationsTestRule.java
@@ -4,6 +4,7 @@
 
 package org.chromium.ui.test.util;
 
+import android.os.Build;
 import android.os.IBinder;
 import android.provider.Settings;
 
@@ -11,7 +12,6 @@
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
-import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 
@@ -71,7 +71,7 @@
             // TODO(https://crbug.com/1225707): Always throw once this works on Android S. The above
             // API is no longer accessible and will crash regardless of filter rules so just warn
             // instead.
-            if (BuildInfo.isAtLeastS()) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                 Log.w(TAG, "Failed to access animation methods", e);
             } else {
                 throw new RuntimeException("Failed to access animation methods", e);
diff --git a/ui/base/ui_base_switches_util.cc b/ui/base/ui_base_switches_util.cc
index 9e76a04..c5d7987 100644
--- a/ui/base/ui_base_switches_util.cc
+++ b/ui/base/ui_base_switches_util.cc
@@ -24,7 +24,8 @@
 #elif defined(OS_WIN)
   return base::FeatureList::IsEnabled(features::kElasticOverscroll);
 #elif defined(OS_ANDROID)
-  return base::android::BuildInfo::GetInstance()->IsAtLeastS() &&
+  return base::android::BuildInfo::GetInstance()->sdk_int() >=
+             base::android::SDK_VERSION_S &&
          !base::CommandLine::ForCurrentProcess()->HasSwitch(
              switches::kDisableOverscrollEdgeEffect) &&
          base::FeatureList::IsEnabled(features::kElasticOverscroll);
diff --git a/ui/message_center/BUILD.gn b/ui/message_center/BUILD.gn
index 8c09394..bce60a0 100644
--- a/ui/message_center/BUILD.gn
+++ b/ui/message_center/BUILD.gn
@@ -100,8 +100,6 @@
         "views/message_popup_view.h",
         "views/message_view.cc",
         "views/message_view.h",
-        "views/message_view_factory.cc",
-        "views/message_view_factory.h",
         "views/notification_background_painter.cc",
         "views/notification_background_painter.h",
         "views/notification_control_buttons_view.cc",
diff --git a/ui/message_center/views/message_popup_collection.cc b/ui/message_center/views/message_popup_collection.cc
index 20553a5..3d4ea4e 100644
--- a/ui/message_center/views/message_popup_collection.cc
+++ b/ui/message_center/views/message_popup_collection.cc
@@ -16,6 +16,7 @@
 #include "ui/message_center/public/cpp/message_center_constants.h"
 #include "ui/message_center/views/message_popup_view.h"
 #include "ui/message_center/views/message_view.h"
+#include "ui/message_center/views/notification_view_md.h"
 
 namespace message_center {
 
@@ -243,7 +244,11 @@
 
 MessagePopupView* MessagePopupCollection::CreatePopup(
     const Notification& notification) {
-  return new MessagePopupView(notification, this);
+  bool a11_feedback_on_init =
+      notification.rich_notification_data()
+          .should_make_spoken_feedback_for_popup_updates;
+  return new MessagePopupView(new NotificationViewMD(notification), this,
+                              a11_feedback_on_init);
 }
 
 void MessagePopupCollection::RestartPopupTimers() {
diff --git a/ui/message_center/views/message_popup_collection.h b/ui/message_center/views/message_popup_collection.h
index 4518920b..77b5abb5 100644
--- a/ui/message_center/views/message_popup_collection.h
+++ b/ui/message_center/views/message_popup_collection.h
@@ -134,8 +134,9 @@
   virtual void AnimationStarted() {}
   virtual void AnimationFinished() {}
 
-  // virtual for testing.
   virtual MessagePopupView* CreatePopup(const Notification& notification);
+
+  // virtual for testing.
   virtual void RestartPopupTimers();
   virtual void PausePopupTimers();
 
diff --git a/ui/message_center/views/message_popup_view.cc b/ui/message_center/views/message_popup_view.cc
index ba9f05d..0743d4f 100644
--- a/ui/message_center/views/message_popup_view.cc
+++ b/ui/message_center/views/message_popup_view.cc
@@ -15,7 +15,6 @@
 #include "ui/message_center/public/cpp/message_center_constants.h"
 #include "ui/message_center/views/message_popup_collection.h"
 #include "ui/message_center/views/message_view.h"
-#include "ui/message_center/views/message_view_factory.h"
 #include "ui/views/layout/fill_layout.h"
 #include "ui/views/widget/widget.h"
 
@@ -30,13 +29,12 @@
 
 namespace message_center {
 
-MessagePopupView::MessagePopupView(const Notification& notification,
-                                   MessagePopupCollection* popup_collection)
-    : message_view_(MessageViewFactory::Create(notification)),
+MessagePopupView::MessagePopupView(MessageView* message_view,
+                                   MessagePopupCollection* popup_collection,
+                                   bool a11y_feedback_on_init)
+    : message_view_(message_view),
       popup_collection_(popup_collection),
-      a11y_feedback_on_init_(
-          notification.rich_notification_data()
-              .should_make_spoken_feedback_for_popup_updates) {
+      a11y_feedback_on_init_(a11y_feedback_on_init) {
   SetLayoutManager(std::make_unique<views::FillLayout>());
 
   if (!message_view_->IsManuallyExpandedOrCollapsed())
diff --git a/ui/message_center/views/message_popup_view.h b/ui/message_center/views/message_popup_view.h
index aec24a8..0c7cf44 100644
--- a/ui/message_center/views/message_popup_view.h
+++ b/ui/message_center/views/message_popup_view.h
@@ -24,8 +24,9 @@
  public:
   METADATA_HEADER(MessagePopupView);
 
-  MessagePopupView(const Notification& notification,
-                   MessagePopupCollection* popup_collection);
+  MessagePopupView(MessageView* message_view,
+                   MessagePopupCollection* popup_collection,
+                   bool a11y_feedback_on_init);
   MessagePopupView(const MessagePopupView&) = delete;
   MessagePopupView& operator=(const MessagePopupView&) = delete;
   ~MessagePopupView() override;
diff --git a/ui/native_theme/native_theme.cc b/ui/native_theme/native_theme.cc
index 7a09fd38..7adb292 100644
--- a/ui/native_theme/native_theme.cc
+++ b/ui/native_theme/native_theme.cc
@@ -149,8 +149,7 @@
 
 bool NativeTheme::UserHasContrastPreference() const {
   return GetPreferredContrast() !=
-             NativeTheme::PreferredContrast::kNoPreference ||
-         InForcedColorsMode();
+         NativeTheme::PreferredContrast::kNoPreference;
 }
 
 bool NativeTheme::InForcedColorsMode() const {
@@ -304,8 +303,11 @@
     notify_observers = true;
   }
 
-  if (notify_observers)
+  if (notify_observers) {
+    DCHECK(theme_to_update_->UserHasContrastPreference() ||
+           !theme_to_update_->InForcedColorsMode());
     theme_to_update_->NotifyOnNativeThemeUpdated();
+  }
 }
 
 NativeTheme::ColorScheme NativeTheme::GetDefaultSystemColorScheme() const {
diff --git a/ui/native_theme/native_theme.h b/ui/native_theme/native_theme.h
index 2162424..b8ad810 100644
--- a/ui/native_theme/native_theme.h
+++ b/ui/native_theme/native_theme.h
@@ -123,7 +123,8 @@
     kNoPreference = 0,
     kMore = 1,
     kLess = 2,
-    kMaxValue = kLess,
+    kCustom = 3,
+    kMaxValue = kCustom,
   };
 
   // IMPORTANT!
@@ -431,8 +432,7 @@
   // Notify observers of caption style changes.
   virtual void NotifyOnCaptionStyleUpdated();
 
-  // Returns whether the user has an explicit contrast preference, i.e. whether
-  // we are in forced colors mode or PreferredContrast is set.
+  // Returns whether the user has an explicit contrast preference.
   virtual bool UserHasContrastPreference() const;
 
   // Returns whether we are in forced colors mode, controlled by system
diff --git a/ui/native_theme/native_theme_win.cc b/ui/native_theme/native_theme_win.cc
index 40d441f..485930b 100644
--- a/ui/native_theme/native_theme_win.cc
+++ b/ui/native_theme/native_theme_win.cc
@@ -761,10 +761,15 @@
   if (!InForcedColorsMode())
     return NativeTheme::CalculatePreferredContrast();
 
-  // According to the spec [1], "when the user agent can determine whether the
-  // forced color palette chosen by the user has a high or low contrast, one of
-  // 'prefers-contrast: more' or 'prefers-contrast: less' must match in addition
-  // to 'prefers-contrast: forced'."
+  // TODO(sartang@microsoft.com): Update the spec page at
+  // https://www.w3.org/TR/css-color-adjust-1/#forced, it currently does not
+  // mention the relation between forced-colors-active and prefers-contrast.
+  //
+  // According to spec [1], "in addition to forced-colors: active, the user
+  // agent must also match one of prefers-contrast: more or
+  // prefers-contrast: less if it can determine that the forced color
+  // palette chosen by the user has a particularly high or low contrast,
+  // and must make prefers-contrast: custom match otherwise".
   //
   // Using WCAG definitions [2], we have decided to match 'more' in Forced
   // Colors Mode if the contrast ratio between the foreground and background
@@ -778,7 +783,8 @@
   // These ratios will act as an experimental baseline that we can adjust based
   // on user feedback.
   //
-  // [1] https://www.w3.org/TR/css-color-adjust-1/#forced
+  // [1]
+  // https://drafts.csswg.org/mediaqueries-5/#valdef-media-forced-colors-active
   // [2] https://www.w3.org/WAI/WCAG21/Understanding/contrast-enhanced
   SkColor bg_color = system_colors_[SystemThemeColor::kWindow];
   SkColor fg_color = system_colors_[SystemThemeColor::kWindowText];
@@ -786,7 +792,7 @@
   if (contrast_ratio >= 7)
     return NativeTheme::PreferredContrast::kMore;
   return contrast_ratio <= 2.5 ? NativeTheme::PreferredContrast::kLess
-                               : NativeTheme::PreferredContrast::kNoPreference;
+                               : NativeTheme::PreferredContrast::kCustom;
 }
 
 NativeTheme::ColorScheme NativeThemeWin::GetDefaultSystemColorScheme() const {
diff --git a/ui/native_theme/native_theme_win_unittest.cc b/ui/native_theme/native_theme_win_unittest.cc
index 27c3ce70..02c7c17 100644
--- a/ui/native_theme/native_theme_win_unittest.cc
+++ b/ui/native_theme/native_theme_win_unittest.cc
@@ -70,7 +70,7 @@
   EXPECT_EQ(theme.CalculatePreferredContrast(), PrefContrast::kMore);
 
   theme.SetSystemColor(SystemThemeColor::kWindowText, SK_ColorRED);
-  EXPECT_EQ(theme.CalculatePreferredContrast(), PrefContrast::kNoPreference);
+  EXPECT_EQ(theme.CalculatePreferredContrast(), PrefContrast::kCustom);
 
   theme.SetSystemColor(SystemThemeColor::kWindowText, SK_ColorYELLOW);
   EXPECT_EQ(theme.CalculatePreferredContrast(), PrefContrast::kLess);
diff --git a/ui/views/animation/animation_builder.cc b/ui/views/animation/animation_builder.cc
index eb13e91..e80a67d 100644
--- a/ui/views/animation/animation_builder.cc
+++ b/ui/views/animation/animation_builder.cc
@@ -5,6 +5,7 @@
 #include "ui/views/animation/animation_builder.h"
 
 #include "base/containers/contains.h"
+#include "base/ranges/algorithm.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animation_sequence.h"
 #include "ui/compositor/layer_animator.h"
@@ -20,18 +21,22 @@
 AnimationBuilder::~AnimationBuilder() {
   // Collect all animations of a view into one vector so we can start them
   // together.
-  std::map<View*, std::vector<ui::LayerAnimationSequence*>> all_animations;
+  base::flat_map<View*, std::vector<ui::LayerAnimationSequence*>>
+      all_animations;
   for (auto& animation : animation_sequences_) {
-    // TODO(elainechien): Change AnimationKey to a struct to avoid this
-    // confusing syntax.
-    View* view = animation.first.first;
+    View* view = animation.first.view;
     if (!view->layer())
       view->SetPaintToLayer();
-    for (auto& s : animation.second)
+    for (auto& s : animation.second) {
+      if (animation_observer_)
+        animation_observer_->ObserveAnimationSequence(s.get());
       all_animations[view].push_back(s.release());
+    }
   }
   for (auto& a : all_animations)
     a.first->layer()->GetAnimator()->StartTogether(a.second);
+  if (animation_observer_)
+    animation_observer_.release();
 }
 
 AnimationBuilder& AnimationBuilder::NewSequence() {
@@ -94,15 +99,31 @@
   return *this;
 }
 
-void AnimationBuilder::OnStarted(base::OnceClosure callback) {}
+AnimationBuilder& AnimationBuilder::OnStarted(base::OnceClosure callback) {
+  GetAnimationObserver()->SetOnStarted(std::move(callback));
+  return *this;
+}
 
-void AnimationBuilder::OnEnded(base::OnceClosure callback) {}
+AnimationBuilder& AnimationBuilder::OnEnded(base::OnceClosure callback) {
+  GetAnimationObserver()->SetOnEnded(std::move(callback));
+  return *this;
+}
 
-void AnimationBuilder::OnWillRepeat(base::RepeatingClosure callback) {}
+AnimationBuilder& AnimationBuilder::OnWillRepeat(
+    base::RepeatingClosure callback) {
+  GetAnimationObserver()->SetOnWillRepeat(std::move(callback));
+  return *this;
+}
 
-void AnimationBuilder::OnAborted(base::OnceClosure callback) {}
+AnimationBuilder& AnimationBuilder::OnAborted(base::OnceClosure callback) {
+  GetAnimationObserver()->SetOnAborted(std::move(callback));
+  return *this;
+}
 
-void AnimationBuilder::OnScheduled(base::OnceClosure callback) {}
+AnimationBuilder& AnimationBuilder::OnScheduled(base::OnceClosure callback) {
+  GetAnimationObserver()->SetOnScheduled(std::move(callback));
+  return *this;
+}
 
 void AnimationBuilder::CreateNewEntry(const AnimationKey& key) {
   auto new_sequence = std::make_unique<ui::LayerAnimationSequence>();
@@ -121,6 +142,13 @@
   animation_sequences_[key].back()->AddElement(std::move(element));
 }
 
+AnimationBuilder::AnimationBuilderObserver*
+AnimationBuilder::GetAnimationObserver() {
+  if (!animation_observer_)
+    animation_observer_ = std::make_unique<AnimationBuilderObserver>();
+  return animation_observer_.get();
+}
+
 AnimationBuilder::AnimationBuilderObserver::AnimationBuilderObserver() =
     default;
 
@@ -136,20 +164,76 @@
   sequences_.emplace_back(sequence->AsWeakPtr());
 }
 
+void AnimationBuilder::AnimationBuilderObserver::SetOnStarted(
+    base::OnceClosure callback) {
+  DCHECK(!on_started_);
+  on_started_ = std::move(callback);
+}
+
+void AnimationBuilder::AnimationBuilderObserver::SetOnEnded(
+    base::OnceClosure callback) {
+  DCHECK(!on_ended_);
+  on_ended_ = std::move(callback);
+}
+
+void AnimationBuilder::AnimationBuilderObserver::SetOnWillRepeat(
+    base::RepeatingClosure callback) {
+  DCHECK(!on_will_repeat_);
+  on_will_repeat_ = std::move(callback);
+}
+
+void AnimationBuilder::AnimationBuilderObserver::SetOnAborted(
+    base::OnceClosure callback) {
+  DCHECK(!on_aborted_);
+  on_aborted_ = std::move(callback);
+}
+
+void AnimationBuilder::AnimationBuilderObserver::SetOnScheduled(
+    base::OnceClosure callback) {
+  DCHECK(!on_scheduled_);
+  on_scheduled_ = std::move(callback);
+}
+
 void AnimationBuilder::AnimationBuilderObserver::OnLayerAnimationStarted(
-    ui::LayerAnimationSequence* sequence) {}
+    ui::LayerAnimationSequence* sequence) {
+  if (on_started_)
+    std::move(on_started_).Run();
+}
 
 void AnimationBuilder::AnimationBuilderObserver::OnLayerAnimationEnded(
-    ui::LayerAnimationSequence* sequence) {}
+    ui::LayerAnimationSequence* sequence) {
+  const auto running =
+      base::ranges::count_if(sequences_, [](const auto& sequence) {
+        return sequence && !sequence->IsFinished(base::TimeTicks::Now());
+      });
+  if (running <= 1) {
+    if (on_ended_)
+      std::move(on_ended_).Run();
+    // TODO(kylixrd): This needs more thought in light of repeating animations
+    // aborts, etc...
+    delete this;
+  }
+}
 
 void AnimationBuilder::AnimationBuilderObserver::OnLayerAnimationWillRepeat(
-    ui::LayerAnimationSequence* sequence) {}
+    ui::LayerAnimationSequence* sequence) {
+  // TODO(kylixrd): This should only be called once for each repeat sequence.
+  // Figure out how to limit this to one invocation.
+}
 
 void AnimationBuilder::AnimationBuilderObserver::OnLayerAnimationAborted(
-    ui::LayerAnimationSequence* sequence) {}
+    ui::LayerAnimationSequence* sequence) {
+  if (on_aborted_)
+    std::move(on_aborted_).Run();
+  // TODO(kylixrd): Probably should propagate the abort to the other
+  // LayerAnimationSequences.
+}
 
 void AnimationBuilder::AnimationBuilderObserver::OnLayerAnimationScheduled(
-    ui::LayerAnimationSequence* sequence) {}
+    ui::LayerAnimationSequence* sequence) {
+  if (on_scheduled_)
+    std::move(on_scheduled_).Run();
+}
 
 void AnimationBuilder::AnimationBuilderObserver::Reset() {
   for (auto& sequence : sequences_) {
diff --git a/ui/views/animation/animation_builder.h b/ui/views/animation/animation_builder.h
index 0678b411..b7dca4d 100644
--- a/ui/views/animation/animation_builder.h
+++ b/ui/views/animation/animation_builder.h
@@ -5,12 +5,13 @@
 #ifndef UI_VIEWS_ANIMATION_ANIMATION_BUILDER_H_
 #define UI_VIEWS_ANIMATION_ANIMATION_BUILDER_H_
 
-#include <map>
 #include <memory>
+#include <tuple>
 #include <utility>
 #include <vector>
 
 #include "base/callback_forward.h"
+#include "base/containers/flat_map.h"
 #include "base/scoped_observation.h"
 #include "ui/compositor/layer_animation_element.h"
 #include "ui/compositor/layer_animation_observer.h"
@@ -45,22 +46,31 @@
 
 
   // Called when the animation starts.
-  void OnStarted(base::OnceClosure callback);
+  AnimationBuilder& OnStarted(base::OnceClosure callback);
   // Called when the animation ends. Not called if animation is aborted.
-  void OnEnded(base::OnceClosure callback);
+  AnimationBuilder& OnEnded(base::OnceClosure callback);
   // Called when a sequence repetition ends and will repeat. Not called if
   // sequence is aborted.
-  void OnWillRepeat(base::RepeatingClosure callback);
+  AnimationBuilder& OnWillRepeat(base::RepeatingClosure callback);
   // Called if animation is aborted for any reason. Should never do anything
   // that may cause another animation to be started.
-  void OnAborted(base::OnceClosure callback);
+  AnimationBuilder& OnAborted(base::OnceClosure callback);
   // Called when the animation is scheduled.
-  void OnScheduled(base::OnceClosure callback);
+  AnimationBuilder& OnScheduled(base::OnceClosure callback);
 
  private:
-  // We may want to change this to our own struct.
-  using AnimationKey =
-      std::pair<View*, ui::LayerAnimationElement::AnimatableProperty>;
+  struct AnimationKey {
+    View* view;
+    ui::LayerAnimationElement::AnimatableProperty property;
+
+    bool operator==(const AnimationKey& key) const {
+      return std::tie(view, property) == std::tie(key.view, key.property);
+    }
+
+    bool operator<(const AnimationKey& key) const {
+      return std::tie(view, property) < std::tie(key.view, key.property);
+    }
+  };
 
   class AnimationBuilderObserver : ui::LayerAnimationObserver {
    public:
@@ -72,6 +82,13 @@
 
     void ObserveAnimationSequence(ui::LayerAnimationSequence* sequence);
 
+    void SetOnStarted(base::OnceClosure callback);
+    void SetOnEnded(base::OnceClosure callback);
+    void SetOnWillRepeat(base::RepeatingClosure callback);
+    void SetOnAborted(base::OnceClosure callback);
+    void SetOnScheduled(base::OnceClosure callback);
+
+    // ui::LayerAnimationObserver
     void OnLayerAnimationStarted(ui::LayerAnimationSequence* sequence) override;
     void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override;
     void OnLayerAnimationWillRepeat(
@@ -84,6 +101,11 @@
     void Reset();
 
     std::vector<base::WeakPtr<ui::LayerAnimationSequence>> sequences_;
+    base::OnceClosure on_started_;
+    base::OnceClosure on_ended_;
+    base::RepeatingClosure on_will_repeat_;
+    base::OnceClosure on_aborted_;
+    base::OnceClosure on_scheduled_;
   };
 
   void CreateNewEntry(const AnimationKey& key);
@@ -91,8 +113,10 @@
   void AddAnimation(const AnimationKey& key,
                     std::unique_ptr<ui::LayerAnimationElement> element);
 
-  std::map<AnimationKey,
-           std::vector<std::unique_ptr<ui::LayerAnimationSequence>>>
+  AnimationBuilderObserver* GetAnimationObserver();
+
+  base::flat_map<AnimationKey,
+                 std::vector<std::unique_ptr<ui::LayerAnimationSequence>>>
       animation_sequences_;
 
   base::TimeDelta duration_ = base::TimeDelta::FromSeconds(1);
diff --git a/ui/views/animation/animation_builder_unittest.cc b/ui/views/animation/animation_builder_unittest.cc
index 88836a7..dd805267 100644
--- a/ui/views/animation/animation_builder_unittest.cc
+++ b/ui/views/animation/animation_builder_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "ui/views/animation/animation_builder.h"
 
+#include "base/bind.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animator.h"
@@ -103,4 +104,56 @@
   EXPECT_FLOAT_EQ(second_delegate->GetOpacityForAnimation(), 0.9f);
 }
 
+TEST_F(AnimationBuilderTest, CheckStartEndCallbacks) {
+  auto first_animating_view = std::make_unique<View>();
+  auto second_animating_view = std::make_unique<View>();
+
+  first_animating_view->SetPaintToLayer();
+  second_animating_view->SetPaintToLayer();
+
+  ui::LayerAnimator* first_layer_animator =
+      first_animating_view->layer()->GetAnimator();
+  // TODO(kylixrd): Consider adding more test support to AnimationBuilder to
+  // avoid reaching behind the curtain for these things.
+  first_layer_animator->set_disable_timer_for_test(true);
+
+  ui::LayerAnimator* second_layer_animator =
+      second_animating_view->layer()->GetAnimator();
+  second_layer_animator->set_disable_timer_for_test(true);
+
+  ui::LayerAnimatorTestController first_test_controller(first_layer_animator);
+  ui::LayerAnimatorTestController second_test_controller(second_layer_animator);
+
+  constexpr auto kDelay = base::TimeDelta::FromSeconds(3);
+  bool started = false;
+  bool ended = false;
+
+  {
+    AnimationBuilder b;
+    b.OnStarted(
+         base::BindOnce([](bool* started) { *started = true; }, &started))
+        .OnEnded(base::BindOnce([](bool* ended) { *ended = true; }, &ended))
+        .NewSequence()
+        .SetDuration(kDelay)
+        .SetOpacity(first_animating_view.get(), 0.4f)
+        .EndSequence()
+        .NewSequence()
+        .SetDuration(kDelay * 2)
+        .SetOpacity(second_animating_view.get(), 0.9f)
+        .EndSequence();
+  }
+
+  first_test_controller.StartThreadedAnimationsIfNeeded();
+  second_test_controller.StartThreadedAnimationsIfNeeded();
+
+  EXPECT_TRUE(started);
+
+  first_animating_view->layer()->GetAnimator()->Step(base::TimeTicks::Now() +
+                                                     kDelay * 2);
+  second_animating_view->layer()->GetAnimator()->Step(base::TimeTicks::Now() +
+                                                      kDelay * 2);
+
+  EXPECT_TRUE(ended);
+}
+
 }  // namespace views
diff --git a/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc b/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
index d9ca998..9307934 100644
--- a/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
+++ b/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
@@ -43,15 +43,12 @@
     int allowed_operations,
     ui::mojom::DragEventSource source) {
   drag_drop_in_progress_ = true;
+  gfx::Point touch_screen_point;
   if (source == ui::mojom::DragEventSource::kTouch) {
-    gfx::Point screen_point = display::win::ScreenWin::DIPToScreenPoint(
-        {screen_location.x(), screen_location.y()});
-    // Send a mouse down and mouse move before do drag drop runs its own event
-    // loop. This is required for ::DoDragDrop to start the drag.
-    ui::SendMouseEvent(screen_point,
-                       MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_ABSOLUTE);
-    ui::SendMouseEvent(screen_point, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE);
-    desktop_host_->SetInTouchDrag(true);
+    touch_screen_point =
+        screen_location + source_window->GetBoundsInScreen().OffsetFromOrigin();
+    source_window->GetHost()->ConvertDIPToPixels(&touch_screen_point);
+    desktop_host_->StartTouchDrag(touch_screen_point);
     // Gesture state gets left in a state where you can't start
     // another drag, unless it's cleaned up. Cleaning it up before starting
     // drag drop also fixes an issue with getting two kGestureScrollBegin events
@@ -89,8 +86,8 @@
     // it's called when it gets a mouse move event as well. (::DoDragDrop
     // doesn't support touch, so Chrome synthesizes mouse events from touch
     // events during drag drop.)
-    // In the touch failure case, when ::DoDragDrop blocks waiting for a right
-    // mouse button down event to start the drag, it only calls
+    // In the touch failure case, when ::DoDragDrop blocks waiting for a mouse
+    // button down event to start the drag, it only calls
     // QueryContinueDrag once, when it gets an event that terminates the blocked
     // drag drop, e.g., a swipe gesture from outside the Chrome window. So, we
     // detect the failure case when a drag drop lasts more than one second, and
@@ -100,7 +97,10 @@
                           drag_source_->num_query_continues() > 1 ||
                               (base::TimeTicks::Now() - start_time <
                                base::TimeDelta::FromSeconds(1)));
-    desktop_host_->SetInTouchDrag(false);
+    desktop_host_->FinishTouchDrag(touch_screen_point);
+    // Move the mouse cursor to where the drag drop started, to avoid issues
+    // when the drop is outside of the Chrome window.
+    ::SetCursorPos(touch_screen_point.x(), touch_screen_point.y());
   }
   drag_source_copy->set_data(nullptr);
 
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
index 6a60f9d..042fb2b 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
@@ -150,10 +150,20 @@
   return host ? host->window()->GetProperty(kContentWindowForRootWindow) : NULL;
 }
 
-void DesktopWindowTreeHostWin::SetInTouchDrag(bool in_touch_drag) {
-  in_touch_drag_ = in_touch_drag;
+void DesktopWindowTreeHostWin::StartTouchDrag(gfx::Point screen_point) {
+  // Send a mouse down and mouse move before do drag drop runs its own event
+  // loop. This is required for ::DoDragDrop to start the drag.
+  ui::SendMouseEvent(screen_point, MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_ABSOLUTE);
+  ui::SendMouseEvent(screen_point, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE);
+  in_touch_drag_ = true;
 }
 
+void DesktopWindowTreeHostWin::FinishTouchDrag(gfx::Point screen_point) {
+  if (in_touch_drag_) {
+    in_touch_drag_ = false;
+    ui::SendMouseEvent(screen_point, MOUSEEVENTF_LEFTUP | MOUSEEVENTF_ABSOLUTE);
+  }
+}
 ////////////////////////////////////////////////////////////////////////////////
 // DesktopWindowTreeHostWin, DesktopWindowTreeHost implementation:
 
@@ -1019,7 +1029,6 @@
   // by the time we attempt to process them.
   if (!GetWidget()->GetNativeView())
     return;
-
   if (in_touch_drag_) {
     POINT event_point;
     event_point.x = event->location().x();
@@ -1031,10 +1040,19 @@
     if (event->type() == ui::ET_TOUCH_MOVED) {
       ui::SendMouseEvent(screen_point, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE);
     } else if (event->type() == ui::ET_TOUCH_RELEASED) {
-      ui::SendMouseEvent(screen_point,
-                         MOUSEEVENTF_RIGHTUP | MOUSEEVENTF_ABSOLUTE);
+      FinishTouchDrag(screen_point);
     }
   }
+  // TODO(crbug.com/229301) Calling ::SetCursorPos for ui::ET_TOUCH_PRESSED
+  // events here would fix web ui tab strip drags when the cursor is not over
+  // the Chrome window - The TODO is to figure out if that's reasonable, since
+  // it would change the cursor pos on every touch event. Or figure out if there
+  // is a less intrusive way of fixing the cursor position. If we can do that,
+  // we can remove the call to ::SetCursorPos in
+  // DesktopDragDropClientWin::StartDragAndDrop. Note that calling SetCursorPos
+  // at the start of StartDragAndDrop breaks touch drag and drop, so it has to
+  // be called some time before we get to StartDragAndDrop.
+
   // Currently we assume the window that has capture gets touch events too.
   aura::WindowTreeHost* host =
       aura::WindowTreeHost::GetForAcceleratedWidget(GetCapture());
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
index 6d300fc..db12990 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
@@ -59,11 +59,35 @@
   // A way of converting an HWND into a content window.
   static aura::Window* GetContentWindowForHWND(HWND hwnd);
 
-  // Set to true when DesktopDragDropClientWin starts a touch-initiated drag
-  // drop and false when it finishes. While in touch drag, if pointer events are
-  // received, the equivalent mouse events are generated, because ole32
-  // ::DoDragDrop does not seem to handle pointer events.
-  void SetInTouchDrag(bool in_touch_drag);
+  // When DesktopDragDropClientWin starts a touch-initiated drag, it calls
+  // this method to record that we're in touch drag mode, and synthesizes
+  // right mouse button down and move events to get ::DoDragDrop started.
+  void StartTouchDrag(gfx::Point screen_point);
+
+  // If in touch drag mode, this method synthesizes a left mouse button up
+  // event to match the left mouse button down event in StartTouchDrag. It
+  // also restores the cursor pos to where the drag started, to avoid leaving
+  // the cursor outside the Chrome window doing the drag drop. This allows
+  // subsequent touch drag drops to succeed. Touch drag drop requires that
+  // the cursor be over the same window as the touch drag point.
+  // This needs to be called in two cases:
+  // 1. The normal case is that ::DoDragDrop starts, we get touch move events,
+  // which we turn into mouse move events, and then we get a touch release
+  // event. Calling FinishTouchDragIfInDrag generates a mouse up, which stops
+  // the drag drop.
+  // 2. ::DoDragDrop exits immediately, w/o us handling any touch events. In
+  // this case, FinishTouchDragIfInDrag makes sure we have a mouse button up to
+  // match the mouse button down, because we won't get a touch release event. We
+  // don't know for sure if ::DoDragDrop exited immediately, other than by
+  // checking if `in_touch_drag_` has been set to false.
+  //
+  // So, we always call FinishTouchDragIfInDrag after ::DoDragDrop exits, to
+  // make sure it gets called, and we make it handle getting called multiple
+  // times. Most of the time, FinishTouchDrag will have already been called when
+  // we get a touch release event, in which case the second call needs to be a
+  // noop, which is accomplished by checking if `in_touch_drag_` is already
+  // false.
+  void FinishTouchDrag(gfx::Point screen_point);
 
  protected:
   // Overridden from DesktopWindowTreeHost:
@@ -317,6 +341,12 @@
   // when that stat is no longer tracked.
   gfx::Point occluded_window_mouse_event_loc_;
 
+  // Set to true when DesktopDragDropClientWin starts a touch-initiated drag
+  // drop and false when it finishes. While in touch drag, if touch move events
+  // are received, the equivalent mouse events are generated, because ole32
+  // ::DoDragDrop does not seem to handle touch events. WinRT drag drop does
+  // support touch, but we've been unable to use it in Chrome. See
+  // https://crbug.com/1236783 for more info.
   bool in_touch_drag_ = false;
 
   // The z-order level of the window; the window exhibits "always on top"