diff --git a/BUILD.gn b/BUILD.gn
index 19aee8c..1985d70f 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -257,6 +257,12 @@
       "//media/cast:cast_unittests",
       "//third_party/catapult/telemetry:bitmaptools($host_toolchain)",
     ]
+    if (is_android) {
+      import("//tools/perf/chrome_telemetry_build/android_browser_types.gni")
+      foreach(_target_suffix, telemetry_android_browser_target_suffixes) {
+        deps += [ "//chrome/test:telemetry_perf_unittests${_target_suffix}" ]
+      }
+    }
   } else if (is_ios) {
     deps += [
       "//ios:all",
@@ -823,6 +829,12 @@
     ]
     if (is_android) {
       deps += [ "//chrome/browser/android/vr:vr_android_unittests" ]
+      import("//tools/perf/chrome_telemetry_build/android_browser_types.gni")
+      foreach(_target_suffix, telemetry_android_browser_target_suffixes) {
+        deps += [
+          "//tools/perf/contrib/vr_benchmarks:vr_perf_tests${_target_suffix}",
+        ]
+      }
     }
   }
 
diff --git a/DEPS b/DEPS
index d6616ff..c601d53 100644
--- a/DEPS
+++ b/DEPS
@@ -209,7 +209,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '357e67e7af3b95411f3f8426ca5de4767eb85d07',
+  'skia_revision': 'a1a3afe95136aa688372af3047ef9d3a4688096b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -221,11 +221,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'fec39fa1fc5985d07d6ae5fb39121b695d553488',
+  'angle_revision': '10f15011da42241e7053bd8a9d95d3a876faf75d',
   # 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': 'c1e4abc1bcfeae3ea17279b1f3d419406769c77d',
+  'swiftshader_revision': 'efe254de5d881c3d65030c6a5801d6969f9dde35',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -328,11 +328,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'b3c371031cc85b7af53d50386c7865fcb35a0a6a',
+  'dawn_revision': 'cac1d35f71cc361952a6da85588844da20600bf8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'quiche_revision': '9c1a2f0b79044aa9061f0eb2b12b4ace2b275045',
+  'quiche_revision': '3e33fe15f6f82bc1287dbf0f4aa2bfe2be1c9e5b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -730,7 +730,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': '5wEAJbMDQJnCxXbN6hMn66IR4akg1G25HQtc_8_7Vz0C',
+          'version': '8d-gGcc4KVhOnn2B-Od7eR421Q-sNZQ0U7dMrNz_VX4C',
       },
     ],
     'condition': 'checkout_android',
@@ -966,7 +966,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '85557a08f4918c65b5bc18868eaeb18e19800983',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '9705687c250c2f5a3e5eb5e9491ded272f53cdc1',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1106,7 +1106,7 @@
     Var('chromium_git') + '/chromium/deps/hunspell_dictionaries.git' + '@' + '18e09b9197a3b1d771c077c530d1a4ebad04c167',
 
   'src/third_party/icu':
-    Var('chromium_git') + '/chromium/deps/icu.git' + '@' + 'f022e298b4f4a782486bb6d5ce6589c998b51fe2',
+    Var('chromium_git') + '/chromium/deps/icu.git' + '@' + 'a0718d4f121727e30b8d52c7a189ebf5ab52421f',
 
   'src/third_party/icu4j': {
       'packages': [
@@ -1349,7 +1349,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'd355130be3c1738804c6cf787a1ff31886f539db',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'ae2171b89cb3e49f73ff100fc1647a93048ca472',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1547,7 +1547,7 @@
   'src/third_party/usrsctp/usrsctplib':
     Var('chromium_git') + '/external/github.com/sctplab/usrsctp' + '@' + '22ba62ffe79c3881581ab430368bf3764d9533eb',
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@d39bb223cf04dea5d35cbd982e140d1540f96352',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@7fd6569e6ad333383889e84dffb17db00bc1ed00',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '732a76d9d3c70d6aa487216495eeb28518349c3a',
@@ -1571,10 +1571,10 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'a0b8774ce8cec1dc8f4308810bf05eb8867c62de',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '7c0e4f92088f8230bd63a81b9b924fb9dadc5be8',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'd3af0bf85d4771b0809a4571bb01ac3f8edeb8a0',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '2a25a969731da40dbcf3468ff58713ea5c643484',
+    Var('webrtc_git') + '/src.git' + '@' + '43eb4f588646c346c97a1e1c1dda00dd837b13db',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1601,7 +1601,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/linux-amd64',
-          'version': 'v0j61b0DK560O1WaRwZ_kGgqR39JhxCdufdFIIheSfAC',
+          'version': 'fVbmAq5SyEKlNV_cPvOrlJbBRmDAWApv309sIMdbgq8C',
         },
       ],
       'dep_type': 'cipd',
@@ -1611,7 +1611,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/windows-amd64',
-          'version': 'JItKnXhnS8XOC3pfEti_mAl6ez2Dgk_qSjs5wXeG5xEC',
+          'version': 'hySBvc3hf9NRFmfS2oG52F9ZzLpvX9bFuyybtZ92CNAC',
         },
       ],
       'dep_type': 'cipd',
@@ -1621,7 +1621,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-amd64',
-          'version': 'WB42GE3e_7-dR5RnBBCICQtMkfpOoJvlT9tMG_6Fj1UC',
+          'version': 'SGAjTHEDDOpAgPQYJcSEmMh4E6afvlKQKzBjwSTf5dwC',
         },
       ],
       'dep_type': 'cipd',
@@ -1635,7 +1635,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@0643b2b983dd1126e4702664fde75dce35f06f2f',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3ed0d614fb4afb27e048f1ff9e387a4885794a04',
     'condition': 'checkout_src_internal',
   },
 
@@ -1665,7 +1665,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'Y11GYI292bpvaXxkHj9X5tufQUeYgOWKF_Ozds2OB7MC',
+        'version': 'nZQbCLVLZXhqeQVND_suj9_gQyVSt-tl7jFkdicBEyYC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 3a07e2d..61ec9599 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -1142,8 +1142,8 @@
     'build/android/gyp/prepare_resources.pydeps',
     'build/android/gyp/process_native_prebuilt.pydeps',
     'build/android/gyp/proguard.pydeps',
-    'build/android/gyp/resources_shrinker/shrinker.pydeps',
     'build/android/gyp/turbine.pydeps',
+    'build/android/gyp/unused_resources.pydeps',
     'build/android/gyp/validate_static_library_dex_references.pydeps',
     'build/android/gyp/write_build_config.pydeps',
     'build/android/gyp/write_native_libraries_java.pydeps',
diff --git a/WATCHLISTS b/WATCHLISTS
index 7af6ca7..0b42058 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -1444,11 +1444,14 @@
     },
     'omnibox': {
       'filepath': '^chrome/browser/autocomplete/|'\
+                  '^chrome/browser/resources/new_tab_page/realbox/|'\
                   '^chrome/browser/resources/omnibox|'\
                   '^chrome/browser/ui/location_bar/|'\
                   '^chrome/browser/ui/omnibox/|'\
                   '^chrome/browser/ui/.*/location_bar/|'\
                   '^chrome/browser/ui/.*/omnibox/|'\
+                  '^chrome/browser/ui/.*/realbox/|'\
+                  '^chrome/test/data/webui/new_tab_page/realbox/|'\
                   '^components/omnibox/|'\
                   '^components/search_engines/'
     },
diff --git a/android_webview/browser/gfx/overlay_processor_webview.cc b/android_webview/browser/gfx/overlay_processor_webview.cc
index cbd13266..039192c 100644
--- a/android_webview/browser/gfx/overlay_processor_webview.cc
+++ b/android_webview/browser/gfx/overlay_processor_webview.cc
@@ -176,7 +176,7 @@
       return;
 
     gpu_thread_sequence_ = std::make_unique<gpu::SchedulerSequence>(
-        gpu_service->GetGpuScheduler());
+        gpu_service->GetGpuScheduler(), gpu_service->main_runner());
 
     render_thread_sequence_->ScheduleGpuTask(
         base::BindOnce(&OverlayProcessorWebView::Manager::SetGpuService,
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index da3f602..547d6e7 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1834,6 +1834,7 @@
     "//base",
     "//base:i18n",
     "//base/third_party/dynamic_annotations",
+    "//base/util/timer",
     "//base/util/values:values_util",
     "//build:branding_buildflags",
     "//cc",
@@ -1888,6 +1889,7 @@
     "//components/services/app_service/public/cpp:app_update",
     "//components/session_manager:base",
     "//components/session_manager/core",
+    "//components/soda",
     "//components/strings",
     "//components/sync",
     "//components/user_manager",
@@ -2473,6 +2475,7 @@
     "//ash/system/machine_learning:user_settings_event_proto",
     "//base",
     "//base/test:test_support",
+    "//base/util/timer:timer",
     "//base/util/values:values_util",
     "//build:branding_buildflags",
     "//chromeos:test_support",
@@ -2510,6 +2513,7 @@
     "//components/quirks",
     "//components/services/app_service/public/cpp:app_update",
     "//components/session_manager/core",
+    "//components/soda",
     "//components/sync_preferences:test_support",
     "//components/ukm:test_support",
     "//components/user_manager",
diff --git a/ash/DEPS b/ash/DEPS
index 21fb9e2..5ff9d8e 100644
--- a/ash/DEPS
+++ b/ash/DEPS
@@ -15,6 +15,7 @@
   "+components/quirks",
   "+components/services/app_service/public",
   "+components/session_manager",
+  "+components/soda",
   "+components/strings",
   "+components/sync",
   "+components/ui_devtools",
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index 766e31a..2e73015 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -259,10 +259,13 @@
       prefs::kImprovedShortcutsNotificationShownCount);
 }
 
-bool IsGuestUserSession() {
+bool ShouldShowStartupNotificationForCurrentUser() {
   const absl::optional<user_manager::UserType> user_type =
       Shell::Get()->session_controller()->GetUserType();
-  return user_type && *user_type == user_manager::USER_TYPE_GUEST;
+  return user_type &&
+         (*user_type == user_manager::USER_TYPE_REGULAR ||
+          *user_type == user_manager::USER_TYPE_CHILD) &&
+         !Shell::Get()->session_controller()->IsUserFirstLogin();
 }
 
 // Increments the number of times the startup notification has been shown
@@ -1815,8 +1818,10 @@
     PrefService* pref_service) {
   DCHECK(pref_service);
   if (::features::IsImprovedKeyboardShortcutsEnabled()) {
-    if (should_show_shortcut_notification_ && !IsGuestUserSession())
+    if (should_show_shortcut_notification_ &&
+        ShouldShowStartupNotificationForCurrentUser()) {
       NotifyShortcutChangesInRelease(pref_service);
+    }
   }
 }
 
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc
index e2ec226..8182e01 100644
--- a/ash/accelerators/accelerator_controller_unittest.cc
+++ b/ash/accelerators/accelerator_controller_unittest.cc
@@ -2316,6 +2316,19 @@
 }
 
 TEST_F(AcceleratorControllerStartupNotificationTest,
+       StartupNotificationNotShownWhenInFirstLogin) {
+  // Set up the shell and controller.
+  SetUpLater(/*improved_shortcuts_enabled=*/true);
+
+  SimulateNewUserFirstLogin("user1@email.com");
+
+  // Notification should not be shown at a new user's first login.
+  auto* notification = message_center()->FindVisibleNotificationById(
+      kStartupNewShortcutNotificationId);
+  EXPECT_FALSE(notification);
+}
+
+TEST_F(AcceleratorControllerStartupNotificationTest,
        StartupNotificationShownOnlyOnce) {
   // Set up the shell and controller.
   SetUpLater(/*improved_shortcuts_enabled=*/true);
diff --git a/ash/ambient/test/ambient_ash_test_base.h b/ash/ambient/test/ambient_ash_test_base.h
index b03de74..6e92cb0 100644
--- a/ash/ambient/test/ambient_ash_test_base.h
+++ b/ash/ambient/test/ambient_ash_test_base.h
@@ -13,6 +13,7 @@
 #include "ash/ambient/ambient_controller.h"
 #include "ash/ambient/test/test_ambient_client.h"
 #include "ash/ambient/ui/ambient_background_image_view.h"
+#include "ash/public/cpp/test/test_image_downloader.h"
 #include "ash/test/ash_test_base.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
 #include "ui/views/view.h"
@@ -177,6 +178,7 @@
  private:
   std::unique_ptr<views::Widget> widget_;
   power_manager::PowerSupplyProperties proto_;
+  TestImageDownloader image_downloader_;
 };
 
 }  // namespace ash
diff --git a/ash/ambient/test/ambient_ash_test_helper.cc b/ash/ambient/test/ambient_ash_test_helper.cc
index c8ee548..a4503b7 100644
--- a/ash/ambient/test/ambient_ash_test_helper.cc
+++ b/ash/ambient/test/ambient_ash_test_helper.cc
@@ -5,12 +5,10 @@
 #include "ash/ambient/test/ambient_ash_test_helper.h"
 
 #include "ash/ambient/test/test_ambient_client.h"
-#include "ash/public/cpp/test/test_image_downloader.h"
 
 namespace ash {
 
 AmbientAshTestHelper::AmbientAshTestHelper() {
-  image_downloader_ = std::make_unique<TestImageDownloader>();
   ambient_client_ = std::make_unique<TestAmbientClient>(&wake_lock_provider_);
 }
 
diff --git a/ash/ambient/test/ambient_ash_test_helper.h b/ash/ambient/test/ambient_ash_test_helper.h
index 7ec665b..651fcda 100644
--- a/ash/ambient/test/ambient_ash_test_helper.h
+++ b/ash/ambient/test/ambient_ash_test_helper.h
@@ -11,7 +11,6 @@
 
 namespace ash {
 
-class TestImageDownloader;
 class TestAmbientClient;
 
 // The helper class to test the Ambient Mode in Ash.
@@ -31,7 +30,6 @@
   }
 
  private:
-  std::unique_ptr<TestImageDownloader> image_downloader_;
   device::TestWakeLockProvider wake_lock_provider_;
   std::unique_ptr<TestAmbientClient> ambient_client_;
 };
diff --git a/ash/app_list/app_list_presenter_unittest.cc b/ash/app_list/app_list_presenter_unittest.cc
index 5f05855..2be0104 100644
--- a/ash/app_list/app_list_presenter_unittest.cc
+++ b/ash/app_list/app_list_presenter_unittest.cc
@@ -88,6 +88,7 @@
 #include "ui/events/event_constants.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/touch_selection/touch_selection_menu_runner.h"
+#include "ui/views/animation/bounds_animator.h"
 #include "ui/views/controls/button/label_button.h"
 #include "ui/views/controls/textfield/textfield.h"
 #include "ui/views/controls/textfield/textfield_test_api.h"
diff --git a/ash/app_list/bubble/app_list_bubble_apps_page.cc b/ash/app_list/bubble/app_list_bubble_apps_page.cc
index 8fb47a1..7a6e3a8 100644
--- a/ash/app_list/bubble/app_list_bubble_apps_page.cc
+++ b/ash/app_list/bubble/app_list_bubble_apps_page.cc
@@ -9,7 +9,9 @@
 #include <string>
 #include <utility>
 
+#include "ash/app_list/app_list_view_delegate.h"
 #include "ash/app_list/bubble/scrollable_apps_grid_view.h"
+#include "ash/app_list/model/app_list_model.h"
 #include "ash/bubble/bubble_utils.h"
 #include "base/check.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
diff --git a/ash/app_list/bubble/scrollable_apps_grid_view.cc b/ash/app_list/bubble/scrollable_apps_grid_view.cc
index 9b9ec41..d43e2e8d 100644
--- a/ash/app_list/bubble/scrollable_apps_grid_view.cc
+++ b/ash/app_list/bubble/scrollable_apps_grid_view.cc
@@ -7,8 +7,10 @@
 #include <limits>
 #include <memory>
 
+#include "ash/app_list/model/app_list_model.h"
 #include "ash/app_list/views/app_list_item_view.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
+#include "ui/views/animation/bounds_animator.h"
 #include "ui/views/view_model_utils.h"
 
 namespace ash {
@@ -92,7 +94,7 @@
 }
 
 void ScrollableAppsGridView::CalculateIdealBounds() {
-  DCHECK(!is_in_folder());
+  DCHECK(!IsInFolder());
 
   int grid_index = 0;
   int model_index = 0;
diff --git a/ash/app_list/paged_view_structure.cc b/ash/app_list/paged_view_structure.cc
index f8073f22..b3c8002 100644
--- a/ash/app_list/paged_view_structure.cc
+++ b/ash/app_list/paged_view_structure.cc
@@ -7,6 +7,8 @@
 #include <algorithm>
 
 #include "ash/app_list/model/app_list_item.h"
+#include "ash/app_list/model/app_list_item_list.h"
+#include "ash/app_list/model/app_list_model.h"
 #include "ash/app_list/views/app_list_item_view.h"
 #include "ash/app_list/views/apps_grid_view.h"
 #include "base/containers/contains.h"
diff --git a/ash/app_list/views/app_list_item_view.cc b/ash/app_list/views/app_list_item_view.cc
index a607356..21b40b2 100644
--- a/ash/app_list/views/app_list_item_view.cc
+++ b/ash/app_list/views/app_list_item_view.cc
@@ -13,7 +13,6 @@
 #include "ash/app_list/model/app_list_folder_item.h"
 #include "ash/app_list/model/app_list_item.h"
 #include "ash/app_list/views/app_list_menu_model_adapter.h"
-#include "ash/app_list/views/apps_grid_view.h"
 #include "ash/public/cpp/app_list/app_list_color_provider.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "ash/public/cpp/app_list/app_list_switches.h"
@@ -256,17 +255,17 @@
   DISALLOW_COPY_AND_ASSIGN(IconImageView);
 };
 
-AppListItemView::AppListItemView(AppsGridView* apps_grid_view,
+AppListItemView::AppListItemView(GridDelegate* grid_delegate,
                                  AppListItem* item,
-                                 AppListViewDelegate* delegate)
-    : Button(),
-      is_folder_(item->GetItemType() == AppListFolderItem::kItemType),
+                                 AppListViewDelegate* view_delegate)
+    : is_folder_(item->GetItemType() == AppListFolderItem::kItemType),
       item_weak_(item),
-      delegate_(delegate),
-      apps_grid_view_(apps_grid_view),
+      grid_delegate_(grid_delegate),
+      view_delegate_(view_delegate),
       is_notification_indicator_enabled_(
           features::IsNotificationIndicatorEnabled()) {
-  DCHECK(delegate_);
+  DCHECK(grid_delegate_);
+  DCHECK(view_delegate_);
   SetFocusBehavior(FocusBehavior::ALWAYS);
 
   auto title = std::make_unique<views::Label>();
@@ -275,7 +274,7 @@
   title->SetFontList(GetAppListConfig().app_title_font());
   title->SetHorizontalAlignment(gfx::ALIGN_CENTER);
   title->SetEnabledColor(AppListColorProvider::Get()->GetAppListItemTextColor(
-      apps_grid_view_->is_in_folder()));
+      grid_delegate_->IsInFolder()));
 
   icon_ = AddChildView(std::make_unique<IconImageView>());
 
@@ -283,7 +282,7 @@
     // Set background blur for folder icon and use mask layer to clip it into
     // circle. Note that blur is only enabled in tablet mode to improve dragging
     // smoothness.
-    if (delegate_->IsInTabletMode())
+    if (view_delegate_->IsInTabletMode())
       SetBackgroundBlurEnabled(true);
     icon_->SetExtendedState(GetAppListConfig(), false /*extended*/,
                             false /*animate*/);
@@ -393,7 +392,7 @@
       SetIcon(icon_image_);
       layer()->SetTransform(gfx::GetScaleTransform(
           GetContentsBounds().CenterPoint(), 1 / kDragDropAppIconScale));
-    } else if (apps_grid_view_->IsDraggedView(this)) {
+    } else if (grid_delegate_->IsDraggedView(this)) {
       // If a drag view has been created for this icon, the item transition to
       // target bounds is handled by the apps grid view bounds animator. At the
       // end of that animation, the layer will be destroyed, causing the
@@ -419,7 +418,7 @@
   } else {
     if (is_folder_) {
       layer()->SetTransform(gfx::Transform());
-    } else if (!apps_grid_view_->IsDraggedView(this)) {
+    } else if (!grid_delegate_->IsDraggedView(this)) {
       // To avoid poor quality icons, update icon image with the correct scale
       // after the transform animation is completed.
       settings.AddObserver(this);
@@ -444,7 +443,7 @@
 
   // EndDrag may delete |this|.
   if (!touch_dragging)
-    apps_grid_view_->EndDrag(/*cancel=*/false);
+    grid_delegate_->EndDrag(/*cancel=*/false);
 }
 
 void AppListItemView::SetMouseDragging(bool mouse_dragging) {
@@ -463,8 +462,7 @@
     const gfx::Point& tap_down_location,
     const gfx::Point& tap_down_root_location) {
   // Show scaled up app icon to indicate draggable state.
-  apps_grid_view_->InitiateDrag(this, AppsGridView::TOUCH, tap_down_location,
-                                tap_down_root_location);
+  grid_delegate_->InitiateDrag(this, tap_down_location, tap_down_root_location);
   SetTouchDragging(true);
 }
 
@@ -501,7 +499,7 @@
 }
 
 const AppListConfig& AppListItemView::GetAppListConfig() const {
-  return apps_grid_view_->GetAppListConfig();
+  return grid_delegate_->GetAppListConfig();
 }
 
 void AppListItemView::SetItemName(const std::u16string& display_name,
@@ -558,16 +556,16 @@
     return;
 
   // GetContextMenuModel is asynchronous and takes a nontrivial amount of time
-  // to complete. If a menu is shown after the icon has moved, |apps_grid_view_|
+  // to complete. If a menu is shown after the icon has moved, |grid_delegate_|
   // gets put in a bad state because the context menu begins to receive drag
   // events, interrupting the app icon drag.
-  if (apps_grid_view_->IsDragViewMoved(*this))
+  if (grid_delegate_->IsDragViewMoved(*this))
     return;
 
   menu_show_initiated_from_key_ = source_type == ui::MENU_SOURCE_KEYBOARD;
 
-  if (!apps_grid_view_->IsSelectedView(this))
-    apps_grid_view_->ClearAnySelectedView();
+  if (!grid_delegate_->IsSelectedView(this))
+    grid_delegate_->ClearAnySelectedView();
 
   int run_types = views::MenuRunner::HAS_MNEMONICS |
                   views::MenuRunner::USE_TOUCHABLE_LAYOUT |
@@ -578,24 +576,24 @@
     run_types |= views::MenuRunner::SEND_GESTURE_EVENTS_TO_OWNER;
 
   gfx::Rect anchor_rect =
-      parent()->GetMirroredRect(apps_grid_view_->GetIdealBounds(this));
+      parent()->GetMirroredRect(grid_delegate_->GetIdealBounds(this));
   // Anchor the menu to the same rect that is used for selection highlight.
   AdaptBoundsForSelectionHighlight(&anchor_rect);
   views::View::ConvertRectToScreen(parent(), &anchor_rect);
 
   AppLaunchedMetricParams metric_params = {
       AppListLaunchedFrom::kLaunchedFromGrid};
-  delegate_->GetAppLaunchedMetricParams(&metric_params);
+  view_delegate_->GetAppLaunchedMetricParams(&metric_params);
 
   context_menu_ = std::make_unique<AppListMenuModelAdapter>(
       item_weak_->GetMetadata()->id, std::move(menu_model), GetWidget(),
       source_type, metric_params, AppListMenuModelAdapter::FULLSCREEN_APP_GRID,
       base::BindOnce(&AppListItemView::OnMenuClosed,
                      weak_ptr_factory_.GetWeakPtr()),
-      delegate_->IsInTabletMode());
+      view_delegate_->IsInTabletMode());
   context_menu_->Run(anchor_rect, views::MenuAnchorPosition::kBubbleRight,
                      run_types);
-  apps_grid_view_->SetSelectedView(this);
+  grid_delegate_->SetSelectedView(this);
 }
 
 void AppListItemView::ShowContextMenuForViewImpl(
@@ -611,7 +609,7 @@
   if (waiting_for_context_menu_options_)
     return;
   waiting_for_context_menu_options_ = true;
-  delegate_->GetContextMenuModel(
+  view_delegate_->GetContextMenuModel(
       item_weak_->id(),
       base::BindOnce(&AppListItemView::OnContextMenuModelReceived,
                      weak_ptr_factory_.GetWeakPtr(), point, source_type));
@@ -627,27 +625,27 @@
 }
 
 void AppListItemView::PaintButtonContents(gfx::Canvas* canvas) {
-  if (apps_grid_view_->IsDraggedView(this))
+  if (grid_delegate_->IsDraggedView(this))
     return;
 
   // TODO(ginko) focus and selection should be unified.
-  if ((apps_grid_view_->IsSelectedView(this) || HasFocus()) &&
-      (delegate_->KeyboardTraversalEngaged() ||
+  if ((grid_delegate_->IsSelectedView(this) || HasFocus()) &&
+      (view_delegate_->KeyboardTraversalEngaged() ||
        waiting_for_context_menu_options_ ||
        (context_menu_ && context_menu_->IsShowingMenu()))) {
     cc::PaintFlags flags;
     flags.setAntiAlias(true);
-    if (delegate_->KeyboardTraversalEngaged()) {
+    if (view_delegate_->KeyboardTraversalEngaged()) {
       flags.setColor(AppListColorProvider::Get()->GetFocusRingColor());
       flags.setStyle(cc::PaintFlags::kStroke_Style);
       flags.setStrokeWidth(kFocusRingWidth);
     } else {
       const AppListColorProvider* color_provider = AppListColorProvider::Get();
-      const SkColor bg_color = apps_grid_view_->is_in_folder()
-                                   ? color_provider->GetFolderBackgroundColor(
-                                         apps_grid_view_->GetAppListConfig()
-                                             .folder_background_color())
-                                   : gfx::kPlaceholderColor;
+      const SkColor bg_color =
+          grid_delegate_->IsInFolder()
+              ? color_provider->GetFolderBackgroundColor(
+                    GetAppListConfig().folder_background_color())
+              : gfx::kPlaceholderColor;
       flags.setColor(SkColorSetA(
           color_provider->GetRippleAttributesBaseColor(bg_color),
           color_provider->GetRippleAttributesHighlightOpacity(bg_color) * 255));
@@ -679,10 +677,9 @@
   if (!ShouldEnterPushedState(event))
     return true;
 
-  apps_grid_view_->InitiateDrag(this, AppsGridView::MOUSE, event.location(),
-                                event.root_location());
+  grid_delegate_->InitiateDrag(this, event.location(), event.root_location());
 
-  if (apps_grid_view_->IsDraggedView(this)) {
+  if (grid_delegate_->IsDraggedView(this)) {
     mouse_drag_timer_.Start(
         FROM_HERE, base::TimeDelta::FromMilliseconds(kMouseDragUIDelayInMs),
         this, &AppListItemView::OnMouseDragTimer);
@@ -701,7 +698,7 @@
 
   gfx::Rect title_bounds = GetTitleBoundsForTargetViewBounds(
       GetAppListConfig(), rect, title_->GetPreferredSize(), icon_scale_);
-  if (!apps_grid_view_->is_in_folder())
+  if (!grid_delegate_->IsInFolder())
     title_bounds.Inset(title_shadow_margins_);
   title_->SetBoundsRect(title_bounds);
 
@@ -730,7 +727,7 @@
   SetMouseDragging(false);
 
   // EndDrag may delete |this|.
-  apps_grid_view_->EndDrag(false /*cancel*/);
+  grid_delegate_->EndDrag(false /*cancel*/);
 }
 
 void AppListItemView::OnMouseCaptureLost() {
@@ -738,27 +735,27 @@
   SetMouseDragging(false);
 
   // EndDrag may delete |this|.
-  apps_grid_view_->EndDrag(true /*cancel*/);
+  grid_delegate_->EndDrag(true /*cancel*/);
 }
 
 bool AppListItemView::OnMouseDragged(const ui::MouseEvent& event) {
   Button::OnMouseDragged(event);
-  if (apps_grid_view_->IsDraggedView(this) && mouse_dragging_) {
+  if (grid_delegate_->IsDraggedView(this) && mouse_dragging_) {
     // Update the drag location of the drag proxy if it has been created.
     // If the drag is no longer happening, it could be because this item
     // got removed, in which case this item has been destroyed. So, bail out
     // now as there will be nothing else to do anyway as
-    // apps_grid_view_->dragging() will be false.
-    if (!apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE, event))
+    // grid_delegate_->dragging() will be false.
+    if (!grid_delegate_->UpdateDragFromItem(/*is_touch=*/false, event))
       return true;
   }
 
-  if (!apps_grid_view_->IsSelectedView(this))
-    apps_grid_view_->ClearAnySelectedView();
+  if (!grid_delegate_->IsSelectedView(this))
+    grid_delegate_->ClearAnySelectedView();
 
   // Show dragging UI when it's confirmed without waiting for the timer.
-  if (ui_state_ != UI_STATE_DRAGGING && apps_grid_view_->dragging() &&
-      apps_grid_view_->IsDraggedView(this)) {
+  if (ui_state_ != UI_STATE_DRAGGING && grid_delegate_->IsDragging() &&
+      grid_delegate_->IsDraggedView(this)) {
     mouse_drag_timer_.Stop();
     SetUIState(UI_STATE_DRAGGING);
   }
@@ -774,12 +771,12 @@
 void AppListItemView::OnFocus() {
   if (focus_silently_)
     return;
-  apps_grid_view_->SetSelectedView(this);
+  grid_delegate_->SetSelectedView(this);
 }
 
 void AppListItemView::OnBlur() {
   SchedulePaint();
-  apps_grid_view_->ClearSelectedView(this);
+  grid_delegate_->ClearSelectedView(this);
 }
 
 void AppListItemView::OnGestureEvent(ui::GestureEvent* event) {
@@ -787,16 +784,15 @@
     case ui::ET_GESTURE_SCROLL_BEGIN:
       if (touch_dragging_) {
         CancelContextMenu();
-        apps_grid_view_->StartDragAndDropHostDragAfterLongPress(
-            AppsGridView::TOUCH);
+        grid_delegate_->StartDragAndDropHostDragAfterLongPress();
         event->SetHandled();
       } else {
         touch_drag_timer_.Stop();
       }
       break;
     case ui::ET_GESTURE_SCROLL_UPDATE:
-      if (touch_dragging_ && apps_grid_view_->IsDraggedView(this)) {
-        apps_grid_view_->UpdateDragFromItem(AppsGridView::TOUCH, *event);
+      if (touch_dragging_ && grid_delegate_->IsDraggedView(this)) {
+        grid_delegate_->UpdateDragFromItem(/*is_touch=*/true, *event);
         event->SetHandled();
       }
       break;
@@ -831,7 +827,7 @@
       touch_drag_timer_.Stop();
       SetTouchDragging(false);
       if (context_menu_ && context_menu_->IsShowingMenu())
-        apps_grid_view_->SetSelectedView(this);
+        grid_delegate_->SetSelectedView(this);
       break;
     case ui::ET_GESTURE_TWO_FINGER_TAP:
       if (touch_dragging_) {
@@ -850,7 +846,7 @@
 void AppListItemView::OnThemeChanged() {
   views::Button::OnThemeChanged();
   title_->SetEnabledColor(AppListColorProvider::Get()->GetAppListItemTextColor(
-      apps_grid_view_->is_in_folder()));
+      grid_delegate_->IsInFolder()));
   SchedulePaint();
 }
 
diff --git a/ash/app_list/views/app_list_item_view.h b/ash/app_list/views/app_list_item_view.h
index 2e25287b..fefdd6b 100644
--- a/ash/app_list/views/app_list_item_view.h
+++ b/ash/app_list/views/app_list_item_view.h
@@ -8,7 +8,6 @@
 #include <memory>
 #include <string>
 #include <utility>
-#include <vector>
 
 #include "ash/app_list/model/app_list_item_observer.h"
 #include "ash/ash_export.h"
@@ -18,7 +17,13 @@
 #include "ui/views/context_menu_controller.h"
 #include "ui/views/controls/button/button.h"
 
+namespace gfx {
+class Point;
+class Rect;
+}  // namespace gfx
+
 namespace ui {
+class LocatedEvent;
 class SimpleMenuModel;
 }  // namespace ui
 
@@ -32,8 +37,10 @@
 class AppListItem;
 class AppListMenuModelAdapter;
 class AppListViewDelegate;
-class AppsGridView;
 
+// An application icon and title. Commonly part of the AppsGridView, but may be
+// used in other contexts. Supports dragging and keyboard selection via the
+// GridDelegate interface.
 class ASH_EXPORT AppListItemView : public views::Button,
                                    public views::ContextMenuController,
                                    public AppListItemObserver,
@@ -41,9 +48,47 @@
  public:
   METADATA_HEADER(AppListItemView);
 
-  AppListItemView(AppsGridView* apps_grid_view,
+  // The parent apps grid (AppsGridView) or a stub. Not named "Delegate" to
+  // differentiate it from AppListViewDelegate.
+  class GridDelegate {
+   public:
+    virtual ~GridDelegate() = default;
+
+    // Whether the parent apps grid (if any) is a folder.
+    virtual bool IsInFolder() const = 0;
+
+    // Methods for keyboard selection.
+    virtual void SetSelectedView(AppListItemView* view) = 0;
+    virtual void ClearSelectedView(AppListItemView* view) = 0;
+    virtual void ClearAnySelectedView() = 0;
+    virtual bool IsSelectedView(const AppListItemView* view) const = 0;
+
+    virtual void InitiateDrag(AppListItemView* view,
+                              const gfx::Point& location,
+                              const gfx::Point& root_location) = 0;
+    virtual void StartDragAndDropHostDragAfterLongPress() = 0;
+
+    // Called from AppListItemView when it receives a drag event. Returns true
+    // if the drag is still happening.
+    virtual bool UpdateDragFromItem(bool is_touch,
+                                    const ui::LocatedEvent& event) = 0;
+    virtual void EndDrag(bool cancel) = 0;
+    virtual bool IsDragging() const = 0;
+    virtual bool IsDraggedView(const AppListItemView* view) const = 0;
+
+    // Whether |view| is being dragged and is not in its drag start position.
+    virtual bool IsDragViewMoved(const AppListItemView& view) const = 0;
+
+    // Returns the ideal bounds for `view` in AppsGridView coordinates.
+    virtual const gfx::Rect& GetIdealBounds(AppListItemView* view) const = 0;
+
+    // TODO(crbug.com/1211592): Eliminate this method.
+    virtual const AppListConfig& GetAppListConfig() const = 0;
+  };
+
+  AppListItemView(GridDelegate* grid_delegate,
                   AppListItem* item,
-                  AppListViewDelegate* delegate);
+                  AppListViewDelegate* view_delegate);
   AppListItemView(const AppListItemView&) = delete;
   AppListItemView& operator=(const AppListItemView&) = delete;
   ~AppListItemView() override;
@@ -253,8 +298,13 @@
 
   AppListItem* item_weak_;  // Owned by AppListModel. Can be nullptr.
 
-  AppListViewDelegate* delegate_;               // Unowned.
-  AppsGridView* apps_grid_view_;                // Parent view, owns this.
+  // Handles dragging and item selection. Might be a stub for items that are not
+  // part of an apps grid.
+  GridDelegate* const grid_delegate_;
+
+  // AppListControllerImpl by another name.
+  AppListViewDelegate* const view_delegate_;
+
   IconImageView* icon_ = nullptr;               // Strongly typed child view.
   views::Label* title_ = nullptr;               // Strongly typed child view.
 
diff --git a/ash/app_list/views/app_list_main_view.cc b/ash/app_list/views/app_list_main_view.cc
index 47f4873d..ba293ed 100644
--- a/ash/app_list/views/app_list_main_view.cc
+++ b/ash/app_list/views/app_list_main_view.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 #include <memory>
+#include <string>
 #include <utility>
 
 #include "ash/app_list/app_list_metrics.h"
@@ -15,6 +16,7 @@
 #include "ash/app_list/model/app_list_model.h"
 #include "ash/app_list/views/app_list_folder_view.h"
 #include "ash/app_list/views/app_list_item_view.h"
+#include "ash/app_list/views/app_list_view.h"
 #include "ash/app_list/views/apps_container_view.h"
 #include "ash/app_list/views/apps_grid_view.h"
 #include "ash/app_list/views/contents_view.h"
diff --git a/ash/app_list/views/app_list_main_view_unittest.cc b/ash/app_list/views/app_list_main_view_unittest.cc
index 7564134b..4a680f83 100644
--- a/ash/app_list/views/app_list_main_view_unittest.cc
+++ b/ash/app_list/views/app_list_main_view_unittest.cc
@@ -5,11 +5,13 @@
 #include "ash/app_list/views/app_list_main_view.h"
 
 #include <memory>
+#include <string>
 
 #include "ash/app_list/app_list_test_view_delegate.h"
 #include "ash/app_list/model/app_list_test_model.h"
 #include "ash/app_list/views/app_list_folder_view.h"
 #include "ash/app_list/views/app_list_item_view.h"
+#include "ash/app_list/views/app_list_view.h"
 #include "ash/app_list/views/apps_container_view.h"
 #include "ash/app_list/views/apps_grid_view.h"
 #include "ash/app_list/views/apps_grid_view_test_api.h"
@@ -96,7 +98,6 @@
 
   // |point| is in |grid_view|'s coordinates.
   AppListItemView* SimulateInitiateDrag(AppsGridView* grid_view,
-                                        AppsGridView::Pointer pointer,
                                         const gfx::Point& point) {
     AppListItemView* view = GetItemViewAtPointInGrid(grid_view, point);
     DCHECK(view);
@@ -106,7 +107,7 @@
     gfx::Point root_window_point = point;
     views::View::ConvertPointToWidget(grid_view, &root_window_point);
 
-    grid_view->InitiateDrag(view, pointer, root_window_point, point);
+    grid_view->InitiateDrag(view, root_window_point, point);
     return view;
   }
 
@@ -184,9 +185,8 @@
   AppListItemView* StartDragForReparent(int index_in_folder) {
     // Start to drag the item in folder.
     views::View* item_view = GetFolderViewModel()->view_at(index_in_folder);
-    AppListItemView* dragged =
-        SimulateInitiateDrag(GetFolderGridView(), AppsGridView::MOUSE,
-                             item_view->bounds().CenterPoint());
+    AppListItemView* dragged = SimulateInitiateDrag(
+        GetFolderGridView(), item_view->bounds().CenterPoint());
     EXPECT_EQ(item_view, dragged);
     EXPECT_TRUE(GetRootGridView()->GetVisible());
     EXPECT_TRUE(GetFolderView()->GetVisible());
diff --git a/ash/app_list/views/apps_grid_view.cc b/ash/app_list/views/apps_grid_view.cc
index 16824a57..40b4135 100644
--- a/ash/app_list/views/apps_grid_view.cc
+++ b/ash/app_list/views/apps_grid_view.cc
@@ -20,6 +20,7 @@
 #include "ash/app_list/views/app_list_folder_view.h"
 #include "ash/app_list/views/app_list_item_view.h"
 #include "ash/app_list/views/app_list_main_view.h"
+#include "ash/app_list/views/app_list_view.h"
 #include "ash/app_list/views/apps_container_view.h"
 #include "ash/app_list/views/contents_view.h"
 #include "ash/app_list/views/ghost_image_view.h"
@@ -55,6 +56,7 @@
 #include "ui/gfx/geometry/vector2d_conversions.h"
 #include "ui/strings/grit/ui_strings.h"
 #include "ui/views/accessibility/view_accessibility.h"
+#include "ui/views/animation/bounds_animator.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/textfield/textfield.h"
@@ -420,6 +422,10 @@
   Update();
 }
 
+bool AppsGridView::IsInFolder() const {
+  return !!folder_delegate_;
+}
+
 void AppsGridView::SetSelectedView(AppListItemView* view) {
   if (IsSelectedView(view) || IsDraggedView(view))
     return;
@@ -453,7 +459,6 @@
 }
 
 void AppsGridView::InitiateDrag(AppListItemView* view,
-                                Pointer pointer,
                                 const gfx::Point& location,
                                 const gfx::Point& root_location) {
   DCHECK(view);
@@ -476,8 +481,8 @@
   drag_view_start_ = gfx::Point(drag_view_->x(), drag_view_->y());
 }
 
-void AppsGridView::StartDragAndDropHostDragAfterLongPress(Pointer pointer) {
-  TryStartDragAndDropHostDrag(pointer, drag_start_grid_view_);
+void AppsGridView::StartDragAndDropHostDragAfterLongPress() {
+  TryStartDragAndDropHostDrag(TOUCH, drag_start_grid_view_);
 }
 
 void AppsGridView::TryStartDragAndDropHostDrag(
@@ -497,7 +502,7 @@
     StartDragAndDropHostDrag(grid_location);
 }
 
-bool AppsGridView::UpdateDragFromItem(Pointer pointer,
+bool AppsGridView::UpdateDragFromItem(bool is_touch,
                                       const ui::LocatedEvent& event) {
   if (!drag_view_)
     return false;  // Drag canceled.
@@ -506,6 +511,7 @@
 
   gfx::Point drag_point_in_grid_view;
   ExtractDragLocation(event.root_location(), &drag_point_in_grid_view);
+  const Pointer pointer = is_touch ? TOUCH : MOUSE;
   UpdateDrag(pointer, drag_point_in_grid_view);
   if (!dragging())
     return false;
@@ -840,6 +846,10 @@
   UpdateDrag(pointer, drag_point);
 }
 
+bool AppsGridView::IsDragging() const {
+  return dragging();
+}
+
 bool AppsGridView::IsDraggedView(const AppListItemView* view) const {
   return drag_view_ == view;
 }
diff --git a/ash/app_list/views/apps_grid_view.h b/ash/app_list/views/apps_grid_view.h
index 36da4ecd..3c7e18f 100644
--- a/ash/app_list/views/apps_grid_view.h
+++ b/ash/app_list/views/apps_grid_view.h
@@ -14,10 +14,11 @@
 #include <tuple>
 #include <vector>
 
-#include "ash/app_list/model/app_list_model.h"
+#include "ash/app_list/app_list_metrics.h"
+#include "ash/app_list/model/app_list_item_list_observer.h"
 #include "ash/app_list/model/app_list_model_observer.h"
 #include "ash/app_list/paged_view_structure.h"
-#include "ash/app_list/views/app_list_view.h"
+#include "ash/app_list/views/app_list_item_view.h"
 #include "ash/ash_export.h"
 #include "ash/public/cpp/pagination/pagination_model.h"
 #include "base/time/time.h"
@@ -26,12 +27,14 @@
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
 #include "ui/gfx/image/image_skia_operations.h"
-#include "ui/views/animation/bounds_animator.h"
 #include "ui/views/animation/bounds_animator_observer.h"
-#include "ui/views/controls/image_view.h"
 #include "ui/views/view.h"
 #include "ui/views/view_model.h"
 
+namespace views {
+class BoundsAnimator;
+}  // namespace views
+
 namespace ash {
 
 namespace test {
@@ -42,7 +45,9 @@
 class ApplicationDragAndDropHost;
 class AppListConfig;
 class AppListItem;
+class AppListItemList;
 class AppListItemView;
+class AppListModel;
 class AppListViewDelegate;
 class AppsGridViewFolderDelegate;
 class ContentsView;
@@ -73,6 +78,7 @@
 // - The main grid of apps in the launcher
 // - The grid of apps in a folder
 class ASH_EXPORT AppsGridView : public views::View,
+                                public AppListItemView::GridDelegate,
                                 public AppListItemListObserver,
                                 public AppListModelObserver,
                                 public ui::ImplicitAnimationObserver,
@@ -130,34 +136,23 @@
   // |item_list|.
   void SetItemList(AppListItemList* item_list);
 
-  void SetSelectedView(AppListItemView* view);
-  void ClearSelectedView(AppListItemView* view);
-  void ClearAnySelectedView();
-  bool IsSelectedView(const AppListItemView* view) const;
-  bool has_selected_view() const { return selected_view_ != nullptr; }
-  AppListItemView* GetSelectedView() const;
-
+  // AppListItemView::GridDelegate:
+  bool IsInFolder() const override;
+  void SetSelectedView(AppListItemView* view) override;
+  void ClearSelectedView(AppListItemView* view) override;
+  void ClearAnySelectedView() override;
+  bool IsSelectedView(const AppListItemView* view) const override;
   void InitiateDrag(AppListItemView* view,
-                    Pointer pointer,
                     const gfx::Point& location,
-                    const gfx::Point& root_location);
-
-  void StartDragAndDropHostDragAfterLongPress(Pointer pointer);
-  void TryStartDragAndDropHostDrag(Pointer pointer,
-                                   const gfx::Point& grid_location);
-
-  // Called from AppListItemView when it receives a drag event. Returns true
-  // if the drag is still happening.
-  bool UpdateDragFromItem(Pointer pointer, const ui::LocatedEvent& event);
-
-  // Called when the user is dragging an app. |point| is in grid view
-  // coordinates.
-  void UpdateDrag(Pointer pointer, const gfx::Point& point);
-  void EndDrag(bool cancel);
-  bool IsDraggedView(const AppListItemView* view) const;
-
-  // Whether |view| IsDraggedView and |view| is not in it's drag start position.
-  bool IsDragViewMoved(const AppListItemView& view) const;
+                    const gfx::Point& root_location) override;
+  void StartDragAndDropHostDragAfterLongPress() override;
+  bool UpdateDragFromItem(bool is_touch,
+                          const ui::LocatedEvent& event) override;
+  void EndDrag(bool cancel) override;
+  bool IsDragging() const override;
+  bool IsDraggedView(const AppListItemView* view) const override;
+  bool IsDragViewMoved(const AppListItemView& view) const override;
+  const gfx::Rect& GetIdealBounds(AppListItemView* view) const override;
 
   void ClearDragState();
   void SetDragViewVisible(bool visible);
@@ -169,7 +164,12 @@
   // Return true if the |bounds_animator_| is animating |view|.
   bool IsAnimatingView(AppListItemView* view);
 
+  // TODO(crbug.com/1211608): Replace these with selected_view().
+  bool has_selected_view() const { return selected_view_ != nullptr; }
+  AppListItemView* GetSelectedView() const;
+
   bool has_dragged_view() const { return drag_view_ != nullptr; }
+  // TODO(crbug.com/1211608): Remove this in favor of IsDragging().
   bool dragging() const { return drag_pointer_ != NONE; }
   const AppListItemView* drag_view() const { return drag_view_; }
 
@@ -200,9 +200,6 @@
   // Stops the timer that triggers a page flip during a drag.
   void StopPageFlipTimer();
 
-  // Returns the ideal bounds of an AppListItemView in AppsGridView coordinates.
-  const gfx::Rect& GetIdealBounds(AppListItemView* view) const;
-
   // Returns the item view of the item at |index|, or nullptr if there is no
   // view at |index|.
   AppListItemView* GetItemViewAt(int index) const;
@@ -278,7 +275,7 @@
 
   // Helper for getting current app list config from the parents in the app list
   // view hierarchy.
-  const AppListConfig& GetAppListConfig() const;
+  const AppListConfig& GetAppListConfig() const override;
 
   // Return the view model.
   views::ViewModelT<AppListItemView>* view_model() { return &view_model_; }
@@ -302,8 +299,6 @@
     folder_delegate_ = folder_delegate;
   }
 
-  bool is_in_folder() const { return !!folder_delegate_; }
-
   AppListItemView* activated_folder_item_view() const {
     return activated_folder_item_view_;
   }
@@ -497,12 +492,19 @@
   // currently dragged item is released.
   void UpdateDropTargetForReorder(const gfx::Point& point);
 
+  // Called when the user is dragging an app. |point| is in grid view
+  // coordinates.
+  void UpdateDrag(Pointer pointer, const gfx::Point& point);
+
   // Returns true if the current drag is occurring within a certain range of the
   // nearest item.
   bool DragIsCloseToItem();
 
   bool DragPointIsOverItem(const gfx::Point& point);
 
+  void TryStartDragAndDropHostDrag(Pointer pointer,
+                                   const gfx::Point& grid_location);
+
   // Prepares |drag_and_drop_host_| for dragging. |grid_location| contains
   // the drag point in this grid view's coordinates.
   void StartDragAndDropHostDrag(const gfx::Point& grid_location);
diff --git a/ash/app_list/views/apps_grid_view_unittest.cc b/ash/app_list/views/apps_grid_view_unittest.cc
index c1c0491..61fed871f 100644
--- a/ash/app_list/views/apps_grid_view_unittest.cc
+++ b/ash/app_list/views/apps_grid_view_unittest.cc
@@ -331,7 +331,7 @@
                                           &view_origin);
         // AppListItemViews that belong to a folder views' AppsGridView also
         // need to have their x coordinate set for RTL.
-        if (grid_view->is_in_folder())
+        if (grid_view->IsInFolder())
           view_origin.set_x(grid_view->GetMirroredXInView(view_origin.x()));
       }
       if (gfx::Rect(view_origin, view->size()).Contains(point))
@@ -492,7 +492,7 @@
     // Ensure that the |root_from| point is correct if RTL.
     root_from.set_x(apps_grid_view->GetMirroredXInView(root_from.x()));
 
-    apps_grid_view->InitiateDrag(view, pointer, root_from, root_from);
+    apps_grid_view->InitiateDrag(view, root_from, root_from);
     current_drag_location_ = root_from;
     // Call UpdateDrag to trigger |apps_grid_view| change to cardified_state.
     UpdateDrag(pointer, from, apps_grid_view);
@@ -1306,7 +1306,7 @@
       folder_apps_grid_view()->pagination_model();
   EXPECT_EQ(1, folder_pagination_model->total_pages());
   EXPECT_EQ(0, folder_pagination_model->selected_page());
-  EXPECT_TRUE(folder_apps_grid_view()->is_in_folder());
+  EXPECT_TRUE(folder_apps_grid_view()->IsInFolder());
 }
 
 TEST_P(AppsGridViewDragAndDropTest, MouseDragItemOutOfFolderFirstPage) {
@@ -1365,7 +1365,7 @@
   EXPECT_EQ(1, folder_apps_grid_view()->pagination_model()->selected_page());
   EXPECT_EQ(4, folder_apps_grid_view()->cols());
   EXPECT_EQ(4, folder_apps_grid_view()->rows_per_page());
-  EXPECT_TRUE(folder_apps_grid_view()->is_in_folder());
+  EXPECT_TRUE(folder_apps_grid_view()->IsInFolder());
 }
 
 TEST_P(AppsGridViewDragAndDropTest, MouseDragItemOutOfFolderSecondPage) {
@@ -1425,7 +1425,7 @@
       model_->CreateAndPopulateFolderWithApps(kTotalItems);
   test_api_->Update();
   test_api_->PressItemAt(0);
-  ASSERT_TRUE(folder_apps_grid_view()->is_in_folder());
+  ASSERT_TRUE(folder_apps_grid_view()->IsInFolder());
   // Switch to second page.
   AnimateFolderViewPageFlip(1);
   // Fill the rest of the root grid view with new app list items. Leave 1 slot
@@ -2609,7 +2609,7 @@
       folder_apps_grid_view()->pagination_model();
   EXPECT_EQ(1, folder_pagination_model->total_pages());
   EXPECT_EQ(0, folder_pagination_model->selected_page());
-  EXPECT_TRUE(folder_apps_grid_view()->is_in_folder());
+  EXPECT_TRUE(folder_apps_grid_view()->IsInFolder());
 }
 
 TEST_P(AppsGridViewDragAndDropTest, MoveAnItemToNewEmptyPage) {
diff --git a/ash/app_list/views/folder_header_view.cc b/ash/app_list/views/folder_header_view.cc
index 8153fba5..18c22cd 100644
--- a/ash/app_list/views/folder_header_view.cc
+++ b/ash/app_list/views/folder_header_view.cc
@@ -17,6 +17,7 @@
 #include "base/macros.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/utf_string_conversions.h"
+#include "ui/base/cursor/cursor.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/compositor/layer.h"
 #include "ui/gfx/canvas.h"
@@ -27,6 +28,7 @@
 #include "ui/views/border.h"
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/focus/focus_manager.h"
 #include "ui/views/native_cursor.h"
 #include "ui/views/painter.h"
 #include "ui/views/view_targeter_delegate.h"
diff --git a/ash/app_list/views/paged_apps_grid_view.cc b/ash/app_list/views/paged_apps_grid_view.cc
index d75b04f..90beb1a 100644
--- a/ash/app_list/views/paged_apps_grid_view.cc
+++ b/ash/app_list/views/paged_apps_grid_view.cc
@@ -11,6 +11,7 @@
 #include "ash/app_list/model/app_list_item.h"
 #include "ash/app_list/views/app_list_item_view.h"
 #include "ash/app_list/views/app_list_main_view.h"
+#include "ash/app_list/views/app_list_view.h"
 #include "ash/app_list/views/contents_view.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
@@ -40,6 +41,7 @@
 #include "ui/gfx/skia_paint_util.h"
 #include "ui/gfx/transform.h"
 #include "ui/gfx/transform_util.h"
+#include "ui/views/animation/bounds_animator.h"
 #include "ui/views/paint_info.h"
 #include "ui/views/view.h"
 #include "ui/views/view_model_utils.h"
@@ -173,9 +175,9 @@
 
   pagination_controller_ = std::make_unique<PaginationController>(
       &pagination_model_,
-      is_in_folder() ? PaginationController::SCROLL_AXIS_HORIZONTAL
-                     : PaginationController::SCROLL_AXIS_VERTICAL,
-      is_in_folder()
+      IsInFolder() ? PaginationController::SCROLL_AXIS_HORIZONTAL
+                   : PaginationController::SCROLL_AXIS_VERTICAL,
+      IsInFolder()
           ? base::DoNothing()
           : base::BindRepeating(&AppListRecordPageSwitcherSourceByEventType),
       IsTabletMode());
@@ -475,7 +477,7 @@
 }
 
 gfx::Insets PagedAppsGridView::GetTilePadding() const {
-  if (is_in_folder()) {
+  if (IsInFolder()) {
     const int tile_padding_in_folder =
         GetAppListConfig().grid_tile_spacing_in_folder() / 2;
     return gfx::Insets(-tile_padding_in_folder, -tile_padding_in_folder);
@@ -520,7 +522,7 @@
 void PagedAppsGridView::TotalPagesChanged(int previous_page_count,
                                           int new_page_count) {
   // Don't record from folder.
-  if (is_in_folder())
+  if (IsInFolder())
     return;
 
   // Initial setup for the AppList starts with -1 pages. Ignore the page count
@@ -682,7 +684,7 @@
     gfx::PointF root_location = event.root_location_f();
     return root_location.y() - mouse_drag_start_point_.y();
   };
-  if (!is_in_folder() &&
+  if (!IsInFolder() &&
       (event.IsMouseEvent() || event.type() == ui::ET_GESTURE_SCROLL_BEGIN) &&
       !IsTabletMode() &&
       ((pagination_model_.selected_page() == 0 &&
@@ -695,7 +697,7 @@
 }
 
 void PagedAppsGridView::MaybeCreateGradientMask() {
-  if (!is_in_folder() && features::IsBackgroundBlurEnabled()) {
+  if (!IsInFolder() && features::IsBackgroundBlurEnabled()) {
     // TODO(newcomer): Improve implementation of the mask layer so we can
     // enable it on all devices https://crbug.com/765292.
     if (!layer()->layer_mask_layer()) {
@@ -715,7 +717,7 @@
 void PagedAppsGridView::StartAppsGridCardifiedView() {
   if (!app_list_features::IsNewDragSpecInLauncherEnabled())
     return;
-  if (is_in_folder())
+  if (IsInFolder())
     return;
   DCHECK(!cardified_state_);
   StopObservingImplicitAnimations();
@@ -735,7 +737,7 @@
 void PagedAppsGridView::EndAppsGridCardifiedView() {
   if (!app_list_features::IsNewDragSpecInLauncherEnabled())
     return;
-  if (is_in_folder())
+  if (IsInFolder())
     return;
   DCHECK(cardified_state_);
   StopObservingImplicitAnimations();
diff --git a/ash/app_list/views/paged_apps_grid_view.h b/ash/app_list/views/paged_apps_grid_view.h
index dfc7c5b5..81057ba 100644
--- a/ash/app_list/views/paged_apps_grid_view.h
+++ b/ash/app_list/views/paged_apps_grid_view.h
@@ -11,6 +11,7 @@
 #include "ash/app_list/views/apps_grid_view.h"
 #include "ash/ash_export.h"
 #include "ash/public/cpp/pagination/pagination_model_observer.h"
+#include "ash/public/cpp/presentation_time_recorder.h"
 #include "base/memory/ref_counted.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/compositor/throughput_tracker.h"
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 0d9c57bd..c0b6ad2 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -1071,18 +1071,27 @@
       <message name="IDS_ASH_HOLDING_SPACE_SCREEN_CAPTURES_TITLE" desc="Title of the screen captures area in the holding space bubble.">
         Screen captures
       </message>
+      <message name="IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_CANCEL" desc="Title of the cancel option in the holding space item context menu.">
+        Cancel
+      </message>
       <message name="IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_COPY_IMAGE_TO_CLIPBOARD" desc="Title of the option to copy an image to the clipboard in the holding space item context menu.">
         Copy image
       </message>
       <message name="IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_HIDE_PREVIEWS" desc="Title of the option to hide previews in the holding space tray context menu.">
         Hide previews
       </message>
+      <message name="IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_PAUSE" desc="Title of the pause option in the holding space item context menu.">
+        Pause
+      </message>
       <message name="IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_PIN" desc="Title of the pin option in the holding space item context menu.">
         Pin
       </message>
       <message name="IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_REMOVE" desc="Title of the remove option in the holding space item context menu.">
         Remove
       </message>
+      <message name="IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_RESUME" desc="Title of the resume option in the holding space item context menu.">
+        Resume
+      </message>
       <message name="IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_SHOW_IN_FOLDER" desc="Title of the option to show item in its folder in the holding space item context menu.">
         Show in folder
       </message>
diff --git a/ash/ash_strings_grd/IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_CANCEL.png.sha1 b/ash/ash_strings_grd/IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_CANCEL.png.sha1
new file mode 100644
index 0000000..82d9dbe
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_CANCEL.png.sha1
@@ -0,0 +1 @@
+058563d92ce33f5bb4157017f1dbc768d725c121
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_PAUSE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_PAUSE.png.sha1
new file mode 100644
index 0000000..aaeb508
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_PAUSE.png.sha1
@@ -0,0 +1 @@
+d51be6562a5a5cf46a13d3e350ed72b107e71d69
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_RESUME.png.sha1 b/ash/ash_strings_grd/IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_RESUME.png.sha1
new file mode 100644
index 0000000..d2b83f5
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_RESUME.png.sha1
@@ -0,0 +1 @@
+a3bc686888118a745b504a2cb1adbcd458def5e8
\ No newline at end of file
diff --git a/ash/capture_mode/capture_mode_notification_view.cc b/ash/capture_mode/capture_mode_notification_view.cc
index 75dbae4..d77ebd3 100644
--- a/ash/capture_mode/capture_mode_notification_view.cc
+++ b/ash/capture_mode/capture_mode_notification_view.cc
@@ -8,9 +8,11 @@
 #include "ash/public/cpp/assistant/assistant_state.h"
 #include "ash/public/cpp/clipboard_history_controller.h"
 #include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
 #include "ash/style/scoped_light_mode_as_default.h"
+#include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/chromeos/events/keyboard_layout_util.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -127,14 +129,15 @@
   label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   label->SetEnabledColor(text_icon_color);
 
-  if (features::IsClipboardHistoryScreenshotNudgeEnabled()) {
-    banner_view->AddChildView(CreateClipboardShortcutView());
-    layout->SetFlexForView(label, 1);
+  if (!Shell::Get()->tablet_mode_controller()->InTabletMode()) {
+    if (features::IsClipboardHistoryScreenshotNudgeEnabled()) {
+      banner_view->AddChildView(CreateClipboardShortcutView());
+      layout->SetFlexForView(label, 1);
+    }
+
+    // Notify the clipboard history of the created notification.
+    ClipboardHistoryController::Get()->OnScreenshotNotificationCreated();
   }
-
-  // Notify the clipboard history of the created notification.
-  ClipboardHistoryController::Get()->OnScreenshotNotificationCreated();
-
   return banner_view;
 }
 
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 998d7d8..b561b22 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -573,15 +573,6 @@
 const base::Feature kPluginVmShowMicrophonePermissions{
     "PluginVmShowMicrophonePermissions", base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Controls whether to show printer statuses on the Print Preview destination
-// dialog.
-const base::Feature kPrinterStatusDialog{"PrinterStatusDialog",
-                                         base::FEATURE_ENABLED_BY_DEFAULT};
-
-// Allows print servers to be selected when beyond a specified limit.
-const base::Feature kPrintServerScaling{"PrintServerScaling",
-                                        base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Controls whether to enable projector.
 const base::Feature kProjector{"Projector", base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index bc26144..814ca9c 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -254,9 +254,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kPrintJobManagementApp;
 COMPONENT_EXPORT(ASH_CONSTANTS)
-extern const base::Feature kPrintServerScaling;
-COMPONENT_EXPORT(ASH_CONSTANTS)
-extern const base::Feature kPrinterStatusDialog;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kProjector;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kProjectorFeaturePod;
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::Feature kQuickAnswers;
diff --git a/ash/content/shimless_rma/resources/fake_shimless_rma_service.js b/ash/content/shimless_rma/resources/fake_shimless_rma_service.js
index 3c7cf8e..3a4b24a2 100644
--- a/ash/content/shimless_rma/resources/fake_shimless_rma_service.js
+++ b/ash/content/shimless_rma/resources/fake_shimless_rma_service.js
@@ -196,17 +196,19 @@
   }
 
   /**
-   * @return {!Promise<!{error: !RmadErrorCode}>}
+   * @return {!Promise<!StateResult>}
    */
   updateChrome() {
-    return this.methods_.resolveMethod('updateChrome');
+    return this.getNextStateForMethod_(
+      'updateChrome', RmaState.kUpdateChrome);
   }
 
   /**
-   * @param {!RmadErrorCode} error
+   * @return {!Promise<!StateResult>}
    */
-  setUpdateChromeResult(error) {
-    this.methods_.setResult('updateChrome', {error: error});
+  updateChromeSkipped() {
+    return this.getNextStateForMethod_(
+      'updateChromeSkipped', RmaState.kUpdateChrome);
   }
 
   /**
@@ -263,7 +265,6 @@
    * @return {!Promise<!StateResult>}
    */
   setRsuDisableWriteProtectCode(code) {
-    // TODO(gavindodd): Send the code over mojo.
     return this.getNextStateForMethod_(
         'setRsuDisableWriteProtectCode', RmaState.kEnterRSUWPDisableCode);
   }
@@ -687,6 +688,7 @@
     this.methods_.register('getCurrentChromeVersion');
     this.methods_.register('checkForChromeUpdates');
     this.methods_.register('updateChrome');
+    this.methods_.register('updateChromeSkipped');
 
     this.methods_.register('setSameOwner');
     this.methods_.register('setDifferentOwner');
diff --git a/ash/projector/projector_controller_impl.cc b/ash/projector/projector_controller_impl.cc
index 026d74b5..b7859c8 100644
--- a/ash/projector/projector_controller_impl.cc
+++ b/ash/projector/projector_controller_impl.cc
@@ -10,6 +10,7 @@
 #include "ash/public/cpp/projector/projector_session.h"
 #include "ash/shell.h"
 #include "base/strings/utf_string_conversions.h"
+#include "media/mojo/mojom/speech_recognition_service.mojom.h"
 
 namespace ash {
 
@@ -33,23 +34,15 @@
 }
 
 void ProjectorControllerImpl::OnTranscription(
-    const std::u16string& text,
-    absl::optional<base::TimeDelta> start_time,
-    absl::optional<base::TimeDelta> end_time,
-    const absl::optional<std::vector<base::TimeDelta>>& word_offsets,
-    bool is_final) {
-  std::string transcript = base::UTF16ToUTF8(text);
-
-  if (is_final && start_time.has_value() && end_time.has_value() &&
-      word_offsets.has_value()) {
-    // Records final transcript.
-    metadata_controller_->RecordTranscription(
-        transcript, start_time.value(), end_time.value(), word_offsets.value());
-  }
-
+    const media::SpeechRecognitionResult& result) {
   // Render transcription.
   if (is_caption_on_) {
-    ui_controller_->OnTranscription(transcript, is_final);
+    ui_controller_->OnTranscription(result.transcription, result.is_final);
+  }
+
+  if (result.is_final && result.timing_information.has_value()) {
+    // Records final transcript.
+    metadata_controller_->RecordTranscription(result);
   }
 }
 
diff --git a/ash/projector/projector_controller_impl.h b/ash/projector/projector_controller_impl.h
index 2f9376e..7ce8712c 100644
--- a/ash/projector/projector_controller_impl.h
+++ b/ash/projector/projector_controller_impl.h
@@ -34,12 +34,7 @@
   // ProjectorController:
   void SetClient(ash::ProjectorClient* client) override;
   void OnSpeechRecognitionAvailable(bool available) override;
-  void OnTranscription(
-      const std::u16string& text,
-      absl::optional<base::TimeDelta> start_time,
-      absl::optional<base::TimeDelta> end_time,
-      const absl::optional<std::vector<base::TimeDelta>>& word_offsets,
-      bool is_final) override;
+  void OnTranscription(const media::SpeechRecognitionResult& result) override;
   void SetProjectorToolsVisible(bool is_visible) override;
   bool IsEligible() const override;
 
diff --git a/ash/projector/projector_controller_unittest.cc b/ash/projector/projector_controller_unittest.cc
index 7370595..a99ff6d0 100644
--- a/ash/projector/projector_controller_unittest.cc
+++ b/ash/projector/projector_controller_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "ash/projector/projector_controller_impl.h"
 
+#include <initializer_list>
 #include <memory>
 #include <string>
 #include <vector>
@@ -15,46 +16,44 @@
 #include "ash/test/ash_test_base.h"
 #include "base/files/file_path.h"
 #include "base/json/json_writer.h"
-#include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/values.h"
-#include "chromeos/services/machine_learning/public/mojom/soda.mojom.h"
+#include "media/mojo/mojom/speech_recognition_service.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkColor.h"
 
 namespace ash {
 namespace {
-
-using chromeos::machine_learning::mojom::FinalResult;
-using chromeos::machine_learning::mojom::PartialResult;
-using chromeos::machine_learning::mojom::SpeechRecognizerEvent;
-using chromeos::machine_learning::mojom::SpeechRecognizerEventPtr;
-using chromeos::machine_learning::mojom::TimingInfo;
 using testing::_;
 using testing::ElementsAre;
 
 void NotifyControllerForFinalSpeechResult(ProjectorControllerImpl* controller) {
-  controller->OnTranscription(
-      u"transcript text 1",
-      base::TimeDelta::FromMilliseconds(0) /* audio_start_time */,
-      base::TimeDelta::FromMilliseconds(3000) /* audio_end_time */,
-      {{base::TimeDelta::FromMilliseconds(1000),
-        base::TimeDelta::FromMilliseconds(2000),
-        base::TimeDelta::FromMilliseconds(2500)}} /* word_offsets*/,
-      true /* is_final */);
+  media::SpeechRecognitionResult result;
+  result.transcription = "transcript text 1";
+  result.is_final = true;
+  result.timing_information = media::TimingInformation();
+  result.timing_information->audio_start_time =
+      base::TimeDelta::FromMilliseconds(0);
+  result.timing_information->audio_end_time =
+      base::TimeDelta::FromMilliseconds(3000);
+
+  std::vector<media::HypothesisParts> hypothesis_parts;
+  std::string hypothesis_text[3] = {"transcript", "text", "1"};
+  int hypothesis_time[3] = {1000, 2000, 2500};
+  for (int i = 0; i < 3; i++) {
+    hypothesis_parts.emplace_back(
+        std::vector<std::string>({hypothesis_text[i]}),
+        base::TimeDelta::FromMilliseconds(hypothesis_time[i]));
+  }
+
+  result.timing_information->hypothesis_parts = std::move(hypothesis_parts);
+  controller->OnTranscription(result);
 }
 
 void NotifyControllerForPartialSpeechResult(
     ProjectorControllerImpl* controller) {
   controller->OnTranscription(
-      u"transcript partial text 1",
-      base::TimeDelta::FromMilliseconds(0) /* audio_start_time */,
-      base::TimeDelta::FromMilliseconds(3000) /* audio_end_time */,
-      {{base::TimeDelta::FromMilliseconds(1000),
-        base::TimeDelta::FromMilliseconds(2000),
-        base::TimeDelta::FromMilliseconds(2500),
-        base::TimeDelta::FromMilliseconds(3000)}} /* word_offsets*/,
-      false /* is_final */);
+      media::SpeechRecognitionResult("transcript partial text 1", false));
 }
 
 }  // namespace
@@ -125,15 +124,7 @@
 TEST_F(ProjectorControllerTest, OnTranscription) {
   // Verify that |RecordTranscription| in |ProjectorMetadataController| is
   // called to record the transcript.
-  EXPECT_CALL(
-      *mock_metadata_controller_,
-      RecordTranscription("transcript text 1",
-                          testing::Eq(base::TimeDelta::FromMilliseconds(0)),
-                          testing::Eq(base::TimeDelta::FromMilliseconds(3000)),
-                          ElementsAre(base::TimeDelta::FromMilliseconds(1000),
-                                      base::TimeDelta::FromMilliseconds(2000),
-                                      base::TimeDelta::FromMilliseconds(2500))))
-      .Times(1);
+  EXPECT_CALL(*mock_metadata_controller_, RecordTranscription(_)).Times(1);
   // Verify that |OnTranscription| in |ProjectorUiController| is not called
   // since capton is off.
   EXPECT_CALL(*mock_ui_controller_, OnTranscription(_, _)).Times(0);
@@ -143,8 +134,7 @@
 TEST_F(ProjectorControllerTest, OnTranscriptionPartialResult) {
   // Verify that |RecordTranscription| in |ProjectorMetadataController| is not
   // called since it is not a final result.
-  EXPECT_CALL(*mock_metadata_controller_, RecordTranscription(_, _, _, _))
-      .Times(0);
+  EXPECT_CALL(*mock_metadata_controller_, RecordTranscription(_)).Times(0);
   // Verify that |OnTranscription| in |ProjectorUiController| is not called
   // since caption is off.
   EXPECT_CALL(*mock_ui_controller_, OnTranscription(_, _)).Times(0);
@@ -154,15 +144,7 @@
 TEST_F(ProjectorControllerTest, OnTranscriptionCaptionOn) {
   // Verify that |SaveMetadata| in |ProjectorMetadataController| is called to
   // record the transcript.
-  EXPECT_CALL(
-      *mock_metadata_controller_,
-      RecordTranscription("transcript text 1",
-                          testing::Eq(base::TimeDelta::FromMilliseconds(0)),
-                          testing::Eq(base::TimeDelta::FromMilliseconds(3000)),
-                          ElementsAre(base::TimeDelta::FromMilliseconds(1000),
-                                      base::TimeDelta::FromMilliseconds(2000),
-                                      base::TimeDelta::FromMilliseconds(2500))))
-      .Times(1);
+  EXPECT_CALL(*mock_metadata_controller_, RecordTranscription(_)).Times(1);
   // Verify that |OnTranscription| in |ProjectorUiController| is called since
   // capton is on.
   EXPECT_CALL(*mock_ui_controller_, OnTranscription("transcript text 1", true))
@@ -172,10 +154,9 @@
 }
 
 TEST_F(ProjectorControllerTest, OnTranscriptionCaptionOnPartialResult) {
-  // Verify that |RecordTranscription| in |ProjectorMetadataController| is
-  // called.
-  EXPECT_CALL(*mock_metadata_controller_, RecordTranscription(_, _, _, _))
-      .Times(0);
+  // Verify that |RecordTranscription| in |ProjectorMetadataController| is not
+  // called since it is not a final result.
+  EXPECT_CALL(*mock_metadata_controller_, RecordTranscription(_)).Times(0);
   // Verify that |OnTranscription| in |ProjectorUiController| is called since
   // capton is on.
   EXPECT_CALL(*mock_ui_controller_,
diff --git a/ash/projector/projector_metadata_controller.cc b/ash/projector/projector_metadata_controller.cc
index 8c2d43a..81740a0 100644
--- a/ash/projector/projector_metadata_controller.cc
+++ b/ash/projector/projector_metadata_controller.cc
@@ -9,6 +9,7 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/task/current_thread.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
@@ -47,13 +48,13 @@
 }
 
 void ProjectorMetadataController::RecordTranscription(
-    const std::string& transcription,
-    const base::TimeDelta start_time,
-    const base::TimeDelta end_time,
-    const std::vector<base::TimeDelta>& word_alignments) {
+    const media::SpeechRecognitionResult& speech_result) {
   DCHECK(metadata_);
+
+  const auto& timing = speech_result.timing_information;
   metadata_->AddTranscript(std::make_unique<ProjectorTranscript>(
-      start_time, end_time, transcription, word_alignments));
+      timing->audio_start_time, timing->audio_end_time,
+      speech_result.transcription, timing->hypothesis_parts.value()));
 }
 
 void ProjectorMetadataController::RecordKeyIdea() {
diff --git a/ash/projector/projector_metadata_controller.h b/ash/projector/projector_metadata_controller.h
index 259b324..a01588cb6 100644
--- a/ash/projector/projector_metadata_controller.h
+++ b/ash/projector/projector_metadata_controller.h
@@ -13,10 +13,10 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequenced_task_runner.h"
+#include "media/mojo/mojom/speech_recognition_service.mojom.h"
 
 namespace base {
 class FilePath;
-class TimeDelta;
 }  // namespace base
 
 namespace ash {
@@ -35,10 +35,7 @@
   virtual void OnRecordingStarted();
   // Records the transcript in metadata. Virtual for testing.
   virtual void RecordTranscription(
-      const std::string& transcription,
-      const base::TimeDelta start_time,
-      const base::TimeDelta end_time,
-      const std::vector<base::TimeDelta>& word_alignments);
+      const media::SpeechRecognitionResult& speech_result);
   // Marks the next transcript as the beginning of a key idea.
   // Virtual for testing.
   virtual void RecordKeyIdea();
diff --git a/ash/projector/projector_metadata_model.cc b/ash/projector/projector_metadata_model.cc
index d33eac8..64efd48 100644
--- a/ash/projector/projector_metadata_model.cc
+++ b/ash/projector/projector_metadata_model.cc
@@ -18,10 +18,24 @@
 constexpr base::StringPiece kStartOffsetKey = "startOffset";
 constexpr base::StringPiece kEndOffsetKey = "endOffset";
 constexpr base::StringPiece kTextKey = "text";
-constexpr base::StringPiece kWordAlignmentKey = "wordAlignment";
+constexpr base::StringPiece kHypothesisPartsKey = "hypothesisParts";
 constexpr base::StringPiece kNameKey = "name";
 constexpr base::StringPiece kCaptionsKey = "captions";
 constexpr base::StringPiece kKeyIdeasKey = "tableOfContent";
+constexpr base::StringPiece kOffset = "offset";
+
+base::Value HypothesisPartsToValue(
+    const media::HypothesisParts& hypothesis_parts) {
+  base::Value text_value(base::Value::Type::LIST);
+  for (auto& part : hypothesis_parts.text)
+    text_value.Append(part);
+
+  base::Value hypothesis_part_value(base::Value::Type::DICTIONARY);
+  hypothesis_part_value.SetKey(kTextKey, std::move(text_value));
+  hypothesis_part_value.SetIntKey(
+      kOffset, hypothesis_parts.hypothesis_part_offset.InMilliseconds());
+  return hypothesis_part_value;
+}
 
 }  // namespace
 
@@ -63,9 +77,9 @@
     const base::TimeDelta start_time,
     const base::TimeDelta end_time,
     const std::string& text,
-    const std::vector<base::TimeDelta>& word_alignments)
+    const std::vector<media::HypothesisParts>& hypothesis_parts)
     : MetadataItem(start_time, end_time, text),
-      word_alignments_(word_alignments) {}
+      hypothesis_parts_(hypothesis_parts) {}
 
 ProjectorTranscript::~ProjectorTranscript() = default;
 
@@ -73,10 +87,17 @@
 //  {
 //      "startOffset": 100
 //      "endOffset": 2100
-//      "text": "Today I'd like to teach..."
-//      "wordAlignments": [
-//         100,
-//         1500,
+//      "text": "Today I would like to teach..."
+//      "hypothesisParts": [
+//        {
+//           "text": ["Today"]
+//           "offset": 100
+//         },
+//         {
+//           "text": ["I"]
+//           "offset": 200
+//         },
+//         ...
 //      ]
 //  }
 //
@@ -85,17 +106,19 @@
 //   "startOffset": INT
 //   "endOffset": INT
 //   "text": STRING
-//   "wordAlignments": LIST
+//   "hypothesisParts": DICT LIST
+//
 base::Value ProjectorTranscript::ToJson() {
   base::Value transcript(base::Value::Type::DICTIONARY);
   transcript.SetIntKey(kStartOffsetKey, start_time_.InMilliseconds());
   transcript.SetIntKey(kEndOffsetKey, end_time_.InMilliseconds());
   transcript.SetStringKey(kTextKey, text_);
 
-  base::Value word_alignments_value(base::Value::Type::LIST);
-  for (auto& word_alignment : word_alignments_)
-    word_alignments_value.Append((int)word_alignment.InMilliseconds());
-  transcript.SetKey(kWordAlignmentKey, std::move(word_alignments_value));
+  base::Value hypothesis_parts_value(base::Value::Type::LIST);
+  for (auto& hypothesis_part : hypothesis_parts_)
+    hypothesis_parts_value.Append(HypothesisPartsToValue(hypothesis_part));
+
+  transcript.SetKey(kHypothesisPartsKey, std::move(hypothesis_parts_value));
   return transcript;
 }
 
@@ -134,9 +157,16 @@
 //      "endOffset": 2100
 //      "text": "Today I'd like to teach you about a central pillar of a
 //      construction learning theory it's called the debugging Loop...",
-//      "wordAlignments": [
-//         100,
-//         1500,
+//      "hypothesisParts": [
+//          {
+//            "text" : ["Today"],
+//            "offset": 100,
+//          },
+//          {
+//            "text": ["I"],
+//            "offset": 1500,
+//          }
+//          ...
 //      ]
 //    }],
 //    "tableOfContent": [
diff --git a/ash/projector/projector_metadata_model.h b/ash/projector/projector_metadata_model.h
index c9abc315..89a7921 100644
--- a/ash/projector/projector_metadata_model.h
+++ b/ash/projector/projector_metadata_model.h
@@ -11,6 +11,7 @@
 
 #include "ash/ash_export.h"
 #include "base/time/time.h"
+#include "media/mojo/mojom/speech_recognition_service.mojom.h"
 
 namespace base {
 class Value;
@@ -21,9 +22,9 @@
 // Base class to describe a metadata item.
 class MetadataItem {
  public:
-  explicit MetadataItem(const base::TimeDelta start_time,
-                        const base::TimeDelta end_time,
-                        const std::string& text);
+  MetadataItem(const base::TimeDelta start_time,
+               const base::TimeDelta end_time,
+               const std::string& text);
   MetadataItem(const MetadataItem&) = delete;
   MetadataItem& operator=(const MetadataItem&) = delete;
   virtual ~MetadataItem();
@@ -48,9 +49,9 @@
 // Class to describe a key idea.
 class ASH_EXPORT ProjectorKeyIdea : public MetadataItem {
  public:
-  explicit ProjectorKeyIdea(const base::TimeDelta start_time,
-                            const base::TimeDelta end_time,
-                            const std::string& text = std::string());
+  ProjectorKeyIdea(const base::TimeDelta start_time,
+                   const base::TimeDelta end_time,
+                   const std::string& text = std::string());
   ProjectorKeyIdea(const ProjectorKeyIdea&) = delete;
   ProjectorKeyIdea& operator=(const ProjectorKeyIdea&) = delete;
   ~ProjectorKeyIdea() override;
@@ -61,11 +62,11 @@
 // Class to describe a transcription.
 class ASH_EXPORT ProjectorTranscript : public MetadataItem {
  public:
-  explicit ProjectorTranscript(
+  ProjectorTranscript(
       const base::TimeDelta start_time,
       const base::TimeDelta end_time,
       const std::string& text,
-      const std::vector<base::TimeDelta>& word_alignments);
+      const std::vector<media::HypothesisParts>& hypothesis_parts);
   ProjectorTranscript(const ProjectorTranscript&) = delete;
   ProjectorTranscript& operator=(const ProjectorTranscript&) = delete;
   ~ProjectorTranscript() override;
@@ -73,14 +74,14 @@
   base::Value ToJson() override;
 
  private:
-  std::vector<base::TimeDelta> word_alignments_;
+  std::vector<media::HypothesisParts> hypothesis_parts_;
 };
 
 // Class to describe a projector metadata of a screencast session, including
 // name, transcriptions, key_ideas, etc
 class ASH_EXPORT ProjectorMetadata {
  public:
-  explicit ProjectorMetadata();
+  ProjectorMetadata();
   ProjectorMetadata(const ProjectorMetadata&) = delete;
   ProjectorMetadata& operator=(const ProjectorMetadata&) = delete;
   ~ProjectorMetadata();
diff --git a/ash/projector/projector_metadata_model_unittest.cc b/ash/projector/projector_metadata_model_unittest.cc
index fd6e2f0..92f83e8b 100644
--- a/ash/projector/projector_metadata_model_unittest.cc
+++ b/ash/projector/projector_metadata_model_unittest.cc
@@ -5,13 +5,16 @@
 #include "ash/projector/projector_metadata_model.h"
 
 #include <memory>
+#include <sstream>
 #include <string>
 #include <vector>
 
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
+#include "base/logging.h"
 #include "base/strings/stringprintf.h"
 #include "base/values.h"
+#include "media/mojo/mojom/speech_recognition_service.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace ash {
@@ -27,7 +30,12 @@
   "endOffset": %i,
   "startOffset": %i,
   "text": "%s",
-  "wordAlignment": %s
+  "hypothesisParts": %s
+})";
+
+constexpr char kSerializedHypothesisPartTemplate[] = R"({
+  "text": %s,
+  "offset": %i
 })";
 
 void AssertSerializedString(const std::string& expected,
@@ -46,13 +54,43 @@
                             start_offset, text.c_str());
 }
 
-std::string BuildTranscriptJson(int start_offset,
-                                int end_offset,
-                                const std::string& text,
-                                const std::string& words_alignments_str) {
+std::string BuildHypothesisParts(
+    const media::HypothesisParts& hypothesis_parts) {
+  std::stringstream ss;
+  ss << "[";
+  for (uint i = 0; i < hypothesis_parts.text.size(); i++) {
+    ss << "\"" << hypothesis_parts.text[i] << "\"";
+    if (i < hypothesis_parts.text.size() - 1)
+      ss << ", ";
+  }
+  ss << "]";
+
+  return base::StringPrintf(
+      kSerializedHypothesisPartTemplate, ss.str().c_str(),
+      int(hypothesis_parts.hypothesis_part_offset.InMilliseconds()));
+}
+
+std::string BuildHypothesisPartsList(
+    const std::vector<media::HypothesisParts>& hypothesis_parts_vector) {
+  std::stringstream ss;
+  ss << "[";
+  for (uint i = 0; i < hypothesis_parts_vector.size(); i++) {
+    ss << BuildHypothesisParts(hypothesis_parts_vector[i]);
+    if (i < hypothesis_parts_vector.size() - 1)
+      ss << ", ";
+  }
+  ss << "]";
+  return ss.str();
+}
+
+std::string BuildTranscriptJson(
+    int start_offset,
+    int end_offset,
+    const std::string& text,
+    const std::vector<media::HypothesisParts>& hypothesis_part) {
   return base::StringPrintf(kSerializedTranscriptTemplate, end_offset,
                             start_offset, text.c_str(),
-                            words_alignments_str.c_str());
+                            BuildHypothesisPartsList(hypothesis_part).c_str());
 }
 
 }  // namespace
@@ -98,18 +136,24 @@
 };
 
 TEST_F(ProjectorTranscriptTest, ToJson) {
+  std::vector<media::HypothesisParts> hypothesis_parts;
+  hypothesis_parts.emplace_back(std::vector<std::string>({"transcript"}),
+                                base::TimeDelta::FromMilliseconds(1000));
+  hypothesis_parts.emplace_back(std::vector<std::string>({"text"}),
+                                base::TimeDelta::FromMilliseconds(2000));
+
+  const auto expected_transcript =
+      BuildTranscriptJson(1000, 3000, "transcript text", hypothesis_parts);
+
   ProjectorTranscript transcript(
       /*start_time=*/base::TimeDelta::FromMilliseconds(1000),
       /*end_time=*/base::TimeDelta::FromMilliseconds(3000), "transcript text",
-      {base::TimeDelta::FromMilliseconds(1000),
-       base::TimeDelta::FromMilliseconds(2000)});
+      std::move(hypothesis_parts));
 
   std::string transcript_str;
   base::JSONWriter::Write(transcript.ToJson(), &transcript_str);
 
-  AssertSerializedString(
-      BuildTranscriptJson(1000, 3000, "transcript text", "[1000,2000]"),
-      transcript_str);
+  AssertSerializedString(expected_transcript, transcript_str);
 }
 
 class ProjectorMetadataTest : public testing::Test {
@@ -122,22 +166,54 @@
 
 TEST_F(ProjectorMetadataTest, Serialize) {
   const char kExpectedMetaData[] = R"({
-    "name": "Screen Recording 1",
     "captions": [
       {
         "endOffset": 3000,
+        "hypothesisParts": [
+          {
+            "offset": 1000,
+            "text": [
+              "transcript"
+            ]
+          },
+          {
+            "offset": 2000,
+            "text": [
+              "text"
+            ]
+          }
+        ],
         "startOffset": 1000,
-        "text": "transcript text",
-        "wordAlignment": [1000, 2000]
+        "text": "transcript text"
       },
       {
         "endOffset": 5000,
+        "hypothesisParts": [
+          {
+            "offset": 3200,
+            "text": [
+              "transcript"
+            ]
+          },
+          {
+            "offset": 4200,
+            "text": [
+              "text"
+            ]
+          },
+          {
+            "offset": 4500,
+            "text": [
+              "2"
+            ]
+          }
+        ],
         "startOffset": 3000,
-        "text": "transcript text 2",
-        "wordAlignment":[3200, 4200, 4500]
+        "text": "transcript text 2"
       }
     ],
-    "tableOfContent":[
+    "name": "Screen Recording 1",
+    "tableOfContent": [
       {
         "endOffset": 5000,
         "startOffset": 3000,
@@ -150,22 +226,31 @@
 
   metadata.SetName("Screen Recording 1");
 
+  std::vector<media::HypothesisParts> first_transcript;
+  first_transcript.emplace_back(std::vector<std::string>({"transcript"}),
+                                base::TimeDelta::FromMilliseconds(1000));
+  first_transcript.emplace_back(std::vector<std::string>({"text"}),
+                                base::TimeDelta::FromMilliseconds(2000));
+
   metadata.AddTranscript(std::make_unique<ProjectorTranscript>(
       /*start_time=*/base::TimeDelta::FromMilliseconds(1000),
       /*end_time=*/base::TimeDelta::FromMilliseconds(3000), "transcript text",
-      std::initializer_list<base::TimeDelta>(
-          {base::TimeDelta::FromMilliseconds(1000),
-           base::TimeDelta::FromMilliseconds(2000)})));
+      std::move(first_transcript)));
 
   metadata.MarkKeyIdea();
 
+  std::vector<media::HypothesisParts> second_transcript;
+  second_transcript.emplace_back(std::vector<std::string>({"transcript"}),
+                                 base::TimeDelta::FromMilliseconds(3200));
+  second_transcript.emplace_back(std::vector<std::string>({"text"}),
+                                 base::TimeDelta::FromMilliseconds(4200));
+  second_transcript.emplace_back(std::vector<std::string>({"2"}),
+                                 base::TimeDelta::FromMilliseconds(4500));
+
   metadata.AddTranscript(std::make_unique<ProjectorTranscript>(
       /*start_time=*/base::TimeDelta::FromMilliseconds(3000),
       /*end_time=*/base::TimeDelta::FromMilliseconds(5000), "transcript text 2",
-      std::initializer_list<base::TimeDelta>(
-          {base::TimeDelta::FromMilliseconds(3200),
-           base::TimeDelta::FromMilliseconds(4200),
-           base::TimeDelta::FromMilliseconds(4500)})));
+      std::move(second_transcript)));
 
   AssertSerializedString(kExpectedMetaData, metadata.Serialize());
 }
diff --git a/ash/projector/test/mock_projector_metadata_controller.h b/ash/projector/test/mock_projector_metadata_controller.h
index dcc398b..df2f491 100644
--- a/ash/projector/test/mock_projector_metadata_controller.h
+++ b/ash/projector/test/mock_projector_metadata_controller.h
@@ -26,11 +26,8 @@
 
   // ProjectorMetadataController:
   MOCK_METHOD0(OnRecordingStarted, void());
-  MOCK_METHOD4(RecordTranscription,
-               void(const std::string& transcription,
-                    const base::TimeDelta start_time,
-                    const base::TimeDelta end_time,
-                    const std::vector<base::TimeDelta>& word_alignments));
+  MOCK_METHOD1(RecordTranscription,
+               void(const media::SpeechRecognitionResult& speech_result));
   MOCK_METHOD0(RecordKeyIdea, void());
   MOCK_METHOD1(SaveMetadata, void(const base::FilePath& video_file_path));
 };
diff --git a/ash/public/cpp/projector/projector_controller.h b/ash/public/cpp/projector/projector_controller.h
index df31259..e2152d4 100644
--- a/ash/public/cpp/projector/projector_controller.h
+++ b/ash/public/cpp/projector/projector_controller.h
@@ -5,10 +5,9 @@
 #ifndef ASH_PUBLIC_CPP_PROJECTOR_PROJECTOR_CONTROLLER_H_
 #define ASH_PUBLIC_CPP_PROJECTOR_PROJECTOR_CONTROLLER_H_
 
-#include <vector>
-
 #include "ash/public/cpp/ash_public_export.h"
 #include "base/time/time.h"
+#include "media/mojo/mojom/speech_recognition_service.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
@@ -34,11 +33,7 @@
 
   // Called when transcription result from mic input is ready.
   virtual void OnTranscription(
-      const std::u16string& text,
-      absl::optional<base::TimeDelta> start_time,
-      absl::optional<base::TimeDelta> end_time,
-      const absl::optional<std::vector<base::TimeDelta>>& word_offsets,
-      bool is_final) = 0;
+      const media::SpeechRecognitionResult& result) = 0;
 
   // Sets projector toolbar visibility.
   virtual void SetProjectorToolsVisible(bool is_visible) = 0;
diff --git a/ash/public/cpp/test/test_image_downloader.cc b/ash/public/cpp/test/test_image_downloader.cc
index 4a881b4..2bc4401 100644
--- a/ash/public/cpp/test/test_image_downloader.cc
+++ b/ash/public/cpp/test/test_image_downloader.cc
@@ -24,8 +24,10 @@
   // Pretend to respond asynchronously.
   base::SequencedTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
-      base::BindOnce(std::move(callback), gfx::test::CreateImageSkia(
-                                              /*width=*/10, /*height=*/20)));
+      base::BindOnce(std::move(callback),
+                     should_fail_ ? gfx::ImageSkia()
+                                  : gfx::test::CreateImageSkia(
+                                        /*width=*/10, /*height=*/20)));
 }
 
 }  // namespace ash
diff --git a/ash/public/cpp/test/test_image_downloader.h b/ash/public/cpp/test/test_image_downloader.h
index 4d4a676..168993c 100644
--- a/ash/public/cpp/test/test_image_downloader.h
+++ b/ash/public/cpp/test/test_image_downloader.h
@@ -15,10 +15,15 @@
   TestImageDownloader();
   ~TestImageDownloader() override;
 
+  void set_should_fail(bool should_fail) { should_fail_ = should_fail; }
+
   // ImageDownloader:
   void Download(const GURL& url,
                 const net::NetworkTrafficAnnotationTag& annotation_tag,
                 DownloadCallback callback) override;
+
+ private:
+  bool should_fail_ = false;
 };
 
 }  // namespace ash
diff --git a/ash/public/cpp/wallpaper_controller_client.h b/ash/public/cpp/wallpaper_controller_client.h
index bff60b6..9ee0aa7 100644
--- a/ash/public/cpp/wallpaper_controller_client.h
+++ b/ash/public/cpp/wallpaper_controller_client.h
@@ -5,7 +5,10 @@
 #ifndef ASH_PUBLIC_CPP_WALLPAPER_CONTROLLER_CLIENT_H_
 #define ASH_PUBLIC_CPP_WALLPAPER_CONTROLLER_CLIENT_H_
 
+#include <string>
+
 #include "ash/public/cpp/ash_public_export.h"
+#include "base/callback.h"
 
 class AccountId;
 
@@ -28,6 +31,14 @@
   // Retrieves the current collection id from the Wallpaper Picker Chrome App
   // for migration.
   virtual void MigrateCollectionIdFromChromeApp() = 0;
+
+  // Downloads and sets a new random wallpaper from the collection of the
+  // specified collection_id.
+  using DailyWallpaperUrlFetchedCallback =
+      base::OnceCallback<void(const std::string&)>;
+  virtual void FetchDailyRefreshWallpaper(
+      const std::string& collection_id,
+      DailyWallpaperUrlFetchedCallback callback) = 0;
 };
 
 }  // namespace ash
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index e62d97c..74d6050 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -30,6 +30,7 @@
     "autoclick_scroll_right.icon",
     "autoclick_scroll_up.icon",
     "battery.icon",
+    "cancel.icon",
     "capture_mode.icon",
     "capture_mode_circle_stop.icon",
     "capture_mode_close.icon",
@@ -167,6 +168,7 @@
     "palette_tray_icon_magnify.icon",
     "palette_tray_icon_metalayer.icon",
     "palette_tray_icon_projector.icon",
+    "pause.icon",
     "phone_hub_battery_saver.icon",
     "phone_hub_battery_saver_outline.icon",
     "phone_hub_default_favicon.icon",
@@ -188,6 +190,7 @@
     "projector_selfie_cam_off.icon",
     "projector_selfie_cam_on.icon",
     "remove_circle_outline.icon",
+    "resume.icon",
     "select_to_speak_next_paragraph.icon",
     "select_to_speak_next_sentence.icon",
     "select_to_speak_pause.icon",
diff --git a/ash/resources/vector_icons/cancel.icon b/ash/resources/vector_icons/cancel.icon
new file mode 100644
index 0000000..4ac87eb
--- /dev/null
+++ b/ash/resources/vector_icons/cancel.icon
@@ -0,0 +1,19 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 16, 5.41f,
+LINE_TO, 14.59f, 4,
+LINE_TO, 10, 8.59f,
+LINE_TO, 5.41f, 4,
+LINE_TO, 4, 5.41f,
+LINE_TO, 8.59f, 10,
+LINE_TO, 4, 14.59f,
+LINE_TO, 5.41f, 16,
+LINE_TO, 10, 11.41f,
+LINE_TO, 14.59f, 16,
+LINE_TO, 16, 14.59f,
+LINE_TO, 11.41f, 10,
+LINE_TO, 16, 5.41f,
+CLOSE
diff --git a/ash/resources/vector_icons/pause.icon b/ash/resources/vector_icons/pause.icon
new file mode 100644
index 0000000..727d1bdf
--- /dev/null
+++ b/ash/resources/vector_icons/pause.icon
@@ -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.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 9, 5,
+H_LINE_TO, 6,
+V_LINE_TO, 15,
+H_LINE_TO, 9,
+V_LINE_TO, 5,
+CLOSE,
+NEW_PATH,
+MOVE_TO, 14, 5,
+H_LINE_TO, 11,
+V_LINE_TO, 15,
+H_LINE_TO, 14,
+V_LINE_TO, 5,
+CLOSE
diff --git a/ash/resources/vector_icons/resume.icon b/ash/resources/vector_icons/resume.icon
new file mode 100644
index 0000000..e21f4a9
--- /dev/null
+++ b/ash/resources/vector_icons/resume.icon
@@ -0,0 +1,10 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 6, 16,
+LINE_TO, 16, 10,
+LINE_TO, 6, 4,
+V_LINE_TO, 16,
+CLOSE
diff --git a/ash/system/holding_space/holding_space_view_delegate.cc b/ash/system/holding_space/holding_space_view_delegate.cc
index 1ea1014..b2c4d629 100644
--- a/ash/system/holding_space/holding_space_view_delegate.cc
+++ b/ash/system/holding_space/holding_space_view_delegate.cc
@@ -534,7 +534,6 @@
   UpdateSelectionUi();
 }
 
-// TODO(crbug.com/1184438): Handle i18n and add icons for in-progress commands.
 ui::SimpleMenuModel* HoldingSpaceViewDelegate::BuildMenuModel() {
   context_menu_model_ = std::make_unique<ui::SimpleMenuModel>(this);
 
@@ -575,18 +574,27 @@
   }
 
   if (is_pausable) {
-    context_menu_model_->AddItem(
-        static_cast<int>(HoldingSpaceCommandId::kPauseItem), u"[I18N] Pause");
+    context_menu_model_->AddItemWithIcon(
+        static_cast<int>(HoldingSpaceCommandId::kPauseItem),
+        l10n_util::GetStringUTF16(IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_PAUSE),
+        ui::ImageModel::FromVectorIcon(kPauseIcon, /*color_id=*/-1,
+                                       kHoldingSpaceIconSize));
   }
 
   if (is_resumable) {
-    context_menu_model_->AddItem(
-        static_cast<int>(HoldingSpaceCommandId::kResumeItem), u"[I18N] Resume");
+    context_menu_model_->AddItemWithIcon(
+        static_cast<int>(HoldingSpaceCommandId::kResumeItem),
+        l10n_util::GetStringUTF16(IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_RESUME),
+        ui::ImageModel::FromVectorIcon(kResumeIcon, /*color_id=*/-1,
+                                       kHoldingSpaceIconSize));
   }
 
   if (is_cancelable) {
-    context_menu_model_->AddItem(
-        static_cast<int>(HoldingSpaceCommandId::kCancelItem), u"[I18N] Cancel");
+    context_menu_model_->AddItemWithIcon(
+        static_cast<int>(HoldingSpaceCommandId::kCancelItem),
+        l10n_util::GetStringUTF16(IDS_ASH_HOLDING_SPACE_CONTEXT_MENU_CANCEL),
+        ui::ImageModel::FromVectorIcon(kCancelIcon, /*color_id=*/-1,
+                                       kHoldingSpaceIconSize));
   }
 
   // The "Pause"/"Resume"/"Cancel" commands are separated from other commands.
diff --git a/ash/system/palette/palette_tray.cc b/ash/system/palette/palette_tray.cc
index 17680aa..177ec0e 100644
--- a/ash/system/palette/palette_tray.cc
+++ b/ash/system/palette/palette_tray.cc
@@ -73,24 +73,6 @@
 // Spacing between buttons in the title view (dp).
 constexpr int kTitleViewChildSpacing = 16;
 
-// Returns true if the |palette_tray| is on an internal display or on every
-// display if requested from the command line.
-bool ShouldShowOnDisplay(PaletteTray* palette_tray) {
-  if (stylus_utils::IsPaletteEnabledOnEveryDisplay())
-    return true;
-
-  // |widget| is null when this function is called from PaletteTray constructor
-  // before it is added to a widget.
-  views::Widget* const widget = palette_tray->GetWidget();
-  if (!widget)
-    return false;
-
-  const display::Display& display =
-      display::Screen::GetScreen()->GetDisplayNearestWindow(
-          widget->GetNativeWindow());
-  return display.IsInternal();
-}
-
 class BatteryView : public views::View {
  public:
   BatteryView() {
@@ -292,6 +274,33 @@
           stylus_utils::IsPaletteEnabledOnEveryDisplay());
 }
 
+bool PaletteTray::ShouldShowOnDisplay() {
+  if (stylus_utils::IsPaletteEnabledOnEveryDisplay())
+    return true;
+
+  // |widget| is null when this function is called from PaletteTray constructor
+  // before it is added to a widget.
+  views::Widget* const widget = GetWidget();
+  if (!widget)
+    return false;
+
+  const display::Display& display =
+      display::Screen::GetScreen()->GetDisplayNearestWindow(
+          widget->GetNativeWindow());
+
+  if (!display.IsInternal())
+    return false;
+
+  for (const ui::TouchscreenDevice& device :
+       ui::DeviceDataManager::GetInstance()->GetTouchscreenDevices()) {
+    if (device.has_stylus && device.target_display_id == display.id()) {
+      return true;
+    }
+  }
+
+  return display_has_stylus_for_testing_;
+}
+
 void PaletteTray::OnStylusEvent(const ui::TouchEvent& event) {
   if (local_state_ && !HasSeenStylus())
     local_state_->SetBoolean(prefs::kHasSeenStylus, true);
@@ -686,10 +695,15 @@
   return local_state_ && local_state_->GetBoolean(prefs::kHasSeenStylus);
 }
 
+void PaletteTray::SetDisplayHasStylusForTesting() {
+  display_has_stylus_for_testing_ = true;
+  UpdateIconVisibility();
+}
+
 void PaletteTray::UpdateIconVisibility() {
   bool visible_preferred =
       is_palette_enabled_ && stylus_utils::HasStylusInput() &&
-      ShouldShowOnDisplay(this) && palette_utils::IsInUserSession();
+      ShouldShowOnDisplay() && palette_utils::IsInUserSession();
   SetVisiblePreferred(visible_preferred);
   if (visible_preferred)
     UpdateLayout();
diff --git a/ash/system/palette/palette_tray.h b/ash/system/palette/palette_tray.h
index 48dc0c3..da07ce2b 100644
--- a/ash/system/palette/palette_tray.h
+++ b/ash/system/palette/palette_tray.h
@@ -39,7 +39,7 @@
 // The PaletteTray shows the palette in the bottom area of the screen. This
 // class also controls the lifetime for all of the tools available in the
 // palette. PaletteTray has one instance per-display. It is only made visible if
-// the display is primary and if the device has stylus hardware.
+// the display is primary and the display has stylus hardware.
 class ASH_EXPORT PaletteTray : public TrayBackgroundView,
                                public SessionObserver,
                                public ShellObserver,
@@ -56,8 +56,8 @@
   // for determining if an event should be propagated through to the palette.
   bool ContainsPointInScreen(const gfx::Point& point);
 
-  // Returns true if the palette should be visible in the UI. This happens when:
-  // there is a stylus input, there is an internal display, and the user has not
+  // Returns true if the palette should be visible in the UI. This happens when
+  // there is a stylus input on an internal display and the user has not
   // disabled it in settings. This can be overridden by passing switches.
   bool ShouldShowPalette() const;
 
@@ -110,6 +110,10 @@
   void OnActiveToolChanged() override;
   aura::Window* GetWindow() override;
 
+  // Returns true if we're on an internal display with a stylus
+  // or on every display if requested from the command line.
+  bool ShouldShowOnDisplay();
+
   // Initializes with Shell's local state and starts to observe it.
   void InitializeWithLocalState();
 
@@ -132,6 +136,10 @@
   // previously, or if the device has an internal stylus.
   bool HasSeenStylus();
 
+  // Have the palette act as though it is on a display with a stylus for
+  // testing purposes.
+  void SetDisplayHasStylusForTesting();
+
   std::unique_ptr<PaletteToolManager> palette_tool_manager_;
   std::unique_ptr<PaletteWelcomeBubble> welcome_bubble_;
   std::unique_ptr<TrayBubbleWrapper> bubble_;
@@ -150,6 +158,9 @@
   // Cached palette pref value.
   bool is_palette_enabled_ = true;
 
+  // Whether the palette should behave as though its display has a stylus.
+  bool display_has_stylus_for_testing_ = false;
+
   // Used to indicate whether the palette bubble is automatically opened by a
   // stylus eject event.
   bool is_bubble_auto_opened_ = false;
diff --git a/ash/system/palette/palette_tray_test_api.h b/ash/system/palette/palette_tray_test_api.h
index 95debc4..4eff4e2 100644
--- a/ash/system/palette/palette_tray_test_api.h
+++ b/ash/system/palette/palette_tray_test_api.h
@@ -36,6 +36,9 @@
     palette_tray_->OnStylusStateChanged(state);
   }
 
+  // Have the tray act as though it is on a display with a stylus
+  void SetDisplayHasStylus() { palette_tray_->SetDisplayHasStylusForTesting(); }
+
  private:
   PaletteTray* palette_tray_ = nullptr;
 
diff --git a/ash/system/palette/palette_tray_unittest.cc b/ash/system/palette/palette_tray_unittest.cc
index 19276e93..e67d634 100644
--- a/ash/system/palette/palette_tray_unittest.cc
+++ b/ash/system/palette/palette_tray_unittest.cc
@@ -80,6 +80,9 @@
     palette_tray_ =
         StatusAreaWidgetTestHelper::GetStatusAreaWidget()->palette_tray();
     test_api_ = std::make_unique<PaletteTrayTestApi>(palette_tray_);
+
+    display::test::DisplayManagerTestApi(display_manager())
+        .SetFirstDisplayAsInternalDisplay();
   }
 
   PrefService* prefs() {
@@ -591,6 +594,12 @@
   }
   ~PaletteTrayTestWithInternalStylus() override = default;
 
+  // PaletteTrayTest:
+  void SetUp() override {
+    PaletteTrayTest::SetUp();
+    test_api_->SetDisplayHasStylus();
+  }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(PaletteTrayTestWithInternalStylus);
 };
@@ -811,6 +820,8 @@
   PaletteTray* external_tray =
       controllers[1]->GetStatusAreaWidget()->palette_tray();
 
+  test_api_->SetDisplayHasStylus();
+
   // The palette tray on the external monitor is not visible.
   EXPECT_TRUE(main_tray->GetVisible());
   EXPECT_FALSE(external_tray->GetVisible());
diff --git a/ash/wallpaper/test_wallpaper_controller_client.cc b/ash/wallpaper/test_wallpaper_controller_client.cc
index adcca05..d592485 100644
--- a/ash/wallpaper/test_wallpaper_controller_client.cc
+++ b/ash/wallpaper/test_wallpaper_controller_client.cc
@@ -6,11 +6,16 @@
 
 namespace ash {
 
+TestWallpaperControllerClient::TestWallpaperControllerClient() = default;
+TestWallpaperControllerClient::~TestWallpaperControllerClient() = default;
+
 void TestWallpaperControllerClient::ResetCounts() {
   open_count_ = 0;
   close_preview_count_ = 0;
   set_default_wallpaper_count_ = 0;
   migrate_collection_id_from_chrome_app_count_ = 0;
+  fetch_daily_refresh_wallpaper_param_ = std::string();
+  fetch_daily_refresh_info_fails_ = false;
 }
 
 // WallpaperControllerClient:
@@ -32,4 +37,12 @@
   migrate_collection_id_from_chrome_app_count_++;
 }
 
+void TestWallpaperControllerClient::FetchDailyRefreshWallpaper(
+    const std::string& collection_id,
+    DailyWallpaperUrlFetchedCallback callback) {
+  fetch_daily_refresh_wallpaper_param_ = collection_id;
+  std::move(callback).Run(fetch_daily_refresh_info_fails_ ? std::string()
+                                                          : "fun_image_url");
+}
+
 }  // namespace ash
diff --git a/ash/wallpaper/test_wallpaper_controller_client.h b/ash/wallpaper/test_wallpaper_controller_client.h
index c4a5c20..6ea6452 100644
--- a/ash/wallpaper/test_wallpaper_controller_client.h
+++ b/ash/wallpaper/test_wallpaper_controller_client.h
@@ -14,13 +14,11 @@
 // A test wallpaper controller client class.
 class TestWallpaperControllerClient : public WallpaperControllerClient {
  public:
-  TestWallpaperControllerClient() = default;
-
+  TestWallpaperControllerClient();
   TestWallpaperControllerClient(const TestWallpaperControllerClient&) = delete;
   TestWallpaperControllerClient& operator=(
       const TestWallpaperControllerClient&) = delete;
-
-  virtual ~TestWallpaperControllerClient() = default;
+  virtual ~TestWallpaperControllerClient();
 
   size_t open_count() const { return open_count_; }
   size_t close_preview_count() const { return close_preview_count_; }
@@ -30,6 +28,12 @@
   size_t migrate_collection_id_from_chrome_app_count() const {
     return migrate_collection_id_from_chrome_app_count_;
   }
+  std::string get_fetch_daily_refresh_wallpaper_param() const {
+    return fetch_daily_refresh_wallpaper_param_;
+  }
+  void set_fetch_daily_refresh_info_fails(bool fails) {
+    fetch_daily_refresh_info_fails_ = fails;
+  }
 
   void ResetCounts();
 
@@ -39,12 +43,17 @@
   void SetDefaultWallpaper(const AccountId& account_id,
                            bool show_wallpaper) override;
   void MigrateCollectionIdFromChromeApp() override;
+  void FetchDailyRefreshWallpaper(
+      const std::string& collection_id,
+      DailyWallpaperUrlFetchedCallback callback) override;
 
  private:
   size_t open_count_ = 0;
   size_t close_preview_count_ = 0;
   size_t set_default_wallpaper_count_ = 0;
   size_t migrate_collection_id_from_chrome_app_count_ = 0;
+  std::string fetch_daily_refresh_wallpaper_param_;
+  bool fetch_daily_refresh_info_fails_ = false;
 };
 
 }  // namespace ash
diff --git a/ash/wallpaper/wallpaper_controller_impl.cc b/ash/wallpaper/wallpaper_controller_impl.cc
index 82ed7e1..79242be 100644
--- a/ash/wallpaper/wallpaper_controller_impl.cc
+++ b/ash/wallpaper/wallpaper_controller_impl.cc
@@ -47,6 +47,7 @@
 #include "base/no_destructor.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/path_service.h"
+#include "base/rand_util.h"
 #include "base/sequenced_task_runner.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/task/post_task.h"
@@ -61,6 +62,7 @@
 #include "components/user_manager/user_type.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/data_decoder/public/cpp/decode_image.h"
+#include "third_party/icu/source/i18n/unicode/gregocal.h"
 #include "ui/compositor/layer.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/manager/managed_display_info.h"
@@ -571,6 +573,18 @@
                           prefs::kSyncableWallpaperInfo);
 }
 
+// Returns a suffix to be appended to the base url of Backdrop wallpapers.
+std::string GetBackdropWallpaperSuffix() {
+  // FIFE url is used for Backdrop wallpapers and the desired image size should
+  // be specified. Currently we are using two times the display size. This is
+  // determined by trial and error and is subject to change.
+  // See crbug/815310 for original discussion.
+  gfx::Size display_size =
+      display::Screen::GetScreen()->GetPrimaryDisplay().size();
+  return "=w" + std::to_string(
+                    2 * std::max(display_size.width(), display_size.height()));
+}
+
 }  // namespace
 
 const char WallpaperControllerImpl::kSmallWallpaperSubDir[] = "small";
@@ -996,12 +1010,11 @@
                        weak_factory_.GetWeakPtr(), account_id,
                        wallpaper_files_id, file_name, CUSTOMIZED, layout,
                        /*show_wallpaper=*/false, image);
-    reload_preview_wallpaper_callback_ =
-        base::BindRepeating(&WallpaperControllerImpl::ShowWallpaperImage,
-                            weak_factory_.GetWeakPtr(), image,
-                            WallpaperInfo{std::string(), layout, CUSTOMIZED,
-                                          base::Time::Now().LocalMidnight()},
-                            /*preview_mode=*/true, /*always_on_top=*/false);
+    reload_preview_wallpaper_callback_ = base::BindRepeating(
+        &WallpaperControllerImpl::ShowWallpaperImage,
+        weak_factory_.GetWeakPtr(), image,
+        WallpaperInfo{std::string(), layout, CUSTOMIZED, base::Time::Now()},
+        /*preview_mode=*/true, /*always_on_top=*/false);
     // Show the preview wallpaper.
     reload_preview_wallpaper_callback_.Run();
   } else {
@@ -1018,11 +1031,12 @@
     bool preview_mode,
     SetOnlineWallpaperCallback callback) {
   DCHECK(callback);
+  WallpaperInfo info = {url.spec(), layout, ONLINE, base::Time::Now()};
   SetOnlineWallpaperIfExists(
       account_id, url.spec(), collection_id, layout, preview_mode,
       base::BindOnce(&WallpaperControllerImpl::OnAttemptSetOnlineWallpaper,
-                     weak_factory_.GetWeakPtr(), account_id, url, layout,
-                     preview_mode, std::move(callback)));
+                     weak_factory_.GetWeakPtr(), account_id, info, preview_mode,
+                     std::move(callback)));
 }
 
 void WallpaperControllerImpl::SetOnlineWallpaperIfExists(
@@ -1310,9 +1324,9 @@
 
 void WallpaperControllerImpl::ShowOneShotWallpaper(
     const gfx::ImageSkia& image) {
-  const WallpaperInfo info = {
-      std::string(), WallpaperLayout::WALLPAPER_LAYOUT_STRETCH,
-      WallpaperType::ONE_SHOT, base::Time::Now().LocalMidnight()};
+  const WallpaperInfo info = {std::string(),
+                              WallpaperLayout::WALLPAPER_LAYOUT_STRETCH,
+                              WallpaperType::ONE_SHOT, base::Time::Now()};
   ShowWallpaperImage(image, info, /*preview_mode=*/false,
                      /*always_on_top=*/false);
 }
@@ -1320,9 +1334,9 @@
 void WallpaperControllerImpl::ShowAlwaysOnTopWallpaper(
     const base::FilePath& image_path) {
   is_always_on_top_wallpaper_ = true;
-  const WallpaperInfo info = {
-      std::string(), WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
-      WallpaperType::ONE_SHOT, base::Time::Now().LocalMidnight()};
+  const WallpaperInfo info = {std::string(),
+                              WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
+                              WallpaperType::ONE_SHOT, base::Time::Now()};
   ReparentWallpaper(GetWallpaperContainerId(locked_));
   ReadAndDecodeWallpaper(
       base::BindOnce(&WallpaperControllerImpl::OnAlwaysOnTopWallpaperDecoded,
@@ -1592,6 +1606,9 @@
     wallpaper_controller_client_->MigrateCollectionIdFromChromeApp();
 
   OnPrefChanged();
+
+  if (IsDailyRefreshEnabled())
+    StartDailyRefreshTimer();
 }
 
 void WallpaperControllerImpl::ShowDefaultWallpaperForTesting() {
@@ -1613,6 +1630,15 @@
   pref_change_registrar_.reset();
 }
 
+void WallpaperControllerImpl::UpdateDailyRefreshWallpaperForTesting() {
+  UpdateDailyRefreshWallpaper();
+}
+
+util::WallClockTimer&
+WallpaperControllerImpl::GetDailyRefreshTimerForTesting() {
+  return daily_refresh_timer_;
+}
+
 void WallpaperControllerImpl::UpdateWallpaperForRootWindow(
     aura::Window* root_window,
     bool lock_state_changed,
@@ -1822,7 +1848,7 @@
 bool WallpaperControllerImpl::InitializeUserWallpaperInfo(
     const AccountId& account_id) {
   const WallpaperInfo info = {std::string(), WALLPAPER_LAYOUT_CENTER_CROPPED,
-                              DEFAULT, base::Time::Now().LocalMidnight()};
+                              DEFAULT, base::Time::Now()};
   return SetUserWallpaperInfo(account_id, info);
 }
 
@@ -1870,12 +1896,11 @@
     confirm_preview_wallpaper_callback_ = base::BindOnce(
         &WallpaperControllerImpl::SetOnlineWallpaperImpl,
         weak_factory_.GetWeakPtr(), params, image, /*show_wallpaper=*/false);
-    reload_preview_wallpaper_callback_ =
-        base::BindRepeating(&WallpaperControllerImpl::ShowWallpaperImage,
-                            weak_factory_.GetWeakPtr(), image,
-                            WallpaperInfo{params.url, params.layout, ONLINE,
-                                          base::Time::Now().LocalMidnight()},
-                            /*preview_mode=*/true, /*always_on_top=*/false);
+    reload_preview_wallpaper_callback_ = base::BindRepeating(
+        &WallpaperControllerImpl::ShowWallpaperImage,
+        weak_factory_.GetWeakPtr(), image,
+        WallpaperInfo{params.url, params.layout, ONLINE, base::Time::Now()},
+        /*preview_mode=*/true, /*always_on_top=*/false);
     // Show the preview wallpaper.
     reload_preview_wallpaper_callback_.Run();
   } else {
@@ -1888,7 +1913,7 @@
     const gfx::ImageSkia& image,
     bool show_wallpaper) {
   WallpaperInfo wallpaper_info = {params.url, params.layout, ONLINE,
-                                  base::Time::Now().LocalMidnight()};
+                                  base::Time::Now()};
   if (!SetUserWallpaperInfo(params.account_id, wallpaper_info)) {
     LOG(ERROR) << "Setting user wallpaper info fails. This should never happen "
                   "except in tests.";
@@ -1975,7 +2000,7 @@
 
   if (show_wallpaper) {
     WallpaperInfo info(cached_default_wallpaper_.file_path.value(), layout,
-                       DEFAULT, base::Time::Now().LocalMidnight());
+                       DEFAULT, base::Time::Now());
     ShowWallpaperImage(cached_default_wallpaper_.image, info,
                        /*preview_mode=*/false, /*always_on_top=*/false);
   }
@@ -2001,8 +2026,7 @@
       base::FilePath(wallpaper_files_id).Append(file_name).value();
   // User's custom wallpaper path is determined by relative path and the
   // appropriate wallpaper resolution.
-  WallpaperInfo info = {relative_path, layout, type,
-                        base::Time::Now().LocalMidnight()};
+  WallpaperInfo info = {relative_path, layout, type, base::Time::Now()};
   if (!SetUserWallpaperInfo(account_id, info)) {
     LOG(ERROR) << "Setting user wallpaper info fails. This should never happen "
                   "except in tests.";
@@ -2235,7 +2259,7 @@
   } else {
     WallpaperInfo info(device_policy_wallpaper_path_.value(),
                        WALLPAPER_LAYOUT_CENTER_CROPPED, DEVICE,
-                       base::Time::Now().LocalMidnight());
+                       base::Time::Now());
     ShowWallpaperImage(image, info, /*preview_mode=*/false,
                        /*always_on_top=*/false);
   }
@@ -2327,22 +2351,20 @@
 
 void WallpaperControllerImpl::OnAttemptSetOnlineWallpaper(
     const AccountId& account_id,
-    const GURL& url,
-    WallpaperLayout layout,
+    WallpaperInfo info,
     bool preview_mode,
     SetOnlineWallpaperCallback callback,
     bool success) {
   if (success) {
-    // Run callback and exit if setting the online wallpaper succeeded.
     std::move(callback).Run(true);
     return;
   }
 
-  // Try again after downloading the image.
-  const OnlineWallpaperParams params = {account_id, url.spec(), layout,
-                                        /*preview_mode=*/false};
+  std::string url = info.location + GetBackdropWallpaperSuffix();
+  const OnlineWallpaperParams params = {account_id, info.location, info.layout,
+                                        preview_mode};
   ImageDownloader::Get()->Download(
-      url, NO_TRAFFIC_ANNOTATION_YET,
+      GURL(url), NO_TRAFFIC_ANNOTATION_YET,
       base::BindOnce(&WallpaperControllerImpl::OnOnlineWallpaperDecoded,
                      weak_factory_.GetWeakPtr(), params, /*save_file=*/true,
                      std::move(callback)));
@@ -2373,4 +2395,95 @@
   pref_service->SetString(kWallpaperCollectionId, collection_id);
 }
 
+bool WallpaperControllerImpl::IsDailyRefreshEnabled() const {
+  return features::IsWallpaperWebUIEnabled() && !GetCollectionId().empty();
+}
+
+std::string WallpaperControllerImpl::GetCollectionId() const {
+  PrefService* pref_service = GetUserPrefServiceSyncable(GetActiveAccountId());
+  CHECK(pref_service);
+  return pref_service->GetString(kWallpaperCollectionId);
+}
+
+void WallpaperControllerImpl::UpdateDailyRefreshWallpaper() {
+  if (!IsDailyRefreshEnabled()) {
+    daily_refresh_timer_.Stop();
+    return;
+  }
+
+  // |wallpaper_controller_cient_| has a slightly shorter lifecycle than
+  // wallpaper controller.
+  if (wallpaper_controller_client_) {
+    wallpaper_controller_client_->FetchDailyRefreshWallpaper(
+        GetCollectionId(),
+        base::BindOnce(&WallpaperControllerImpl::SetDailyWallpaper,
+                       weak_factory_.GetWeakPtr(), GetActiveAccountId(),
+                       ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
+                       /*preview_mode=*/false));
+  } else {
+    StartDailyRefreshTimer();
+  }
+}
+
+void WallpaperControllerImpl::SetDailyWallpaper(const AccountId& account_id,
+                                                WallpaperLayout layout,
+                                                bool preview_mode,
+                                                const std::string& image_url) {
+  if (!image_url.empty()) {
+    SetOnlineWallpaper(
+        account_id, GURL(image_url), /*collection_id=*/std::string(), layout,
+        preview_mode,
+        base::BindOnce(&WallpaperControllerImpl::OnSetDailyWallpaper,
+                       weak_factory_.GetWeakPtr()));
+  } else {
+    OnFetchDailyWallpaperFailed();
+  }
+}
+
+void WallpaperControllerImpl::OnSetDailyWallpaper(bool success) {
+  if (success) {
+    StartDailyRefreshTimer();
+  } else {
+    OnFetchDailyWallpaperFailed();
+  }
+}
+
+void WallpaperControllerImpl::StartDailyRefreshTimer() {
+  using base::Time;
+  using base::TimeDelta;
+
+  TimeDelta daily_refresh_delay = GetTimeToNextDailyRefreshUpdate();
+
+  // Add random delay within 1 hour, to prevent hot spotting, and reduce
+  // multiple wallpaper transitions for sync users with multiple devices
+  auto random_delay = TimeDelta::FromMillisecondsD(
+      base::RandDouble() * Time::kMillisecondsPerSecond *
+      Time::kSecondsPerHour);
+  daily_refresh_delay += random_delay;
+
+  StartDailyRefreshTimer(daily_refresh_delay);
+}
+
+void WallpaperControllerImpl::OnFetchDailyWallpaperFailed() {
+  StartDailyRefreshTimer(base::TimeDelta::FromHours(1));
+}
+
+void WallpaperControllerImpl::StartDailyRefreshTimer(base::TimeDelta delay) {
+  base::Time desired_run_time = base::Time::Now() + delay;
+  daily_refresh_timer_.Start(
+      FROM_HERE, desired_run_time,
+      base::BindOnce(&WallpaperControllerImpl::UpdateDailyRefreshWallpaper,
+                     weak_factory_.GetWeakPtr()));
+}
+
+base::TimeDelta WallpaperControllerImpl::GetTimeToNextDailyRefreshUpdate()
+    const {
+  WallpaperInfo info;
+  if (!GetUserWallpaperInfo(GetActiveAccountId(), &info))
+    return base::TimeDelta();
+  return info.date.ToDeltaSinceWindowsEpoch() -
+         base::Time::Now().ToDeltaSinceWindowsEpoch() +
+         base::TimeDelta::FromDays(1);
+}
+
 }  // namespace ash
diff --git a/ash/wallpaper/wallpaper_controller_impl.h b/ash/wallpaper/wallpaper_controller_impl.h
index 207e4ec..eefa609a 100644
--- a/ash/wallpaper/wallpaper_controller_impl.h
+++ b/ash/wallpaper/wallpaper_controller_impl.h
@@ -26,7 +26,9 @@
 #include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
 #include "base/scoped_observation.h"
+#include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "base/util/timer/wall_clock_timer.h"
 #include "components/account_id/account_id.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "ui/compositor/compositor_lock.h"
@@ -354,6 +356,10 @@
 
   void set_bypass_decode_for_testing() { bypass_decode_for_testing_ = true; }
 
+  // Exposed for testing.
+  void UpdateDailyRefreshWallpaperForTesting();
+  util::WallClockTimer& GetDailyRefreshTimerForTesting();
+
  private:
   FRIEND_TEST_ALL_PREFIXES(WallpaperControllerTest, BasicReparenting);
   FRIEND_TEST_ALL_PREFIXES(WallpaperControllerTest,
@@ -558,14 +564,49 @@
   void HandleWallpaperInfoSyncedIn(const AccountId& account_id,
                                    WallpaperInfo info);
   void OnAttemptSetOnlineWallpaper(const AccountId& account_id,
-                                   const GURL& url,
-                                   WallpaperLayout layout,
+                                   WallpaperInfo info,
                                    bool preview_mode,
                                    SetOnlineWallpaperCallback callback,
                                    bool success);
-
   constexpr bool IsWallpaperTypeSyncable(WallpaperType type);
 
+  // If daily refresh wallpapers is enabled by the user.
+  bool IsDailyRefreshEnabled() const;
+
+  // The id of the collection to query for new wallpapers when daily refresh is
+  // enabled. Is an empty string when it is not enabled.
+  std::string GetCollectionId() const;
+
+  // With daily refresh enabled, this updates the wallpaper by asking for a
+  // wallpaper from within the user specified collection.
+  void UpdateDailyRefreshWallpaper();
+
+  // Callback from the client providing a url to a wallpaper from the user
+  // specified collection when daily refresh is enabled. If |image_url| is
+  // empty, fetching the url failed, and should be tried again soon.
+  void SetDailyWallpaper(const AccountId& account_id,
+                         WallpaperLayout layout,
+                         bool preview_mode,
+                         const std::string& image_url);
+
+  // Called after attempting to download and set a daily refresh wallpaper.
+  // On failure retry again in a while.
+  void OnSetDailyWallpaper(bool success);
+
+  // Starts a wall clock timer, to update the wallpaper 24 hours since the last
+  // wallpaper was set.
+  void StartDailyRefreshTimer();
+
+  // Starts a wall clock timer to retry fetching a daily refresh wallpaper.
+  void OnFetchDailyWallpaperFailed();
+
+  // Starts a wall clock timer with the specified |delay|.
+  void StartDailyRefreshTimer(base::TimeDelta delay);
+
+  // Time to next wallpaper update for daily refresh; 24 hours since last
+  // wallpaper set.
+  base::TimeDelta GetTimeToNextDailyRefreshUpdate() const;
+
   bool locked_ = false;
 
   WallpaperMode wallpaper_mode_ = WALLPAPER_NONE;
@@ -672,6 +713,8 @@
   // May be null in tests.
   PrefService* local_state_ = nullptr;
 
+  util::WallClockTimer daily_refresh_timer_;
+
   base::WeakPtrFactory<WallpaperControllerImpl> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(WallpaperControllerImpl);
diff --git a/ash/wallpaper/wallpaper_controller_unittest.cc b/ash/wallpaper/wallpaper_controller_unittest.cc
index be0bf37..8bcfbddd 100644
--- a/ash/wallpaper/wallpaper_controller_unittest.cc
+++ b/ash/wallpaper/wallpaper_controller_unittest.cc
@@ -14,6 +14,7 @@
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/test/shell_test_api.h"
+#include "ash/public/cpp/test/test_image_downloader.h"
 #include "ash/public/cpp/wallpaper_controller_client.h"
 #include "ash/public/cpp/wallpaper_controller_observer.h"
 #include "ash/public/cpp/wallpaper_types.h"
@@ -44,6 +45,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time_override.h"
+#include "base/util/timer/wall_clock_timer.h"
 #include "components/prefs/scoped_user_pref_update.h"
 #include "components/user_manager/fake_user_manager.h"
 #include "components/user_manager/scoped_user_manager.h"
@@ -299,6 +301,13 @@
                        base::Time::Now().LocalMidnight());
 }
 
+base::Time DayBeforeYesterdayish() {
+  base::TimeDelta today_delta =
+      base::Time::Now().LocalMidnight().ToDeltaSinceWindowsEpoch();
+  base::TimeDelta yesterday_delta = today_delta - base::TimeDelta::FromDays(2);
+  return base::Time::FromDeltaSinceWindowsEpoch(yesterday_delta);
+}
+
 // A test implementation of the WallpaperControllerObserver interface.
 class TestWallpaperControllerObserver : public WallpaperControllerObserver {
  public:
@@ -358,6 +367,8 @@
     in_process_data_decoder_.service().SimulateImageDecoderCrashForTesting(
         true);
 
+    test_image_downloader_ = std::make_unique<TestImageDownloader>();
+
     controller_ = Shell::Get()->wallpaper_controller();
     controller_->set_wallpaper_reload_no_delay_for_test();
 
@@ -369,6 +380,16 @@
                       custom_wallpaper_dir_.GetPath(), policy_wallpaper);
   }
 
+  void TearDown() override {
+    // Although pref services outlive wallpaper controller in the os, in ash
+    // tests, they are destroyed in tear down (See |AshTestHelper|). We don't
+    // want this timer to run a task after tear down, since it relies on a pref
+    // service being around.
+    controller_->GetDailyRefreshTimerForTesting().Stop();
+
+    AshTestBase::TearDown();
+  }
+
   WallpaperView* wallpaper_view() {
     return Shell::Get()
         ->GetPrimaryRootWindowController()
@@ -604,6 +625,7 @@
 
   user_manager::FakeUserManager* fake_user_manager_ = nullptr;
   std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
+  std::unique_ptr<TestImageDownloader> test_image_downloader_;
 
   const AccountId kChildAccountId = AccountId::FromUserEmail(kChildEmail);
   const std::string kChildWallpaperFilesId = GetDummyFileId(kChildAccountId);
@@ -1125,6 +1147,7 @@
 
 TEST_F(WallpaperControllerTest, SetOnlineWallpaper) {
   SetBypassDecode();
+
   gfx::ImageSkia image = CreateImage(640, 480, kWallpaperColor);
   WallpaperLayout layout = WALLPAPER_LAYOUT_CENTER_CROPPED;
   SimulateUserLogin(kUser1);
@@ -3010,6 +3033,8 @@
   TestWallpaperControllerClient client;
   controller_->SetClient(&client);
 
+  SetBypassDecode();
+
   PutWallpaperInfoInPrefs(account_id_1, InfoWithType(CUSTOMIZED),
                           GetLocalPrefService(), prefs::kUserWallpaperInfo);
 
@@ -3197,4 +3222,159 @@
   EXPECT_EQ("", actual);
 }
 
+TEST_F(WallpaperControllerTest, UpdateDailyRefreshWallpaper) {
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
+
+  TestWallpaperControllerClient client;
+  controller_->SetClient(&client);
+
+  std::string expected{"fun_collection"};
+  SimulateUserLogin(kUser1);
+
+  controller_->SetDailyRefreshCollectionId(expected);
+  controller_->SetUserWallpaperInfo(
+      account_id_1, WallpaperInfo(std::string(), WALLPAPER_LAYOUT_CENTER, DAILY,
+                                  DayBeforeYesterdayish()));
+
+  controller_->UpdateDailyRefreshWallpaperForTesting();
+  EXPECT_EQ(expected, client.get_fetch_daily_refresh_wallpaper_param());
+}
+
+TEST_F(WallpaperControllerTest, UpdateDailyRefreshWallpaperCalledOnLogin) {
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
+
+  TestWallpaperControllerClient client;
+  controller_->SetClient(&client);
+
+  std::string expected{"fun_collection"};
+  SimulateUserLogin(kUser1);
+
+  controller_->SetDailyRefreshCollectionId(expected);
+  controller_->SetUserWallpaperInfo(
+      account_id_1, WallpaperInfo(std::string(), WALLPAPER_LAYOUT_CENTER, DAILY,
+                                  DayBeforeYesterdayish()));
+
+  ClearLogin();
+  SimulateUserLogin(kUser1);
+
+  // |daily_refresh_timer_| adds a task to the sequence, as opposed to execute
+  // within the task that it is called in.
+  RunAllTasksUntilIdle();
+
+  EXPECT_EQ(expected, client.get_fetch_daily_refresh_wallpaper_param());
+}
+
+TEST_F(WallpaperControllerTest, UpdateDailyRefreshWallpaper_NotEnabled) {
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
+
+  TestWallpaperControllerClient client;
+  controller_->SetClient(&client);
+
+  SimulateUserLogin(kUser1);
+  controller_->SetUserWallpaperInfo(
+      account_id_1, WallpaperInfo(std::string(), WALLPAPER_LAYOUT_CENTER, DAILY,
+                                  DayBeforeYesterdayish()));
+
+  controller_->UpdateDailyRefreshWallpaperForTesting();
+  EXPECT_EQ(std::string(), client.get_fetch_daily_refresh_wallpaper_param());
+}
+
+TEST_F(WallpaperControllerTest,
+       UpdateDailyRefreshWallpaper_TimerStartsOnPrefServiceChange) {
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
+
+  TestWallpaperControllerClient client;
+  controller_->SetClient(&client);
+
+  using base::Time;
+  using base::TimeDelta;
+
+  SimulateUserLogin(kUser1);
+  controller_->SetDailyRefreshCollectionId("fun_collection");
+  controller_->SetUserWallpaperInfo(
+      account_id_1, WallpaperInfo(std::string(), WALLPAPER_LAYOUT_CENTER, DAILY,
+                                  base::Time::Now().LocalMidnight()));
+
+  controller_->OnActiveUserPrefServiceChanged(
+      GetProfilePrefService(account_id_1));
+
+  Time run_time =
+      controller_->GetDailyRefreshTimerForTesting().desired_run_time();
+  TimeDelta delta = run_time.ToDeltaSinceWindowsEpoch();
+
+  TimeDelta update_time =
+      Time::Now().LocalMidnight().ToDeltaSinceWindowsEpoch() +
+      TimeDelta::FromDays(1);
+
+  ASSERT_GE(delta, update_time - TimeDelta::FromMinutes(1));
+  ASSERT_LE(delta,
+            update_time + TimeDelta::FromHours(1) + TimeDelta::FromMinutes(1));
+}
+
+TEST_F(WallpaperControllerTest,
+       UpdateDailyRefreshWallpaper_RetryTimerTriggersOnFailedFetchInfo) {
+  using base::Time;
+  using base::TimeDelta;
+
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
+
+  TestWallpaperControllerClient client;
+  controller_->SetClient(&client);
+  client.set_fetch_daily_refresh_info_fails(true);
+
+  SimulateUserLogin(kUser1);
+  controller_->SetDailyRefreshCollectionId("fun_collection");
+  controller_->SetUserWallpaperInfo(
+      account_id_1, WallpaperInfo(std::string(), WALLPAPER_LAYOUT_CENTER, DAILY,
+                                  DayBeforeYesterdayish()));
+
+  controller_->UpdateDailyRefreshWallpaperForTesting();
+  Time run_time =
+      controller_->GetDailyRefreshTimerForTesting().desired_run_time();
+  TimeDelta delay = run_time - Time::Now();
+
+  TimeDelta one_hour = TimeDelta::FromHours(1);
+  // Lave a little wiggle room.
+  ASSERT_GE(delay, one_hour - TimeDelta::FromMinutes(1));
+  ASSERT_LE(delay, one_hour + TimeDelta::FromMinutes(1));
+}
+
+TEST_F(WallpaperControllerTest,
+       UpdateDailyRefreshWallpaper_RetryTimerTriggersOnFailedFetchData) {
+  using base::Time;
+  using base::TimeDelta;
+
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
+
+  TestWallpaperControllerClient client;
+  controller_->SetClient(&client);
+
+  SimulateUserLogin(kUser1);
+  controller_->SetDailyRefreshCollectionId("fun_collection");
+  controller_->SetUserWallpaperInfo(
+      account_id_1, WallpaperInfo(std::string(), WALLPAPER_LAYOUT_CENTER, DAILY,
+                                  DayBeforeYesterdayish()));
+
+  test_image_downloader_->set_should_fail(true);
+
+  controller_->UpdateDailyRefreshWallpaperForTesting();
+
+  RunAllTasksUntilIdle();
+
+  Time run_time =
+      controller_->GetDailyRefreshTimerForTesting().desired_run_time();
+  TimeDelta delay = run_time - Time::Now();
+
+  TimeDelta one_hour = TimeDelta::FromHours(1);
+  // Lave a little wiggle room.
+  ASSERT_GE(delay, one_hour - TimeDelta::FromMinutes(1));
+  ASSERT_LE(delay, one_hour + TimeDelta::FromMinutes(1));
+}
+
 }  // namespace ash
diff --git a/build/android/gyp/compile_resources.py b/build/android/gyp/compile_resources.py
index 48409de..42b34f2 100755
--- a/build/android/gyp/compile_resources.py
+++ b/build/android/gyp/compile_resources.py
@@ -177,29 +177,10 @@
       '--no-xml-namespaces',
       action='store_true',
       help='Whether to strip xml namespaces from processed xml resources.')
-  input_opts.add_argument(
-      '--short-resource-paths',
-      action='store_true',
-      help='Whether to shorten resource paths inside the apk or module.')
-  input_opts.add_argument(
-      '--strip-resource-names',
-      action='store_true',
-      help='Whether to strip resource names from the resource table of the apk '
-      'or module.')
 
   output_opts.add_argument('--arsc-path', help='Apk output for arsc format.')
   output_opts.add_argument('--proto-path', help='Apk output for proto format.')
   group = input_opts.add_mutually_exclusive_group()
-  group.add_argument(
-      '--optimized-arsc-path',
-      help='Output for `aapt2 optimize` for arsc format (enables the step).')
-  group.add_argument(
-      '--optimized-proto-path',
-      help='Output for `aapt2 optimize` for proto format (enables the step).')
-  input_opts.add_argument(
-      '--resources-config-paths',
-      default='[]',
-      help='GN list of paths to aapt2 resources config files.')
 
   output_opts.add_argument(
       '--info-path', help='Path to output info file for the partial apk.')
@@ -222,11 +203,6 @@
   output_opts.add_argument(
       '--emit-ids-out', help='Path to file produced by aapt2 --emit-ids.')
 
-  output_opts.add_argument(
-      '--resources-path-map-out-path',
-      help='Path to file produced by aapt2 that maps original resource paths '
-      'to shortened resource paths inside the apk or module.')
-
   input_opts.add_argument(
       '--is-bundle-module',
       action='store_true',
@@ -257,20 +233,10 @@
       options.values_filter_rules)
   options.extra_main_r_text_files = build_utils.ParseGnList(
       options.extra_main_r_text_files)
-  options.resources_config_paths = build_utils.ParseGnList(
-      options.resources_config_paths)
-
-  if options.optimized_proto_path and not options.proto_path:
-    # We could write to a temp file, but it's simpler to require it.
-    parser.error('--optimized-proto-path requires --proto-path')
 
   if not options.arsc_path and not options.proto_path:
     parser.error('One of --arsc-path or --proto-path is required.')
 
-  if options.resources_path_map_out_path and not options.short_resource_paths:
-    parser.error(
-        '--resources-path-map-out-path requires --short-resource-paths')
-
   if options.package_id and options.shared_resources:
     parser.error('--package-id and --shared-resources are mutually exclusive')
 
@@ -846,9 +812,6 @@
 
   # We always create a binary arsc file first, then convert to proto, so flags
   # such as --shared-lib can be supported.
-  arsc_path = build.arsc_path
-  if arsc_path is None:
-    _, arsc_path = tempfile.mkstmp()
   link_command += ['-o', build.arsc_path]
 
   logging.debug('Starting: aapt2 link')
@@ -896,100 +859,9 @@
         build.arsc_path, build.proto_path
     ])
 
-  if build.arsc_path is None:
-    os.remove(arsc_path)
-
-  if options.optimized_proto_path:
-    _OptimizeApk(build.optimized_proto_path, options, build.temp_dir,
-                 build.proto_path, build.r_txt_path)
-  elif options.optimized_arsc_path:
-    _OptimizeApk(build.optimized_arsc_path, options, build.temp_dir,
-                 build.arsc_path, build.r_txt_path)
-
   return desired_manifest_package_name
 
 
-def _CombineResourceConfigs(resources_config_paths, out_config_path):
-  with open(out_config_path, 'w') as out_config:
-    for config_path in resources_config_paths:
-      with open(config_path) as config:
-        out_config.write(config.read())
-        out_config.write('\n')
-
-
-def _OptimizeApk(output, options, temp_dir, unoptimized_path, r_txt_path):
-  """Optimize intermediate .ap_ file with aapt2.
-
-  Args:
-    output: Path to write to.
-    options: The command-line options.
-    temp_dir: A temporary directory.
-    unoptimized_path: path of the apk to optimize.
-    r_txt_path: path to the R.txt file of the unoptimized apk.
-  """
-  optimize_command = [
-      options.aapt2_path,
-      'optimize',
-      unoptimized_path,
-      '-o',
-      output,
-  ]
-
-  # Optimize the resources.arsc file by obfuscating resource names and only
-  # allow usage via R.java constant.
-  if options.strip_resource_names:
-    no_collapse_resources = _ExtractNonCollapsableResources(r_txt_path)
-    gen_config_path = os.path.join(temp_dir, 'aapt2.config')
-    if options.resources_config_paths:
-      _CombineResourceConfigs(options.resources_config_paths, gen_config_path)
-    with open(gen_config_path, 'a') as config:
-      for resource in no_collapse_resources:
-        config.write('{}#no_collapse\n'.format(resource))
-
-    optimize_command += [
-        '--collapse-resource-names',
-        '--resources-config-path',
-        gen_config_path,
-    ]
-
-  if options.short_resource_paths:
-    optimize_command += ['--shorten-resource-paths']
-  if options.resources_path_map_out_path:
-    optimize_command += [
-        '--resource-path-shortening-map', options.resources_path_map_out_path
-    ]
-
-  logging.debug('Running aapt2 optimize')
-  build_utils.CheckOutput(
-      optimize_command, print_stdout=False, print_stderr=False)
-
-
-def _ExtractNonCollapsableResources(rtxt_path):
-  """Extract resources that should not be collapsed from the R.txt file
-
-  Resources of type ID are references to UI elements/views. They are used by
-  UI automation testing frameworks. They are kept in so that they don't break
-  tests, even though they may not actually be used during runtime. See
-  https://crbug.com/900993
-  App icons (aka mipmaps) are sometimes referenced by other apps by name so must
-  be keps as well. See https://b/161564466
-
-  Args:
-    rtxt_path: Path to R.txt file with all the resources
-  Returns:
-    List of resources in the form of <resource_type>/<resource_name>
-  """
-  resources = []
-  _NO_COLLAPSE_TYPES = ['id', 'mipmap']
-  with open(rtxt_path) as rtxt:
-    for line in rtxt:
-      for resource_type in _NO_COLLAPSE_TYPES:
-        if ' {} '.format(resource_type) in line:
-          resource_name = line.split()[2]
-          resources.append('{}/{}'.format(resource_type, resource_name))
-  return resources
-
-
 @contextlib.contextmanager
 def _CreateStableIdsFile(in_path, out_path, package_name):
   """Transforms a file generated by --emit-ids from another package.
@@ -1018,8 +890,6 @@
       (options.r_text_out, build.r_txt_path),
       (options.arsc_path, build.arsc_path),
       (options.proto_path, build.proto_path),
-      (options.optimized_arsc_path, build.optimized_arsc_path),
-      (options.optimized_proto_path, build.optimized_proto_path),
       (options.proguard_file, build.proguard_path),
       (options.proguard_file_main_dex, build.proguard_main_dex_path),
       (options.emit_ids_out, build.emit_ids_path),
diff --git a/build/android/gyp/optimize_resources.py b/build/android/gyp/optimize_resources.py
new file mode 100755
index 0000000..d3b11636
--- /dev/null
+++ b/build/android/gyp/optimize_resources.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python3
+#
+# 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 argparse
+import logging
+import os
+import sys
+
+from util import build_utils
+
+
+def _ParseArgs(args):
+  """Parses command line options.
+
+  Returns:
+    An options object as from argparse.ArgumentParser.parse_args()
+  """
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--aapt2-path',
+                      required=True,
+                      help='Path to the Android aapt2 tool.')
+  parser.add_argument(
+      '--short-resource-paths',
+      action='store_true',
+      help='Whether to shorten resource paths inside the apk or module.')
+  parser.add_argument(
+      '--strip-resource-names',
+      action='store_true',
+      help='Whether to strip resource names from the resource table of the apk '
+      'or module.')
+  parser.add_argument('--proto-path',
+                      required=True,
+                      help='Input proto format resources APK.')
+  parser.add_argument('--resources-config-paths',
+                      default='[]',
+                      help='GN list of paths to aapt2 resources config files.')
+  parser.add_argument('--r-text-in',
+                      required=True,
+                      help='Path to R.txt. Used to exclude id/ resources.')
+  parser.add_argument(
+      '--resources-path-map-out-path',
+      help='Path to file produced by aapt2 that maps original resource paths '
+      'to shortened resource paths inside the apk or module.')
+  parser.add_argument('--optimized-proto-path',
+                      required=True,
+                      help='Output for `aapt2 optimize`.')
+  options = parser.parse_args(args)
+
+  options.resources_config_paths = build_utils.ParseGnList(
+      options.resources_config_paths)
+
+  if options.resources_path_map_out_path and not options.short_resource_paths:
+    parser.error(
+        '--resources-path-map-out-path requires --short-resource-paths')
+  return options
+
+
+def _CombineResourceConfigs(resources_config_paths, out_config_path):
+  with open(out_config_path, 'w') as out_config:
+    for config_path in resources_config_paths:
+      with open(config_path) as config:
+        out_config.write(config.read())
+        out_config.write('\n')
+
+
+def _ExtractNonCollapsableResources(rtxt_path):
+  """Extract resources that should not be collapsed from the R.txt file
+
+  Resources of type ID are references to UI elements/views. They are used by
+  UI automation testing frameworks. They are kept in so that they don't break
+  tests, even though they may not actually be used during runtime. See
+  https://crbug.com/900993
+  App icons (aka mipmaps) are sometimes referenced by other apps by name so must
+  be keps as well. See https://b/161564466
+
+  Args:
+    rtxt_path: Path to R.txt file with all the resources
+  Returns:
+    List of resources in the form of <resource_type>/<resource_name>
+  """
+  resources = []
+  _NO_COLLAPSE_TYPES = ['id', 'mipmap']
+  with open(rtxt_path) as rtxt:
+    for line in rtxt:
+      for resource_type in _NO_COLLAPSE_TYPES:
+        if ' {} '.format(resource_type) in line:
+          resource_name = line.split()[2]
+          resources.append('{}/{}'.format(resource_type, resource_name))
+  return resources
+
+
+def _OptimizeApk(output, options, temp_dir, unoptimized_path, r_txt_path):
+  """Optimize intermediate .ap_ file with aapt2.
+
+  Args:
+    output: Path to write to.
+    options: The command-line options.
+    temp_dir: A temporary directory.
+    unoptimized_path: path of the apk to optimize.
+    r_txt_path: path to the R.txt file of the unoptimized apk.
+  """
+  optimize_command = [
+      options.aapt2_path,
+      'optimize',
+      unoptimized_path,
+      '-o',
+      output,
+  ]
+
+  # Optimize the resources.pb file by obfuscating resource names and only
+  # allow usage via R.java constant.
+  if options.strip_resource_names:
+    no_collapse_resources = _ExtractNonCollapsableResources(r_txt_path)
+    gen_config_path = os.path.join(temp_dir, 'aapt2.config')
+    if options.resources_config_paths:
+      _CombineResourceConfigs(options.resources_config_paths, gen_config_path)
+    with open(gen_config_path, 'a') as config:
+      for resource in no_collapse_resources:
+        config.write('{}#no_collapse\n'.format(resource))
+
+    optimize_command += [
+        '--collapse-resource-names',
+        '--resources-config-path',
+        gen_config_path,
+    ]
+
+  if options.short_resource_paths:
+    optimize_command += ['--shorten-resource-paths']
+  if options.resources_path_map_out_path:
+    optimize_command += [
+        '--resource-path-shortening-map', options.resources_path_map_out_path
+    ]
+
+  logging.debug('Running aapt2 optimize')
+  build_utils.CheckOutput(optimize_command,
+                          print_stdout=False,
+                          print_stderr=False)
+
+
+def main(args):
+  options = _ParseArgs(args)
+  with build_utils.TempDir() as temp_dir:
+    _OptimizeApk(options.optimized_proto_path, options, temp_dir,
+                 options.proto_path, options.r_text_in)
+
+
+if __name__ == '__main__':
+  main(sys.argv[1:])
diff --git a/build/android/gyp/optimize_resources.pydeps b/build/android/gyp/optimize_resources.pydeps
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build/android/gyp/optimize_resources.pydeps
diff --git a/build/android/gyp/resources_shrinker/BUILD.gn b/build/android/gyp/resources_shrinker/BUILD.gn
deleted file mode 100644
index e6381e1..0000000
--- a/build/android/gyp/resources_shrinker/BUILD.gn
+++ /dev/null
@@ -1,15 +0,0 @@
-import("//build/config/android/rules.gni")
-
-java_binary("resources_shrinker") {
-  sources = [ "//build/android/gyp/resources_shrinker/Shrinker.java" ]
-  main_class = "build.android.gyp.resources_shrinker.Shrinker"
-  deps = [
-    "//third_party/android_deps:com_android_tools_common_java",
-    "//third_party/android_deps:com_android_tools_layoutlib_layoutlib_api_java",
-    "//third_party/android_deps:com_android_tools_sdk_common_java",
-    "//third_party/android_deps:com_google_guava_guava_java",
-    "//third_party/android_deps:org_jetbrains_kotlin_kotlin_stdlib_java",
-    "//third_party/r8:r8_java",
-  ]
-  wrapper_script_name = "helper/resources_shrinker"
-}
diff --git a/build/android/gyp/resources_shrinker/shrinker.pydeps b/build/android/gyp/resources_shrinker/shrinker.pydeps
deleted file mode 100644
index 92c8905e..0000000
--- a/build/android/gyp/resources_shrinker/shrinker.pydeps
+++ /dev/null
@@ -1,30 +0,0 @@
-# Generated by running:
-#   build/print_python_deps.py --root build/android/gyp/resources_shrinker --output build/android/gyp/resources_shrinker/shrinker.pydeps build/android/gyp/resources_shrinker/shrinker.py
-../../../../third_party/jinja2/__init__.py
-../../../../third_party/jinja2/_compat.py
-../../../../third_party/jinja2/asyncfilters.py
-../../../../third_party/jinja2/asyncsupport.py
-../../../../third_party/jinja2/bccache.py
-../../../../third_party/jinja2/compiler.py
-../../../../third_party/jinja2/defaults.py
-../../../../third_party/jinja2/environment.py
-../../../../third_party/jinja2/exceptions.py
-../../../../third_party/jinja2/filters.py
-../../../../third_party/jinja2/idtracking.py
-../../../../third_party/jinja2/lexer.py
-../../../../third_party/jinja2/loaders.py
-../../../../third_party/jinja2/nodes.py
-../../../../third_party/jinja2/optimizer.py
-../../../../third_party/jinja2/parser.py
-../../../../third_party/jinja2/runtime.py
-../../../../third_party/jinja2/tests.py
-../../../../third_party/jinja2/utils.py
-../../../../third_party/jinja2/visitor.py
-../../../../third_party/markupsafe/__init__.py
-../../../../third_party/markupsafe/_compat.py
-../../../../third_party/markupsafe/_native.py
-../../../gn_helpers.py
-../util/__init__.py
-../util/build_utils.py
-../util/resource_utils.py
-shrinker.py
diff --git a/build/android/gyp/resources_shrinker/shrinker.py b/build/android/gyp/unused_resources.py
similarity index 65%
rename from build/android/gyp/resources_shrinker/shrinker.py
rename to build/android/gyp/unused_resources.py
index 2800ce29..4b603d1 100755
--- a/build/android/gyp/resources_shrinker/shrinker.py
+++ b/build/android/gyp/unused_resources.py
@@ -24,16 +24,21 @@
   parser.add_argument(
       '--dependencies-res-zips',
       required=True,
+      action='append',
       help='Resources zip archives to investigate for unused resources.')
-  parser.add_argument('--dex',
+  parser.add_argument('--dexes',
+                      action='append',
                       required=True,
                       help='Path to dex file, or zip with dex files.')
   parser.add_argument(
       '--proguard-mapping',
-      required=True,
       help='Path to proguard mapping file for the optimized dex.')
-  parser.add_argument('--r-text', required=True, help='Path to R.txt')
-  parser.add_argument('--android-manifest',
+  parser.add_argument('--r-texts',
+                      action='append',
+                      required=True,
+                      help='Path to R.txt')
+  parser.add_argument('--android-manifests',
+                      action='append',
                       required=True,
                       help='Path to AndroidManifest')
   parser.add_argument('--output-config',
@@ -54,20 +59,31 @@
     for dependency_res_zip in options.dependencies_res_zips:
       dep_subdirs += resource_utils.ExtractDeps([dependency_res_zip], temp_dir)
 
-    build_utils.CheckOutput([
-        options.script, '--rtxts', options.r_text, '--manifests',
-        options.android_manifest, '--resourceDirs', ':'.join(dep_subdirs),
-        '--dex', options.dex, '--mapping', options.proguard_mapping,
-        '--outputConfig', options.output_config
-    ])
+    cmd = [
+        options.script,
+        '--rtxts',
+        ':'.join(options.r_texts),
+        '--manifests',
+        ':'.join(options.android_manifests),
+        '--resourceDirs',
+        ':'.join(dep_subdirs),
+        '--dexes',
+        ':'.join(options.dexes),
+        '--outputConfig',
+        options.output_config,
+    ]
+    if options.proguard_mapping:
+      cmd += [
+          '--mapping',
+          options.proguard_mapping,
+      ]
+    build_utils.CheckOutput(cmd)
 
   if options.depfile:
-    depfile_deps = options.dependencies_res_zips + [
-        options.r_text,
-        options.android_manifest,
-        options.dex,
-        options.proguard_mapping,
-    ]
+    depfile_deps = (options.dependencies_res_zips + options.r_texts +
+                    options.android_manifests + options.dexes)
+    if options.proguard_mapping:
+      depfile_deps.append(options.proguard_mapping)
     build_utils.WriteDepfile(options.depfile, options.output_config,
                              depfile_deps)
 
diff --git a/build/android/gyp/unused_resources.pydeps b/build/android/gyp/unused_resources.pydeps
new file mode 100644
index 0000000..4753ec3
--- /dev/null
+++ b/build/android/gyp/unused_resources.pydeps
@@ -0,0 +1,30 @@
+# Generated by running:
+#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/unused_resources.pydeps build/android/gyp/unused_resources.py
+../../../third_party/jinja2/__init__.py
+../../../third_party/jinja2/_compat.py
+../../../third_party/jinja2/asyncfilters.py
+../../../third_party/jinja2/asyncsupport.py
+../../../third_party/jinja2/bccache.py
+../../../third_party/jinja2/compiler.py
+../../../third_party/jinja2/defaults.py
+../../../third_party/jinja2/environment.py
+../../../third_party/jinja2/exceptions.py
+../../../third_party/jinja2/filters.py
+../../../third_party/jinja2/idtracking.py
+../../../third_party/jinja2/lexer.py
+../../../third_party/jinja2/loaders.py
+../../../third_party/jinja2/nodes.py
+../../../third_party/jinja2/optimizer.py
+../../../third_party/jinja2/parser.py
+../../../third_party/jinja2/runtime.py
+../../../third_party/jinja2/tests.py
+../../../third_party/jinja2/utils.py
+../../../third_party/jinja2/visitor.py
+../../../third_party/markupsafe/__init__.py
+../../../third_party/markupsafe/_compat.py
+../../../third_party/markupsafe/_native.py
+../../gn_helpers.py
+unused_resources.py
+util/__init__.py
+util/build_utils.py
+util/resource_utils.py
diff --git a/build/android/gyp/write_build_config.py b/build/android/gyp/write_build_config.py
index 489dc60..752ab30 100755
--- a/build/android/gyp/write_build_config.py
+++ b/build/android/gyp/write_build_config.py
@@ -966,7 +966,10 @@
   parser.add_option('--resources-zip', help='Path to target\'s resources zip.')
   parser.add_option('--package-name',
       help='Java package name for these resources.')
-  parser.add_option('--android-manifest', help='Path to android manifest.')
+  parser.add_option('--android-manifest',
+                    help='Path to the root android manifest.')
+  parser.add_option('--merged-android-manifest',
+                    help='Path to the merged android manifest.')
   parser.add_option('--resource-dirs', action='append', default=[],
                     help='GYP-list of resource dirs')
   parser.add_option(
@@ -1347,6 +1350,9 @@
   if options.android_manifest:
     deps_info['android_manifest'] = options.android_manifest
 
+  if options.merged_android_manifest:
+    deps_info['merged_android_manifest'] = options.merged_android_manifest
+
   if options.bundled_srcjars:
     deps_info['bundled_srcjars'] = build_utils.ParseGnList(
         options.bundled_srcjars)
diff --git a/build/android/unused_resources/BUILD.gn b/build/android/unused_resources/BUILD.gn
new file mode 100644
index 0000000..1596104
--- /dev/null
+++ b/build/android/unused_resources/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/rules.gni")
+
+java_binary("unused_resources") {
+  sources = [ "//build/android/unused_resources/UnusedResources.java" ]
+  main_class = "build.android.unused_resources.UnusedResources"
+  deps = [
+    "//third_party/android_deps:com_android_tools_common_java",
+    "//third_party/android_deps:com_android_tools_layoutlib_layoutlib_api_java",
+    "//third_party/android_deps:com_android_tools_sdk_common_java",
+    "//third_party/android_deps:com_google_guava_guava_java",
+    "//third_party/android_deps:org_jetbrains_kotlin_kotlin_stdlib_java",
+    "//third_party/r8:r8_java",
+  ]
+  wrapper_script_name = "helper/unused_resources"
+}
diff --git a/build/android/gyp/resources_shrinker/Shrinker.java b/build/android/unused_resources/UnusedResources.java
similarity index 94%
rename from build/android/gyp/resources_shrinker/Shrinker.java
rename to build/android/unused_resources/UnusedResources.java
index 50e2f93..6334223 100644
--- a/build/android/gyp/resources_shrinker/Shrinker.java
+++ b/build/android/unused_resources/UnusedResources.java
@@ -19,7 +19,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package build.android.gyp.resources_shrinker;
+package build.android.unused_resources;
 
 import static com.android.ide.common.symbols.SymbolIo.readFromAapt;
 import static com.android.utils.SdkUtils.endsWithIgnoreCase;
@@ -44,6 +44,7 @@
 import com.google.common.collect.Maps;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.Closeables;
+import com.google.common.io.Files;
 
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
@@ -54,7 +55,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
@@ -77,7 +77,7 @@
     - Reduce dependencies unless absolutely required.
 */
 
-public class Shrinker {
+public class UnusedResources {
     private static final String ANDROID_RES = "android_res/";
     private static final String DOT_DEX = ".dex";
     private static final String DOT_CLASS = ".class";
@@ -97,9 +97,6 @@
     private final StringWriter mDebugOutput;
     private final PrintWriter mDebugPrinter;
 
-    /** Easy way to invoke more verbose output for debugging */
-    private boolean mDebug = false;
-
     /** The computed set of unused resources */
     private List<Resource> mUnused;
 
@@ -136,8 +133,8 @@
         }
     }
 
-    public Shrinker(Iterable<File> rTxtFiles, Iterable<File> classes, Iterable<File> manifests,
-            File mapping, Iterable<File> resources, File reportFile) {
+    public UnusedResources(Iterable<File> rTxtFiles, Iterable<File> classes,
+            Iterable<File> manifests, File mapping, Iterable<File> resources, File reportFile) {
         mRTxtFiles = rTxtFiles;
         mProguardMapping = mapping;
         mClasses = classes;
@@ -213,13 +210,11 @@
             throws IOException, SAXException, ParserConfigurationException {
         for (File resDir : resources) {
             File[] resourceFolders = resDir.listFiles();
-            if (resourceFolders != null) {
-                for (File folder : resourceFolders) {
-                    ResourceFolderType folderType =
-                            ResourceFolderType.getFolderType(folder.getName());
-                    if (folderType != null) {
-                        recordResources(folderType, folder);
-                    }
+            assert resourceFolders != null : "Invalid resource directory " + resDir;
+            for (File folder : resourceFolders) {
+                ResourceFolderType folderType = ResourceFolderType.getFolderType(folder.getName());
+                if (folderType != null) {
+                    recordResources(folderType, folder);
                 }
             }
         }
@@ -392,7 +387,7 @@
 
             @Override
             public void referencedInt(int value) {
-                Shrinker.this.referencedInt("dex", value, file, name);
+                UnusedResources.this.referencedInt("dex", value, file, name);
             }
 
             @Override
@@ -502,8 +497,7 @@
 
     private void referencedInt(String context, int value, File file, String currentClass) {
         Resource resource = mModel.getResource(value);
-        if (ResourceUsageModel.markReachable(resource) && mDebug) {
-            assert mDebugPrinter != null : "mDebug is true, but mDebugPrinter is null.";
+        if (ResourceUsageModel.markReachable(resource) && mDebugPrinter != null) {
             mDebugPrinter.println("Marking " + resource + " reachable: referenced from " + context
                     + " in " + file + ":" + currentClass);
         }
@@ -563,7 +557,7 @@
                                         .map(s -> new File(s))
                                         .collect(Collectors.toList());
                     break;
-                case "--dex":
+                case "--dexes":
                     classes = Arrays.stream(args[i + 1].split(":"))
                                       .map(s -> new File(s))
                                       .collect(Collectors.toList());
@@ -591,9 +585,10 @@
                     throw new IllegalArgumentException(args[i] + " is not a valid arg.");
             }
         }
-        Shrinker shrinker = new Shrinker(rTxtFiles, classes, manifests, mapping, resources, log);
-        shrinker.analyze();
-        shrinker.close();
-        shrinker.emitConfig(configPath);
+        UnusedResources unusedResources =
+                new UnusedResources(rTxtFiles, classes, manifests, mapping, resources, log);
+        unusedResources.analyze();
+        unusedResources.close();
+        unusedResources.emitConfig(configPath);
     }
 }
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index 11eb48c..3486660 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -360,6 +360,12 @@
       args += [ "--treat-as-locale-paks" ]
     }
 
+    if (defined(invoker.merged_android_manifest)) {
+      args += [
+        "--merged-android-manifest",
+        rebase_path(invoker.merged_android_manifest, root_build_dir),
+      ]
+    }
     if (defined(invoker.android_manifest)) {
       inputs += [ invoker.android_manifest ]
       args += [
@@ -2249,29 +2255,15 @@
   #     Use resource IDs provided by another APK target when compiling resources
   #     (via. "aapt2 link --stable-ids")
   #
-  #   short_resource_paths: (optional)
-  #     Rename the paths within a the apk to be randomly generated short
-  #     strings to reduce binary size.
-  #
-  #   strip_resource_names: (optional)
-  #     Strip resource names from the resources table of the apk.
   #
   # Output variables:
   #   arsc_output: Path to output .ap_ file (optional).
   #
   #   proto_output: Path to output .proto.ap_ file (optional).
   #
-  #   optimized_arsc_output: Path to optimized .ap_ file (optional).
-  #
-  #   optimized_proto_output: Path to optimized .proto.ap_ file (optional).
-  #
   #   r_text_out_path: (optional):
   #       Path for the corresponding generated R.txt file.
   #
-  #   resources_path_map_out_path: (optional):
-  #       Path for the generated map between original resource paths and
-  #       shortend resource paths.
-  #
   #   proguard_file: (optional)
   #       Path to proguard configuration file for this apk target.
   #
@@ -2301,9 +2293,6 @@
     if (defined(invoker.arsc_output)) {
       _arsc_output = invoker.arsc_output
     }
-    if (defined(invoker.optimized_arsc_output)) {
-      _optimized_arsc_output = invoker.optimized_arsc_output
-    }
     _final_srcjar_path = "${target_gen_dir}/${target_name}.srcjar"
 
     _script = "//build/android/gyp/compile_resources.py"
@@ -2371,36 +2360,6 @@
         rebase_path(invoker.size_info_path, root_build_dir),
       ]
     }
-    if (defined(_optimized_arsc_output)) {
-      _outputs += [ _optimized_arsc_output ]
-      _args += [
-        "--optimized-arsc-path",
-        rebase_path(_optimized_arsc_output, root_build_dir),
-      ]
-    }
-    if (defined(invoker.optimized_proto_output)) {
-      _outputs += [ invoker.optimized_proto_output ]
-      _args += [
-        "--optimized-proto-path",
-        rebase_path(invoker.optimized_proto_output, root_build_dir),
-      ]
-    }
-    if (defined(invoker.resources_config_paths)) {
-      _inputs += invoker.resources_config_paths
-      _rebased_resource_configs =
-          rebase_path(invoker.resources_config_paths, root_build_dir)
-      _args += [ "--resources-config-paths=${_rebased_resource_configs}" ]
-    }
-    if (defined(invoker.short_resource_paths) && invoker.short_resource_paths) {
-      _args += [ "--short-resource-paths" ]
-      if (defined(invoker.resources_path_map_out_path)) {
-        _outputs += [ invoker.resources_path_map_out_path ]
-        _args += [
-          "--resources-path-map-out-path",
-          rebase_path(invoker.resources_path_map_out_path, root_build_dir),
-        ]
-      }
-    }
 
     if (defined(invoker.r_java_root_package_name)) {
       _args += [
@@ -2409,10 +2368,6 @@
       ]
     }
 
-    if (defined(invoker.strip_resource_names) && invoker.strip_resource_names) {
-      _args += [ "--strip-resource-names" ]
-    }
-
     # Useful to have android:debuggable in the manifest even for Release
     # builds. Just omit it for officai
     if (debuggable_apks) {
@@ -2639,35 +2594,115 @@
     }
   }
 
+  # A template that is used to optimize compiled resources using aapt2 optimize.
+  #
+  #   proto_input_path:
+  #     Path to input compiled .proto.ap_ file.
+  #
+  #   short_resource_paths: (optional)
+  #     Rename the paths within a the apk to be randomly generated short
+  #     strings to reduce binary size.
+  #
+  #   strip_resource_names: (optional)
+  #     Strip resource names from the resources table of the apk.
+  #
+  #   resources_configs_paths: (optional)
+  #     List of resource configs to use for optimization.
+  #
+  #   optimized_proto_output:
+  #     Path to output optimized .proto.ap_ file.
+  #
+  #   resources_path_map_out_path: (optional):
+  #       Path for the generated map between original resource paths and
+  #       shortened resource paths.
+  template("optimize_resources") {
+    forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
+    action_with_pydeps(target_name) {
+      forward_variables_from(invoker, [ "deps" ])
+      script = "//build/android/gyp/optimize_resources.py"
+      outputs = [ invoker.optimized_proto_output ]
+      inputs = [
+        android_sdk_tools_bundle_aapt2,
+        invoker.r_text_path,
+        invoker.proto_input_path,
+      ]
+      args = [
+        "--aapt2-path",
+        rebase_path(android_sdk_tools_bundle_aapt2, root_build_dir),
+        "--r-text-in",
+        rebase_path(invoker.r_text_path, root_build_dir),
+        "--proto-path",
+        rebase_path(invoker.proto_input_path, root_build_dir),
+        "--optimized-proto-path",
+        rebase_path(invoker.optimized_proto_output, root_build_dir),
+      ]
+
+      if (defined(invoker.resources_config_paths)) {
+        inputs += invoker.resources_config_paths
+        _rebased_resource_configs =
+            rebase_path(invoker.resources_config_paths, root_build_dir)
+        args += [ "--resources-config-paths=${_rebased_resource_configs}" ]
+      }
+
+      if (defined(invoker.short_resource_paths) &&
+          invoker.short_resource_paths) {
+        args += [ "--short-resource-paths" ]
+        if (defined(invoker.resources_path_map_out_path)) {
+          outputs += [ invoker.resources_path_map_out_path ]
+          args += [
+            "--resources-path-map-out-path",
+            rebase_path(invoker.resources_path_map_out_path, root_build_dir),
+          ]
+        }
+      }
+
+      if (defined(invoker.strip_resource_names) &&
+          invoker.strip_resource_names) {
+        args += [ "--strip-resource-names" ]
+      }
+    }
+  }
+
+  # A template that is used to find unused resources.
   template("unused_resources") {
-    _rebased_build_config = rebase_path(invoker.build_config, root_build_dir)
-    _shrinker_dep = "//build/android/gyp/resources_shrinker:resources_shrinker"
-    _shrinker_script = "$root_build_dir/bin/helper/resources_shrinker"
     action_with_pydeps(target_name) {
       forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "deps" ])
-      script = "//build/android/gyp/resources_shrinker/shrinker.py"
-      inputs = [
-        invoker.build_config,
-        invoker.proguard_mapping_path,
-        _shrinker_script,
-      ]
+      script = "//build/android/gyp/unused_resources.py"
+      depfile = "$target_gen_dir/${target_name}.d"
+      _unused_resources_script = "$root_build_dir/bin/helper/unused_resources"
+      inputs = [ _unused_resources_script ]
       outputs = [ invoker.output_config ]
       if (!defined(deps)) {
         deps = []
       }
-      deps += [ _shrinker_dep ]
+      deps += [ "//build/android/unused_resources:unused_resources" ]
       args = [
         "--script",
-        rebase_path(_shrinker_script, root_build_dir),
-        "--dependencies-res-zips=@FileArg($_rebased_build_config:deps_info:dependency_zips)",
-        "--proguard-mapping",
-        rebase_path(invoker.proguard_mapping_path, root_build_dir),
-        "--r-text=@FileArg($_rebased_build_config:deps_info:r_text_path)",
-        "--dex=@FileArg($_rebased_build_config:final_dex:path)",
-        "--android-manifest=@FileArg($_rebased_build_config:deps_info:android_manifest)",
+        rebase_path(_unused_resources_script, root_build_dir),
         "--output-config",
         rebase_path(invoker.output_config, root_build_dir),
+        "--depfile",
+        rebase_path(depfile, root_build_dir),
       ]
+
+      if (defined(invoker.proguard_mapping_path)) {
+        inputs += [ invoker.proguard_mapping_path ]
+        args += [
+          "--proguard-mapping",
+          rebase_path(invoker.proguard_mapping_path, root_build_dir),
+        ]
+      }
+
+      foreach(_build_config, invoker.module_build_configs) {
+        inputs += [ _build_config ]
+        _rebased_build_config = rebase_path(_build_config, root_build_dir)
+        args += [
+          "--dependencies-res-zips=@FileArg($_rebased_build_config:deps_info:dependency_zips)",
+          "--r-texts=@FileArg($_rebased_build_config:deps_info:r_text_path)",
+          "--dexes=@FileArg($_rebased_build_config:final_dex:path)",
+          "--android-manifests=@FileArg($_rebased_build_config:deps_info:merged_android_manifest)",
+        ]
+      }
     }
   }
 
@@ -3622,6 +3657,7 @@
             [
               "android_manifest",
               "android_manifest_dep",
+              "merged_android_manifest",
               "final_dex_path",
               "loadable_modules",
               "native_lib_placeholders",
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index d33240d..bab3386 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -2077,6 +2077,8 @@
   #   uncompress_dex: Store final .dex files uncompressed in the apk.
   #   strip_resource_names: True if resource names should be stripped from the
   #     resources.arsc file in the apk or module.
+  #   strip_unused_resources: True if unused resources should be stripped from
+  #     the apk or module.
   #   short_resource_paths: True if resource paths should be shortened in the
   #     apk or module.
   #   resources_config_paths: List of paths to the aapt2 optimize config files
@@ -2163,20 +2165,8 @@
 
     if (!_is_bundle_module) {
       _final_apk_path = invoker.final_apk_path
-      _final_rtxt_path = "${_final_apk_path}.R.txt"
     }
 
-    _short_resource_paths =
-        defined(invoker.short_resource_paths) && invoker.short_resource_paths &&
-        enable_arsc_obfuscation
-    _strip_resource_names =
-        defined(invoker.strip_resource_names) && invoker.strip_resource_names &&
-        enable_arsc_obfuscation
-    _optimize_resources = _strip_resource_names || _short_resource_paths
-
-    if (!_is_bundle_module && _short_resource_paths) {
-      _final_pathmap_path = "${_final_apk_path}.pathmap.txt"
-    }
     _res_size_info_path = "$target_out_dir/$target_name.ap_.info"
     if (!_is_bundle_module) {
       _final_apk_path_no_ext_list =
@@ -2193,20 +2183,12 @@
     if (_is_bundle_module) {
       # Path to the intermediate proto-format resources zip file.
       _proto_resources_path = "$target_out_dir/$target_name.proto.ap_"
-      if (_optimize_resources) {
-        _optimized_proto_resources_path =
-            "$target_out_dir/$target_name.optimized.proto.ap_"
-      }
     } else {
       # resource_sizes.py needs to be able to find the unpacked resources.arsc
       # file based on apk name to compute normatlized size.
       _resource_sizes_arsc_path =
           "$root_out_dir/arsc/" +
           rebase_path(_final_apk_path_no_ext, root_build_dir) + ".ap_"
-      if (_optimize_resources) {
-        _optimized_arsc_resources_path =
-            "$target_out_dir/$target_name.optimized.ap_"
-      }
     }
 
     if (defined(invoker.version_code)) {
@@ -2449,11 +2431,6 @@
           "${_shared_resources_allowlist_target}__compile_resources"
     }
 
-    if (_short_resource_paths) {
-      _resources_path_map_out_path =
-          "${target_gen_dir}/${_template_name}_resources_path_map.txt"
-    }
-
     _compile_resources_target = "${_template_name}__compile_resources"
     _compile_resources_rtxt_out =
         "${target_gen_dir}/${_compile_resources_target}_R.txt"
@@ -2479,13 +2456,10 @@
                                "resource_exclusion_exceptions",
                                "resource_exclusion_regex",
                                "resource_values_filter_rules",
-                               "resources_config_paths",
                                "shared_resources",
                                "shared_resources_allowlist_locales",
                                "uses_split",
                              ])
-      short_resource_paths = _short_resource_paths
-      strip_resource_names = _strip_resource_names
       android_manifest = _android_manifest
       android_manifest_dep = ":$_merge_manifest_target"
       version_code = _version_code
@@ -2511,9 +2485,6 @@
       if (_enable_main_dex_list) {
         proguard_file_main_dex = _generated_proguard_main_dex_config
       }
-      if (_short_resource_paths) {
-        resources_path_map_out_path = _resources_path_map_out_path
-      }
 
       build_config = _build_config
       build_config_dep = ":$_build_config_target"
@@ -2550,9 +2521,6 @@
       if (_is_bundle_module) {
         is_bundle_module = true
         proto_output = _proto_resources_path
-        if (_optimize_resources) {
-          optimized_proto_output = _optimized_proto_resources_path
-        }
 
         if (defined(invoker.base_module_target)) {
           include_resource =
@@ -2560,8 +2528,6 @@
               "/" + get_label_info(invoker.base_module_target, "name") + ".ap_"
           _link_against = invoker.base_module_target
         }
-      } else if (_optimize_resources) {
-        optimized_arsc_output = _optimized_arsc_resources_path
       }
 
       if (defined(_link_against)) {
@@ -2584,6 +2550,55 @@
     }
     _srcjar_deps += [ ":$_compile_resources_target" ]
 
+    # We don't ship apks anymore, only optimize bundle builds
+    if (_is_bundle_module) {
+      _short_resource_paths =
+          defined(invoker.short_resource_paths) &&
+          invoker.short_resource_paths && enable_arsc_obfuscation
+      _strip_resource_names =
+          defined(invoker.strip_resource_names) &&
+          invoker.strip_resource_names && enable_arsc_obfuscation
+      _strip_unused_resources = defined(invoker.strip_unused_resources) &&
+                                invoker.strip_unused_resources
+      _optimize_resources = _strip_resource_names || _short_resource_paths ||
+                            _strip_unused_resources
+    }
+
+    if (_is_bundle_module && _optimize_resources) {
+      _optimized_proto_resources_path =
+          "$target_out_dir/$target_name.optimized.proto.ap_"
+      if (_short_resource_paths) {
+        _resources_path_map_out_path =
+            "${target_gen_dir}/${_template_name}_resources_path_map.txt"
+      }
+      _optimize_resources_target = "${_template_name}__optimize_resources"
+      optimize_resources(_optimize_resources_target) {
+        deps = _deps + [ ":$_compile_resources_target" ]
+        short_resource_paths = _short_resource_paths
+        strip_resource_names = _strip_resource_names
+        if (_short_resource_paths) {
+          resources_path_map_out_path = _resources_path_map_out_path
+        }
+        r_text_path = _compile_resources_rtxt_out
+        proto_input_path = _proto_resources_path
+        optimized_proto_output = _optimized_proto_resources_path
+        if (_strip_unused_resources) {
+          # These need to be kept in sync with the target names + output paths
+          # in the android_app_bundle template.
+          _unused_resources_target = "${_template_name}__unused_resources"
+          _unused_resources_config_path =
+              "$target_gen_dir/${_template_name}_unused_resources.config"
+          resources_config_paths = [ _unused_resources_config_path ]
+          deps += [ ":$_unused_resources_target" ]
+        } else {
+          resources_config_paths = []
+        }
+        if (defined(invoker.resources_config_paths)) {
+          resources_config_paths += invoker.resources_config_paths
+        }
+      }
+    }
+
     if (defined(_resource_sizes_arsc_path)) {
       _copy_arsc_target = "${_template_name}__copy_arsc"
       copy(_copy_arsc_target) {
@@ -2596,36 +2611,6 @@
       _final_deps += [ ":$_copy_arsc_target" ]
     }
 
-    if (!_is_bundle_module) {
-      # Output the R.txt file to a more easily discoverable location for
-      # archiving. This is necessary when stripping resource names so that we
-      # have an archive of resource names to ids for shipped apks (for
-      # debugging purposes). We copy the file rather than change the location
-      # of the original because other targets rely on the location of the R.txt
-      # file.
-      _copy_rtxt_target = "${_template_name}__copy_rtxt"
-      copy(_copy_rtxt_target) {
-        deps = [ ":$_compile_resources_target" ]
-        sources = [ _compile_resources_rtxt_out ]
-        outputs = [ _final_rtxt_path ]
-      }
-      _final_deps += [ ":$_copy_rtxt_target" ]
-
-      if (_short_resource_paths) {
-        # Do the same for path map
-        _copy_pathmap_target = "${_template_name}__copy_pathmap"
-        copy(_copy_pathmap_target) {
-          deps = [ ":$_compile_resources_target" ]
-          sources = [ _resources_path_map_out_path ]
-          outputs = [ _final_pathmap_path ]
-
-          # The monochrome_public_apk_checker test needs pathmap when run on swarming.
-          data = [ _final_pathmap_path ]
-        }
-        _final_deps += [ ":$_copy_pathmap_target" ]
-      }
-    }
-
     _generate_native_libraries_java =
         (!_is_bundle_module || _is_base_module) &&
         (_native_libs_deps != [] || _secondary_abi_native_libs_deps != []) &&
@@ -2788,6 +2773,7 @@
       supports_android = true
       requires_android = true
       srcjar_deps = _srcjar_deps
+      merged_android_manifest = _android_manifest
       if (defined(_final_dex_path)) {
         final_dex_path = _final_dex_path
       }
@@ -3055,6 +3041,9 @@
                        ":$_build_config_target",
                        ":$_compile_resources_target",
                      ] + _all_native_libs_deps
+      if (_optimize_resources) {
+        _final_deps += [ ":$_optimize_resources_target" ]
+      }
       if (defined(_final_dex_target_dep)) {
         not_needed([ "_final_dex_target_dep" ])
       }
@@ -3198,11 +3187,7 @@
             deps += [ _final_dex_target_dep ]
           }
 
-          if (_optimize_resources) {
-            packaged_resources_path = _optimized_arsc_resources_path
-          } else {
-            packaged_resources_path = _arsc_resources_path
-          }
+          packaged_resources_path = _arsc_resources_path
 
           if (defined(_native_libs_filearg)) {
             native_libs_filearg = _native_libs_filearg
@@ -3481,7 +3466,6 @@
                                "resource_exclusion_regex",
                                "resource_ids_provider_dep",
                                "resource_values_filter_rules",
-                               "resources_config_paths",
                                "require_native_mocks",
                                "secondary_abi_loadable_modules",
                                "secondary_abi_shared_libraries",
@@ -3490,13 +3474,11 @@
                                "shared_resources",
                                "shared_resources_allowlist_locales",
                                "shared_resources_allowlist_target",
-                               "short_resource_paths",
                                "sources",
                                "srcjar_deps",
                                "static_library_dependent_targets",
                                "static_library_provider",
                                "static_library_synchronized_proguard",
-                               "strip_resource_names",
                                "target_sdk_version",
                                "testonly",
                                "uncompress_dex",
@@ -3624,6 +3606,7 @@
                                "static_library_provider",
                                "static_library_synchronized_proguard",
                                "strip_resource_names",
+                               "strip_unused_resources",
                                "target_sdk_version",
                                "testonly",
                                "uncompress_shared_libraries",
@@ -4732,6 +4715,7 @@
     _all_create_module_targets = []
     _all_module_zip_paths = []
     _all_module_build_configs = []
+    _all_module_unused_resources_deps = []
     foreach(_module, _modules) {
       _module_target = _module.module_target
       _module_build_config = _module.build_config
@@ -4750,7 +4734,6 @@
       # this file *must* be named ${_module.name}.zip
       _create_module_target = "${_target_name}__${_module.name}__create"
       _module_zip_path = "$target_gen_dir/$target_name/${_module.name}.zip"
-
       create_android_app_bundle_module(_create_module_target) {
         forward_variables_from(invoker,
                                [
@@ -4801,6 +4784,27 @@
       ]
       _all_module_zip_paths += [ _module_zip_path ]
       _all_module_build_configs += [ _module_build_config ]
+      _all_module_unused_resources_deps += [
+        "${_module_target}__compile_resources",
+        _dex_target_for_module,
+        _module_build_config_target,
+      ]
+    }
+    if (defined(invoker.strip_unused_resources) &&
+        invoker.strip_unused_resources) {
+      # Resources only live in the base module so we define the unused resources
+      # target only on the base module target.
+      _unused_resources_target = "${_base_target_name}__unused_resources"
+      _unused_resources_config =
+          "${_base_target_gen_dir}/${_base_target_name}_unused_resources.config"
+      unused_resources(_unused_resources_target) {
+        deps = _all_module_unused_resources_deps
+        module_build_configs = _all_module_build_configs
+        if (_proguard_enabled) {
+          proguard_mapping_path = _proguard_mapping_path
+        }
+        output_config = _unused_resources_config
+      }
     }
 
     _all_rebased_module_zip_paths =
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index b1ae5bed..cc39756 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-4.20210603.0.1
+4.20210603.4.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index b1ae5bed..cc39756 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-4.20210603.0.1
+4.20210603.4.1
diff --git a/buildtools/reclient_cfgs/OWNERS b/buildtools/reclient_cfgs/OWNERS
new file mode 100644
index 0000000..ae14021
--- /dev/null
+++ b/buildtools/reclient_cfgs/OWNERS
@@ -0,0 +1,5 @@
+abdelaal@google.com
+msavigny@google.com
+tikuta@google.com
+ukai@google.com
+yyanagisawa@google.com
diff --git a/buildtools/reclient_cfgs/win-cross-experiments/rewrapper_windows.cfg b/buildtools/reclient_cfgs/win-cross-experiments/rewrapper_windows.cfg
index 95d0386..1448dfa 100644
--- a/buildtools/reclient_cfgs/win-cross-experiments/rewrapper_windows.cfg
+++ b/buildtools/reclient_cfgs/win-cross-experiments/rewrapper_windows.cfg
@@ -1,4 +1,4 @@
-platform=container-image=docker://gcr.io/goma-foundry-experiments/re-client/chromium-win-cross@sha256:962da618112151bdf80418e55914a1e16ab64cea3846d23642c8fd191740d428,OSFamily=Linux

+platform=container-image=docker://gcr.io/goma-foundry-experiments/re-client/chromium-win-cross@sha256:f18b6081b813cb64beab5c6d9b6f5608daffbc4abde6137a5a2185779da6332d,OSFamily=Linux

 server_address=pipe://reproxy.pipe

 labels=type=compile,compiler=clang-cl,lang=cpp

 exec_strategy=remote_local_fallback

diff --git a/cc/paint/oop_pixeltest.cc b/cc/paint/oop_pixeltest.cc
index 4a479a8..d0dbbf18 100644
--- a/cc/paint/oop_pixeltest.cc
+++ b/cc/paint/oop_pixeltest.cc
@@ -1798,7 +1798,7 @@
       avg_error = std::max(is_record_filter ? 50.f : 2.f, avg_error);
     } else if (GetMatrixStrategy(GetParam()) == MatrixStrategy::kPerspective) {
       error_pixels_percentage =
-          std::max(is_record_filter ? 12.f : 4.f, error_pixels_percentage);
+          std::max(is_record_filter ? 13.f : 4.f, error_pixels_percentage);
       max_abs_error = std::max(is_record_filter ? 255 : 36, max_abs_error);
       avg_error = std::max(is_record_filter ? 60.f : 36.f, avg_error);
     }
diff --git a/chrome/VERSION b/chrome/VERSION
index cc52d18..ad8cd96 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=93
 MINOR=0
-BUILD=4532
+BUILD=4533
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 4a99266..1aad36e 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -581,6 +581,7 @@
   deps += web_feed_deps
 
   srcjar_deps = [
+    ":autofill_verification_status_generated_enum",
     ":chrome_android_java_enums_srcjar",
     ":chrome_android_java_google_api_keys_srcjar",
     ":chrome_strict_mode_switch",
@@ -779,6 +780,10 @@
   ]
 }
 
+java_cpp_enum("autofill_verification_status_generated_enum") {
+  sources = [ "//components/autofill/core/browser/data_model/autofill_structured_address_component.h" ]
+}
+
 java_cpp_enum("chrome_android_java_enums_srcjar") {
   sources = [
     "//chrome/browser/android/customtabs/detached_resource_request.h",
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index c485c30a..c5089dd 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -179,10 +179,23 @@
       png_to_webp = !is_java_debug
     }
 
-    # Removes metadata needed for Resources.getIdentifier("resource_name").
-    strip_resource_names = !is_java_debug
+    # We only optimize resources for bundles since APKs are not shipped.
+    # Resources only live in the base module atm as such we only need to set
+    # these on the base module
+    if (_is_bundle) {
+      # Removes metadata needed for Resources.getIdentifier("resource_name").
+      strip_resource_names = !is_java_debug
 
-    short_resource_paths = true
+      # Shortens resource file names in apk eg: res/drawable/button.xml -> res/a.xml
+      short_resource_paths = true
+
+      # Removes unused resources from the apk. Only enabled on official builds
+      # since it adds a slow step and serializes the build graph causing fewer
+      # expensive tasks (eg: proguarding, resource optimization) to be run in
+      # parallel by adding dependencies between them (adds around 10-20
+      # seconds on my machine).
+      strip_unused_resources = is_official_build
+    }
 
     if (!defined(aapt_locale_allowlist)) {
       # Include resource strings files only for supported locales.
@@ -539,13 +552,16 @@
         }
       }
 
-      # Resources config for blocklisting resource names from obfuscation
-      resources_config_paths = [
-        "//android_webview/aapt2.config",
-        "//chrome/android/aapt2.config",
-      ]
-      if (defined(invoker.resources_config_paths)) {
-        resources_config_paths += invoker.resources_config_paths
+      # We only optimize resources in bundles.
+      if (_is_bundle_module) {
+        # Resources config for blocklisting resource names from obfuscation
+        resources_config_paths = [
+          "//android_webview/aapt2.config",
+          "//chrome/android/aapt2.config",
+        ]
+        if (defined(invoker.resources_config_paths)) {
+          resources_config_paths += invoker.resources_config_paths
+        }
       }
 
       if (defined(invoker.never_incremental)) {
diff --git a/chrome/android/expectations/monochrome_public_bundle.proguard_flags.expected b/chrome/android/expectations/monochrome_public_bundle.proguard_flags.expected
index 55b8825..236f59a 100644
--- a/chrome/android/expectations/monochrome_public_bundle.proguard_flags.expected
+++ b/chrome/android/expectations/monochrome_public_bundle.proguard_flags.expected
@@ -646,16 +646,6 @@
     <init>();
 }
 
-# File: obj/third_party/androidx/androidx_startup_startup_runtime_java/proguard.txt
-# This Proguard rule ensures that ComponentInitializers are are neither shrunk nor obfuscated.
-# This is because they are discovered and instantiated during application initialization.
--keep class * extends androidx.startup.Initializer {
-    # Keep the public no-argument constructor while allowing other methods to be optimized.
-    <init>();
-}
-
--assumenosideeffects class androidx.startup.StartupLogger
-
 # File: obj/third_party/androidx/androidx_transition_transition_java/proguard.txt
 # Copyright (C) 2017 The Android Open Source Project
 #
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java
index a6f57fd8..fcd95a59 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java
@@ -48,6 +48,7 @@
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Integration tests of the {@link StartSurface} for cases where there are no tabs. See {@link
@@ -112,7 +113,7 @@
     @Feature({"StartSurface"})
     // clang-format off
     @CommandLineFlags.Add({BASE_PARAMS + "/single"})
-    public void testShow_SingleAsHomepage_NoTabs() {
+    public void testShow_SingleAsHomepage_NoTabs() throws TimeoutException {
         // clang-format on
         CriteriaHelper.pollUiThread(
                 ()
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
index 52550a3..09c60b0 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
@@ -38,8 +38,6 @@
 import static org.chromium.chrome.test.util.ViewUtils.waitForView;
 
 import android.os.Build;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
 import android.support.test.runner.lifecycle.Stage;
@@ -232,8 +230,13 @@
                 mLayoutChangedCallbackHelper.notifyCalled();
             }
         };
-        mActivityTestRule.getActivity().getLayoutManagerSupplier().addObserver(
-                (obs) -> { obs.addObserver(mLayoutObserver); });
+        mActivityTestRule.getActivity().getLayoutManagerSupplier().addObserver((manager) -> {
+            if (manager.getActiveLayout() != null) {
+                mCurrentlyActiveLayout = manager.getActiveLayout().getLayoutType();
+                mLayoutChangedCallbackHelper.notifyCalled();
+            }
+            manager.addObserver(mLayoutObserver);
+        });
     }
 
     @Test
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 8f84d93..0f4d292 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -667,8 +667,9 @@
                         if (getCompositorViewHolder() == null) return null;
                         return getCompositorViewHolder().getLayerTitleCache();
                     },
-                    mOverviewModeBehaviorSupplier, mLayoutStateProviderOneshotSupplier,
+                    mOverviewModeBehaviorSupplier,
                     mRootUiCoordinator::getTopUiThemeColorProvider);
+            mLayoutStateProviderOneshotSupplier.set(mLayoutManager);
             // clang-format on
             mOverviewModeController = mLayoutManager;
         }
@@ -686,8 +687,9 @@
                         if (getCompositorViewHolder() == null) return null;
                         return getCompositorViewHolder().getLayerTitleCache();
                     },
-                    mOverviewModeBehaviorSupplier, mLayoutStateProviderOneshotSupplier,
+                    mOverviewModeBehaviorSupplier,
                     mRootUiCoordinator::getTopUiThemeColorProvider);
+            mLayoutStateProviderOneshotSupplier.set(mLayoutManager);
             // clang-format on
             mOverviewModeController = mLayoutManager;
         }
@@ -2416,7 +2418,8 @@
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
-        Boolean result = KeyboardShortcuts.dispatchKeyEvent(event, this, mUIWithNativeInitialized);
+        Boolean result = KeyboardShortcuts.dispatchKeyEvent(event, mUIWithNativeInitialized,
+                getFullscreenManager(), /* menuOrKeyboardActionController= */ this);
         return result != null ? result : super.dispatchKeyEvent(event);
     }
 
@@ -2435,7 +2438,8 @@
         }
         boolean isCurrentTabVisible = !mOverviewModeController.overviewVisible()
                 && (!isTablet() || getCurrentTabModel().getCount() != 0);
-        return KeyboardShortcuts.onKeyDown(event, this, isCurrentTabVisible, true)
+        return KeyboardShortcuts.onKeyDown(event, isCurrentTabVisible, true, getTabModelSelector(),
+                       /* menuOrKeyboardActionController= */ this, getToolbarManager())
                 || super.onKeyDown(keyCode, event);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/DEPS
index 2056347..4f7036b2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/DEPS
@@ -23,9 +23,6 @@
   "ChromeTabbedActivity\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
-  "KeyboardShortcuts\.java": [
-    "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
-  ],
   "LaunchIntentDispatcher\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
   ],
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/KeyboardShortcuts.java b/chrome/android/java/src/org/chromium/chrome/browser/KeyboardShortcuts.java
index a8759b5..aa2505a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/KeyboardShortcuts.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/KeyboardShortcuts.java
@@ -13,10 +13,13 @@
 
 import org.chromium.base.annotations.VerifiesOnN;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.app.ChromeActivity;
+import org.chromium.chrome.browser.fullscreen.FullscreenManager;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
+import org.chromium.chrome.browser.toolbar.ToolbarManager;
+import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.device.gamepad.GamepadList;
 
@@ -47,14 +50,16 @@
      * the key event. So the keys handled here cannot be overridden by any view or web page.
      *
      * @param event The KeyEvent to handle.
-     * @param activity The ChromeActivity in which the key was pressed.
      * @param uiInitialized Whether the UI has been initialized. If this is false, most keys will
      *                      not be handled.
+     * @param fullscreenManager Manages fullscreen state.
+     * @param menuOrKeyboardActionController Controls keyboard actions.
      * @return True if the event was handled. False if the event was ignored. Null if the event
      *         should be handled by the activity's parent class.
      */
-    public static Boolean dispatchKeyEvent(KeyEvent event, ChromeActivity activity,
-            boolean uiInitialized) {
+    public static Boolean dispatchKeyEvent(KeyEvent event, boolean uiInitialized,
+            FullscreenManager fullscreenManager,
+            MenuOrKeyboardActionController menuOrKeyboardActionController) {
         int keyCode = event.getKeyCode();
         if (!uiInitialized) {
             if (keyCode == KeyEvent.KEYCODE_SEARCH || keyCode == KeyEvent.KEYCODE_MENU) return true;
@@ -64,19 +69,23 @@
         switch (keyCode) {
             case KeyEvent.KEYCODE_SEARCH:
                 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
-                    activity.onMenuOrKeyboardAction(R.id.focus_url_bar, false);
+                    menuOrKeyboardActionController.onMenuOrKeyboardAction(
+                            R.id.focus_url_bar, false);
                 }
                 // Always consume the SEARCH key events to prevent android from showing
                 // the default app search UI, which locks up Chrome.
                 return true;
             case KeyEvent.KEYCODE_MENU:
                 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
-                    activity.onMenuOrKeyboardAction(R.id.show_menu, false);
+                    menuOrKeyboardActionController.onMenuOrKeyboardAction(R.id.show_menu, false);
                 }
                 return true;
             case KeyEvent.KEYCODE_ESCAPE:
                 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
-                    if (activity.exitFullscreenIfShowing()) return true;
+                    if (fullscreenManager.getPersistentFullscreenMode()) {
+                        fullscreenManager.exitPersistentFullscreenMode();
+                        return true;
+                    }
                 }
                 break;
             case KeyEvent.KEYCODE_TV:
@@ -186,15 +195,19 @@
      * the key event. So the keys handled here *can* be overridden by any view or web page.
      *
      * @param event The KeyEvent to handle.
-     * @param activity The ChromeActivity in which the key was pressed.
-     * @param isCurrentTabVisible Whether page-related actions are valid, e.g. reload, zoom in.
-     *                            This should be false when in the tab switcher.
+     * @param isCurrentTabVisible Whether page-related actions are valid, e.g. reload, zoom in. This
+     *         should be false when in the tab switcher.
      * @param tabSwitchingEnabled Whether shortcuts that switch between tabs are enabled (e.g.
-     *                            Ctrl+Tab, Ctrl+3).
+     *         Ctrl+Tab, Ctrl+3).
+     * @param tabModelSelector The current tab modelSelector.
+     * @param menuOrKeyboardActionController Controls keyboard actions.
+     * @param toolbarManager Manages the toolbar.
      * @return Whether the key event was handled.
      */
-    public static boolean onKeyDown(KeyEvent event, ChromeActivity activity,
-            boolean isCurrentTabVisible, boolean tabSwitchingEnabled) {
+    public static boolean onKeyDown(KeyEvent event, boolean isCurrentTabVisible,
+            boolean tabSwitchingEnabled, TabModelSelector tabModelSelector,
+            MenuOrKeyboardActionController menuOrKeyboardActionController,
+            ToolbarManager toolbarManager) {
         int keyCode = event.getKeyCode();
         if (event.getRepeatCount() != 0 || KeyEvent.isModifierKey(keyCode)) return false;
         if (KeyEvent.isGamepadButton(keyCode)) {
@@ -207,26 +220,31 @@
             return false;
         }
 
-        TabModel curModel = activity.getCurrentTabModel();
-        int count = curModel.getCount();
+        TabModel currentTabModel = tabModelSelector.getCurrentModel();
+        Tab currentTab = tabModelSelector.getCurrentTab();
+        WebContents currentWebContents = currentTab == null ? null : currentTab.getWebContents();
 
+        int tabCount = currentTabModel.getCount();
         int metaState = getMetaState(event);
         int keyCodeAndMeta = keyCode | metaState;
 
         switch (keyCodeAndMeta) {
             case CTRL | SHIFT | KeyEvent.KEYCODE_T:
-                activity.onMenuOrKeyboardAction(R.id.open_recently_closed_tab, false);
+                menuOrKeyboardActionController.onMenuOrKeyboardAction(
+                        R.id.open_recently_closed_tab, false);
                 return true;
             case CTRL | KeyEvent.KEYCODE_T:
-                activity.onMenuOrKeyboardAction(curModel.isIncognito()
-                        ? R.id.new_incognito_tab_menu_id
-                        : R.id.new_tab_menu_id, false);
+                menuOrKeyboardActionController.onMenuOrKeyboardAction(currentTabModel.isIncognito()
+                                ? R.id.new_incognito_tab_menu_id
+                                : R.id.new_tab_menu_id,
+                        false);
                 return true;
             case CTRL | KeyEvent.KEYCODE_N:
-                activity.onMenuOrKeyboardAction(R.id.new_tab_menu_id, false);
+                menuOrKeyboardActionController.onMenuOrKeyboardAction(R.id.new_tab_menu_id, false);
                 return true;
             case CTRL | SHIFT | KeyEvent.KEYCODE_N:
-                activity.onMenuOrKeyboardAction(R.id.new_incognito_tab_menu_id, false);
+                menuOrKeyboardActionController.onMenuOrKeyboardAction(
+                        R.id.new_incognito_tab_menu_id, false);
                 return true;
             // Alt+E represents a special character ´ (latin code: &#180) in Android.
             // If an EditText or ContentView has focus, Alt+E will be swallowed by
@@ -235,20 +253,20 @@
             case ALT | KeyEvent.KEYCODE_F:
             case KeyEvent.KEYCODE_F10:
             case KeyEvent.KEYCODE_BUTTON_Y:
-                activity.onMenuOrKeyboardAction(R.id.show_menu, false);
+                menuOrKeyboardActionController.onMenuOrKeyboardAction(R.id.show_menu, false);
                 return true;
         }
 
         if (isCurrentTabVisible) {
             if (tabSwitchingEnabled && (metaState == CTRL || metaState == ALT)) {
                 int numCode = keyCode - KeyEvent.KEYCODE_0;
-                if (numCode > 0 && numCode <= Math.min(count, 8)) {
+                if (numCode > 0 && numCode <= Math.min(tabCount, 8)) {
                     // Ctrl+1 to Ctrl+8: select tab by index
-                    TabModelUtils.setIndex(curModel, numCode - 1);
+                    TabModelUtils.setIndex(currentTabModel, numCode - 1);
                     return true;
-                } else if (numCode == 9 && count != 0) {
+                } else if (numCode == 9 && tabCount != 0) {
                     // Ctrl+9: select last tab
-                    TabModelUtils.setIndex(curModel, count - 1);
+                    TabModelUtils.setIndex(currentTabModel, tabCount - 1);
                     return true;
                 }
             }
@@ -257,102 +275,101 @@
                 case CTRL | KeyEvent.KEYCODE_TAB:
                 case CTRL | KeyEvent.KEYCODE_PAGE_DOWN:
                 case KeyEvent.KEYCODE_BUTTON_R1:
-                    if (tabSwitchingEnabled && count > 1) {
-                        TabModelUtils.setIndex(curModel, (curModel.index() + 1) % count);
+                    if (tabSwitchingEnabled && tabCount > 1) {
+                        TabModelUtils.setIndex(
+                                currentTabModel, (currentTabModel.index() + 1) % tabCount);
                     }
                     return true;
                 case CTRL | SHIFT | KeyEvent.KEYCODE_TAB:
                 case CTRL | KeyEvent.KEYCODE_PAGE_UP:
                 case KeyEvent.KEYCODE_BUTTON_L1:
-                    if (tabSwitchingEnabled && count > 1) {
-                        TabModelUtils.setIndex(curModel, (curModel.index() + count - 1) % count);
+                    if (tabSwitchingEnabled && tabCount > 1) {
+                        TabModelUtils.setIndex(currentTabModel,
+                                (currentTabModel.index() + tabCount - 1) % tabCount);
                     }
                     return true;
                 case CTRL | KeyEvent.KEYCODE_W:
                 case CTRL | KeyEvent.KEYCODE_F4:
                 case KeyEvent.KEYCODE_BUTTON_B:
-                    TabModelUtils.closeCurrentTab(curModel);
+                    TabModelUtils.closeCurrentTab(currentTabModel);
                     return true;
                 case CTRL | KeyEvent.KEYCODE_F:
                 case CTRL | KeyEvent.KEYCODE_G:
                 case CTRL | SHIFT | KeyEvent.KEYCODE_G:
                 case KeyEvent.KEYCODE_F3:
                 case SHIFT | KeyEvent.KEYCODE_F3:
-                    activity.onMenuOrKeyboardAction(R.id.find_in_page_id, false);
+                    menuOrKeyboardActionController.onMenuOrKeyboardAction(
+                            R.id.find_in_page_id, false);
                     return true;
                 case CTRL | KeyEvent.KEYCODE_L:
                 case ALT | KeyEvent.KEYCODE_D:
                 case KeyEvent.KEYCODE_BUTTON_X:
-                    activity.onMenuOrKeyboardAction(R.id.focus_url_bar, false);
+                    menuOrKeyboardActionController.onMenuOrKeyboardAction(
+                            R.id.focus_url_bar, false);
                     return true;
                 case CTRL | SHIFT | KeyEvent.KEYCODE_B:
-                    activity.onMenuOrKeyboardAction(R.id.all_bookmarks_menu_id, false);
+                    menuOrKeyboardActionController.onMenuOrKeyboardAction(
+                            R.id.all_bookmarks_menu_id, false);
                     return true;
                 case KeyEvent.KEYCODE_BOOKMARK:
                 case CTRL | KeyEvent.KEYCODE_D:
-                    activity.onMenuOrKeyboardAction(R.id.bookmark_this_page_id, false);
+                    menuOrKeyboardActionController.onMenuOrKeyboardAction(
+                            R.id.bookmark_this_page_id, false);
                     return true;
                 case CTRL | KeyEvent.KEYCODE_H:
-                    activity.onMenuOrKeyboardAction(R.id.open_history_menu_id, false);
+                    menuOrKeyboardActionController.onMenuOrKeyboardAction(
+                            R.id.open_history_menu_id, false);
                     return true;
                 case CTRL | KeyEvent.KEYCODE_P:
-                    activity.onMenuOrKeyboardAction(R.id.print_id, false);
+                    menuOrKeyboardActionController.onMenuOrKeyboardAction(R.id.print_id, false);
                     return true;
                 case CTRL | KeyEvent.KEYCODE_PLUS:
                 case CTRL | KeyEvent.KEYCODE_EQUALS:
                 case CTRL | SHIFT | KeyEvent.KEYCODE_PLUS:
                 case CTRL | SHIFT | KeyEvent.KEYCODE_EQUALS:
                 case KeyEvent.KEYCODE_ZOOM_IN:
-                    ZoomController.zoomIn(getCurrentWebContents(activity));
+                    ZoomController.zoomIn(currentWebContents);
                     return true;
                 case CTRL | KeyEvent.KEYCODE_MINUS:
                 case KeyEvent.KEYCODE_ZOOM_OUT:
-                    ZoomController.zoomOut(getCurrentWebContents(activity));
+                    ZoomController.zoomOut(currentWebContents);
                     return true;
                 case CTRL | KeyEvent.KEYCODE_0:
-                    ZoomController.zoomReset(getCurrentWebContents(activity));
+                    ZoomController.zoomReset(currentWebContents);
                     return true;
                 case SHIFT | CTRL | KeyEvent.KEYCODE_R:
                 case CTRL | KeyEvent.KEYCODE_R:
                 case SHIFT | KeyEvent.KEYCODE_F5:
                 case KeyEvent.KEYCODE_F5:
-                    Tab tab = activity.getActivityTab();
-                    if (tab != null) {
+                    if (currentTab != null) {
                         if ((keyCodeAndMeta & SHIFT) == SHIFT) {
-                            tab.reloadIgnoringCache();
+                            currentTab.reloadIgnoringCache();
                         } else {
-                            tab.reload();
+                            currentTab.reload();
                         }
 
-                        if (activity.getToolbarManager() != null
-                                && tab.getWebContents() != null
-                                && tab.getWebContents().focusLocationBarByDefault()) {
-                            activity.getToolbarManager().revertLocationBarChanges();
-                        } else {
-                            if (tab.getView() != null) tab.getView().requestFocus();
+                        if (toolbarManager != null && currentWebContents != null
+                                && currentWebContents.focusLocationBarByDefault()) {
+                            toolbarManager.revertLocationBarChanges();
+                        } else if (currentTab.getView() != null) {
+                            currentTab.getView().requestFocus();
                         }
                     }
                     return true;
                 case ALT | KeyEvent.KEYCODE_DPAD_LEFT:
-                    tab = activity.getActivityTab();
-                    if (tab != null && tab.canGoBack()) tab.goBack();
+                    if (currentTab != null && currentTab.canGoBack()) currentTab.goBack();
                     return true;
                 case ALT | KeyEvent.KEYCODE_DPAD_RIGHT:
                 case KeyEvent.KEYCODE_FORWARD:
                 case KeyEvent.KEYCODE_BUTTON_START:
-                    tab = activity.getActivityTab();
-                    if (tab != null && tab.canGoForward()) tab.goForward();
+                    if (currentTab != null && currentTab.canGoForward()) currentTab.goForward();
                     return true;
                 case CTRL | SHIFT | KeyEvent.KEYCODE_SLASH:  // i.e. Ctrl+?
-                    activity.onMenuOrKeyboardAction(R.id.help_id, false);
+                    menuOrKeyboardActionController.onMenuOrKeyboardAction(R.id.help_id, false);
                     return true;
             }
         }
 
         return false;
     }
-
-    private static WebContents getCurrentWebContents(ChromeActivity activity) {
-        return activity.getActivityTab().getWebContents();
-    }
 }
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 467cd4c..508ad95 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
@@ -7,6 +7,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.Activity;
+import android.app.Fragment;
 import android.app.SearchManager;
 import android.app.assist.AssistContent;
 import android.content.ActivityNotFoundException;
@@ -2507,6 +2508,12 @@
         }
     }
 
+    @Override
+    public void onAttachFragment(Fragment fragment) {
+        if (mRootUiCoordinator == null) return;
+        mRootUiCoordinator.onAttachFragment(fragment);
+    }
+
     private boolean shouldDisableHardwareAcceleration() {
         // Low end devices should disable hardware acceleration for memory gains.
         return SysUtils.isLowEndDevice();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java
index 5c24525..94d3023 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java
@@ -104,43 +104,91 @@
         void onCouldNotNormalize(AutofillProfile profile);
     }
 
+    @VisibleForTesting
+    static class ValueWithStatus {
+        static final ValueWithStatus EMPTY = new ValueWithStatus("", VerificationStatus.NO_STATUS);
+
+        private final String mValue;
+        private final @VerificationStatus int mStatus;
+
+        ValueWithStatus(String value, @VerificationStatus int status) {
+            mValue = value;
+            mStatus = status;
+        }
+
+        String getValue() {
+            return mValue;
+        }
+
+        @VerificationStatus
+        int getStatus() {
+            return mStatus;
+        }
+    }
+
     /**
      * Autofill address information.
+     * The creation and/or modification of an AutofillProfile is assumed to involve the user (e.g.
+     * data reviewed by the user in the {@link
+     * org.chromium.chrome.browser.autofill.settings.AddressEditor}), therefore all new values gain
+     * {@link VerificationStatus.USER_VERIFIED} status.
      */
     public static class AutofillProfile {
         private String mGUID;
         private String mOrigin;
         private boolean mIsLocal;
-        private String mHonorificPrefix;
-        private String mFullName;
-        private String mCompanyName;
-        private String mStreetAddress;
-        private String mRegion;
-        private String mLocality;
-        private String mDependentLocality;
-        private String mPostalCode;
-        private String mSortingCode;
-        private String mCountryCode;
-        private String mPhoneNumber;
-        private String mEmailAddress;
+        private ValueWithStatus mHonorificPrefix;
+        private ValueWithStatus mFullName;
+        private ValueWithStatus mCompanyName;
+        private ValueWithStatus mStreetAddress;
+        private ValueWithStatus mRegion;
+        private ValueWithStatus mLocality;
+        private ValueWithStatus mDependentLocality;
+        private ValueWithStatus mPostalCode;
+        private ValueWithStatus mSortingCode;
+        private ValueWithStatus mCountryCode;
+        private ValueWithStatus mPhoneNumber;
+        private ValueWithStatus mEmailAddress;
         private String mLabel;
         private String mLanguageCode;
 
         @CalledByNative("AutofillProfile")
-        public static AutofillProfile create(String guid, String origin, boolean isLocal,
-                String honorificPrefix, String fullName, String companyName, String streetAddress,
-                String region, String locality, String dependentLocality, String postalCode,
-                String sortingCode, String country, String phoneNumber, String emailAddress,
-                String languageCode) {
-            return new AutofillProfile(guid, origin, isLocal, honorificPrefix, fullName,
-                    companyName, streetAddress, region, locality, dependentLocality, postalCode,
-                    sortingCode, country, phoneNumber, emailAddress, languageCode);
+        private static AutofillProfile create(String guid, String origin, boolean isLocal,
+                String honorificPrefix, @VerificationStatus int honorificPrefixStatus,
+                String fullName, @VerificationStatus int fullNameStatus, String companyName,
+                @VerificationStatus int companyNameStatus, String streetAddress,
+                @VerificationStatus int streetAddressStatus, String region,
+                @VerificationStatus int regionStatus, String locality,
+                @VerificationStatus int localityStatus, String dependentLocality,
+                @VerificationStatus int dependentLocalityStatus, String postalCode,
+                @VerificationStatus int postalCodeStatus, String sortingCode,
+                @VerificationStatus int sortingCodeStatus, String countryCode,
+                @VerificationStatus int countryCodeStatus, String phoneNumber,
+                @VerificationStatus int phoneNumberStatus, String emailAddress,
+                @VerificationStatus int emailAddressStatus, String languageCode) {
+            return new AutofillProfile(guid, origin, isLocal,
+                    new ValueWithStatus(honorificPrefix, honorificPrefixStatus),
+                    new ValueWithStatus(fullName, fullNameStatus),
+                    new ValueWithStatus(companyName, companyNameStatus),
+                    new ValueWithStatus(streetAddress, streetAddressStatus),
+                    new ValueWithStatus(region, regionStatus),
+                    new ValueWithStatus(locality, localityStatus),
+                    new ValueWithStatus(dependentLocality, dependentLocalityStatus),
+                    new ValueWithStatus(postalCode, postalCodeStatus),
+                    new ValueWithStatus(sortingCode, sortingCodeStatus),
+                    new ValueWithStatus(countryCode, countryCodeStatus),
+                    new ValueWithStatus(phoneNumber, phoneNumberStatus),
+                    new ValueWithStatus(emailAddress, emailAddressStatus), languageCode);
         }
 
-        public AutofillProfile(String guid, String origin, boolean isLocal, String honorificPrefix,
-                String fullName, String companyName, String streetAddress, String region,
-                String locality, String dependentLocality, String postalCode, String sortingCode,
-                String countryCode, String phoneNumber, String emailAddress, String languageCode) {
+        @VisibleForTesting
+        AutofillProfile(String guid, String origin, boolean isLocal,
+                ValueWithStatus honorificPrefix, ValueWithStatus fullName,
+                ValueWithStatus companyName, ValueWithStatus streetAddress, ValueWithStatus region,
+                ValueWithStatus locality, ValueWithStatus dependentLocality,
+                ValueWithStatus postalCode, ValueWithStatus sortingCode,
+                ValueWithStatus countryCode, ValueWithStatus phoneNumber,
+                ValueWithStatus emailAddress, String languageCode) {
             mGUID = guid;
             mOrigin = origin;
             mIsLocal = isLocal;
@@ -160,16 +208,46 @@
         }
 
         /**
+         * Builds a profile with the given values, assuming those are reviewed by the user and thus
+         * are marked {@link VerificationStatus.USER_VERIFIED}.
+         */
+        public AutofillProfile(String guid, String origin, boolean isLocal, String honorificPrefix,
+                String fullName, String companyName, String streetAddress, String region,
+                String locality, String dependentLocality, String postalCode, String sortingCode,
+                String countryCode, String phoneNumber, String emailAddress, String languageCode) {
+            this(guid, origin, isLocal,
+                    new ValueWithStatus(honorificPrefix, VerificationStatus.USER_VERIFIED),
+                    new ValueWithStatus(fullName, VerificationStatus.USER_VERIFIED),
+                    new ValueWithStatus(companyName, VerificationStatus.USER_VERIFIED),
+                    new ValueWithStatus(streetAddress, VerificationStatus.USER_VERIFIED),
+                    new ValueWithStatus(region, VerificationStatus.USER_VERIFIED),
+                    new ValueWithStatus(locality, VerificationStatus.USER_VERIFIED),
+                    new ValueWithStatus(dependentLocality, VerificationStatus.USER_VERIFIED),
+                    new ValueWithStatus(postalCode, VerificationStatus.USER_VERIFIED),
+                    new ValueWithStatus(sortingCode, VerificationStatus.USER_VERIFIED),
+                    new ValueWithStatus(countryCode, VerificationStatus.USER_VERIFIED),
+                    new ValueWithStatus(phoneNumber, VerificationStatus.USER_VERIFIED),
+                    new ValueWithStatus(emailAddress, VerificationStatus.USER_VERIFIED),
+                    languageCode);
+        }
+
+        /**
          * Builds an empty local profile with "settings" origin and country code from the default
-         * locale. All other fields are empty strings, because JNI does not handle null strings.
+         * locale. All other fields are empty strings with {@link VerificationStatus.NO_STATUS},
+         * because JNI does not handle null strings.
          */
         public AutofillProfile() {
             this("" /* guid */, AutofillEditorBase.SETTINGS_ORIGIN /* origin */, true /* isLocal */,
-                    "" /* honorificPrefix */, "" /* fullName */, "" /* companyName */,
-                    "" /* streetAddress */, "" /* region */, "" /* locality */,
-                    "" /* dependentLocality */, "" /* postalCode */, "" /* sortingCode */,
-                    Locale.getDefault().getCountry() /* country */, "" /* phoneNumber */,
-                    "" /* emailAddress */, "" /* languageCode */);
+                    ValueWithStatus.EMPTY /* honorificPrefix */,
+                    ValueWithStatus.EMPTY /* fullName */, ValueWithStatus.EMPTY /* companyName */,
+                    ValueWithStatus.EMPTY /* streetAddress */, ValueWithStatus.EMPTY /* region */,
+                    ValueWithStatus.EMPTY /* locality */,
+                    ValueWithStatus.EMPTY /* dependentLocality */,
+                    ValueWithStatus.EMPTY /* postalCode */, ValueWithStatus.EMPTY /* sortingCode */,
+                    new ValueWithStatus(Locale.getDefault().getCountry(),
+                            VerificationStatus.USER_VERIFIED) /* country */,
+                    ValueWithStatus.EMPTY /* phoneNumber */,
+                    ValueWithStatus.EMPTY /* emailAddress */, "" /* languageCode */);
         }
 
         /* Builds an AutofillProfile that is an exact copy of the one passed as parameter. */
@@ -177,18 +255,27 @@
             mGUID = profile.getGUID();
             mOrigin = profile.getOrigin();
             mIsLocal = profile.getIsLocal();
-            mHonorificPrefix = profile.getHonorificPrefix();
-            mFullName = profile.getFullName();
-            mCompanyName = profile.getCompanyName();
-            mStreetAddress = profile.getStreetAddress();
-            mRegion = profile.getRegion();
-            mLocality = profile.getLocality();
-            mDependentLocality = profile.getDependentLocality();
-            mPostalCode = profile.getPostalCode();
-            mSortingCode = profile.getSortingCode();
-            mCountryCode = profile.getCountryCode();
-            mPhoneNumber = profile.getPhoneNumber();
-            mEmailAddress = profile.getEmailAddress();
+            mHonorificPrefix = new ValueWithStatus(
+                    profile.getHonorificPrefix(), profile.getHonorificPrefixStatus());
+            mFullName = new ValueWithStatus(profile.getFullName(), profile.getFullNameStatus());
+            mCompanyName =
+                    new ValueWithStatus(profile.getCompanyName(), profile.getCompanyNameStatus());
+            mStreetAddress = new ValueWithStatus(
+                    profile.getStreetAddress(), profile.getStreetAddressStatus());
+            mRegion = new ValueWithStatus(profile.getRegion(), profile.getRegionStatus());
+            mLocality = new ValueWithStatus(profile.getLocality(), profile.getLocalityStatus());
+            mDependentLocality = new ValueWithStatus(
+                    profile.getDependentLocality(), profile.getDependentLocalityStatus());
+            mPostalCode =
+                    new ValueWithStatus(profile.getPostalCode(), profile.getPostalCodeStatus());
+            mSortingCode =
+                    new ValueWithStatus(profile.getSortingCode(), profile.getSortingCodeStatus());
+            mCountryCode =
+                    new ValueWithStatus(profile.getCountryCode(), profile.getCountryCodeStatus());
+            mPhoneNumber =
+                    new ValueWithStatus(profile.getPhoneNumber(), profile.getPhoneNumberStatus());
+            mEmailAddress =
+                    new ValueWithStatus(profile.getEmailAddress(), profile.getEmailAddressStatus());
             mLanguageCode = profile.getLanguageCode();
             mLabel = profile.getLabel();
         }
@@ -216,37 +303,81 @@
 
         @CalledByNative("AutofillProfile")
         public String getHonorificPrefix() {
-            return mHonorificPrefix;
+            return mHonorificPrefix.getValue();
+        }
+
+        @CalledByNative("AutofillProfile")
+        private @VerificationStatus int getHonorificPrefixStatus() {
+            return mHonorificPrefix.getStatus();
         }
 
         @CalledByNative("AutofillProfile")
         public String getFullName() {
-            return mFullName;
+            return mFullName.getValue();
+        }
+
+        @CalledByNative("AutofillProfile")
+        @VisibleForTesting
+        @VerificationStatus
+        int getFullNameStatus() {
+            return mFullName.getStatus();
         }
 
         @CalledByNative("AutofillProfile")
         public String getCompanyName() {
-            return mCompanyName;
+            return mCompanyName.getValue();
+        }
+
+        @CalledByNative("AutofillProfile")
+        @VerificationStatus
+        int getCompanyNameStatus() {
+            return mCompanyName.getStatus();
         }
 
         @CalledByNative("AutofillProfile")
         public String getStreetAddress() {
-            return mStreetAddress;
+            return mStreetAddress.getValue();
+        }
+
+        @CalledByNative("AutofillProfile")
+        @VisibleForTesting
+        @VerificationStatus
+        int getStreetAddressStatus() {
+            return mStreetAddress.getStatus();
         }
 
         @CalledByNative("AutofillProfile")
         public String getRegion() {
-            return mRegion;
+            return mRegion.getValue();
+        }
+
+        @CalledByNative("AutofillProfile")
+        @VisibleForTesting
+        @VerificationStatus
+        int getRegionStatus() {
+            return mRegion.getStatus();
         }
 
         @CalledByNative("AutofillProfile")
         public String getLocality() {
-            return mLocality;
+            return mLocality.getValue();
+        }
+
+        @CalledByNative("AutofillProfile")
+        @VisibleForTesting
+        @VerificationStatus
+        int getLocalityStatus() {
+            return mLocality.getStatus();
         }
 
         @CalledByNative("AutofillProfile")
         public String getDependentLocality() {
-            return mDependentLocality;
+            return mDependentLocality.getValue();
+        }
+
+        @CalledByNative("AutofillProfile")
+        private @VerificationStatus int getDependentLocalityStatus() {
+            return mDependentLocality.getStatus();
         }
 
         public String getLabel() {
@@ -255,27 +386,54 @@
 
         @CalledByNative("AutofillProfile")
         public String getPostalCode() {
-            return mPostalCode;
+            return mPostalCode.getValue();
+        }
+
+        @CalledByNative("AutofillProfile")
+        @VisibleForTesting
+        @VerificationStatus
+        int getPostalCodeStatus() {
+            return mPostalCode.getStatus();
         }
 
         @CalledByNative("AutofillProfile")
         public String getSortingCode() {
-            return mSortingCode;
+            return mSortingCode.getValue();
+        }
+
+        @CalledByNative("AutofillProfile")
+        private @VerificationStatus int getSortingCodeStatus() {
+            return mSortingCode.getStatus();
         }
 
         @CalledByNative("AutofillProfile")
         public String getCountryCode() {
-            return mCountryCode;
+            return mCountryCode.getValue();
+        }
+
+        @CalledByNative("AutofillProfile")
+        private @VerificationStatus int getCountryCodeStatus() {
+            return mCountryCode.getStatus();
         }
 
         @CalledByNative("AutofillProfile")
         public String getPhoneNumber() {
-            return mPhoneNumber;
+            return mPhoneNumber.getValue();
+        }
+
+        @CalledByNative("AutofillProfile")
+        private @VerificationStatus int getPhoneNumberStatus() {
+            return mPhoneNumber.getStatus();
         }
 
         @CalledByNative("AutofillProfile")
         public String getEmailAddress() {
-            return mEmailAddress;
+            return mEmailAddress.getValue();
+        }
+
+        @CalledByNative("AutofillProfile")
+        private @VerificationStatus int getEmailAddressStatus() {
+            return mEmailAddress.getStatus();
         }
 
         @CalledByNative("AutofillProfile")
@@ -300,51 +458,53 @@
         }
 
         public void setHonorificPrefix(String honorificPrefix) {
-            mHonorificPrefix = honorificPrefix;
+            mHonorificPrefix =
+                    new ValueWithStatus(honorificPrefix, VerificationStatus.USER_VERIFIED);
         }
 
         public void setFullName(String fullName) {
-            mFullName = fullName;
+            mFullName = new ValueWithStatus(fullName, VerificationStatus.USER_VERIFIED);
         }
 
         public void setCompanyName(String companyName) {
-            mCompanyName = companyName;
+            mCompanyName = new ValueWithStatus(companyName, VerificationStatus.USER_VERIFIED);
         }
 
         public void setStreetAddress(String streetAddress) {
-            mStreetAddress = streetAddress;
+            mStreetAddress = new ValueWithStatus(streetAddress, VerificationStatus.USER_VERIFIED);
         }
 
         public void setRegion(String region) {
-            mRegion = region;
+            mRegion = new ValueWithStatus(region, VerificationStatus.USER_VERIFIED);
         }
 
         public void setLocality(String locality) {
-            mLocality = locality;
+            mLocality = new ValueWithStatus(locality, VerificationStatus.USER_VERIFIED);
         }
 
         public void setDependentLocality(String dependentLocality) {
-            mDependentLocality = dependentLocality;
+            mDependentLocality =
+                    new ValueWithStatus(dependentLocality, VerificationStatus.USER_VERIFIED);
         }
 
         public void setPostalCode(String postalCode) {
-            mPostalCode = postalCode;
+            mPostalCode = new ValueWithStatus(postalCode, VerificationStatus.USER_VERIFIED);
         }
 
         public void setSortingCode(String sortingCode) {
-            mSortingCode = sortingCode;
+            mSortingCode = new ValueWithStatus(sortingCode, VerificationStatus.USER_VERIFIED);
         }
 
         public void setCountryCode(String countryCode) {
-            mCountryCode = countryCode;
+            mCountryCode = new ValueWithStatus(countryCode, VerificationStatus.USER_VERIFIED);
         }
 
         public void setPhoneNumber(String phoneNumber) {
-            mPhoneNumber = phoneNumber;
+            mPhoneNumber = new ValueWithStatus(phoneNumber, VerificationStatus.USER_VERIFIED);
         }
 
         public void setEmailAddress(String emailAddress) {
-            mEmailAddress = emailAddress;
+            mEmailAddress = new ValueWithStatus(emailAddress, VerificationStatus.USER_VERIFIED);
         }
 
         public void setLanguageCode(String languageCode) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java
index 3839a19..f039106 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChrome.java
@@ -26,7 +26,6 @@
 import org.chromium.chrome.browser.compositor.overlays.strip.StripLayoutHelperManager;
 import org.chromium.chrome.browser.device.DeviceClassManager;
 import org.chromium.chrome.browser.fullscreen.FullscreenManager;
-import org.chromium.chrome.browser.layouts.LayoutStateProvider;
 import org.chromium.chrome.browser.layouts.components.VirtualView;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabLaunchType;
@@ -86,7 +85,6 @@
      * @param tabContentManagerSupplier Supplier of the {@link TabContentManager} instance.
      * @param layerTitleCacheSupplier Supplier of the {@link LayerTitleCache}.
      * @param overviewModeBehaviorSupplier Supplier of the {@link OverviewModeBehavior}.
-     * @param layoutStateProviderOneshotSupplier Supplier of the {@link LayoutStateProvider}.
      * @param topUiThemeColorProvider {@link ThemeColorProvider} for top UI.
      */
     public LayoutManagerChrome(LayoutManagerHost host, ViewGroup contentContainer,
@@ -94,10 +92,9 @@
             ObservableSupplier<TabContentManager> tabContentManagerSupplier,
             Supplier<LayerTitleCache> layerTitleCacheSupplier,
             OneshotSupplierImpl<OverviewModeBehavior> overviewModeBehaviorSupplier,
-            OneshotSupplierImpl<LayoutStateProvider> layoutStateProviderOneshotSupplier,
             Supplier<TopUiThemeColorProvider> topUiThemeColorProvider) {
         super(host, contentContainer, tabContentManagerSupplier, layerTitleCacheSupplier,
-                layoutStateProviderOneshotSupplier, topUiThemeColorProvider);
+                topUiThemeColorProvider);
         Context context = host.getContext();
         LayoutRenderHost renderHost = host.getLayoutRenderHost();
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromePhone.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromePhone.java
index 866caea4..2b8bbe48 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromePhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromePhone.java
@@ -14,7 +14,6 @@
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.compositor.layouts.phone.SimpleAnimationLayout;
 import org.chromium.chrome.browser.device.DeviceClassManager;
-import org.chromium.chrome.browser.layouts.LayoutStateProvider;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
@@ -42,7 +41,6 @@
      * @param tabContentManagerSupplier Supplier of the {@link TabContentManager} instance.
      * @param layerTitleCacheSupplier Supplier of the {@link LayerTitleCache}.
      * @param overviewModeBehaviorSupplier Supplier of the {@link OverviewModeBehavior}.
-     * @param layoutStateProviderOneshotSupplier Supplier of the {@link LayoutStateProvider}.
      * @param topUiThemeColorProvider {@link ThemeColorProvider} for top UI.
      */
     public LayoutManagerChromePhone(LayoutManagerHost host, ViewGroup contentContainer,
@@ -50,11 +48,9 @@
             ObservableSupplier<TabContentManager> tabContentManagerSupplier,
             Supplier<LayerTitleCache> layerTitleCacheSupplier,
             OneshotSupplierImpl<OverviewModeBehavior> overviewModeBehaviorSupplier,
-            OneshotSupplierImpl<LayoutStateProvider> layoutStateProviderOneshotSupplier,
             Supplier<TopUiThemeColorProvider> topUiThemeColorProvider) {
         super(host, contentContainer, true, startSurface, tabContentManagerSupplier,
-                layerTitleCacheSupplier, overviewModeBehaviorSupplier,
-                layoutStateProviderOneshotSupplier, topUiThemeColorProvider);
+                layerTitleCacheSupplier, overviewModeBehaviorSupplier, topUiThemeColorProvider);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromeTablet.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromeTablet.java
index 80fc087..556b5bc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromeTablet.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromeTablet.java
@@ -13,7 +13,6 @@
 import org.chromium.chrome.browser.compositor.LayerTitleCache;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.compositor.overlays.strip.StripLayoutHelperManager;
-import org.chromium.chrome.browser.layouts.LayoutStateProvider;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
@@ -40,18 +39,15 @@
      * @param tabContentManagerSupplier Supplier of the {@link TabContentManager} instance.
      * @param layerTitleCacheSupplier Supplier of the {@link LayerTitleCache}.
      * @param overviewModeBehaviorSupplier Supplier of the {@link OverviewModeBehavior}.
-     * @param layoutStateProviderOneshotSupplier Supplier of the {@link LayoutStateProvider}.
      * @param topUiThemeColorProvider {@link ThemeColorProvider} for top UI.
      */
     public LayoutManagerChromeTablet(LayoutManagerHost host, ViewGroup contentContainer,
             ObservableSupplier<TabContentManager> tabContentManagerSupplier,
             Supplier<LayerTitleCache> layerTitleCacheSupplier,
             OneshotSupplierImpl<OverviewModeBehavior> overviewModeBehaviorSupplier,
-            OneshotSupplierImpl<LayoutStateProvider> layoutStateProviderOneshotSupplier,
             Supplier<TopUiThemeColorProvider> topUiThemeColorProvider) {
         super(host, contentContainer, false, null, tabContentManagerSupplier,
-                layerTitleCacheSupplier, overviewModeBehaviorSupplier,
-                layoutStateProviderOneshotSupplier, topUiThemeColorProvider);
+                layerTitleCacheSupplier, overviewModeBehaviorSupplier, topUiThemeColorProvider);
 
         mTabStripLayoutHelperManager = new StripLayoutHelperManager(host.getContext(), this,
                 mHost.getLayoutRenderHost(), () -> mTitleCache, layerTitleCacheSupplier);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java
index e6ed389e..7d9184c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerImpl.java
@@ -20,7 +20,6 @@
 import org.chromium.base.TraceEvent;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
-import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsUtils;
@@ -37,7 +36,6 @@
 import org.chromium.chrome.browser.gesturenav.HistoryNavigationCoordinator;
 import org.chromium.chrome.browser.layouts.CompositorModelChangeProcessor;
 import org.chromium.chrome.browser.layouts.EventFilter;
-import org.chromium.chrome.browser.layouts.LayoutStateProvider;
 import org.chromium.chrome.browser.layouts.LayoutType;
 import org.chromium.chrome.browser.layouts.ManagedLayoutManager;
 import org.chromium.chrome.browser.layouts.SceneOverlay;
@@ -169,9 +167,6 @@
     /** A map of {@link SceneOverlay} to its position relative to the others. */
     private Map<Class, Integer> mOverlayOrderMap = new HashMap<>();
 
-    /** The supplier used to supply the LayoutStateProvider. */
-    private final OneshotSupplierImpl<LayoutStateProvider> mLayoutStateProviderOneshotSupplier;
-
     /** The supplier of {@link ThemeColorProvider} for top UI. */
     private final Supplier<TopUiThemeColorProvider> mTopUiThemeColorProvider;
 
@@ -256,19 +251,15 @@
      * @param contentContainer A {@link ViewGroup} for Android views to be bound to.
      * @param tabContentManagerSupplier Supplier of the {@link TabContentManager} instance.
      * @param layerTitleCacheSupplier A supplier of the cache of title textures.
-     * @param layoutStateProviderOneshotSupplier Supplier used to supply the {@link
-     *         LayoutStateProvider}.
      * @param topUiThemeColorProvider {@link ThemeColorProvider} for top UI.
      */
     public LayoutManagerImpl(LayoutManagerHost host, ViewGroup contentContainer,
             ObservableSupplier<TabContentManager> tabContentManagerSupplier,
             Supplier<LayerTitleCache> layerTitleCacheSupplier,
-            OneshotSupplierImpl<LayoutStateProvider> layoutStateProviderOneshotSupplier,
             Supplier<TopUiThemeColorProvider> topUiThemeColorProvider) {
         mHost = host;
         mPxToDp = 1.f / mHost.getContext().getResources().getDisplayMetrics().density;
         mTabContentManagerSupplier = tabContentManagerSupplier;
-        mLayoutStateProviderOneshotSupplier = layoutStateProviderOneshotSupplier;
         mLayerTitleCacheSupplier = layerTitleCacheSupplier;
         mTopUiThemeColorProvider = topUiThemeColorProvider;
         mContext = host.getContext();
@@ -297,8 +288,6 @@
 
         mFrameRequestSupplier =
                 new CompositorModelChangeProcessor.FrameRequestSupplier(this::requestUpdate);
-
-        mLayoutStateProviderOneshotSupplier.set(this);
     }
 
     /**
@@ -904,18 +893,22 @@
     public void startHiding(int nextTabId, boolean hintAtTabSelection) {
         requestUpdate();
         if (hintAtTabSelection) {
-            notifyObserversOnTabSelectionHinted(nextTabId);
-
             // TODO(crbug.com/1108496): Remove after migrates to LayoutStateObserver.
             for (SceneChangeObserver observer : mSceneChangeObservers) {
                 observer.onTabSelectionHinted(nextTabId);
             }
+
+            for (LayoutStateObserver observer : mLayoutObservers) {
+                observer.onTabSelectionHinted(nextTabId);
+            }
         }
 
         Layout layoutBeingHidden = getActiveLayout();
-        notifyObserversLayoutStartedHiding(layoutBeingHidden.getLayoutType(),
-                shouldShowToolbarAnimationOnHide(layoutBeingHidden, nextTabId),
-                shouldDelayHideAnimation(layoutBeingHidden));
+        for (LayoutStateObserver observer : mLayoutObservers) {
+            observer.onStartedHiding(layoutBeingHidden.getLayoutType(),
+                    shouldShowToolbarAnimationOnHide(layoutBeingHidden, nextTabId),
+                    shouldDelayHideAnimation(layoutBeingHidden));
+        }
     }
 
     @Override
@@ -925,7 +918,9 @@
         assert mNextActiveLayout != null : "Need to have a next active layout.";
         if (mNextActiveLayout != null) {
             // Notify LayoutObservers the active layout is finished hiding.
-            notifyObserversLayoutFinishedHiding(getActiveLayout().getLayoutType());
+            for (LayoutStateObserver observer : mLayoutObservers) {
+                observer.onFinishedHiding(getActiveLayout().getLayoutType());
+            }
 
             startShowing(mNextActiveLayout, true);
         }
@@ -934,7 +929,9 @@
     @Override
     public void doneShowing() {
         // Notify LayoutObservers the active layout is finished showing.
-        notifyObserversLayoutFinishedShowing(getActiveLayout().getLayoutType());
+        for (LayoutStateObserver observer : mLayoutObservers) {
+            observer.onFinishedShowing(getActiveLayout().getLayoutType());
+        }
     }
 
     /**
@@ -993,8 +990,10 @@
             observer.onSceneChange(getActiveLayout());
         }
 
-        notifyObserversLayoutStartedShowing(
-                layout.getLayoutType(), shouldShowToolbarAnimationOnShow(animate));
+        for (LayoutStateObserver observer : mLayoutObservers) {
+            observer.onStartedShowing(
+                    layout.getLayoutType(), shouldShowToolbarAnimationOnShow(animate));
+        }
     }
 
     /**
@@ -1011,6 +1010,11 @@
         return layout == mActiveLayout;
     }
 
+    @Override
+    public int getActiveLayoutType() {
+        return getActiveLayout() != null ? getActiveLayout().getLayoutType() : LayoutType.NONE;
+    }
+
     /**
      * Get a list of virtual views for accessibility.
      *
@@ -1124,48 +1128,6 @@
         mLayoutObservers.removeObserver(listener);
     }
 
-    protected final void notifyObserversLayoutStartedShowing(
-            @LayoutType int layoutType, boolean showToolbar) {
-        mLayoutStateProviderOneshotSupplier.onAvailable((unused) -> {
-            for (LayoutStateObserver observer : mLayoutObservers) {
-                observer.onStartedShowing(layoutType, showToolbar);
-            }
-        });
-    }
-
-    protected final void notifyObserversLayoutFinishedShowing(@LayoutType int layoutType) {
-        mLayoutStateProviderOneshotSupplier.onAvailable((unused) -> {
-            for (LayoutStateObserver observer : mLayoutObservers) {
-                observer.onFinishedShowing(layoutType);
-            }
-        });
-    }
-
-    protected final void notifyObserversLayoutStartedHiding(
-            @LayoutType int layoutType, boolean showToolbar, boolean delayAnimation) {
-        mLayoutStateProviderOneshotSupplier.onAvailable((unused) -> {
-            for (LayoutStateObserver observer : mLayoutObservers) {
-                observer.onStartedHiding(layoutType, showToolbar, delayAnimation);
-            }
-        });
-    }
-
-    protected final void notifyObserversLayoutFinishedHiding(@LayoutType int layoutType) {
-        mLayoutStateProviderOneshotSupplier.onAvailable((unused) -> {
-            for (LayoutStateObserver observer : mLayoutObservers) {
-                observer.onFinishedHiding(layoutType);
-            }
-        });
-    }
-
-    protected final void notifyObserversOnTabSelectionHinted(int tabId) {
-        mLayoutStateProviderOneshotSupplier.onAvailable((unused) -> {
-            for (LayoutStateObserver observer : mLayoutObservers) {
-                observer.onTabSelectionHinted(tabId);
-            }
-        });
-    }
-
     protected boolean shouldShowToolbarAnimationOnShow(boolean isAnimate) {
         return false;
     }
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 7cfc41c..79cf4e0 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
@@ -466,8 +466,9 @@
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
-        Boolean result = KeyboardShortcuts.dispatchKeyEvent(
-                event, this, mToolbarCoordinator.toolbarIsInitialized());
+        Boolean result = KeyboardShortcuts.dispatchKeyEvent(event,
+                mToolbarCoordinator.toolbarIsInitialized(), getFullscreenManager(),
+                /* menuOrKeyboardActionController= */ this);
         return result != null ? result : super.dispatchKeyEvent(event);
     }
 
@@ -494,7 +495,8 @@
         if (!mToolbarCoordinator.toolbarIsInitialized()) {
             return super.onKeyDown(keyCode, event);
         }
-        return KeyboardShortcuts.onKeyDown(event, this, true, false)
+        return KeyboardShortcuts.onKeyDown(event, true, false, getTabModelSelector(),
+                       /* menuOrKeyboardActionController= */ this, getToolbarManager())
                 || super.onKeyDown(keyCode, event);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabCompositorContentInitializer.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabCompositorContentInitializer.java
index 174a8267..abc768b4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabCompositorContentInitializer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabCompositorContentInitializer.java
@@ -9,7 +9,6 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.ObservableSupplier;
-import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.chrome.browser.compositor.CompositorViewHolder;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
@@ -79,7 +78,7 @@
                     if (mCompositorViewHolder.get() == null) return null;
                     return mCompositorViewHolder.get().getLayerTitleCache();
                 },
-                new OneshotSupplierImpl<>(), () -> mTopUiThemeColorProvider);
+                () -> mTopUiThemeColorProvider);
         // clang-format on
 
         mCompositorViewHolderInitializer.initializeCompositorContent(layoutDriver,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java
index c3a7096..1a812c6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadController.java
@@ -9,6 +9,8 @@
 import android.content.pm.PackageManager;
 import android.util.Pair;
 
+import androidx.annotation.NonNull;
+
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Callback;
 import org.chromium.base.ContextUtils;
@@ -158,11 +160,10 @@
     /**
      * Requests the stoarge permission from Java.
      * @param delegate The permission delegate to be used for file access request.
-     * TODO(crbug/1209228): Make the delegate non-null.
      * @param callback Callback to notify if the permission is granted or not.
      */
     public static void requestFileAccessPermission(
-            AndroidPermissionDelegate delegate, final Callback<Boolean> callback) {
+            @NonNull AndroidPermissionDelegate delegate, final Callback<Boolean> callback) {
         requestFileAccessPermissionHelper(delegate, result -> {
             boolean granted = result.first;
             String permissions = result.second;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManager.java b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManager.java
index 4ca26a1..0439b0c4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManager.java
@@ -194,11 +194,13 @@
         mDisplayListener = new DisplayListener() {
             @Override
             public void onDisplayAdded(int displayId) {
+                if (!isNormalDisplay(displayId)) return;
                 sActivityTypePendingMergeOnStartup = null;
             }
 
             @Override
             public void onDisplayRemoved(int displayId) {
+                if (!isNormalDisplay(displayId)) return;
                 if (displayId == mDisplayId) {
                     // If activity on removed display is in the foreground, do tab merge.
                     // Note that activity on removed display may be recreated because of the
@@ -221,7 +223,7 @@
 
             @Override
             public void onDisplayChanged(int displayId) {
-                if (displayId == mDisplayId) return;
+                if (displayId == mDisplayId || !isNormalDisplay(displayId)) return;
                 List<Integer> ids = sTestDisplayIds != null
                     ? sTestDisplayIds
                     : ApiCompatibilityUtils.getTargetableDisplayIds(mActivity);
@@ -233,6 +235,30 @@
         displayManager.registerDisplayListener(mDisplayListener, null);
     }
 
+    /**
+     * Check if the given display is what Chrome can use for showing activity/tab.
+     * It should be either the default display, or secondary one such as external,
+     * wireless display.
+     * @param id ID of the display.
+     * @return {@code true} if the display is a normal one.
+     */
+    private boolean isNormalDisplay(int id) {
+        if (id == Display.DEFAULT_DISPLAY || sTestDisplayIds != null) return true;
+        Display display = getDisplayFromId(id);
+        return (display != null && (display.getFlags() & Display.FLAG_PRESENTATION) != 0);
+    }
+
+    private @Nullable Display getDisplayFromId(int id) {
+        DisplayManager displayManager =
+                (DisplayManager) mActivity.getSystemService(Context.DISPLAY_SERVICE);
+        if (displayManager == null) return null;
+        Display[] displays = displayManager.getDisplays();
+        for (Display display : displays) {
+            if (display.getDisplayId() == id) return display;
+        }
+        return null;
+    }
+
     @Override
     public void onResumeWithNative() {
         if (isTabModelMergingEnabled()) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
index f61b4dbb..5d6f975 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
@@ -50,6 +50,7 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.toolbar.VoiceToolbarButtonController;
 import org.chromium.chrome.browser.ui.native_page.NativePage;
 import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
 import org.chromium.chrome.browser.util.KeyNavigationUtil;
@@ -657,6 +658,11 @@
         mShouldShowLensButtonWhenUnfocused = shouldShow;
     }
 
+    /* package */ void setShouldShowMicButtonWhenUnfocusedForTesting(boolean shouldShow) {
+        assert mIsTablet;
+        mShouldShowMicButtonWhenUnfocused = shouldShow;
+    }
+
     /**
      * @param shouldShow Whether buttons should be displayed in the URL bar when it's not
      *                          focused.
@@ -972,12 +978,16 @@
     private boolean shouldShowMicButton() {
         if (mIsTablet && mShouldShowButtonsWhenUnfocused) {
             return mVoiceRecognitionHandler != null
-                    && mVoiceRecognitionHandler.isVoiceSearchEnabled() && mNativeInitialized
+                    && mVoiceRecognitionHandler.isVoiceSearchEnabled()
+                    && !VoiceToolbarButtonController.isToolbarMicEnabled() && mNativeInitialized
                     && (mUrlHasFocus || mIsUrlFocusChangeInProgress);
         } else {
             boolean deleteButtonVisible = shouldShowDeleteButton();
+            boolean canShowMicButton =
+                    !mIsTablet || !VoiceToolbarButtonController.isToolbarMicEnabled();
             return mVoiceRecognitionHandler != null
-                    && mVoiceRecognitionHandler.isVoiceSearchEnabled() && !deleteButtonVisible
+                    && mVoiceRecognitionHandler.isVoiceSearchEnabled() && canShowMicButton
+                    && !deleteButtonVisible
                     && (mUrlHasFocus || mIsUrlFocusChangeInProgress || mUrlFocusChangeFraction > 0f
                             || mShouldShowMicButtonWhenUnfocused);
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeSurveyController.java b/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeSurveyController.java
index d28c2af5..d36a507 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeSurveyController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/survey/ChromeSurveyController.java
@@ -53,12 +53,15 @@
  */
 public class ChromeSurveyController implements InfoBarAnimationListener {
     private static final String TAG = "ChromeSurveyCtrler";
+    private static final int DOWNLOAD_ATTEMPTS_HIST_NUM_BUCKETS = 20;
 
     @VisibleForTesting
     static final long REQUIRED_VISIBILITY_DURATION_MS = 5000;
     @VisibleForTesting
     public static final String COMMAND_LINE_PARAM_NAME = "survey_override_site_id";
     @VisibleForTesting
+    static final String MAX_DOWNLOAD_ATTEMPTS = "max-download-attempts";
+    @VisibleForTesting
     static final String MAX_NUMBER = "max-number";
     @VisibleForTesting
     static final String SITE_ID_PARAM_NAME = "site-id";
@@ -110,6 +113,7 @@
 
     private final String mTriggerId;
     private final String mPrefKeyPromptDisplayed;
+    private final String mPrefKeyDownloadAttempts;
     private final @Nullable ActivityLifecycleDispatcher mLifecycleDispatcher;
 
     @VisibleForTesting
@@ -118,6 +122,8 @@
         mTriggerId = triggerId;
         mPrefKeyPromptDisplayed =
                 ChromePreferenceKeys.CHROME_SURVEY_PROMPT_DISPLAYED_TIMESTAMP.createKey(mTriggerId);
+        mPrefKeyDownloadAttempts =
+                ChromePreferenceKeys.CHROME_SURVEY_DOWNLOAD_ATTEMPTS.createKey(mTriggerId);
         mLifecycleDispatcher = lifecycleDispatcher;
     }
 
@@ -263,6 +269,19 @@
         return false;
     }
 
+    private void recordDownloadAttempted() {
+        SharedPreferencesManager.getInstance().incrementInt(mPrefKeyDownloadAttempts);
+    }
+
+    /** Return whether the number of download attempts falls within the max cap. */
+    private boolean isDownloadAttemptAllowed() {
+        int maxDownloadAttempts = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
+                ChromeFeatureList.CHROME_SURVEY_NEXT_ANDROID, MAX_DOWNLOAD_ATTEMPTS, 0);
+        int downloadAttemptsMade =
+                SharedPreferencesManager.getInstance().readInt(mPrefKeyDownloadAttempts, 0);
+        return maxDownloadAttempts <= 0 || downloadAttemptsMade < maxDownloadAttempts;
+    }
+
     /**
      * Checks if the tab is valid for a survey (i.e. not null, no null webcontents & not incognito).
      * @param tab The tab to be checked.
@@ -386,6 +405,7 @@
             public void onSurveyTriggered() {
                 recordInfoBarClosingState(InfoBarClosingState.ACCEPTED_SURVEY);
                 recordInfoBarDisplayed();
+                recordSurveyAccepted();
             }
 
             @Override
@@ -433,6 +453,14 @@
                 "Android.Survey.InfoBarClosingState", value, InfoBarClosingState.NUM_ENTRIES);
     }
 
+    private void recordSurveyAccepted() {
+        int downloadAttemptsMade =
+                SharedPreferencesManager.getInstance().readInt(mPrefKeyDownloadAttempts, 0);
+        RecordHistogram.recordLinearCountHistogram("Android.Survey.DownloadAttemptsBeforeAccepted",
+                downloadAttemptsMade, 1, DOWNLOAD_ATTEMPTS_HIST_NUM_BUCKETS,
+                DOWNLOAD_ATTEMPTS_HIST_NUM_BUCKETS + 1);
+    }
+
     static class StartDownloadIfEligibleTask extends AsyncTask<Boolean> {
         ChromeSurveyController mController;
         final TabModelSelector mSelector;
@@ -452,13 +480,16 @@
                         FilteringResult.FORCE_SURVEY_ON_COMMAND_PRESENT);
                 return true;
             }
-            return !mController.hasInfoBarBeenDisplayed()
+            return !mController.hasInfoBarBeenDisplayed() && mController.isDownloadAttemptAllowed()
                     && mController.isRandomlySelectedForSurvey();
         }
 
         @Override
         protected void onPostExecute(Boolean result) {
-            if (result) mController.startDownload(ContextUtils.getApplicationContext(), mSelector);
+            if (result) {
+                mController.startDownload(ContextUtils.getApplicationContext(), mSelector);
+                mController.recordDownloadAttempted();
+            }
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 7156ecc..82aa59a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -812,16 +812,7 @@
         mLayoutStateObserver = new LayoutStateProvider.LayoutStateObserver() {
             @Override
             public void onStartedShowing(@LayoutType int layoutType, boolean showToolbar) {
-                if (layoutType == LayoutType.TAB_SWITCHER) {
-                    mLocationBarModel.setIsShowingTabSwitcher(true);
-                    mToolbar.setTabSwitcherMode(true, showToolbar, false);
-                    updateButtonStatus();
-                    if (mLocationBarModel.shouldShowLocationBarInOverviewMode()) {
-                        assert mLocationBar instanceof LocationBarCoordinator;
-                        ((LocationBarCoordinator) mLocationBar).startAutocompletePrefetch();
-                    }
-                }
-                mToolbar.setContentAttached(layoutType == LayoutType.BROWSING);
+                updateForLayout(layoutType, showToolbar, false);
             }
 
             @Override
@@ -895,6 +886,28 @@
     }
 
     /**
+     * Handle a layout change event.
+     * @param layoutType The layout being switched to.
+     * @param showToolbar Whether the toolbar should be shown.
+     * @param shouldFocusOmnibox Whether we should attempt to focus the omnibox.
+     */
+    private void updateForLayout(
+            @LayoutType int layoutType, boolean showToolbar, boolean shouldFocusOmnibox) {
+        if (layoutType == LayoutType.TAB_SWITCHER) {
+            mLocationBarModel.setIsShowingTabSwitcher(true);
+            mToolbar.setTabSwitcherMode(true, showToolbar, false);
+            updateButtonStatus();
+            if (mLocationBarModel.shouldShowLocationBarInOverviewMode()) {
+                assert mLocationBar instanceof LocationBarCoordinator;
+                ((LocationBarCoordinator) mLocationBar).startAutocompletePrefetch();
+            }
+        }
+        mToolbar.setContentAttached(layoutType == LayoutType.BROWSING);
+
+        if (shouldFocusOmnibox) maybeFocusOmnibox(layoutType, mActivityTabProvider.get());
+    }
+
+    /**
      * May set Omnibox focused if the Tab has the flag to require focusing the Omnibox.
      */
     private void maybeFocusOmnibox(@LayoutType int layout, Tab tab) {
@@ -1767,6 +1780,15 @@
 
         mLayoutStateProvider = layoutStateProvider;
         mLayoutStateProvider.addObserver(mLayoutStateObserver);
+
+        if (mLayoutStateProvider.isLayoutVisible(LayoutType.TAB_SWITCHER)) {
+            // TODO(1210431): We shouldn't need to post this. Instead we should wait until the
+            //                dependencies are ready. This logic was introduced to move asynchronous
+            //                observer events from the infra (LayoutManager) into the feature using
+            //                it.
+            mControlContainer.post(() -> updateForLayout(LayoutType.TAB_SWITCHER, true, true));
+        }
+
         mAppThemeColorProvider.setLayoutStateProvider(mLayoutStateProvider);
         mLocationBarModel.setLayoutStateProvider(mLayoutStateProvider);
         if (mBottomControlsCoordinatorSupplier.get() != null) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java
index 16779d6..1e16b7b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java
@@ -29,6 +29,7 @@
 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.TAB_SWITCHER_BUTTON_IS_VISIBLE;
 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.TRANSLATION_Y;
 
+import android.os.Handler;
 import android.view.View;
 import android.view.View.OnClickListener;
 
@@ -337,6 +338,14 @@
                 }
             }
         };
+
+        if (mLayoutStateProvider.isLayoutVisible(LayoutType.TAB_SWITCHER)) {
+            new Handler().post(() -> {
+                mLayoutStateObserver.onStartedShowing(LayoutType.TAB_SWITCHER, true);
+                mLayoutStateObserver.onFinishedShowing(LayoutType.TAB_SWITCHER);
+            });
+        }
+
         mLayoutStateProvider.addObserver(mLayoutStateObserver);
     }
 
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 6c92d82..a2f6546 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
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.ui;
 
+import android.app.Fragment;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -67,6 +68,7 @@
 import org.chromium.chrome.browser.share.ShareDelegate;
 import org.chromium.chrome.browser.share.ShareDelegate.ShareOrigin;
 import org.chromium.chrome.browser.share.ShareUtils;
+import org.chromium.chrome.browser.share.qrcode.QrCodeDialog;
 import org.chromium.chrome.browser.tab.AccessibilityVisibilityHandler;
 import org.chromium.chrome.browser.tab.AutofillSessionLifetimeController;
 import org.chromium.chrome.browser.tab.Tab;
@@ -295,6 +297,13 @@
         return mTopUiThemeColorProvider;
     }
 
+    public void onAttachFragment(Fragment fragment) {
+        if (fragment instanceof QrCodeDialog) {
+            QrCodeDialog qrCodeDialog = (QrCodeDialog) fragment;
+            qrCodeDialog.setAndroidPermissionDelegate(mActivity.getWindowAndroid());
+        }
+    }
+
     @Override
     public void onDestroy() {
         // TODO(meiliang): Understand why we need to set most of the class member instances to null
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/PersonalDataManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/PersonalDataManagerTest.java
index 76e8ebd..a3d3359 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/PersonalDataManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/PersonalDataManagerTest.java
@@ -21,6 +21,7 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.chrome.browser.autofill.PersonalDataManager.ValueWithStatus;
 import org.chromium.chrome.test.ChromeBrowserTestRule;
 
 import java.util.LinkedList;
@@ -227,6 +228,55 @@
     @Test
     @SmallTest
     @Feature({"Autofill"})
+    public void testRespectVerificationStatuses() throws TimeoutException {
+        AutofillProfile profileWithDifferentStatuses = new AutofillProfile("" /* guid */,
+                "" /* origin */, true,
+                new ValueWithStatus("" /* honorific prefix */, VerificationStatus.NO_STATUS),
+                new ValueWithStatus("John Smith", VerificationStatus.PARSED),
+                new ValueWithStatus("" /* company */, VerificationStatus.NO_STATUS),
+                new ValueWithStatus("1 Main\nApt A", VerificationStatus.FORMATTED),
+                new ValueWithStatus("Quebec", VerificationStatus.OBSERVED),
+                new ValueWithStatus("Montreal", VerificationStatus.USER_VERIFIED),
+                new ValueWithStatus("" /* dependent locality */, VerificationStatus.NO_STATUS),
+                new ValueWithStatus("H3B 2Y5", VerificationStatus.SERVER_PARSED),
+                new ValueWithStatus("" /* sorting code */, VerificationStatus.NO_STATUS),
+                new ValueWithStatus("Canada", VerificationStatus.USER_VERIFIED),
+                new ValueWithStatus("" /* phone */, VerificationStatus.NO_STATUS),
+                new ValueWithStatus("" /* email */, VerificationStatus.NO_STATUS),
+                "" /* language code */);
+        String guid = mHelper.setProfile(profileWithDifferentStatuses);
+        Assert.assertEquals(1, mHelper.getNumberOfProfilesForSettings());
+
+        AutofillProfile storedProfile = mHelper.getProfile(guid);
+        // When converted to C++ and back the verification statuses for name and address components
+        // should be preserved.
+        Assert.assertEquals(VerificationStatus.PARSED, storedProfile.getFullNameStatus());
+        Assert.assertEquals(VerificationStatus.FORMATTED, storedProfile.getStreetAddressStatus());
+        Assert.assertEquals(VerificationStatus.OBSERVED, storedProfile.getRegionStatus());
+        Assert.assertEquals(VerificationStatus.USER_VERIFIED, storedProfile.getLocalityStatus());
+        Assert.assertEquals(VerificationStatus.SERVER_PARSED, storedProfile.getPostalCodeStatus());
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Autofill"})
+    public void testValuesSetInProfileGainUserVerifiedStatus() {
+        AutofillProfile profile = new AutofillProfile();
+        Assert.assertEquals(VerificationStatus.NO_STATUS, profile.getFullNameStatus());
+        Assert.assertEquals(VerificationStatus.NO_STATUS, profile.getStreetAddressStatus());
+        Assert.assertEquals(VerificationStatus.NO_STATUS, profile.getLocalityStatus());
+
+        profile.setFullName("Homer Simpson");
+        Assert.assertEquals(VerificationStatus.USER_VERIFIED, profile.getFullNameStatus());
+        profile.setStreetAddress("123 Main St.");
+        Assert.assertEquals(VerificationStatus.USER_VERIFIED, profile.getStreetAddressStatus());
+        profile.setLocality("Springfield");
+        Assert.assertEquals(VerificationStatus.USER_VERIFIED, profile.getLocalityStatus());
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Autofill"})
     public void testMultilineStreetAddress() throws TimeoutException {
         final String streetAddress1 = "Chez Mireille COPEAU Appartment. 2\n"
                 + "Entree A Batiment Jonquille\n"
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java
index c427b86e..13996b1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java
@@ -121,8 +121,6 @@
 
     private float mDpToPx;
 
-    private OneshotSupplierImpl<LayoutStateProvider> mLayoutStateProviderSupplier;
-
     class LayoutObserverCallbackHelper extends CallbackHelper {
         @LayoutType
         public int layoutType;
@@ -213,13 +211,9 @@
         OneshotSupplierImpl<OverviewModeBehavior> overviewModeBehaviorSupplier =
                 new OneshotSupplierImpl<>();
 
-        if (mLayoutStateProviderSupplier == null) {
-            mLayoutStateProviderSupplier = new OneshotSupplierImpl<>();
-        }
-
         mManagerPhone = new LayoutManagerChromePhone(layoutManagerHost, container, mStartSurface,
                 tabContentManagerSupplier, null, overviewModeBehaviorSupplier,
-                mLayoutStateProviderSupplier, () -> mTopUiThemeColorProvider);
+                () -> mTopUiThemeColorProvider);
         verify(mStartSurfaceController)
                 .addOverviewModeObserver(mStartSurfaceOverviewModeCaptor.capture());
 
@@ -472,17 +466,10 @@
             performToolbarSideSwipe(ScrollDirection.RIGHT);
             Assert.assertEquals(
                     LayoutType.TOOLBAR_SWIPE, mManager.getActiveLayout().getLayoutType());
-            Assert.assertTrue(
-                    mLayoutStateProviderSupplier.get().isLayoutVisible(LayoutType.TOOLBAR_SWIPE));
+            Assert.assertTrue(mManager.isLayoutVisible(LayoutType.TOOLBAR_SWIPE));
         });
 
-        // The |startedShowingCallback| callCount 0 is reserved for the default layout during
-        // initialization. Because LayoutManager does not explicitly hide the old layout when a new
-        // layout is forced to show, the callCount for |finishedShowingCallback|,
-        // |startedHidingCallback|, and |finishedHidingCallback| are still 0.
-        // TODO(crbug.com/1108496): update the callCount when LayoutManager explicitly hide the old
-        // layout.
-        startedShowingCallback.waitForCallback(1);
+        startedShowingCallback.waitForCallback(0);
         Assert.assertEquals(LayoutType.TOOLBAR_SWIPE, startedShowingCallback.layoutType);
 
         finishedShowingCallback.waitForCallback(0);
@@ -491,8 +478,7 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             finishToolbarSideSwipe();
             Assert.assertEquals(LayoutType.BROWSING, mManager.getActiveLayout().getLayoutType());
-            Assert.assertTrue(
-                    mLayoutStateProviderSupplier.get().isLayoutVisible(LayoutType.BROWSING));
+            Assert.assertTrue(mManager.isLayoutVisible(LayoutType.BROWSING));
         });
 
         startedHidingCallback.waitForCallback(0);
@@ -501,7 +487,7 @@
         finishedHidingCallback.waitForCallback(0);
         Assert.assertEquals(LayoutType.TOOLBAR_SWIPE, finishedHidingCallback.layoutType);
 
-        startedShowingCallback.waitForCallback(2);
+        startedShowingCallback.waitForCallback(1);
         Assert.assertEquals(LayoutType.BROWSING, startedShowingCallback.layoutType);
 
         finishedShowingCallback.waitForCallback(1);
@@ -529,8 +515,7 @@
                     "layoutManager is way too long to end motion", simulateTime(mManager, 1000));
             Assert.assertEquals(
                     LayoutType.TAB_SWITCHER, mManager.getActiveLayout().getLayoutType());
-            Assert.assertTrue(
-                    mLayoutStateProviderSupplier.get().isLayoutVisible(LayoutType.TAB_SWITCHER));
+            Assert.assertTrue(mManager.isLayoutVisible(LayoutType.TAB_SWITCHER));
         });
 
         // The |startedShowingCallback| callCount 0 is reserved for the default layout during
@@ -550,8 +535,7 @@
             Assert.assertTrue(
                     "layoutManager is way too long to end motion", simulateTime(mManager, 1000));
 
-            Assert.assertTrue(
-                    mLayoutStateProviderSupplier.get().isLayoutVisible(LayoutType.BROWSING));
+            Assert.assertTrue(mManager.isLayoutVisible(LayoutType.BROWSING));
         });
 
         startedHidingCallback.waitForCallback(0);
@@ -589,17 +573,10 @@
             Assert.assertThat("Incorrect active LayoutType",
                     mManager.getActiveLayout().getLayoutType(), is(LayoutType.SIMPLE_ANIMATION));
             Assert.assertThat("Incorrect active Layout",
-                    mLayoutStateProviderSupplier.get().isLayoutVisible(LayoutType.SIMPLE_ANIMATION),
-                    is(true));
+                    mManager.isLayoutVisible(LayoutType.SIMPLE_ANIMATION), is(true));
         });
 
-        // The |startedShowingCallback| callCount 0 is reserved for the default layout during
-        // initialization. Because LayoutManager does not explicitly hide the old layout when a new
-        // layout is forced to show, the callCount for |finishedShowingCallback|,
-        // |startedHidingCallback|, and |finishedHidingCallback| are still 0.
-        // TODO(crbug.com/1108496): update the callCount when LayoutManager explicitly hide the old
-        // layout.
-        startedShowingCallback.waitForCallback(1);
+        startedShowingCallback.waitForCallback(0);
         Assert.assertThat("startedShowingCallback with incorrect LayoutType",
                 startedShowingCallback.layoutType, is(LayoutType.SIMPLE_ANIMATION));
 
@@ -626,7 +603,7 @@
         Assert.assertThat("finishedHidingCallback with incorrectLayoutType",
                 finishedHidingCallback.layoutType, is(LayoutType.SIMPLE_ANIMATION));
 
-        startedShowingCallback.waitForCallback(2);
+        startedShowingCallback.waitForCallback(1);
         Assert.assertThat("startedShowingCallback with incorrectLayoutType",
                 startedShowingCallback.layoutType, is(LayoutType.BROWSING));
 
@@ -641,46 +618,46 @@
             LayoutObserverCallbackHelper startedHidingCallback,
             LayoutObserverCallbackHelper finishedHidingCallback) throws TimeoutException {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            mLayoutStateProviderSupplier = new OneshotSupplierImpl<>();
+            initializeLayoutManagerPhone(2, 0);
+            mManager.addObserver(new LayoutStateProvider.LayoutStateObserver() {
+                @Override
+                public void onStartedShowing(int layoutType, boolean showToolbar) {
+                    Log.d(TAG, "Started to show: " + layoutType);
+                    startedShowingCallback.layoutType = layoutType;
+                    startedShowingCallback.notifyCalled();
+                }
 
-            mLayoutStateProviderSupplier.onAvailable((layoutStateProvider) -> {
-                layoutStateProvider.addObserver(new LayoutStateProvider.LayoutStateObserver() {
-                    @Override
-                    public void onStartedShowing(int layoutType, boolean showToolbar) {
-                        Log.d(TAG, "Started to show: " + layoutType);
-                        startedShowingCallback.layoutType = layoutType;
-                        startedShowingCallback.notifyCalled();
-                    }
+                @Override
+                public void onFinishedShowing(int layoutType) {
+                    Log.d(TAG, "finished to show: " + layoutType);
+                    finishedShowingCallback.layoutType = layoutType;
+                    finishedShowingCallback.notifyCalled();
+                }
 
-                    @Override
-                    public void onFinishedShowing(int layoutType) {
-                        Log.d(TAG, "finished to show: " + layoutType);
-                        finishedShowingCallback.layoutType = layoutType;
-                        finishedShowingCallback.notifyCalled();
-                    }
+                @Override
+                public void onStartedHiding(
+                        int layoutType, boolean showToolbar, boolean delayAnimation) {
+                    Log.d(TAG, "Started to hide: " + layoutType);
+                    startedHidingCallback.layoutType = layoutType;
+                    startedHidingCallback.notifyCalled();
+                }
 
-                    @Override
-                    public void onStartedHiding(
-                            int layoutType, boolean showToolbar, boolean delayAnimation) {
-                        Log.d(TAG, "Started to hide: " + layoutType);
-                        startedHidingCallback.layoutType = layoutType;
-                        startedHidingCallback.notifyCalled();
-                    }
-
-                    @Override
-                    public void onFinishedHiding(int layoutType) {
-                        Log.d(TAG, "finished to hide: " + layoutType);
-                        finishedHidingCallback.layoutType = layoutType;
-                        finishedHidingCallback.notifyCalled();
-                    }
-                });
+                @Override
+                public void onFinishedHiding(int layoutType) {
+                    Log.d(TAG, "finished to hide: " + layoutType);
+                    finishedHidingCallback.layoutType = layoutType;
+                    finishedHidingCallback.notifyCalled();
+                }
             });
 
-            initializeLayoutManagerPhone(2, 0);
             Assert.assertEquals(LayoutType.BROWSING, mManager.getActiveLayout().getLayoutType());
         });
 
-        startedShowingCallback.waitForCallback(0);
+        if (mManager.isLayoutVisible(LayoutType.BROWSING)) {
+            startedShowingCallback.layoutType = LayoutType.BROWSING;
+        } else {
+            startedShowingCallback.waitForCallback(0);
+        }
         Assert.assertEquals(LayoutType.BROWSING, startedShowingCallback.layoutType);
     }
 
@@ -692,19 +669,15 @@
         CallbackHelper tabSelectionHintedCallback = new CallbackHelper();
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            mLayoutStateProviderSupplier = new OneshotSupplierImpl<>();
-
-            mLayoutStateProviderSupplier.onAvailable((layoutStateProvider) -> {
-                layoutStateProvider.addObserver(new LayoutStateProvider.LayoutStateObserver() {
-                    @Override
-                    public void onTabSelectionHinted(int tabId) {
-                        Log.d(TAG, "onTabSelectionHinted");
-                        tabSelectionHintedCallback.notifyCalled();
-                    }
-                });
+            initializeLayoutManagerPhone(2, 0);
+            mManager.addObserver(new LayoutStateProvider.LayoutStateObserver() {
+                @Override
+                public void onTabSelectionHinted(int tabId) {
+                    Log.d(TAG, "onTabSelectionHinted");
+                    tabSelectionHintedCallback.notifyCalled();
+                }
             });
 
-            initializeLayoutManagerPhone(2, 0);
             mManager.showOverview(true);
 
             Assert.assertTrue(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java
index 054ce6468..0654131 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java
@@ -297,7 +297,7 @@
                 mActivityTestRule.getActivity().getLayoutManager().hideOverview(false);
             }
         });
-        LayoutTestUtils.waitForLayout(
-                mActivityTestRule.getActivity().getLayoutManager(), LayoutType.TAB_SWITCHER);
+        LayoutTestUtils.waitForLayout(mActivityTestRule.getActivity().getLayoutManager(),
+                inSwitcher ? LayoutType.TAB_SWITCHER : LayoutType.BROWSING);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelMergingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelMergingTest.java
index d72c902..817b97c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelMergingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelMergingTest.java
@@ -530,6 +530,7 @@
         waitForActivityStateChange(ActivityState.RESUMED, mActivity2, false);
         waitForActivityStateChange(ActivityState.RESUMED, mActivity1, true);
 
+        MultiInstanceManager.setTestDisplayIds(Collections.singletonList(0));
         m1.setCurrentDisplayIdForTesting(0);
         m2.setCurrentDisplayIdForTesting(1);
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java
index d233903..596d6d8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java
@@ -653,7 +653,8 @@
             }
         });
 
-        LayoutTestUtils.waitForLayout(mActivity.getLayoutManager(), LayoutType.TAB_SWITCHER);
+        LayoutTestUtils.waitForLayout(mActivity.getLayoutManager(),
+                shown ? LayoutType.TAB_SWITCHER : LayoutType.BROWSING);
         ThreadUtils.runOnUiThreadBlocking(mTestSupport::endAllAnimations);
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/SceneOverlayTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/SceneOverlayTest.java
index 95bd9bec..89b2127 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/SceneOverlayTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/SceneOverlayTest.java
@@ -5,8 +5,6 @@
 package org.chromium.chrome.browser.compositor.layouts;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -23,11 +21,9 @@
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.supplier.ObservableSupplier;
-import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchPanel;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
-import org.chromium.chrome.browser.layouts.LayoutStateProvider;
 import org.chromium.chrome.browser.layouts.SceneOverlay;
 import org.chromium.chrome.browser.theme.TopUiThemeColorProvider;
 import org.chromium.chrome.browser.toolbar.bottom.ScrollingBottomViewSceneLayer;
@@ -59,9 +55,6 @@
     private ObservableSupplier<TabContentManager> mTabContentManagerSupplier;
 
     @Mock
-    private OneshotSupplierImpl<LayoutStateProvider> mLayoutStateProviderOneshotSupplier;
-
-    @Mock
     private TopUiThemeColorProvider mTopUiThemeColorProvider;
 
     private LayoutManagerImpl mLayoutManager;
@@ -73,11 +66,9 @@
         when(mLayoutManagerHost.getContext()).thenReturn(mContext);
         when(mContext.getResources()).thenReturn(mResources);
         when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
-        doNothing().when(mLayoutStateProviderOneshotSupplier).set(any());
 
         mLayoutManager = new LayoutManagerImpl(mLayoutManagerHost, mContainerView,
-                mTabContentManagerSupplier, null, mLayoutStateProviderOneshotSupplier,
-                () -> mTopUiThemeColorProvider);
+                mTabContentManagerSupplier, null, () -> mTopUiThemeColorProvider);
     }
 
     @Test
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java
index acf2d61..bdf6d7b1 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java
@@ -99,6 +99,7 @@
                 LocationBarMediatorTest.GSAStateShadow.class,
                 LocationBarMediatorTest.DownloadUtilsShadow.class})
 @Features.EnableFeatures(ChromeFeatureList.OMNIBOX_ASSISTANT_VOICE_SEARCH)
+@Features.DisableFeatures(ChromeFeatureList.VOICE_BUTTON_IN_TOP_TOOLBAR)
 public class LocationBarMediatorTest {
     @Implements(UrlUtilities.class)
     static class ShadowUrlUtilities {
@@ -897,6 +898,20 @@
 
     @Test
     public void testButtonVisibility_phone() {
+        // Regression test for phones: toolbar mic visibility shouldn't impact the location
+        // bar mic.
+        verifyPhoneMicButtonVisibility();
+    }
+
+    @Test
+    @Features.EnableFeatures(ChromeFeatureList.VOICE_BUTTON_IN_TOP_TOOLBAR)
+    public void testButtonVisibility_toolbarMicEnabled_phone() {
+        // Regression test for phones: toolbar mic visibility shouldn't impact the location
+        // bar mic.
+        verifyPhoneMicButtonVisibility();
+    }
+
+    private void verifyPhoneMicButtonVisibility() {
         VoiceRecognitionHandler voiceRecognitionHandler = mock(VoiceRecognitionHandler.class);
         mMediator.setVoiceRecognitionHandlerForTesting(voiceRecognitionHandler);
         mMediator.onFinishNativeInitialization();
@@ -918,10 +933,39 @@
     }
 
     @Test
+    public void testMicButtonVisibility_toolbarMicDisabled_tablet() {
+        verifyMicButtonVisibilityWhenFocusChanges(true);
+    }
+
+    @Test
+    @Features.EnableFeatures(ChromeFeatureList.VOICE_BUTTON_IN_TOP_TOOLBAR)
+    public void testMicButtonVisibility_toolbarMicEnabled_tablet() {
+        verifyMicButtonVisibilityWhenFocusChanges(false);
+    }
+
+    // Sets up and executes a test for visibility of a mic button on a tablet.
+    // The mic button should not be visible if toolbar mic is visible as well.
+    private void verifyMicButtonVisibilityWhenFocusChanges(boolean shouldBeVisible) {
+        VoiceRecognitionHandler voiceRecognitionHandler = mock(VoiceRecognitionHandler.class);
+        mTabletMediator.setVoiceRecognitionHandlerForTesting(voiceRecognitionHandler);
+        mTabletMediator.onFinishNativeInitialization();
+        mTabletMediator.setShouldShowButtonsWhenUnfocusedForTablet(true);
+        mTabletMediator.setIsUrlBarFocusedWithoutAnimationsForTesting(true);
+        mTabletMediator.onUrlFocusChange(true);
+        doReturn("").when(mUrlCoordinator).getTextWithAutocomplete();
+        doReturn(true).when(voiceRecognitionHandler).isVoiceSearchEnabled();
+        Mockito.reset(mLocationBarTablet);
+
+        mTabletMediator.updateButtonVisibility();
+        verify(mLocationBarTablet).setMicButtonVisibility(shouldBeVisible);
+    }
+
+    @Test
     public void testButtonVisibility_showMicUnfocused() {
         VoiceRecognitionHandler voiceRecognitionHandler = mock(VoiceRecognitionHandler.class);
         mMediator.setVoiceRecognitionHandlerForTesting(voiceRecognitionHandler);
         mMediator.onFinishNativeInitialization();
+        mTabletMediator.setShouldShowButtonsWhenUnfocusedForTablet(false);
         mMediator.setShouldShowMicButtonWhenUnfocusedForPhone(true);
         doReturn(true).when(voiceRecognitionHandler).isVoiceSearchEnabled();
 
@@ -930,12 +974,37 @@
     }
 
     @Test
+    public void testButtonVisibility_showMicUnfocused_toolbarMicDisabled_tablet() {
+        verifyMicButtonVisibilityWhenShowMicUnfocused(true);
+    }
+
+    @Test
+    @Features.EnableFeatures(ChromeFeatureList.VOICE_BUTTON_IN_TOP_TOOLBAR)
+    public void testButtonVisibility_showMicUnfocused_toolbarMicEnabled_tablet() {
+        verifyMicButtonVisibilityWhenShowMicUnfocused(false);
+    }
+
+    private void verifyMicButtonVisibilityWhenShowMicUnfocused(boolean shouldBeVisible) {
+        mTabletMediator.onFinishNativeInitialization();
+        mTabletMediator.setShouldShowButtonsWhenUnfocusedForTablet(false);
+        mTabletMediator.setShouldShowMicButtonWhenUnfocusedForTesting(true);
+        VoiceRecognitionHandler voiceRecognitionHandler = mock(VoiceRecognitionHandler.class);
+        mTabletMediator.setVoiceRecognitionHandlerForTesting(voiceRecognitionHandler);
+        doReturn(true).when(voiceRecognitionHandler).isVoiceSearchEnabled();
+        Mockito.reset(mLocationBarTablet);
+
+        mTabletMediator.updateButtonVisibility();
+        verify(mLocationBarTablet).setMicButtonVisibility(shouldBeVisible);
+    }
+
+    @Test
     public void testButtonVisibility_tablet() {
         doReturn(mTab).when(mLocationBarDataProvider).getTab();
         mTabletMediator.onFinishNativeInitialization();
         Mockito.reset(mLocationBarTablet);
         mTabletMediator.updateButtonVisibility();
 
+        verify(mLocationBarTablet).setMicButtonVisibility(false);
         verify(mLocationBarTablet).setBookmarkButtonVisibility(true);
         verify(mLocationBarTablet).setSaveOfflineButtonVisibility(true, true);
     }
@@ -948,6 +1017,7 @@
         Mockito.reset(mLocationBarTablet);
         mTabletMediator.updateButtonVisibility();
 
+        verify(mLocationBarTablet).setMicButtonVisibility(false);
         verify(mLocationBarTablet).setBookmarkButtonVisibility(false);
         verify(mLocationBarTablet).setSaveOfflineButtonVisibility(false, true);
     }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerFlowTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerFlowTest.java
index db64006..5833761 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerFlowTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerFlowTest.java
@@ -144,7 +144,8 @@
 
     private final TestSurveyController mTestSurveyController = new TestSurveyController();
 
-    private String mPrefKey;
+    private String mPrefKeyPromptShown;
+    private String mPrefKeyDownloadAttempts;
     private SharedPreferencesManager mSharedPreferencesManager;
 
     private TabModelSelectorObserver mTabModelSelectorObserver;
@@ -155,7 +156,8 @@
         ShadowChromeFeatureList.sEnableSurvey = true;
         ShadowChromeFeatureList.sParamValues.put(
                 ChromeSurveyController.SITE_ID_PARAM_NAME, TEST_TRIGGER_ID);
-        ShadowChromeFeatureList.sParamValues.put(ChromeSurveyController.MAX_NUMBER, "99");
+        // By setting MAX_NUMBER to 1, #isRandomSelectedBySurvey is always true.
+        ShadowChromeFeatureList.sParamValues.put(ChromeSurveyController.MAX_NUMBER, "1");
         ShadowInfoBarContainer.sInfoBarContainer = mMockInfoBarContainer;
         ShadowSurveyInfoBar.sShowInfoBarCallback = new PayloadCallbackHelper<>();
 
@@ -166,8 +168,11 @@
         SurveyController.setInstanceForTesting(mTestSurveyController);
 
         ChromeSurveyController.forceIsUMAEnabledForTesting(true);
-        mPrefKey = ChromePreferenceKeys.CHROME_SURVEY_PROMPT_DISPLAYED_TIMESTAMP.createKey(
-                TEST_TRIGGER_ID);
+        mPrefKeyPromptShown =
+                ChromePreferenceKeys.CHROME_SURVEY_PROMPT_DISPLAYED_TIMESTAMP.createKey(
+                        TEST_TRIGGER_ID);
+        mPrefKeyDownloadAttempts =
+                ChromePreferenceKeys.CHROME_SURVEY_DOWNLOAD_ATTEMPTS.createKey(TEST_TRIGGER_ID);
         mSharedPreferencesManager = SharedPreferencesManager.getInstance();
     }
 
@@ -176,6 +181,7 @@
         ChromeSurveyController.forceIsUMAEnabledForTesting(false);
         ShadowChromeFeatureList.sParamValues.clear();
         ShadowChromeFeatureList.sEnableSurvey = false;
+        ShadowRecordHistogram.reset();
 
         CommandLine.getInstance().removeSwitch(ChromeSurveyController.COMMAND_LINE_PARAM_NAME);
         CommandLine.getInstance().removeSwitch(ChromeSwitches.CHROME_FORCE_ENABLE_SURVEY);
@@ -210,31 +216,27 @@
 
     @Test
     public void testStartDownloadIfEligibleTask() {
+        assertDownloadAttempted(false);
         initializeChromeSurveyController();
-
-        Assert.assertEquals("Download should be triggered.", 1,
-                mTestSurveyController.downloadIfApplicableCallback.getCallCount());
+        assertDownloadAttempted(true);
     }
 
     @Test
     public void testStartDownloadIfEligibleTask_ShowedBefore() {
         CommandLine.getInstance().removeSwitch(ChromeSwitches.CHROME_FORCE_ENABLE_SURVEY);
-        mSharedPreferencesManager.writeLong(mPrefKey, 1000L);
+        mSharedPreferencesManager.writeLong(mPrefKeyPromptShown, 1000L);
 
         initializeChromeSurveyController();
-
-        Assert.assertEquals("Download should not trigger for user that has seen the survey prompt.",
-                0, mTestSurveyController.downloadIfApplicableCallback.getCallCount());
+        assertDownloadAttempted(false);
     }
 
     @Test
     public void testStartDownloadIfEligibleTask_ShowedBefore_ForceEnabled() {
-        mSharedPreferencesManager.writeLong(mPrefKey, 1000L);
+        mSharedPreferencesManager.writeLong(mPrefKeyPromptShown, 1000L);
 
+        assertDownloadAttempted(false);
         initializeChromeSurveyController();
-
-        Assert.assertEquals("Download should be triggered.", 1,
-                mTestSurveyController.downloadIfApplicableCallback.getCallCount());
+        assertDownloadAttempted(true);
     }
 
     @Test
@@ -244,9 +246,7 @@
                 ChromePreferenceKeys.PRIVACY_METRICS_REPORTING, false);
 
         initializeChromeSurveyController();
-
-        Assert.assertEquals("Download should not be triggered.", 0,
-                mTestSurveyController.downloadIfApplicableCallback.getCallCount());
+        assertDownloadAttempted(false);
     }
 
     @Test
@@ -255,13 +255,52 @@
         mSharedPreferencesManager.writeBoolean(
                 ChromePreferenceKeys.PRIVACY_METRICS_REPORTING, true);
 
+        assertDownloadAttempted(false);
         initializeChromeSurveyController();
+        assertDownloadAttempted(true);
+    }
 
-        Assert.assertEquals("Download should not be triggered.", 1,
+    @Test
+    public void testStartDownloadIfEligibleTask_DownloadCapZero() {
+        CommandLine.getInstance().removeSwitch(ChromeSwitches.CHROME_FORCE_ENABLE_SURVEY);
+        ShadowChromeFeatureList.sParamValues.put(ChromeSurveyController.MAX_DOWNLOAD_ATTEMPTS, "0");
+
+        initializeChromeSurveyController();
+        assertDownloadAttempted(true);
+    }
+
+    @Test
+    public void testStartDownloadIfEligibleTask_DownloadWithinCap() {
+        CommandLine.getInstance().removeSwitch(ChromeSwitches.CHROME_FORCE_ENABLE_SURVEY);
+        ShadowChromeFeatureList.sParamValues.put(
+                ChromeSurveyController.MAX_DOWNLOAD_ATTEMPTS, "99");
+
+        assertDownloadAttempted(false);
+        initializeChromeSurveyController();
+        assertDownloadAttempted(true);
+    }
+
+    @Test
+    public void testStartDownloadIfEligibleTask_DownloadReachCap() {
+        CommandLine.getInstance().removeSwitch(ChromeSwitches.CHROME_FORCE_ENABLE_SURVEY);
+        ShadowChromeFeatureList.sParamValues.put(ChromeSurveyController.MAX_DOWNLOAD_ATTEMPTS, "2");
+        mSharedPreferencesManager.writeInt(mPrefKeyDownloadAttempts, 2);
+
+        initializeChromeSurveyController();
+        Assert.assertEquals("Download should not be triggered.", 0,
                 mTestSurveyController.downloadIfApplicableCallback.getCallCount());
     }
 
     @Test
+    public void testStartDownloadIfEligibleTask_DownloadCapZero_ForceEnable() {
+        ShadowChromeFeatureList.sParamValues.put(ChromeSurveyController.MAX_DOWNLOAD_ATTEMPTS, "0");
+
+        assertDownloadAttempted(false);
+        initializeChromeSurveyController();
+        assertDownloadAttempted(true);
+    }
+
+    @Test
     public void testPresentSurvey_ValidTab_SurveyInfobarDelegate() {
         presentSurveyInfoBarInValidTab();
     }
@@ -353,6 +392,22 @@
 
         surveyInfoBarDelegate.onSurveyTriggered();
         assertInfoBarClosingStateRecorded(InfoBarClosingState.ACCEPTED_SURVEY);
+        assertDownloadAttemptRecordedWithSample(1);
+        assertInfoBarDisplayedRecorded();
+    }
+
+    @Test
+    public void testSurveyInfoBarDelegate_onSurveyTriggered_DownloadBefore() {
+        final int downloadAttempted = 3;
+        mSharedPreferencesManager.writeInt(mPrefKeyDownloadAttempts, downloadAttempted);
+
+        presentSurveyInfoBarInValidTab();
+        SurveyInfoBarDelegate surveyInfoBarDelegate =
+                ShadowSurveyInfoBar.sShowInfoBarCallback.getOnlyPayloadBlocking();
+
+        surveyInfoBarDelegate.onSurveyTriggered();
+        assertInfoBarClosingStateRecorded(InfoBarClosingState.ACCEPTED_SURVEY);
+        assertDownloadAttemptRecordedWithSample(downloadAttempted + 1);
         assertInfoBarDisplayedRecorded();
     }
 
@@ -499,11 +554,29 @@
 
     private void assertInfoBarDisplayedRecorded() {
         Assert.assertTrue("SharedPreference for InfoBarShown is not recorded.",
-                mSharedPreferencesManager.contains(mPrefKey));
+                SharedPreferencesManager.getInstance().contains(mPrefKeyPromptShown));
     }
 
     private void assertInfoBarDisplayedNotRecorded(String reason) {
-        Assert.assertFalse(reason, mSharedPreferencesManager.contains(mPrefKey));
+        Assert.assertFalse(
+                reason, SharedPreferencesManager.getInstance().contains(mPrefKeyPromptShown));
+    }
+
+    private void assertDownloadAttempted(boolean attempted) {
+        int expectedCount = attempted ? 1 : 0;
+        Assert.assertEquals("Times of download triggered does not match.", expectedCount,
+                mTestSurveyController.downloadIfApplicableCallback.getCallCount());
+        Assert.assertEquals("Download attempt count is not recorded as expected.", expectedCount,
+                mSharedPreferencesManager.readInt(mPrefKeyDownloadAttempts));
+    }
+
+    private void assertDownloadAttemptRecordedWithSample(int sample) {
+        Assert.assertEquals(String.format("<Android.Survey.DownloadAttemptsBeforeAccepted> "
+                                            + "with sample <%d> is not recorded.",
+                                    sample),
+                1,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        "Android.Survey.DownloadAttemptsBeforeAccepted", sample));
     }
 
     private static class TestSurveyController extends SurveyController {
diff --git a/chrome/android/modules/chrome_bundle_tmpl.gni b/chrome/android/modules/chrome_bundle_tmpl.gni
index 1823343..e2c5808 100644
--- a/chrome/android/modules/chrome_bundle_tmpl.gni
+++ b/chrome/android/modules/chrome_bundle_tmpl.gni
@@ -168,6 +168,9 @@
     is_multi_abi = _is_multi_abi
     validate_services = _enable_chrome_module
 
+    # For this to be respected, it must also be set on the base module target.
+    strip_unused_resources = is_official_build
+
     # List of DFMs that are installed by default by wrapper scripts, to make
     # testing easier. This removes the need to manually specify, e.g.,
     # "-m dev_ui" on every install or run.
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 8bdbb5f2..7830c2c 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-93.0.4530.0_rc-r1-merged.afdo.bz2
+chromeos-chrome-amd64-93.0.4531.0_rc-r1-merged.afdo.bz2
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 1f84d79..9d146edb 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -295,6 +295,7 @@
       <!-- Web app intent picker strings -->
       <if expr="is_win or is_macosx or is_linux">
         <part file="protocol_handler_intent_picker_strings.grdp" />
+        <part file="url_handler_intent_picker_strings.grdp" />
       </if>
 
       <!-- Chrome-OS-specific strings -->
@@ -10681,6 +10682,18 @@
     <message name="IDS_FILE_SYSTEM_ACCESS_ORIGIN_SCOPED_READ_PERMISSION_DIRECTORY_TEXT" desc="Text of dialog asking user if they intended to share a particular directory">
       <ph name="ORIGIN">$1<ex>example.com</ex></ph> will be able to view files in <ph name="FOLDERNAME">$2<ex>My Project</ex></ph> until you close all tabs for this site
     </message>
+    <message name="IDS_FILE_SYSTEM_ACCESS_WRITE_PERMISSION_FILE_TEXT" desc="Text of the dialog for confirming origin scoped write access to files using the File System Access API">
+      <ph name="ORIGIN">$1<ex>example.com</ex></ph> will be able to edit <ph name="FILENAME">$2<ex>README.md</ex></ph>
+    </message>
+    <message name="IDS_FILE_SYSTEM_ACCESS_WRITE_PERMISSION_DIRECTORY_TEXT" desc="Text of the dialog for confirming origin scoped write access to a directory using the File System Access API">
+      <ph name="ORIGIN">$1<ex>example.com</ex></ph> will be able to edit files in <ph name="FOLDERNAME">$2<ex>My Project</ex></ph>
+    </message>
+    <message name="IDS_FILE_SYSTEM_ACCESS_READ_PERMISSION_FILE_TEXT" desc="Text of dialog for confirming read access to files using the File System Access API">
+      <ph name="ORIGIN">$1<ex>example.com</ex></ph> will be able to view <ph name="FILENAME">$2<ex>README.md</ex></ph>
+    </message>
+    <message name="IDS_FILE_SYSTEM_ACCESS_READ_PERMISSION_DIRECTORY_TEXT" desc="Text of dialog asking user if they intended to share a particular directory">
+      <ph name="ORIGIN">$1<ex>example.com</ex></ph> will be able to view files in <ph name="FOLDERNAME">$2<ex>My Project</ex></ph>
+    </message>
     <message name="IDS_FILE_SYSTEM_ACCESS_EDIT_FILE_PERMISSION_TITLE" desc="Title of dialog asking user to confirm giving read and write access to a file using the File System Access API">
       Let site edit <ph name="FILE_NAME">$1<ex>README.md</ex></ph>?
     </message>
diff --git a/chrome/app/generated_resources_grd/IDS_FILE_SYSTEM_ACCESS_READ_PERMISSION_DIRECTORY_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_FILE_SYSTEM_ACCESS_READ_PERMISSION_DIRECTORY_TEXT.png.sha1
new file mode 100644
index 0000000..d54bef3
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_FILE_SYSTEM_ACCESS_READ_PERMISSION_DIRECTORY_TEXT.png.sha1
@@ -0,0 +1 @@
+ec3f53e330829c5e749574ffa5ac0bf8bcf64752
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_FILE_SYSTEM_ACCESS_READ_PERMISSION_FILE_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_FILE_SYSTEM_ACCESS_READ_PERMISSION_FILE_TEXT.png.sha1
new file mode 100644
index 0000000..f736c56a
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_FILE_SYSTEM_ACCESS_READ_PERMISSION_FILE_TEXT.png.sha1
@@ -0,0 +1 @@
+c4aef23649559effb8eecd04f301beae0cd354c7
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_FILE_SYSTEM_ACCESS_WRITE_PERMISSION_DIRECTORY_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_FILE_SYSTEM_ACCESS_WRITE_PERMISSION_DIRECTORY_TEXT.png.sha1
new file mode 100644
index 0000000..03a3c654
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_FILE_SYSTEM_ACCESS_WRITE_PERMISSION_DIRECTORY_TEXT.png.sha1
@@ -0,0 +1 @@
+0c665834063c633ea0ea92b3bd0eae57bf1500d6
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_FILE_SYSTEM_ACCESS_WRITE_PERMISSION_FILE_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_FILE_SYSTEM_ACCESS_WRITE_PERMISSION_FILE_TEXT.png.sha1
new file mode 100644
index 0000000..7d672f6
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_FILE_SYSTEM_ACCESS_WRITE_PERMISSION_FILE_TEXT.png.sha1
@@ -0,0 +1 @@
+8ae5add6abe1c825fd9083853abf1e6978d9c6ee
\ No newline at end of file
diff --git a/chrome/app/url_handler_intent_picker_strings.grdp b/chrome/app/url_handler_intent_picker_strings.grdp
new file mode 100644
index 0000000..76ee4d85
--- /dev/null
+++ b/chrome/app/url_handler_intent_picker_strings.grdp
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- URL-Handler-Intent-Picker strings (included from generated_resources.grd). -->
+<grit-part>
+  <message name="IDS_URL_HANDLER_INTENT_PICKER_TITLE" desc="Title for the URL handler intent picker.">
+    Which application do you want to use?
+  </message>
+  <message name="IDS_URL_HANDLER_INTENT_PICKER_REMEMBER_SELECTION" desc="Label for the checkbox in the URL handler intent picker to save the current selection so that the URL handler intent picker will not be shown again.">
+    Remember my choice
+  </message>
+  <message name="IDS_URL_HANDLER_INTENT_PICKER_OK_BUTTON_TEXT" desc="Label for the button in the URL handler intent picker dialog that dismisses the dialog and launches an application or the browser.">
+    Open
+  </message>
+  <message name="IDS_URL_HANDLER_INTENT_PICKER_CANCEL_BUTTON_TEXT" desc="Label for the button in the URL handler intent picker dialog that dismisses the dialog and does not launch an application.">
+    Cancel
+  </message>
+  <message name="IDS_URL_HANDLER_INTENT_PICKER_APP_TITLE" desc="Title for an appplication with profile name as an option in the URL handler intent picker dialog.">
+    <ph name="APP_NAME">$1<ex>Demo App</ex></ph> (<ph name="PROFILE_NAME">$2<ex>Work</ex></ph>)
+  </message>
+  <message name="IDS_URL_HANDLER_INTENT_PICKER_APP_ORIGIN_LABEL" desc="Label for the url origin of each item in the web app list in the URL handler intent picker dialog">
+    Publisher: <ph name="APP_ORIGIN">$1<ex>example.com</ex></ph>
+  </message>
+</grit-part>
\ No newline at end of file
diff --git a/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_APP_ORIGIN_LABEL.png.sha1 b/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_APP_ORIGIN_LABEL.png.sha1
new file mode 100644
index 0000000..96cd31a
--- /dev/null
+++ b/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_APP_ORIGIN_LABEL.png.sha1
@@ -0,0 +1 @@
+40a13b6251935c0554ef427b641e21d2f7e28888
\ No newline at end of file
diff --git a/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_APP_TITLE.png.sha1 b/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_APP_TITLE.png.sha1
new file mode 100644
index 0000000..a6589cf
--- /dev/null
+++ b/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_APP_TITLE.png.sha1
@@ -0,0 +1 @@
+9608d55671802fd8ae3c2b4b2a86647c7b40f24e
\ No newline at end of file
diff --git a/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_CANCEL_BUTTON_TEXT.png.sha1 b/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_CANCEL_BUTTON_TEXT.png.sha1
new file mode 100644
index 0000000..5d3f120
--- /dev/null
+++ b/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_CANCEL_BUTTON_TEXT.png.sha1
@@ -0,0 +1 @@
+342612bcf19974912ad7d228806c4d73689e4ebd
\ No newline at end of file
diff --git a/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_OK_BUTTON_TEXT.png.sha1 b/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_OK_BUTTON_TEXT.png.sha1
new file mode 100644
index 0000000..cd3c299
--- /dev/null
+++ b/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_OK_BUTTON_TEXT.png.sha1
@@ -0,0 +1 @@
+ef4966e91c84df1d2cce46336610042b22acd33f
\ No newline at end of file
diff --git a/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_REMEMBER_SELECTION.png.sha1 b/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_REMEMBER_SELECTION.png.sha1
new file mode 100644
index 0000000..30a3413
--- /dev/null
+++ b/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_REMEMBER_SELECTION.png.sha1
@@ -0,0 +1 @@
+9afee1a236cb08a50a63278cd1b83b5677e71cbb
\ No newline at end of file
diff --git a/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_TITLE.png.sha1 b/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_TITLE.png.sha1
new file mode 100644
index 0000000..e197462
--- /dev/null
+++ b/chrome/app/url_handler_intent_picker_strings_grdp/IDS_URL_HANDLER_INTENT_PICKER_TITLE.png.sha1
@@ -0,0 +1 @@
+70bfb6eec8119a51ace9de1175e0196e2926d76e
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index bfd9846..57b2312 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1662,12 +1662,6 @@
     "site_isolation/site_details.h",
     "speech/chrome_speech_recognition_manager_delegate.cc",
     "speech/chrome_speech_recognition_manager_delegate.h",
-    "speech/network_speech_recognizer.cc",
-    "speech/network_speech_recognizer.h",
-    "speech/speech_recognizer.cc",
-    "speech/speech_recognizer.h",
-    "speech/speech_recognizer_delegate.cc",
-    "speech/speech_recognizer_delegate.h",
     "ssl/certificate_error_reporter.cc",
     "ssl/certificate_error_reporter.h",
     "ssl/chrome_security_blocking_page_factory.cc",
@@ -4570,8 +4564,13 @@
       "speech/cros_speech_recognition_service.h",
       "speech/cros_speech_recognition_service_factory.cc",
       "speech/cros_speech_recognition_service_factory.h",
+      "speech/network_speech_recognizer.cc",
+      "speech/network_speech_recognizer.h",
       "speech/on_device_speech_recognizer.cc",
       "speech/on_device_speech_recognizer.h",
+      "speech/speech_recognizer.cc",
+      "speech/speech_recognizer.h",
+      "speech/speech_recognizer_delegate.h",
       "speech/tts_chromeos.cc",
       "speech/tts_chromeos.h",
       "speech/tts_controller_delegate_impl.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index e2eb9acdc..6034ac6 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -188,6 +188,7 @@
 #include "ui/events/event_switches.h"
 #include "ui/gfx/switches.h"
 #include "ui/gl/buildflags.h"
+#include "ui/gl/gl_features.h"
 #include "ui/gl/gl_switches.h"
 #include "ui/native_theme/native_theme_features.h"
 
@@ -5160,13 +5161,6 @@
      flag_descriptions::kEnableAssistantAppSupportDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::assistant::features::kAssistantAppSupport)},
 
-    {"enable-assistant-media-session-integration",
-     flag_descriptions::kEnableAssistantMediaSessionIntegrationName,
-     flag_descriptions::kEnableAssistantMediaSessionIntegrationDescription,
-     kOsCrOS,
-     FEATURE_VALUE_TYPE(
-         chromeos::assistant::features::kEnableMediaSessionIntegration)},
-
     {"enable-quick-answers", flag_descriptions::kEnableQuickAnswersName,
      flag_descriptions::kEnableQuickAnswersDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kQuickAnswers)},
@@ -5467,10 +5461,6 @@
      flag_descriptions::kMeteredShowToggleDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(features::kMeteredShowToggle)},
 
-    {"printer-status-dialog", flag_descriptions::kPrinterStatusDialogName,
-     flag_descriptions::kPrinterStatusDialogDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(chromeos::features::kPrinterStatusDialog)},
-
     {"wifi-sync-allow-deletes", flag_descriptions::kWifiSyncAllowDeletesName,
      flag_descriptions::kWifiSyncAllowDeletesDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kWifiSyncAllowDeletes)},
@@ -5510,10 +5500,6 @@
      flag_descriptions::kSelectToSpeakNavigationControlDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(features::kSelectToSpeakNavigationControl)},
 
-    {"print-server-scaling", flag_descriptions::kPrintServerScalingName,
-     flag_descriptions::kPrintServerScalingDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(chromeos::features::kPrintServerScaling)},
-
     {"enable-networking-in-diagnostics-app",
      flag_descriptions::kEnableNetworkingInDiagnosticsAppName,
      flag_descriptions::kEnableNetworkingInDiagnosticsAppDescription, kOsCrOS,
@@ -7217,6 +7203,12 @@
      FEATURE_VALUE_TYPE(ash::features::kWebApkGenerator)},
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+    {"use-passthrough-command-decoder",
+     flag_descriptions::kUsePassthroughCommandDecoderName,
+     flag_descriptions::kUsePassthroughCommandDecoderDescription,
+     kOsMac | kOsLinux | kOsCrOS | kOsAndroid,
+     FEATURE_VALUE_TYPE(features::kDefaultPassthroughCommandDecoder)},
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/accessibility/live_caption_controller.cc b/chrome/browser/accessibility/live_caption_controller.cc
index 83cec98..e4e58e9 100644
--- a/chrome/browser/accessibility/live_caption_controller.cc
+++ b/chrome/browser/accessibility/live_caption_controller.cc
@@ -211,7 +211,7 @@
 
 bool LiveCaptionController::DispatchTranscription(
     LiveCaptionSpeechRecognitionHost* live_caption_speech_recognition_host,
-    const media::mojom::SpeechRecognitionResultPtr& result) {
+    const media::SpeechRecognitionResult& result) {
   if (!caption_bubble_controller_)
     return false;
   return caption_bubble_controller_->OnTranscription(
diff --git a/chrome/browser/accessibility/live_caption_controller.h b/chrome/browser/accessibility/live_caption_controller.h
index cc7ba37..4b917f5 100644
--- a/chrome/browser/accessibility/live_caption_controller.h
+++ b/chrome/browser/accessibility/live_caption_controller.h
@@ -58,7 +58,7 @@
   // this returns false.
   bool DispatchTranscription(
       LiveCaptionSpeechRecognitionHost* live_caption_speech_recognition_host,
-      const media::mojom::SpeechRecognitionResultPtr& result);
+      const media::SpeechRecognitionResult& result);
 
   void OnLanguageIdentificationEvent(
       const media::mojom::LanguageIdentificationEventPtr& event);
diff --git a/chrome/browser/accessibility/live_caption_controller_browsertest.cc b/chrome/browser/accessibility/live_caption_controller_browsertest.cc
index 54e616c..42582573 100644
--- a/chrome/browser/accessibility/live_caption_controller_browsertest.cc
+++ b/chrome/browser/accessibility/live_caption_controller_browsertest.cc
@@ -127,7 +127,7 @@
   bool DispatchTranscriptionToProfile(std::string text, Profile* profile) {
     return GetControllerForProfile(profile)->DispatchTranscription(
         GetLiveCaptionSpeechRecognitionHost(),
-        media::mojom::SpeechRecognitionResult::New(text, false /* is_final */));
+        media::SpeechRecognitionResult(text, /* is_final */ false));
   }
 
   void OnError() { OnErrorOnProfile(browser()->profile()); }
diff --git a/chrome/browser/accessibility/live_caption_speech_recognition_host.cc b/chrome/browser/accessibility/live_caption_speech_recognition_host.cc
index bacff63..6543a22 100644
--- a/chrome/browser/accessibility/live_caption_speech_recognition_host.cc
+++ b/chrome/browser/accessibility/live_caption_speech_recognition_host.cc
@@ -42,7 +42,7 @@
 }
 
 void LiveCaptionSpeechRecognitionHost::OnSpeechRecognitionRecognitionEvent(
-    media::mojom::SpeechRecognitionResultPtr result,
+    const media::SpeechRecognitionResult& result,
     OnSpeechRecognitionRecognitionEventCallback reply) {
   LiveCaptionController* live_caption_controller = GetLiveCaptionController();
   if (!live_caption_controller) {
diff --git a/chrome/browser/accessibility/live_caption_speech_recognition_host.h b/chrome/browser/accessibility/live_caption_speech_recognition_host.h
index 3db8765..0f04d9b 100644
--- a/chrome/browser/accessibility/live_caption_speech_recognition_host.h
+++ b/chrome/browser/accessibility/live_caption_speech_recognition_host.h
@@ -44,7 +44,7 @@
 
   // media::mojom::SpeechRecognitionRecognizerClient:
   void OnSpeechRecognitionRecognitionEvent(
-      media::mojom::SpeechRecognitionResultPtr result,
+      const media::SpeechRecognitionResult& result,
       OnSpeechRecognitionRecognitionEventCallback reply) override;
   void OnLanguageIdentificationEvent(
       media::mojom::LanguageIdentificationEventPtr event) override;
diff --git a/chrome/browser/accessibility/live_caption_speech_recognition_host_browsertest.cc b/chrome/browser/accessibility/live_caption_speech_recognition_host_browsertest.cc
index 43691376..3ed4faf 100644
--- a/chrome/browser/accessibility/live_caption_speech_recognition_host_browsertest.cc
+++ b/chrome/browser/accessibility/live_caption_speech_recognition_host_browsertest.cc
@@ -68,7 +68,7 @@
                                            std::string text,
                                            bool expected_success) {
     remotes_[frame_host]->OnSpeechRecognitionRecognitionEvent(
-        media::mojom::SpeechRecognitionResult::New(text, /*is_final=*/false),
+        media::SpeechRecognitionResult(text, /*is_final=*/false),
         base::BindOnce(&LiveCaptionSpeechRecognitionHostTest::
                            DispatchTranscriptionCallback,
                        base::Unretained(this), expected_success));
diff --git a/chrome/browser/android/webapk/webapk_installer.cc b/chrome/browser/android/webapk/webapk_installer.cc
index ed25985..4e502e8 100644
--- a/chrome/browser/android/webapk/webapk_installer.cc
+++ b/chrome/browser/android/webapk/webapk_installer.cc
@@ -287,7 +287,14 @@
     if (icon_url == shortcut_info.splash_image_url.spec()) {
       if (shortcut_info.splash_image_url !=
           shortcut_info.best_primary_icon_url) {
-        image->set_image_data(it->second.unsafe_data);
+        // WebAPK updates uses the image data from fetched bitmap; installs use
+        // the image data from icon_url_to_murmur2_hash.
+        if (!splash_icon.drawsNothing()) {
+          SetImageData(image, splash_icon);
+        } else {
+          image->set_image_data(it->second.unsafe_data);
+        }
+
         if (shortcut_info.is_splash_image_maskable) {
           image->add_purposes(webapk::Image::MASKABLE);
         } else {
diff --git a/chrome/browser/android/webapk/webapk_installer_unittest.cc b/chrome/browser/android/webapk/webapk_installer_unittest.cc
index 890462a..cc7b81e 100644
--- a/chrome/browser/android/webapk/webapk_installer_unittest.cc
+++ b/chrome/browser/android/webapk/webapk_installer_unittest.cc
@@ -604,8 +604,7 @@
             manifest.icons(1).hash());
   EXPECT_THAT(manifest.icons(1).usages(),
               testing::ElementsAre(webapk::Image::SPLASH_ICON));
-  EXPECT_EQ(icon_url_to_murmur2_hash[best_splash_icon_url].unsafe_data,
-            manifest.icons(1).image_data());
+  EXPECT_TRUE(manifest.icons(1).has_image_data());
 
   // Check protobuf fields for unused icon.
   EXPECT_EQ(kUnusedIconPath, manifest.icons(2).src());
@@ -775,8 +774,6 @@
   EXPECT_THAT(manifest.icons(1).usages(),
               testing::ElementsAre(webapk::Image::SPLASH_ICON));
   EXPECT_TRUE(manifest.icons(1).has_image_data());
-  EXPECT_EQ(manifest.icons(1).image_data(),
-            icon_url_to_murmur2_hash[best_icon_url].unsafe_data);
 
   // Check protobuf fields for unused icon.
   EXPECT_EQ(kUnusedIconPath, manifest.icons(2).src());
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index 25ead0e2..6e0338f 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -310,6 +310,27 @@
                                /*ignore_profile_picker=*/true);
 }
 
+// Open the urls in the last used browser from a regular profile.
+void OpenUrlsInBrowser(const std::vector<GURL>& urls,
+                       Profile* safe_last_profile) {
+  Profile* profile =
+      g_browser_process->profile_manager()->GetLastUsedProfileAllowedByPolicy();
+  Browser* browser = chrome::FindLastActiveWithProfile(profile);
+  // if no browser window exists then create one with no tabs to be filled in
+  if (!browser) {
+    browser = Browser::Create(
+        Browser::CreateParams(safe_last_profile, /*user_gesture=*/true));
+    browser->window()->Show();
+  }
+
+  base::CommandLine dummy(base::CommandLine::NO_PROGRAM);
+  chrome::startup::IsFirstRun first_run =
+      first_run::IsChromeFirstRun() ? chrome::startup::IS_FIRST_RUN
+                                    : chrome::startup::IS_NOT_FIRST_RUN;
+  StartupBrowserCreatorImpl launch(base::FilePath(), dummy, first_run);
+  launch.OpenURLsInBrowser(browser, false, urls);
+}
+
 }  // namespace
 
 // Returns the last profile. This is extracted as a standalone function in order
@@ -1559,26 +1580,9 @@
     return;
   }
 
-  if (StartupBrowserCreator::MaybeHandleProfileAgnosticUrls(urls))
-    return;
-
-  // Pick the last used browser from a regular profile to open the urls.
-  Profile* profile =
-      g_browser_process->profile_manager()->GetLastUsedProfileAllowedByPolicy();
-  Browser* browser = chrome::FindLastActiveWithProfile(profile);
-  // if no browser window exists then create one with no tabs to be filled in
-  if (!browser) {
-    browser = Browser::Create(
-        Browser::CreateParams([self safeLastProfileForNewWindows], true));
-    browser->window()->Show();
-  }
-
-  base::CommandLine dummy(base::CommandLine::NO_PROGRAM);
-  chrome::startup::IsFirstRun first_run =
-      first_run::IsChromeFirstRun() ? chrome::startup::IS_FIRST_RUN
-                                    : chrome::startup::IS_NOT_FIRST_RUN;
-  StartupBrowserCreatorImpl launch(base::FilePath(), dummy, first_run);
-  launch.OpenURLsInBrowser(browser, false, urls);
+  StartupBrowserCreator::MaybeHandleProfileAgnosticUrls(
+      urls, base::BindOnce(&OpenUrlsInBrowser, urls,
+                           [self safeLastProfileForNewWindows]));
 }
 
 - (void)getUrl:(NSAppleEventDescriptor*)event
diff --git a/chrome/browser/app_controller_mac_browsertest.mm b/chrome/browser/app_controller_mac_browsertest.mm
index d23dfe4..668e94f 100644
--- a/chrome/browser/app_controller_mac_browsertest.mm
+++ b/chrome/browser/app_controller_mac_browsertest.mm
@@ -76,10 +76,14 @@
 #include "content/public/test/test_navigation_observer.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/browser/extension_dialog_auto_confirm.h"
 #include "extensions/common/extension.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #import "ui/events/test/cocoa_test_event_utils.h"
+#include "ui/views/test/dialog_test.h"
+#include "ui/views/widget/any_widget_observer.h"
+#include "ui/views/widget/widget.h"
 
 using base::SysUTF16ToNSString;
 
@@ -156,6 +160,12 @@
   return CreateAndWaitForProfile(ProfileManager::GetSystemProfilePath());
 }
 
+void AutoCloseDialog(views::Widget* widget) {
+  // Call CancelDialog to close the dialog, but the actual behavior will be
+  // determined by the ScopedTestDialogAutoConfirm configs.
+  views::test::CancelDialog(widget);
+}
+
 }  // namespace
 
 @interface TestOpenShortcutOnStartup : NSObject
@@ -946,7 +956,9 @@
 };
 
 IN_PROC_BROWSER_TEST_F(StartupWebAppUrlHandlingBrowserTest,
-                       WebAppLaunch_InScopeUrl) {
+                       DialogCancelled_NoLaunch) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       "WebAppUrlHandlerIntentPickerView");
   apps::UrlHandlerInfo url_handler;
   url_handler.origin = url::Origin::Create(GURL(kStartUrl));
 
@@ -955,6 +967,62 @@
   // Start URL is in app scope.
   SendAppleEventToOpenUrlToAppController(GURL(kStartUrl));
 
+  // The waiter will get the dialog when it shows up and close it.
+  waiter.WaitIfNeededAndGet()->CloseWithReason(
+      views::Widget::ClosedReason::kEscKeyPressed);
+
+  // Wait for app launch task to complete.
+  content::RunAllTasksUntilIdle();
+
+  // When dialog is closed, nothing will happen.
+  ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile()));
+  ASSERT_FALSE(web_app::AppBrowserController::IsForWebApp(browser(), app_id));
+}
+
+IN_PROC_BROWSER_TEST_F(StartupWebAppUrlHandlingBrowserTest,
+                       DialogAccepted_BrowserLaunch) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       "WebAppUrlHandlerIntentPickerView");
+
+  apps::UrlHandlerInfo url_handler;
+  url_handler.origin = url::Origin::Create(GURL(kStartUrl));
+
+  web_app::AppId app_id = InstallWebAppWithUrlHandlers({url_handler});
+
+  // Select the first choice, which is the browser.
+  extensions::ScopedTestDialogAutoConfirm auto_confirm(
+      extensions::ScopedTestDialogAutoConfirm::ACCEPT_AND_OPTION, 0);
+  SendAppleEventToOpenUrlToAppController(GURL(kStartUrl));
+  AutoCloseDialog(waiter.WaitIfNeededAndGet());
+
+  // Wait for browser launch task to complete.
+  content::RunAllTasksUntilIdle();
+
+  ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile()));
+  ASSERT_FALSE(web_app::AppBrowserController::IsForWebApp(browser(), app_id));
+  TabStripModel* tab_strip = browser()->tab_strip_model();
+  ASSERT_EQ(2, tab_strip->count());
+  // Check the link of the new tab that was opened.
+  content::WebContents* web_contents = tab_strip->GetWebContentsAt(1);
+  EXPECT_EQ(GURL(kStartUrl), web_contents->GetVisibleURL());
+}
+
+IN_PROC_BROWSER_TEST_F(StartupWebAppUrlHandlingBrowserTest,
+                       DialogAccepted_WebAppLaunch_InScopeUrl) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       "WebAppUrlHandlerIntentPickerView");
+  apps::UrlHandlerInfo url_handler;
+  url_handler.origin = url::Origin::Create(GURL(kStartUrl));
+
+  web_app::AppId app_id = InstallWebAppWithUrlHandlers({url_handler});
+
+  // Select the second choice, which is the app.
+  extensions::ScopedTestDialogAutoConfirm auto_confirm(
+      extensions::ScopedTestDialogAutoConfirm::ACCEPT_AND_OPTION, 1);
+  // kStartUrl is in app scope.
+  SendAppleEventToOpenUrlToAppController(GURL(kStartUrl));
+  AutoCloseDialog(waiter.WaitIfNeededAndGet());
+
   // Wait for app launch task to complete.
   content::RunAllTasksUntilIdle();
 
@@ -965,7 +1033,6 @@
   ASSERT_TRUE(app_browser);
   ASSERT_TRUE(web_app::AppBrowserController::IsForWebApp(app_browser, app_id));
 
-  // Verify the launched URL of the app window.
   TabStripModel* tab_strip = app_browser->tab_strip_model();
   ASSERT_EQ(1, tab_strip->count());
   content::WebContents* web_contents = tab_strip->GetWebContentsAt(0);
@@ -973,13 +1040,20 @@
 }
 
 IN_PROC_BROWSER_TEST_F(StartupWebAppUrlHandlingBrowserTest,
-                       WebAppLaunch_DifferentOriginUrl) {
+                       DialogAccepted_WebAppLaunch_DifferentOriginUrl) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       "WebAppUrlHandlerIntentPickerView");
   apps::UrlHandlerInfo url_handler;
   url_handler.origin = url::Origin::Create(GURL("https://example.com"));
   web_app::AppId app_id = InstallWebAppWithUrlHandlers({url_handler});
 
+  // Select the second choice, which is the app.
+  extensions::ScopedTestDialogAutoConfirm auto_confirm(
+      extensions::ScopedTestDialogAutoConfirm::ACCEPT_AND_OPTION, 1);
+
   // URL is not in app scope but matches url_handlers of installed app.
   SendAppleEventToOpenUrlToAppController(GURL("https://example.com/abc/def"));
+  AutoCloseDialog(waiter.WaitIfNeededAndGet());
 
   // Wait for app launch task to complete.
   content::RunAllTasksUntilIdle();
diff --git a/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_watch_apitest.cc b/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_watch_apitest.cc
index b7a1f95..a30226e 100644
--- a/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_watch_apitest.cc
+++ b/chrome/browser/apps/platform_apps/api/media_galleries/media_galleries_watch_apitest.cc
@@ -214,8 +214,16 @@
     ExecuteCmdAndCheckReply(kRemoveGalleryWatchCmd, kRemoveGalleryWatchOK);
 }
 
+// TODO(crbug.com/1047645): Flaky on Linux and Windows.
+#if defined(OS_LINUX) || defined(OS_WIN)
+#define MAYBE_CorrectResponseOnModifyingWatchedGallery \
+  DISABLED_CorrectResponseOnModifyingWatchedGallery
+#else
+#define MAYBE_CorrectResponseOnModifyingWatchedGallery \
+  CorrectResponseOnModifyingWatchedGallery
+#endif
 IN_PROC_BROWSER_TEST_F(MediaGalleriesGalleryWatchApiTest,
-                       CorrectResponseOnModifyingWatchedGallery) {
+                       MAYBE_CorrectResponseOnModifyingWatchedGallery) {
   if (!GalleryWatchesSupported())
     return;
 
diff --git a/chrome/browser/ash/accessibility/dictation.cc b/chrome/browser/ash/accessibility/dictation.cc
index 54d76289..38413fd 100644
--- a/chrome/browser/ash/accessibility/dictation.cc
+++ b/chrome/browser/ash/accessibility/dictation.cc
@@ -135,8 +135,7 @@
 void Dictation::OnSpeechResult(
     const std::u16string& transcription,
     bool is_final,
-    const absl::optional<SpeechRecognizerDelegate::TranscriptTiming>&
-        word_offsets) {
+    const absl::optional<media::SpeechRecognitionResult>& word_offsets) {
   // If the first character of text isn't a space, add a space before it.
   // NetworkSpeechRecognizer adds the preceding space but
   // OnDeviceSpeechRecognizer does not. This is also done in
diff --git a/chrome/browser/ash/accessibility/dictation.h b/chrome/browser/ash/accessibility/dictation.h
index af1c751..6e6faaa 100644
--- a/chrome/browser/ash/accessibility/dictation.h
+++ b/chrome/browser/ash/accessibility/dictation.h
@@ -38,11 +38,10 @@
   friend class DictationTest;
 
   // SpeechRecognizerDelegate:
-  void OnSpeechResult(
-      const std::u16string& transcription,
-      bool is_final,
-      const absl::optional<SpeechRecognizerDelegate::TranscriptTiming>&
-          word_offsets) override;
+  void OnSpeechResult(const std::u16string& transcription,
+                      bool is_final,
+                      const absl::optional<media::SpeechRecognitionResult>&
+                          full_result) override;
   void OnSpeechSoundLevelChanged(int16_t level) override;
   void OnSpeechRecognitionStateChanged(
       SpeechRecognizerStatus new_state) override;
diff --git a/chrome/browser/ash/accessibility/dictation_browsertest.cc b/chrome/browser/ash/accessibility/dictation_browsertest.cc
index f7b52a35..b17c58f 100644
--- a/chrome/browser/ash/accessibility/dictation_browsertest.cc
+++ b/chrome/browser/ash/accessibility/dictation_browsertest.cc
@@ -153,7 +153,7 @@
       EXPECT_TRUE(fake_service_->is_capturing_audio());
       base::RunLoop loop;
       fake_service_->SendSpeechRecognitionResult(
-          media::mojom::SpeechRecognitionResult::New(result, is_final));
+          media::SpeechRecognitionResult(result, is_final));
       loop.RunUntilIdle();
     }
   }
diff --git a/chrome/browser/ash/borealis/borealis_app_launcher.cc b/chrome/browser/ash/borealis/borealis_app_launcher.cc
index e04d8d0f..ff44730 100644
--- a/chrome/browser/ash/borealis/borealis_app_launcher.cc
+++ b/chrome/browser/ash/borealis/borealis_app_launcher.cc
@@ -29,11 +29,9 @@
                                  const std::string& app_id,
                                  const std::vector<std::string>& args,
                                  OnLaunchedCallback callback) {
-  // Do not launch anything when using the installer app.
-  //
-  // TODO(b/170677773): Launch a _certain_ application...
+  // Launching the borealis app is a legacy way of launching its main app
   if (app_id == kBorealisAppId) {
-    std::move(callback).Run(LaunchResult::kSuccess);
+    Launch(ctx, kBorealisMainAppId, args, std::move(callback));
     return;
   }
 
diff --git a/chrome/browser/ash/borealis/borealis_app_launcher_unittest.cc b/chrome/browser/ash/borealis/borealis_app_launcher_unittest.cc
index 708274d..eef1a1d 100644
--- a/chrome/browser/ash/borealis/borealis_app_launcher_unittest.cc
+++ b/chrome/browser/ash/borealis/borealis_app_launcher_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/base64.h"
 #include "base/bind.h"
 #include "base/test/bind.h"
 #include "chrome/browser/ash/borealis/borealis_context.h"
@@ -55,8 +56,8 @@
  public:
   BorealisAppLauncherTest()
       : ctx_(BorealisContext::CreateBorealisContextForTesting(&profile_)) {
-    ctx_->set_vm_name("test_vm_name");
-    ctx_->set_container_name("test_container_name");
+    ctx_->set_vm_name("borealis");
+    ctx_->set_container_name("penguin");
   }
 
  protected:
@@ -89,10 +90,27 @@
   std::unique_ptr<BorealisContext> ctx_;
 };
 
-TEST_F(BorealisAppLauncherTest, LauncherAppAlwaysWorks) {
+TEST_F(BorealisAppLauncherTest, LauncherAppLaunchesMainApp) {
   CallbackFactory callback_check;
+
+  // We add the main app to the registry, so that it will be launched.
+  std::string desktop_file_id;
+  ASSERT_TRUE(base::Base64Decode("c3RlYW0=", &desktop_file_id));
+  ASSERT_EQ(SetDummyApp(desktop_file_id), kBorealisMainAppId);
+
   EXPECT_CALL(callback_check,
               Call(BorealisAppLauncher::LaunchResult::kSuccess));
+  Cicerone()->SetOnLaunchContainerApplicationCallback(
+      base::BindLambdaForTesting(
+          [&](const vm_tools::cicerone::LaunchContainerApplicationRequest&
+                  request,
+              chromeos::DBusMethodCallback<
+                  vm_tools::cicerone::LaunchContainerApplicationResponse>
+                  callback) {
+            vm_tools::cicerone::LaunchContainerApplicationResponse response;
+            response.set_success(true);
+            std::move(callback).Run(response);
+          }));
   BorealisAppLauncher::Launch(Context(), kBorealisAppId,
                               callback_check.BindOnce());
 }
diff --git a/chrome/browser/ash/borealis/borealis_context_manager_mock.h b/chrome/browser/ash/borealis/borealis_context_manager_mock.h
index dbc4483..e7639718 100644
--- a/chrome/browser/ash/borealis/borealis_context_manager_mock.h
+++ b/chrome/browser/ash/borealis/borealis_context_manager_mock.h
@@ -14,7 +14,7 @@
  public:
   BorealisContextManagerMock();
 
-  ~BorealisContextManagerMock();
+  ~BorealisContextManagerMock() override;
 
   MOCK_METHOD(void,
               StartBorealis,
diff --git a/chrome/browser/ash/borealis/borealis_installer.cc b/chrome/browser/ash/borealis/borealis_installer.cc
index 3b2ef73..e4d3611b3 100644
--- a/chrome/browser/ash/borealis/borealis_installer.cc
+++ b/chrome/browser/ash/borealis/borealis_installer.cc
@@ -17,6 +17,10 @@
       return "kInactive";
     case InstallingState::kInstallingDlc:
       return "kInstallingDlc";
+    case InstallingState::kStartingUp:
+      return "kStartingUp";
+    case InstallingState::kAwaitingApplications:
+      return "kAwaitingApplications";
   }
 }
 
diff --git a/chrome/browser/ash/borealis/borealis_installer.h b/chrome/browser/ash/borealis/borealis_installer.h
index c7a0e9ad..1553dfc6 100644
--- a/chrome/browser/ash/borealis/borealis_installer.h
+++ b/chrome/browser/ash/borealis/borealis_installer.h
@@ -22,6 +22,8 @@
   enum class InstallingState {
     kInactive,
     kInstallingDlc,
+    kStartingUp,
+    kAwaitingApplications,
   };
 
   // Observer class for the Borealis installation related events.
diff --git a/chrome/browser/ash/borealis/borealis_installer_impl.cc b/chrome/browser/ash/borealis/borealis_installer_impl.cc
index 987a025d..49c68f0 100644
--- a/chrome/browser/ash/borealis/borealis_installer_impl.cc
+++ b/chrome/browser/ash/borealis/borealis_installer_impl.cc
@@ -8,6 +8,7 @@
 
 #include "base/bind.h"
 #include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
 #include "chrome/browser/ash/borealis/borealis_context_manager.h"
 #include "chrome/browser/ash/borealis/borealis_features.h"
 #include "chrome/browser/ash/borealis/borealis_metrics.h"
@@ -29,17 +30,32 @@
 
 namespace borealis {
 
+namespace {
+
+// Time to wait for borealis' main app to appear. This is done almost
+// immediately by garcon on launch so a short timeout is sufficient.
+constexpr base::TimeDelta kWaitForMainAppTimeout =
+    base::TimeDelta::FromSeconds(5);
+
+}  // namespace
+
 class BorealisInstallerImpl::Installation
     : public Transition<BorealisInstallerImpl::InstallInfo,
                         BorealisInstallerImpl::InstallInfo,
-                        BorealisInstallResult> {
+                        BorealisInstallResult>,
+      public guest_os::GuestOsRegistryService::Observer {
  public:
-  explicit Installation(
+  Installation(
+      Profile* profile,
+      base::TimeDelta main_app_timeout,
       base::RepeatingCallback<void(double)> update_progress_callback,
       base::RepeatingCallback<void(InstallingState)> update_state_callback)
-      : installation_start_tick_(base::TimeTicks::Now()),
+      : profile_(profile),
+        installation_start_tick_(base::TimeTicks::Now()),
+        main_app_timeout_(main_app_timeout),
         update_progress_callback_(std::move(update_progress_callback)),
         update_state_callback_(std::move(update_state_callback)),
+        apps_observation_(this),
         weak_factory_(this) {}
 
   base::TimeTicks start_time() { return installation_start_tick_; }
@@ -76,7 +92,12 @@
 
     // If success, continue to the next state.
     if (install_result.error == dlcservice::kErrorNone) {
-      Succeed(std::move(install_info_));
+      // We are in the callback of DLC completion, and the first thing startup
+      // will do is try to mount the DLC, so we need to use a PostTask to avoid
+      // deadlocking ourselves.
+      base::SequencedTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(&Installation::StartupBorealis,
+                                    weak_factory_.GetWeakPtr()));
       return;
     }
 
@@ -112,11 +133,91 @@
     Fail(result);
   }
 
+  // As part of its installation we perform a dry run of borealis. This ensures
+  // that the VM works somewhat and allows the container_guest daemon to update
+  // Chrome. See go/borealis-mid-launch for details.
+  void StartupBorealis() {
+    SetState(InstallingState::kStartingUp);
+    BorealisService::GetForProfile(profile_)->ContextManager().StartBorealis(
+        base::BindOnce(&Installation::OnBorealisStarted,
+                       weak_factory_.GetWeakPtr()));
+  }
+
+  void OnBorealisStarted(BorealisContextManager::ContextOrFailure result) {
+    if (result) {
+      WaitForMainApp();
+      return;
+    }
+    LOG(ERROR) << "Failed to start borealis (code "
+               << static_cast<int>(result.Error().error())
+               << "): " << result.Error().description();
+    Fail(BorealisInstallResult::kStartupFailed);
+  }
+
+  void WaitForMainApp() {
+    SetState(InstallingState::kAwaitingApplications);
+    guest_os::GuestOsRegistryService* apps_registry =
+        guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile_);
+    apps_observation_.Observe(apps_registry);
+    absl::optional<guest_os::GuestOsRegistryService::Registration> main_app =
+        apps_registry->GetRegistration(kBorealisMainAppId);
+    if (main_app.has_value() && main_app->VmType() ==
+                                    guest_os::GuestOsRegistryService::VmType::
+                                        ApplicationList_VmType_BOREALIS) {
+      apps_observation_.Reset();
+      MainAppFound(true);
+      return;
+    }
+    base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&Installation::MainAppFound, weak_factory_.GetWeakPtr(),
+                       false),
+        main_app_timeout_);
+  }
+
+  void OnRegistryUpdated(
+      guest_os::GuestOsRegistryService* registry_service,
+      guest_os::GuestOsRegistryService::VmType vm_type,
+      const std::vector<std::string>& updated_apps,
+      const std::vector<std::string>& removed_apps,
+      const std::vector<std::string>& inserted_apps) override {
+    if (vm_type != guest_os::GuestOsRegistryService::VmType::
+                       ApplicationList_VmType_BOREALIS) {
+      return;
+    }
+
+    for (const auto& app : inserted_apps) {
+      if (app == kBorealisMainAppId) {
+        MainAppFound(true);
+        break;
+      }
+    }
+  }
+
+  void MainAppFound(bool found) {
+    // We use the presence of the install_info_ object to prevent races here, so
+    // return if it has already been removed.
+    if (!install_info_)
+      return;
+    if (!found) {
+      install_info_.reset();
+      LOG(ERROR) << "Failed to verify that the main app has been created";
+      Fail(BorealisInstallResult::kMainAppNotPresent);
+      return;
+    }
+    Succeed(std::move(install_info_));
+  }
+
+  Profile* const profile_;
   base::TimeTicks installation_start_tick_;
+  base::TimeDelta main_app_timeout_;
   InstallingState installing_state_;
   base::RepeatingCallback<void(double)> update_progress_callback_;
   base::RepeatingCallback<void(InstallingState)> update_state_callback_;
   std::unique_ptr<BorealisInstallerImpl::InstallInfo> install_info_;
+  base::ScopedObservation<guest_os::GuestOsRegistryService,
+                          guest_os::GuestOsRegistryService::Observer>
+      apps_observation_;
   base::WeakPtrFactory<Installation> weak_factory_;
 };
 
@@ -206,7 +307,9 @@
 };
 
 BorealisInstallerImpl::BorealisInstallerImpl(Profile* profile)
-    : profile_(profile), weak_ptr_factory_(this) {}
+    : profile_(profile),
+      main_app_timeout_(kWaitForMainAppTimeout),
+      weak_ptr_factory_(this) {}
 
 BorealisInstallerImpl::~BorealisInstallerImpl() = default;
 
@@ -244,6 +347,7 @@
   install_info->vm_name = "borealis";
   install_info->container_name = "penguin";
   in_progress_installation_ = std::make_unique<Installation>(
+      profile_, main_app_timeout_,
       base::BindRepeating(&BorealisInstallerImpl::UpdateProgress,
                           weak_ptr_factory_.GetWeakPtr()),
       base::BindRepeating(&BorealisInstallerImpl::UpdateInstallingState,
@@ -293,6 +397,11 @@
   observers_.RemoveObserver(observer);
 }
 
+void BorealisInstallerImpl::SetMainAppTimeoutForTesting(
+    base::TimeDelta timeout) {
+  main_app_timeout_ = timeout;
+}
+
 void BorealisInstallerImpl::UpdateProgress(double state_progress) {
   if (state_progress < 0 || state_progress > 1) {
     LOG(ERROR) << "Unexpected progress value " << state_progress
@@ -306,7 +415,15 @@
   switch (installing_state_) {
     case InstallingState::kInstallingDlc:
       start_range = 0;
-      end_range = 1;
+      end_range = 0.5;
+      break;
+    case InstallingState::kStartingUp:
+      start_range = 0.5;
+      end_range = 0.8;
+      break;
+    case InstallingState::kAwaitingApplications:
+      start_range = 0.8;
+      end_range = 1.0;
       break;
     case InstallingState::kInactive:
       NOTREACHED();
@@ -327,6 +444,8 @@
   for (auto& observer : observers_) {
     observer.OnStateUpdated(installing_state_);
   }
+  // The state just changed, so the progress towards that state is 0.
+  UpdateProgress(0);
 }
 
 void BorealisInstallerImpl::OnInstallComplete(
@@ -349,6 +468,7 @@
       profile_->GetPrefs()->SetBoolean(prefs::kBorealisInstalledOnDevice, true);
       RecordBorealisInstallOverallTimeHistogram(duration);
     }
+    // TODO(b/188713071): Clean up if installation fails.
     RecordBorealisInstallResultHistogram(result);
   }
   for (auto& observer : observers_) {
diff --git a/chrome/browser/ash/borealis/borealis_installer_impl.h b/chrome/browser/ash/borealis/borealis_installer_impl.h
index a92d42c..4f841f9 100644
--- a/chrome/browser/ash/borealis/borealis_installer_impl.h
+++ b/chrome/browser/ash/borealis/borealis_installer_impl.h
@@ -43,6 +43,9 @@
   void AddObserver(Observer* observer) override;
   void RemoveObserver(Observer* observer) override;
 
+  // Override the timeout to wait for the main app to appear.
+  void SetMainAppTimeoutForTesting(base::TimeDelta timeout);
+
  private:
   // Holds information about (un)install operations.
   struct InstallInfo {
@@ -71,6 +74,8 @@
   std::unique_ptr<Installation> in_progress_installation_;
   std::unique_ptr<Uninstallation> in_progress_uninstallation_;
 
+  base::TimeDelta main_app_timeout_;
+
   base::WeakPtrFactory<BorealisInstallerImpl> weak_ptr_factory_;
 };
 
diff --git a/chrome/browser/ash/borealis/borealis_installer_unittest.cc b/chrome/browser/ash/borealis/borealis_installer_unittest.cc
index aec7c8e..57d8806 100644
--- a/chrome/browser/ash/borealis/borealis_installer_unittest.cc
+++ b/chrome/browser/ash/borealis/borealis_installer_unittest.cc
@@ -2,30 +2,39 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chrome/browser/ash/borealis/borealis_disk_manager_dispatcher.h"
 #include "chrome/browser/ash/borealis/borealis_installer_impl.h"
 
 #include <memory>
 
+#include "base/base64.h"
 #include "base/callback_helpers.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ash/borealis/borealis_context.h"
 #include "chrome/browser/ash/borealis/borealis_context_manager.h"
 #include "chrome/browser/ash/borealis/borealis_context_manager_mock.h"
+#include "chrome/browser/ash/borealis/borealis_disk_manager_dispatcher.h"
 #include "chrome/browser/ash/borealis/borealis_features.h"
 #include "chrome/browser/ash/borealis/borealis_metrics.h"
 #include "chrome/browser/ash/borealis/borealis_prefs.h"
 #include "chrome/browser/ash/borealis/borealis_service.h"
 #include "chrome/browser/ash/borealis/borealis_service_fake.h"
 #include "chrome/browser/ash/borealis/borealis_util.h"
+#include "chrome/browser/ash/borealis/borealis_window_manager.h"
+#include "chrome/browser/ash/borealis/infra/described.h"
 #include "chrome/browser/ash/borealis/testing/callback_factory.h"
 #include "chrome/browser/ash/guest_os/guest_os_registry_service.h"
 #include "chrome/browser/ash/guest_os/guest_os_registry_service_factory.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/test/base/testing_profile.h"
+#include "chromeos/dbus/cicerone/cicerone_client.h"
+#include "chromeos/dbus/cicerone/fake_cicerone_client.h"
 #include "chromeos/dbus/concierge/concierge_client.h"
 #include "chromeos/dbus/concierge/fake_concierge_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/dlcservice/fake_dlcservice_client.h"
+#include "chromeos/dbus/seneschal/seneschal_client.h"
 #include "chromeos/dbus/vm_applications/apps.pb.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/network_service_instance.h"
@@ -39,7 +48,7 @@
 
 using ::testing::_;
 using ::testing::Mock;
-using ::testing::StrictMock;
+using ::testing::NiceMock;
 using InstallingState = BorealisInstaller::InstallingState;
 using BorealisInstallResult = BorealisInstallResult;
 
@@ -56,19 +65,40 @@
   BorealisInstallerTest() = default;
   ~BorealisInstallerTest() override = default;
 
+  // Disallow copy and assign.
+  BorealisInstallerTest(const BorealisInstallerTest&) = delete;
+  BorealisInstallerTest& operator=(const BorealisInstallerTest&) = delete;
+
  protected:
   void SetUp() override {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::ConciergeClient::InitializeFake(/*fake_cicerone_client=*/nullptr);
+    chromeos::CiceroneClient::InitializeFake();
+    chromeos::ConciergeClient::InitializeFake(
+        static_cast<chromeos::FakeCiceroneClient*>(
+            chromeos::CiceroneClient::Get()));
+    chromeos::DlcserviceClient::InitializeFake();
+    chromeos::SeneschalClient::InitializeFake();
     histogram_tester_ = std::make_unique<base::HistogramTester>();
     CreateProfile();
 
+    test_features_ = std::make_unique<BorealisFeatures>(profile_.get());
+    test_context_manager_ =
+        std::make_unique<NiceMock<BorealisContextManagerMock>>();
+    test_window_manager_ =
+        std::make_unique<BorealisWindowManager>(profile_.get());
+    test_disk_dispatcher_ = std::make_unique<BorealisDiskManagerDispatcher>();
+    fake_service_ = BorealisServiceFake::UseFakeForTesting(profile_.get());
+    fake_service_->SetFeaturesForTesting(test_features_.get());
+    fake_service_->SetContextManagerForTesting(test_context_manager_.get());
+    fake_service_->SetWindowManagerForTesting(test_window_manager_.get());
+    fake_service_->SetDiskManagerDispatcherForTesting(
+        test_disk_dispatcher_.get());
+
     installer_impl_ = std::make_unique<BorealisInstallerImpl>(profile_.get());
     installer_ = installer_impl_.get();
-    observer_ = std::make_unique<StrictMock<MockObserver>>();
+    observer_ = std::make_unique<NiceMock<MockObserver>>();
     installer_->AddObserver(observer_.get());
 
-    chromeos::DlcserviceClient::InitializeFake();
     fake_dlcservice_client_ = static_cast<chromeos::FakeDlcserviceClient*>(
         chromeos::DlcserviceClient::Get());
     UpdateCurrentDlcs();
@@ -78,28 +108,15 @@
   }
 
   void TearDown() override {
+    ctx_.reset();
     observer_.reset();
     profile_.reset();
     histogram_tester_.reset();
 
+    chromeos::SeneschalClient::Shutdown();
+    chromeos::DlcserviceClient::Shutdown();
     chromeos::ConciergeClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
-    chromeos::DlcserviceClient::Shutdown();
-  }
-
-  // Set expectations for observer events up to and including |end_state|.
-  void ExpectObserverEventsUntil(InstallingState end_state) {
-    InstallingState states[] = {
-        InstallingState::kInstallingDlc,
-    };
-
-    for (InstallingState state : states) {
-      EXPECT_CALL(*observer_, OnStateUpdated(state));
-      if (state == end_state)
-        return;
-    }
-
-    NOTREACHED();
   }
 
   void StartAndRunToCompletion() {
@@ -107,6 +124,41 @@
     task_environment_.RunUntilIdle();
   }
 
+  void CreateFakeMainApp() {
+    std::string desktop_file_id;
+    ASSERT_TRUE(base::Base64Decode("c3RlYW0=", &desktop_file_id));
+    vm_tools::apps::ApplicationList list;
+    list.set_vm_name(ctx_->vm_name());
+    list.set_container_name(ctx_->container_name());
+    list.set_vm_type(vm_tools::apps::ApplicationList_VmType_BOREALIS);
+    vm_tools::apps::App* app = list.add_apps();
+    app->set_desktop_file_id(desktop_file_id);
+    vm_tools::apps::App::LocaleString::Entry* entry =
+        app->mutable_name()->add_values();
+    entry->set_locale(std::string());
+    entry->set_value(desktop_file_id);
+    app->set_no_display(false);
+    guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile_.get())
+        ->UpdateApplicationList(list);
+  }
+
+  void PrepareSuccessfulInstallation() {
+    feature_list_.InitAndEnableFeature(features::kBorealis);
+    fake_dlcservice_client_->set_install_error(dlcservice::kErrorNone);
+    ctx_ = BorealisContext::CreateBorealisContextForTesting(profile_.get());
+    ctx_->set_vm_name("borealis");
+    ctx_->set_container_name("penguin");
+    EXPECT_CALL(*test_context_manager_, StartBorealis)
+        .WillOnce(testing::Invoke(
+            [this](BorealisContextManager::ResultCallback callback) {
+              std::move(callback).Run(
+                  BorealisContextManager::ContextOrFailure(ctx_.get()));
+              // Make a fake main app. We do this inside the callback as it is a
+              // better way to simulate garcon's callback.
+              CreateFakeMainApp();
+            }));
+  }
+
   void UpdateCurrentDlcs() {
     base::RunLoop run_loop;
     fake_dlcservice_client_->GetExistingDlcs(base::BindOnce(
@@ -122,6 +174,12 @@
 
   content::BrowserTaskEnvironment task_environment_;
   std::unique_ptr<TestingProfile> profile_;
+  std::unique_ptr<BorealisContext> ctx_;
+  std::unique_ptr<BorealisFeatures> test_features_;
+  std::unique_ptr<BorealisContextManagerMock> test_context_manager_;
+  std::unique_ptr<BorealisWindowManager> test_window_manager_;
+  std::unique_ptr<BorealisDiskManagerDispatcher> test_disk_dispatcher_;
+  BorealisServiceFake* fake_service_;
   std::unique_ptr<base::HistogramTester> histogram_tester_;
   std::unique_ptr<BorealisInstallerImpl> installer_impl_;
   BorealisInstaller* installer_;
@@ -138,10 +196,6 @@
     profile_builder.SetProfileName("defaultprofile");
     profile_ = profile_builder.Build();
   }
-
-  // Disallow copy and assign.
-  BorealisInstallerTest(const BorealisInstallerTest&) = delete;
-  BorealisInstallerTest& operator=(const BorealisInstallerTest&) = delete;
 };
 
 class BorealisInstallerTestDlc
@@ -180,12 +234,9 @@
 }
 
 TEST_F(BorealisInstallerTest, SucessfulInstallation) {
-  feature_list_.InitAndEnableFeature(features::kBorealis);
-  fake_dlcservice_client_->set_install_error(dlcservice::kErrorNone);
+  PrepareSuccessfulInstallation();
 
-  ExpectObserverEventsUntil(InstallingState::kInstallingDlc);
   EXPECT_CALL(*observer_, OnInstallationEnded(BorealisInstallResult::kSuccess));
-
   StartAndRunToCompletion();
 
   UpdateCurrentDlcs();
@@ -195,11 +246,33 @@
       BorealisService::GetForProfile(profile_.get())->Features().IsEnabled());
 }
 
+TEST_F(BorealisInstallerTest, HandlesMainAppPreExisting) {
+  PrepareSuccessfulInstallation();
+
+  // Normally we add the main app after signaling completion, which this a
+  // better way of modeling how garcon works. In this test we add the main app
+  // well before, to simulate when garcon actually wins the race.
+  CreateFakeMainApp();
+
+  EXPECT_CALL(*observer_, OnInstallationEnded(BorealisInstallResult::kSuccess));
+  StartAndRunToCompletion();
+}
+
+TEST_F(BorealisInstallerTest, InstallationHasAllStages) {
+  PrepareSuccessfulInstallation();
+
+  EXPECT_CALL(*observer_, OnStateUpdated(InstallingState::kInstallingDlc));
+  EXPECT_CALL(*observer_, OnStateUpdated(InstallingState::kStartingUp));
+  EXPECT_CALL(*observer_,
+              OnStateUpdated(InstallingState::kAwaitingApplications));
+
+  StartAndRunToCompletion();
+}
+
 TEST_F(BorealisInstallerTest, CancelledInstallation) {
   feature_list_.InitAndEnableFeature(features::kBorealis);
   fake_dlcservice_client_->set_install_error(dlcservice::kErrorNone);
 
-  ExpectObserverEventsUntil(InstallingState::kInstallingDlc);
   EXPECT_CALL(*observer_, OnCancelInitiated());
   EXPECT_CALL(*observer_,
               OnInstallationEnded(BorealisInstallResult::kCancelled));
@@ -216,10 +289,8 @@
 }
 
 TEST_F(BorealisInstallerTest, InstallationInProgess) {
-  feature_list_.InitAndEnableFeature(features::kBorealis);
-  fake_dlcservice_client_->set_install_error(dlcservice::kErrorNone);
+  PrepareSuccessfulInstallation();
 
-  ExpectObserverEventsUntil(InstallingState::kInstallingDlc);
   EXPECT_CALL(
       *observer_,
       OnInstallationEnded(BorealisInstallResult::kBorealisInstallInProgress));
@@ -237,8 +308,7 @@
 }
 
 TEST_F(BorealisInstallerTest, CancelledThenSuccessfulInstallation) {
-  feature_list_.InitAndEnableFeature(features::kBorealis);
-  fake_dlcservice_client_->set_install_error(dlcservice::kErrorNone);
+  PrepareSuccessfulInstallation();
 
   EXPECT_CALL(*observer_, OnCancelInitiated());
 
@@ -250,7 +320,6 @@
   EXPECT_FALSE(
       BorealisService::GetForProfile(profile_.get())->Features().IsEnabled());
 
-  ExpectObserverEventsUntil(InstallingState::kInstallingDlc);
   EXPECT_CALL(*observer_, OnInstallationEnded(BorealisInstallResult::kSuccess));
 
   installer_->Start();
@@ -264,10 +333,8 @@
 }
 
 TEST_F(BorealisInstallerTest, SucessfulInstallationRecordMetrics) {
-  feature_list_.InitAndEnableFeature(features::kBorealis);
-  fake_dlcservice_client_->set_install_error(dlcservice::kErrorNone);
+  PrepareSuccessfulInstallation();
 
-  ExpectObserverEventsUntil(InstallingState::kInstallingDlc);
   EXPECT_CALL(*observer_, OnInstallationEnded(BorealisInstallResult::kSuccess));
   StartAndRunToCompletion();
 
@@ -282,7 +349,6 @@
   // This error is arbitrarily chosen for simplicity.
   fake_dlcservice_client_->set_install_error(dlcservice::kErrorInternal);
 
-  ExpectObserverEventsUntil(InstallingState::kInstallingDlc);
   EXPECT_CALL(*observer_,
               OnInstallationEnded(BorealisInstallResult::kDlcInternalError));
   StartAndRunToCompletion();
@@ -294,6 +360,46 @@
   histogram_tester_->ExpectTotalCount(kBorealisInstallOverallTimeHistogram, 0);
 }
 
+TEST_F(BorealisInstallerTest, ReportsStartupFailureAsError) {
+  feature_list_.InitAndEnableFeature(features::kBorealis);
+  fake_dlcservice_client_->set_install_error(dlcservice::kErrorNone);
+  EXPECT_CALL(*test_context_manager_, StartBorealis)
+      .WillOnce(
+          testing::Invoke([](BorealisContextManager::ResultCallback callback) {
+            std::move(callback).Run(
+                BorealisContextManager::ContextOrFailure::Unexpected(
+                    Described<BorealisStartupResult>{
+                        BorealisStartupResult::kStartVmFailed, "Some Error"}));
+          }));
+
+  EXPECT_CALL(*observer_, OnStateUpdated(InstallingState::kInstallingDlc));
+  EXPECT_CALL(*observer_, OnStateUpdated(InstallingState::kStartingUp));
+  EXPECT_CALL(*observer_,
+              OnInstallationEnded(BorealisInstallResult::kStartupFailed));
+
+  StartAndRunToCompletion();
+}
+
+TEST_F(BorealisInstallerTest, ReportsMainAppMissingAsError) {
+  feature_list_.InitAndEnableFeature(features::kBorealis);
+  fake_dlcservice_client_->set_install_error(dlcservice::kErrorNone);
+  ctx_ = BorealisContext::CreateBorealisContextForTesting(profile_.get());
+  EXPECT_CALL(*test_context_manager_, StartBorealis)
+      .WillOnce(testing::Invoke(
+          [this](BorealisContextManager::ResultCallback callback) {
+            std::move(callback).Run(
+                BorealisContextManager::ContextOrFailure(ctx_.get()));
+          }));
+
+  // Set a zero timeout otherwise the in-progress timeout gets cleaned up.
+  installer_impl_->SetMainAppTimeoutForTesting(base::TimeDelta::FromSeconds(0));
+
+  EXPECT_CALL(*observer_,
+              OnInstallationEnded(BorealisInstallResult::kMainAppNotPresent));
+
+  StartAndRunToCompletion();
+}
+
 // Note that we don't check if the DLC has/hasn't been installed, since the
 // mocked DLC service will always succeed, so we only care about how the error
 // code returned by the service is handled by the installer.
@@ -301,7 +407,7 @@
   feature_list_.InitAndEnableFeature(features::kBorealis);
   fake_dlcservice_client_->set_install_error(GetParam().first);
 
-  ExpectObserverEventsUntil(InstallingState::kInstallingDlc);
+  EXPECT_CALL(*observer_, OnStateUpdated(InstallingState::kInstallingDlc));
   EXPECT_CALL(*observer_, OnInstallationEnded(GetParam().second));
 
   StartAndRunToCompletion();
@@ -337,13 +443,8 @@
   void SetUp() override {
     BorealisInstallerTest::SetUp();
     // Install borealis.
-    feature_list_.InitAndEnableFeature(features::kBorealis);
-    fake_dlcservice_client_->set_install_error(dlcservice::kErrorNone);
-    ExpectObserverEventsUntil(InstallingState::kInstallingDlc);
-    EXPECT_CALL(*observer_,
-                OnInstallationEnded(BorealisInstallResult::kSuccess));
-    installer_->Start();
-    task_environment_.RunUntilIdle();
+    PrepareSuccessfulInstallation();
+    StartAndRunToCompletion();
     ASSERT_TRUE(
         BorealisService::GetForProfile(profile_.get())->Features().IsEnabled());
   }
@@ -388,10 +489,7 @@
   CallbackFactory callback_factory;
   EXPECT_CALL(callback_factory, Call(BorealisUninstallResult::kShutdownFailed));
 
-  testing::StrictMock<BorealisContextManagerMock> mock_manager;
-  BorealisServiceFake::UseFakeForTesting(profile_.get())
-      ->SetContextManagerForTesting(&mock_manager);
-  EXPECT_CALL(mock_manager, ShutDownBorealis(testing::_))
+  EXPECT_CALL(*test_context_manager_, ShutDownBorealis(testing::_))
       .WillOnce(testing::Invoke(
           [](base::OnceCallback<void(BorealisShutdownResult)> callback) {
             std::move(callback).Run(BorealisShutdownResult::kFailed);
@@ -415,6 +513,11 @@
   EXPECT_CALL(callback_factory,
               Call(BorealisUninstallResult::kRemoveDiskFailed));
 
+  EXPECT_CALL(*test_context_manager_, ShutDownBorealis(testing::_))
+      .WillOnce(testing::Invoke(
+          [](base::OnceCallback<void(BorealisShutdownResult)> callback) {
+            std::move(callback).Run(BorealisShutdownResult::kSuccess);
+          }));
   chromeos::FakeConciergeClient* fake_concierge_client =
       chromeos::FakeConciergeClient::Get();
   fake_concierge_client->set_destroy_disk_image_response(absl::nullopt);
@@ -436,6 +539,11 @@
   EXPECT_CALL(callback_factory,
               Call(BorealisUninstallResult::kRemoveDlcFailed));
 
+  EXPECT_CALL(*test_context_manager_, ShutDownBorealis(testing::_))
+      .WillOnce(testing::Invoke(
+          [](base::OnceCallback<void(BorealisShutdownResult)> callback) {
+            std::move(callback).Run(BorealisShutdownResult::kSuccess);
+          }));
   fake_dlcservice_client_->set_uninstall_error("some failure");
 
   installer_->Uninstall(callback_factory.BindOnce());
@@ -459,6 +567,11 @@
           .size(),
       1);
 
+  EXPECT_CALL(*test_context_manager_, ShutDownBorealis(testing::_))
+      .WillOnce(testing::Invoke(
+          [](base::OnceCallback<void(BorealisShutdownResult)> callback) {
+            std::move(callback).Run(BorealisShutdownResult::kSuccess);
+          }));
   installer_->Uninstall(callback_factory.BindOnce());
   task_environment_.RunUntilIdle();
 
@@ -493,14 +606,29 @@
   EXPECT_CALL(callback_factory, Call(BorealisUninstallResult::kSuccess))
       .Times(2);
 
+  EXPECT_CALL(*test_context_manager_, ShutDownBorealis(testing::_))
+      .WillOnce(testing::Invoke(
+          [](base::OnceCallback<void(BorealisShutdownResult)> callback) {
+            std::move(callback).Run(BorealisShutdownResult::kSuccess);
+          }));
   installer_->Uninstall(callback_factory.BindOnce());
   task_environment_.RunUntilIdle();
 
+  EXPECT_CALL(*test_context_manager_, ShutDownBorealis(testing::_))
+      .WillOnce(testing::Invoke(
+          [](base::OnceCallback<void(BorealisShutdownResult)> callback) {
+            std::move(callback).Run(BorealisShutdownResult::kSuccess);
+          }));
   installer_->Uninstall(callback_factory.BindOnce());
   task_environment_.RunUntilIdle();
 }
 
 TEST_F(BorealisUninstallerTest, SuccessfulUninstallationRecordsMetrics) {
+  EXPECT_CALL(*test_context_manager_, ShutDownBorealis(testing::_))
+      .WillOnce(testing::Invoke(
+          [](base::OnceCallback<void(BorealisShutdownResult)> callback) {
+            std::move(callback).Run(BorealisShutdownResult::kSuccess);
+          }));
   installer_->Uninstall(base::DoNothing());
   task_environment_.RunUntilIdle();
 
@@ -511,8 +639,12 @@
 }
 
 TEST_F(BorealisUninstallerTest, FailedUninstallationRecordsMetrics) {
-  // Arbitrarily choose to fail via DLC removal.
-  fake_dlcservice_client_->set_uninstall_error("some failure");
+  // Fail via shutdown, as that is the first step.
+  EXPECT_CALL(*test_context_manager_, ShutDownBorealis(testing::_))
+      .WillOnce(testing::Invoke(
+          [](base::OnceCallback<void(BorealisShutdownResult)> callback) {
+            std::move(callback).Run(BorealisShutdownResult::kFailed);
+          }));
 
   installer_->Uninstall(base::DoNothing());
   task_environment_.RunUntilIdle();
@@ -521,7 +653,7 @@
                                       1);
   histogram_tester_->ExpectUniqueSample(
       kBorealisUninstallResultHistogram,
-      BorealisUninstallResult::kRemoveDlcFailed, 1);
+      BorealisUninstallResult::kShutdownFailed, 1);
 }
 
 }  // namespace
diff --git a/chrome/browser/ash/borealis/borealis_metrics.h b/chrome/browser/ash/borealis/borealis_metrics.h
index 0cb6cdf..f28861c 100644
--- a/chrome/browser/ash/borealis/borealis_metrics.h
+++ b/chrome/browser/ash/borealis/borealis_metrics.h
@@ -36,7 +36,9 @@
   kDlcUnknownError = 9,
   kOffline = 10,
   kDlcNeedUpdateError = 11,
-  kMaxValue = kDlcNeedUpdateError,
+  kStartupFailed = 12,
+  kMainAppNotPresent = 13,
+  kMaxValue = kMainAppNotPresent,
 };
 
 // These values are persisted to logs. Entries should not be renumbered and
diff --git a/chrome/browser/ash/child_accounts/child_user_service.cc b/chrome/browser/ash/child_accounts/child_user_service.cc
index b6809b4..733c2dd 100644
--- a/chrome/browser/ash/child_accounts/child_user_service.cc
+++ b/chrome/browser/ash/child_accounts/child_user_service.cc
@@ -4,18 +4,58 @@
 
 #include "chrome/browser/ash/child_accounts/child_user_service.h"
 
+#include "base/bind.h"
+#include "base/check.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/time/time.h"
+#include "base/values.h"
 #include "chrome/browser/ash/child_accounts/time_limits/app_activity_registry.h"
 #include "chrome/browser/ash/child_accounts/time_limits/app_time_controller.h"
 #include "chrome/browser/ash/child_accounts/time_limits/app_types.h"
 #include "chrome/browser/ash/child_accounts/time_limits/web_time_limit_enforcer.h"
+#include "chrome/browser/ash/child_accounts/usage_time_limit_processor.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_context.h"
 #include "extensions/common/constants.h"
 #include "url/gurl.h"
 
 namespace ash {
 
+namespace {
+// UMA histogram FamilyUser.TimeLimitPolicyTypes
+// Reports TimeLimitPolicyType which indicates what time limit policies
+// are enabled for current Family Link user. Could report multiple buckets if
+// multiple policies are enabled.
+constexpr char kTimeLimitPolicyTypesHistogramName[] =
+    "FamilyUser.TimeLimitPolicyTypes";
+
+ChildUserService::TimeLimitPolicyType GetTimeLimitPolicyType(
+    usage_time_limit::PolicyType policy_type) {
+  ChildUserService::TimeLimitPolicyType time_limit_policy;
+  switch (policy_type) {
+    case usage_time_limit::PolicyType::kFixedLimit:
+      time_limit_policy = ChildUserService::TimeLimitPolicyType::kBedTimeLimit;
+      break;
+    case usage_time_limit::PolicyType::kUsageLimit:
+      time_limit_policy =
+          ChildUserService::TimeLimitPolicyType::kScreenTimeLimit;
+      break;
+    case usage_time_limit::PolicyType::kOverride:
+      time_limit_policy =
+          ChildUserService::TimeLimitPolicyType::kOverrideTimeLimit;
+      break;
+    case usage_time_limit::PolicyType::kNoPolicy:
+      time_limit_policy = ChildUserService::TimeLimitPolicyType::kNoTimeLimit;
+      break;
+    default:
+      NOTREACHED();
+  }
+  return time_limit_policy;
+}
+}  // namespace
+
 // static
 const char ChildUserService::kFamilyLinkHelperAppPackageName[] =
     "com.google.android.apps.kids.familylinkhelper";
@@ -39,10 +79,23 @@
              : nullptr;
 }
 
-ChildUserService::ChildUserService(content::BrowserContext* context) {
-  DCHECK(context);
+// static
+const char* ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest() {
+  return kTimeLimitPolicyTypesHistogramName;
+}
+
+ChildUserService::ChildUserService(content::BrowserContext* context)
+    : profile_(Profile::FromBrowserContext(context)) {
+  DCHECK(profile_);
   app_time_controller_ = std::make_unique<app_time::AppTimeController>(
-      Profile::FromBrowserContext(context));
+      profile_, base::BindRepeating(&ChildUserService::ReportTimeLimitPolicy,
+                                    base::Unretained(this)));
+
+  pref_change_registrar_.Init(profile_->GetPrefs());
+  pref_change_registrar_.Add(
+      prefs::kUsageTimeLimit,
+      base::BindRepeating(&ChildUserService::ReportTimeLimitPolicy,
+                          base::Unretained(this)));
 }
 
 ChildUserService::~ChildUserService() = default;
@@ -132,20 +185,48 @@
   return app_time_controller_->web_time_enforcer()->time_limit();
 }
 
-void ChildUserService::GetEnabledAppTimeLimitPolicies(
-    std::set<FamilyUserParentalControlMetrics::TimeLimitPolicyType>*
-        enabled_policies) {
-  if (!app_time_controller_ || !enabled_policies)
-    return;
+void ChildUserService ::ReportTimeLimitPolicy() const {
+  const base::DictionaryValue* time_limit_prefs =
+      profile_->GetPrefs()->GetDictionary(prefs::kUsageTimeLimit);
+  DCHECK(time_limit_prefs);
 
-  if (app_time_controller_->HasWebTimeLimitRestriction()) {
-    enabled_policies->insert(
-        FamilyUserParentalControlMetrics::TimeLimitPolicyType::kWebTimeLimit);
+  std::set<usage_time_limit::PolicyType> enabled_policies =
+      usage_time_limit::GetEnabledTimeLimitPolicies(*time_limit_prefs);
+
+  for (const auto& policy_type : enabled_policies) {
+    TimeLimitPolicyType time_limit_policy = GetTimeLimitPolicyType(policy_type);
+
+    // `enabled_policies` does not contains
+    // usage_time_limit::PolicyType::kNoPolicy, so `time_limit_policy` never
+    // equals to TimeLimitPolicyType::kNoTimeLimit.
+    if (time_limit_policy == TimeLimitPolicyType::kNoTimeLimit)
+      continue;
+
+    base::UmaHistogramEnumeration(
+        /*name=*/kTimeLimitPolicyTypesHistogramName,
+        /*sample=*/time_limit_policy);
   }
 
+  bool has_policy_enabled = !enabled_policies.empty();
+
   if (app_time_controller_->HasAppTimeLimitRestriction()) {
-    enabled_policies->insert(
-        FamilyUserParentalControlMetrics::TimeLimitPolicyType::kAppTimeLimit);
+    base::UmaHistogramEnumeration(
+        /*name=*/kTimeLimitPolicyTypesHistogramName,
+        /*sample=*/TimeLimitPolicyType::kAppTimeLimit);
+    has_policy_enabled = true;
+  }
+
+  if (app_time_controller_->HasWebTimeLimitRestriction()) {
+    base::UmaHistogramEnumeration(
+        /*name=*/kTimeLimitPolicyTypesHistogramName,
+        /*sample=*/TimeLimitPolicyType::kWebTimeLimit);
+    has_policy_enabled = true;
+  }
+
+  if (!has_policy_enabled) {
+    base::UmaHistogramEnumeration(
+        /*name=*/kTimeLimitPolicyTypesHistogramName,
+        /*sample=*/TimeLimitPolicyType::kNoTimeLimit);
   }
 }
 
@@ -155,6 +236,7 @@
     app_time_controller_->RecordMetricsOnShutdown();
     app_time_controller_.reset();
   }
+  pref_change_registrar_.Remove(prefs::kUsageTimeLimit);
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ash/child_accounts/child_user_service.h b/chrome/browser/ash/child_accounts/child_user_service.h
index 308ea52..8481cc3 100644
--- a/chrome/browser/ash/child_accounts/child_user_service.h
+++ b/chrome/browser/ash/child_accounts/child_user_service.h
@@ -9,10 +9,10 @@
 #include <set>
 #include <string>
 
-#include "chrome/browser/ash/child_accounts/family_user_parental_control_metrics.h"
 #include "chrome/browser/ash/child_accounts/time_limits/app_activity_report_interface.h"
 #include "chrome/browser/ash/child_accounts/time_limits/app_time_limit_interface.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/prefs/pref_change_registrar.h"
 
 namespace base {
 class TimeDelta;
@@ -27,6 +27,7 @@
 }  // namespace enterprise_management
 
 class GURL;
+class Profile;
 
 namespace ash {
 namespace app_time {
@@ -55,11 +56,32 @@
     ChildUserService* const service_;
   };
 
+  // These enum values represent the current Family Link user's time limit
+  // policy type for the Family Experiences team's metrics. Multiple time
+  // limit policy types might be enabled at the same time. These values are
+  // logged to UMA. Entries should not be renumbered and numeric values should
+  // never be reused. Please keep in sync with "TimeLimitPolicyType" in
+  // src/tools/metrics/histograms/enums.xml.
+  enum class TimeLimitPolicyType {
+    kNoTimeLimit = 0,
+    kOverrideTimeLimit = 1,
+    kBedTimeLimit = 2,
+    kScreenTimeLimit = 3,
+    kWebTimeLimit = 4,
+    kAppTimeLimit = 5,  // Does not cover blocked apps.
+
+    // Used for UMA. Update kMaxValue to the last value. Add future entries
+    // above this comment. Sync with enums.xml.
+    kMaxValue = kAppTimeLimit
+  };
+
   // Family Link helper(for child and teens) is an app available to supervised
   // users and the companion app of Family Link app(for parents).
   static const char kFamilyLinkHelperAppPackageName[];
   static const char kFamilyLinkHelperAppPlayStoreURL[];
 
+  static const char* GetTimeLimitPolicyTypesHistogramNameForTest();
+
   explicit ChildUserService(content::BrowserContext* context);
   ChildUserService(const ChildUserService&) = delete;
   ChildUserService& operator=(const ChildUserService&) = delete;
@@ -96,17 +118,19 @@
   // |features::kWebTimeLimits| features are enabled.
   base::TimeDelta GetWebTimeLimit() const;
 
-  // Checks whether app time limit and chrome app time limit are enabled and
-  // inserts the enabled types to `enabled_policies`.
-  void GetEnabledAppTimeLimitPolicies(
-      std::set<FamilyUserParentalControlMetrics::TimeLimitPolicyType>*
-          enabled_policies);
+  // Report enabled TimeLimitPolicyType.
+  void ReportTimeLimitPolicy() const;
 
  private:
   // KeyedService:
   void Shutdown() override;
 
   std::unique_ptr<app_time::AppTimeController> app_time_controller_;
+
+  // Preference changes observer.
+  PrefChangeRegistrar pref_change_registrar_;
+
+  Profile* const profile_;
 };
 
 }  // namespace ash
diff --git a/chrome/browser/ash/child_accounts/family_user_parental_control_metrics.cc b/chrome/browser/ash/child_accounts/family_user_parental_control_metrics.cc
index e4b8c67..70d4553 100644
--- a/chrome/browser/ash/child_accounts/family_user_parental_control_metrics.cc
+++ b/chrome/browser/ash/child_accounts/family_user_parental_control_metrics.cc
@@ -4,45 +4,16 @@
 
 #include "chrome/browser/ash/child_accounts/family_user_parental_control_metrics.h"
 
-#include <set>
-
 #include "base/check.h"
-#include "base/metrics/histogram_functions.h"
-#include "base/values.h"
 #include "chrome/browser/ash/child_accounts/child_user_service.h"
 #include "chrome/browser/ash/child_accounts/child_user_service_factory.h"
-#include "chrome/browser/ash/child_accounts/usage_time_limit_processor.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/supervised_user/supervised_user_service.h"
 #include "chrome/browser/supervised_user/supervised_user_service_factory.h"
-#include "chrome/browser/supervised_user/supervised_user_url_filter.h"
-#include "chrome/common/pref_names.h"
-#include "components/prefs/pref_service.h"
 #include "components/user_manager/user_manager.h"
 
 namespace ash {
 
-namespace {
-
-// UMA histogram FamilyUser.TimeLimitPolicyTypes
-// Reports TimeLimitPolicyType which indicates what time limit policies
-// are enabled for current Family Link user. Could report multiple buckets if
-// multiple policies are enabled.
-constexpr char kTimeLimitPolicyTypesHistogramName[] =
-    "FamilyUser.TimeLimitPolicyTypes";
-
-// UMA histogram FamilyUser.WebFilterType
-// Reports WebFilterType which indicates web filter behaviour are used for
-// current Family Link user.
-constexpr char kWebFilterTypeHistogramName[] = "FamilyUser.WebFilterType";
-
-// UMA histogram FamilyUser.ManualSiteListType
-// Reports ManualSiteListType which indicates approved list and blocked list
-// usage for current Family Link user.
-constexpr char kManagedSiteListHistogramName[] = "FamilyUser.ManagedSiteList";
-
-}  // namespace
-
 FamilyUserParentalControlMetrics::FamilyUserParentalControlMetrics(
     Profile* profile)
     : profile_(profile),
@@ -54,79 +25,25 @@
 
 FamilyUserParentalControlMetrics::~FamilyUserParentalControlMetrics() = default;
 
-// static
-const char* FamilyUserParentalControlMetrics::
-    GetTimeLimitPolicyTypesHistogramNameForTest() {
-  return kTimeLimitPolicyTypesHistogramName;
-}
-
-// static
-const char*
-FamilyUserParentalControlMetrics::GetWebFilterTypeHistogramNameForTest() {
-  return kWebFilterTypeHistogramName;
-}
-
-// static
-const char*
-FamilyUserParentalControlMetrics::GetManagedSiteListHistogramNameForTest() {
-  return kManagedSiteListHistogramName;
-}
-
-void FamilyUserParentalControlMetrics::ReportTimeLimitPolicy() const {
-  const base::DictionaryValue* time_limit_prefs =
-      profile_->GetPrefs()->GetDictionary(prefs::kUsageTimeLimit);
-  DCHECK(time_limit_prefs);
-
-  std::set<TimeLimitPolicyType> enabled_policies;
-  usage_time_limit::GetEnabledTimeLimitPolicies(&enabled_policies,
-                                                *time_limit_prefs);
-  ChildUserService* child_user_service =
-      ChildUserServiceFactory::GetForBrowserContext(profile_);
-  if (child_user_service)
-    child_user_service->GetEnabledAppTimeLimitPolicies(&enabled_policies);
-
-  if (enabled_policies.empty()) {
-    base::UmaHistogramEnumeration(
-        /*name=*/kTimeLimitPolicyTypesHistogramName,
-        /*sample=*/TimeLimitPolicyType::kNoTimeLimit);
-    return;
-  }
-
-  for (const TimeLimitPolicyType& policy : enabled_policies) {
-    base::UmaHistogramEnumeration(
-        /*name=*/kTimeLimitPolicyTypesHistogramName,
-        /*sample=*/policy);
-  }
-}
-void FamilyUserParentalControlMetrics::ReportWebFilterPolicy() const {
-  // Ignores reports when prefs::kDefaultSupervisedUserFilteringBehavior is
-  // reset to default value. It might happen during sign out.
-  SupervisedUserService* supervised_user_service =
-      SupervisedUserServiceFactory::GetForProfile(profile_);
-  if (supervised_user_service->IsFilteringBehaviorPrefDefault() ||
-      !supervised_user_service->GetURLFilter()) {
-    return;
-  }
-
-  base::UmaHistogramEnumeration(
-      kWebFilterTypeHistogramName,
-      supervised_user_service->GetURLFilter()->GetWebFilterType());
-  base::UmaHistogramEnumeration(
-      kManagedSiteListHistogramName,
-      supervised_user_service->GetURLFilter()->GetManagedSiteList());
-}
-
 void FamilyUserParentalControlMetrics::OnNewDay() {
   // Reports Family Link user time limit policy type.
-  ReportTimeLimitPolicy();
+  ChildUserService* child_user_service =
+      ChildUserServiceFactory::GetForBrowserContext(profile_);
+  DCHECK(child_user_service);
+  child_user_service->ReportTimeLimitPolicy();
 
-  // Ignores the first report during OOBE. Prefs related to web filter policy
-  // may not have been successfully sync during OOBE process, which introduces
-  // bias.
+  SupervisedUserService* supervised_user_service =
+      SupervisedUserServiceFactory::GetForProfile(profile_);
+  DCHECK(supervised_user_service);
+  // Ignores the first report during OOBE. Prefs related to web filter
+  // policy may not have been successfully sync during OOBE process, which
+  // introduces bias.
   if (first_report_on_current_device_) {
     first_report_on_current_device_ = false;
   } else {
-    ReportWebFilterPolicy();
+    // Ignores reports when web filter prefs are reset to default value. It
+    // might happen during sign out.
+    supervised_user_service->ReportNonDefaultWebFilterValue();
   }
 }
 
diff --git a/chrome/browser/ash/child_accounts/family_user_parental_control_metrics.h b/chrome/browser/ash/child_accounts/family_user_parental_control_metrics.h
index fb760334..f86846e 100644
--- a/chrome/browser/ash/child_accounts/family_user_parental_control_metrics.h
+++ b/chrome/browser/ash/child_accounts/family_user_parental_control_metrics.h
@@ -12,30 +12,10 @@
 namespace ash {
 
 // A class for recording time limit metrics and web filter metrics for Family
-// Link users on Chrome OS. These metrics will be recorded at the beginning of
-// the first active session daily.
+// Link users on Chrome OS at the beginning of the first active session daily.
 class FamilyUserParentalControlMetrics
     : public FamilyUserMetricsService::Observer {
  public:
-  // These enum values represent the current Family Link user's time limit
-  // policy type for the Family Experiences team's metrics. Multiple time limit
-  // policy types might be enabled at the same time. These values are
-  // logged to UMA. Entries should not be renumbered and numeric values should
-  // never be reused. Please keep in sync with "TimeLimitPolicyType" in
-  // src/tools/metrics/histograms/enums.xml.
-  enum class TimeLimitPolicyType {
-    kNoTimeLimit = 0,
-    kOverrideTimeLimit = 1,
-    kBedTimeLimit = 2,
-    kScreenTimeLimit = 3,
-    kWebTimeLimit = 4,
-    kAppTimeLimit = 5,
-
-    // Used for UMA. Update kMaxValue to the last value. Add future entries
-    // above this comment. Sync with enums.xml.
-    kMaxValue = kAppTimeLimit
-  };
-
   explicit FamilyUserParentalControlMetrics(Profile* profile);
   FamilyUserParentalControlMetrics(const FamilyUserParentalControlMetrics&) =
       delete;
@@ -43,20 +23,10 @@
       const FamilyUserParentalControlMetrics&) = delete;
   ~FamilyUserParentalControlMetrics() override;
 
-  static const char* GetTimeLimitPolicyTypesHistogramNameForTest();
-  static const char* GetWebFilterTypeHistogramNameForTest();
-  static const char* GetManagedSiteListHistogramNameForTest();
-
-  // TODO(crbug/1152622): listen to the policy by using PrefChangeRegistrar to
-  // observe pref. Report when policy change in addition to OnNewDay().
   // FamilyUserMetricsService::Observer:
   void OnNewDay() override;
 
  private:
-  void ReportTimeLimitPolicy() const;
-  void ReportWebFilterPolicy() const;
-
- private:
   Profile* const profile_;
   bool first_report_on_current_device_ = false;
 };
diff --git a/chrome/browser/ash/child_accounts/family_user_parental_control_metrics_unittest.cc b/chrome/browser/ash/child_accounts/family_user_parental_control_metrics_unittest.cc
index cf14763..7ba2e0b 100644
--- a/chrome/browser/ash/child_accounts/family_user_parental_control_metrics_unittest.cc
+++ b/chrome/browser/ash/child_accounts/family_user_parental_control_metrics_unittest.cc
@@ -101,11 +101,11 @@
     profile_builder.SetSupervisedUserId(supervised_users::kChildAccountSUID);
     profile_ = profile_builder.Build();
     EXPECT_TRUE(profile_->IsChild());
-    parental_control_metrics_ =
-        std::make_unique<FamilyUserParentalControlMetrics>(profile_.get());
     supervised_user_service_ =
         SupervisedUserServiceFactory::GetForProfile(profile_.get());
     supervised_user_service_->Init();
+    parental_control_metrics_ =
+        std::make_unique<FamilyUserParentalControlMetrics>(profile_.get());
   }
 
   void TearDown() override {
@@ -118,7 +118,7 @@
     ChildUserService* service =
         ChildUserServiceFactory::GetForBrowserContext(profile_.get());
     ChildUserService::TestApi test_api = ChildUserService::TestApi(service);
-    DCHECK(test_api.app_time_controller());
+    EXPECT_TRUE(test_api.app_time_controller());
     return test_api.app_time_controller()->app_registry();
   }
 
@@ -130,6 +130,8 @@
   content::BrowserTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
 
+  base::HistogramTester histogram_tester_;
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<FamilyUserParentalControlMetrics> parental_control_metrics_;
@@ -137,7 +139,7 @@
 };
 
 TEST_F(FamilyUserParentalControlMetricsTest, BedAndScreenTimeLimitMetrics) {
-  base::HistogramTester histogram_tester;
+  ASSERT_TRUE(ChildUserServiceFactory::GetForBrowserContext(profile_.get()));
 
   base::Value policy_content =
       utils::CreateTimeLimitPolicy(utils::CreateTime(6, 0));
@@ -155,30 +157,43 @@
       /*last_updated=*/base::Time::Now());
 
   GetPrefs()->Set(prefs::kUsageTimeLimit, policy_content);
-  // Triggers report:
+
+  histogram_tester_.ExpectBucketCount(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
+      /*sample=*/
+      ChildUserService::TimeLimitPolicyType::kBedTimeLimit,
+      /*expected_count=*/1);
+  histogram_tester_.ExpectBucketCount(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
+      /*sample=*/
+      ChildUserService::TimeLimitPolicyType::kScreenTimeLimit,
+      /*expected_count=*/1);
+
+  histogram_tester_.ExpectTotalCount(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
+      /*expected_count=*/2);
+
+  // Tests daily report.
   OnNewDay();
 
-  histogram_tester.ExpectBucketCount(
-      FamilyUserParentalControlMetrics::
-          GetTimeLimitPolicyTypesHistogramNameForTest(),
+  histogram_tester_.ExpectBucketCount(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
       /*sample=*/
-      FamilyUserParentalControlMetrics::TimeLimitPolicyType::kBedTimeLimit,
-      /*expected_count=*/1);
-  histogram_tester.ExpectBucketCount(
-      FamilyUserParentalControlMetrics::
-          GetTimeLimitPolicyTypesHistogramNameForTest(),
-      /*sample=*/
-      FamilyUserParentalControlMetrics::TimeLimitPolicyType::kScreenTimeLimit,
-      /*expected_count=*/1);
-
-  histogram_tester.ExpectTotalCount(
-      FamilyUserParentalControlMetrics::
-          GetTimeLimitPolicyTypesHistogramNameForTest(),
+      ChildUserService::TimeLimitPolicyType::kBedTimeLimit,
       /*expected_count=*/2);
+  histogram_tester_.ExpectBucketCount(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
+      /*sample=*/
+      ChildUserService::TimeLimitPolicyType::kScreenTimeLimit,
+      /*expected_count=*/2);
+
+  histogram_tester_.ExpectTotalCount(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
+      /*expected_count=*/4);
 }
 
 TEST_F(FamilyUserParentalControlMetricsTest, OverrideTimeLimitMetrics) {
-  base::HistogramTester histogram_tester;
+  ASSERT_TRUE(ChildUserServiceFactory::GetForBrowserContext(profile_.get()));
 
   // Adds override time policy created at 1 day ago.
   base::Value policy_content =
@@ -191,22 +206,17 @@
       /*duration=*/base::TimeDelta::FromHours(2));
   GetPrefs()->Set(prefs::kUsageTimeLimit, policy_content);
 
-  // Triggers report.
-  OnNewDay();
-
   // The override time limit policy would not get reported since the difference
   // between reported and created time are greater than 1 day.
-  histogram_tester.ExpectBucketCount(
-      FamilyUserParentalControlMetrics::
-          GetTimeLimitPolicyTypesHistogramNameForTest(),
+  histogram_tester_.ExpectBucketCount(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
       /*sample=*/
-      FamilyUserParentalControlMetrics::TimeLimitPolicyType::kOverrideTimeLimit,
+      ChildUserService::TimeLimitPolicyType::kOverrideTimeLimit,
       /*expected_count=*/0);
-  histogram_tester.ExpectUniqueSample(
-      FamilyUserParentalControlMetrics::
-          GetTimeLimitPolicyTypesHistogramNameForTest(),
+  histogram_tester_.ExpectUniqueSample(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
       /*sample=*/
-      FamilyUserParentalControlMetrics::TimeLimitPolicyType::kNoTimeLimit,
+      ChildUserService::TimeLimitPolicyType::kNoTimeLimit,
       /*expected_count=*/1);
 
   // Adds override time policy. Created and reported within 1 day.
@@ -217,26 +227,28 @@
       /*duration=*/base::TimeDelta::FromHours(2));
   GetPrefs()->Set(prefs::kUsageTimeLimit, policy_content);
 
-  // Triggers report.
-  OnNewDay();
-
   // The override time limit policy would get reported since the created
   // time and reported time are within 1 day.
-  histogram_tester.ExpectBucketCount(
-      FamilyUserParentalControlMetrics::
-          GetTimeLimitPolicyTypesHistogramNameForTest(),
+  histogram_tester_.ExpectBucketCount(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
       /*sample=*/
-      FamilyUserParentalControlMetrics::TimeLimitPolicyType::kOverrideTimeLimit,
+      ChildUserService::TimeLimitPolicyType::kOverrideTimeLimit,
       /*expected_count=*/1);
 
-  histogram_tester.ExpectTotalCount(
-      FamilyUserParentalControlMetrics::
-          GetTimeLimitPolicyTypesHistogramNameForTest(),
+  // Tests daily report.
+  OnNewDay();
+
+  histogram_tester_.ExpectBucketCount(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
+      /*sample=*/
+      ChildUserService::TimeLimitPolicyType::kOverrideTimeLimit,
       /*expected_count=*/2);
+  histogram_tester_.ExpectTotalCount(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
+      /*expected_count=*/3);
 }
 
 TEST_F(FamilyUserParentalControlMetricsTest, AppAndWebTimeLimitMetrics) {
-  base::HistogramTester histogram_tester;
   apps::AppServiceTest app_service_test_;
   ArcAppTest arc_test_;
 
@@ -274,45 +286,59 @@
     *value = builder.value().Clone();
   }
 
+  histogram_tester_.ExpectBucketCount(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
+      /*sample=*/
+      ChildUserService::TimeLimitPolicyType::kWebTimeLimit,
+      /*expected_count=*/1);
+  histogram_tester_.ExpectBucketCount(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
+      /*sample=*/
+      ChildUserService::TimeLimitPolicyType::kAppTimeLimit,
+      /*expected_count=*/1);
+
+  // Tests daily report.
   OnNewDay();
 
-  histogram_tester.ExpectBucketCount(
-      FamilyUserParentalControlMetrics::
-          GetTimeLimitPolicyTypesHistogramNameForTest(),
+  histogram_tester_.ExpectBucketCount(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
       /*sample=*/
-      FamilyUserParentalControlMetrics::TimeLimitPolicyType::kWebTimeLimit,
-      /*expected_count=*/1);
-  histogram_tester.ExpectBucketCount(
-      FamilyUserParentalControlMetrics::
-          GetTimeLimitPolicyTypesHistogramNameForTest(),
-      /*sample=*/
-      FamilyUserParentalControlMetrics::TimeLimitPolicyType::kAppTimeLimit,
-      /*expected_count=*/1);
-  histogram_tester.ExpectTotalCount(
-      FamilyUserParentalControlMetrics::
-          GetTimeLimitPolicyTypesHistogramNameForTest(),
+      ChildUserService::TimeLimitPolicyType::kWebTimeLimit,
       /*expected_count=*/2);
+  histogram_tester_.ExpectBucketCount(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
+      /*sample=*/
+      ChildUserService::TimeLimitPolicyType::kAppTimeLimit,
+      /*expected_count=*/2);
+
+  histogram_tester_.ExpectTotalCount(
+      ChildUserService::GetTimeLimitPolicyTypesHistogramNameForTest(),
+      /*expected_count=*/4);
 }
 
 TEST_F(FamilyUserParentalControlMetricsTest, WebFilterTypeMetric) {
-  base::HistogramTester histogram_tester;
-
+  // Overriding the value of prefs::kSupervisedUserSafeSites and
+  // prefs::kDefaultSupervisedUserFilteringBehavior in default storage is
+  // needed, otherwise no report could be triggered policies change or
+  // OnNewDay(). Since the default values are the same of override values, the
+  // WebFilterType doesn't change and no report here.
   GetPrefs()->SetInteger(prefs::kDefaultSupervisedUserFilteringBehavior,
                          SupervisedUserURLFilter::ALLOW);
   GetPrefs()->SetBoolean(prefs::kSupervisedUserSafeSites, true);
+
+  // Tests daily report.
   OnNewDay();
-  histogram_tester.ExpectUniqueSample(
-      FamilyUserParentalControlMetrics::FamilyUserParentalControlMetrics::
-          GetWebFilterTypeHistogramNameForTest(),
+  histogram_tester_.ExpectUniqueSample(
+      SupervisedUserURLFilter::GetWebFilterTypeHistogramNameForTest(),
       /*sample=*/
       SupervisedUserURLFilter::WebFilterType::kTryToBlockMatureSites,
       /*expected_count=*/1);
 
   // Tests filter "allow all sites".
   GetPrefs()->SetBoolean(prefs::kSupervisedUserSafeSites, false);
-  OnNewDay();
-  histogram_tester.ExpectBucketCount(
-      FamilyUserParentalControlMetrics::GetWebFilterTypeHistogramNameForTest(),
+
+  histogram_tester_.ExpectBucketCount(
+      SupervisedUserURLFilter::GetWebFilterTypeHistogramNameForTest(),
       /*sample=*/
       SupervisedUserURLFilter::WebFilterType::kAllowAllSites,
       /*expected_count=*/1);
@@ -320,30 +346,32 @@
   // Tests filter "only allow certain sites" on Family Link app.
   GetPrefs()->SetInteger(prefs::kDefaultSupervisedUserFilteringBehavior,
                          SupervisedUserURLFilter::BLOCK);
-  OnNewDay();
-  histogram_tester.ExpectBucketCount(
-      FamilyUserParentalControlMetrics::GetWebFilterTypeHistogramNameForTest(),
+
+  histogram_tester_.ExpectBucketCount(
+      SupervisedUserURLFilter::GetWebFilterTypeHistogramNameForTest(),
       /*sample=*/
       SupervisedUserURLFilter::WebFilterType::kCertainSites,
       /*expected_count=*/1);
 
-  histogram_tester.ExpectTotalCount(
-      FamilyUserParentalControlMetrics::GetWebFilterTypeHistogramNameForTest(),
+  histogram_tester_.ExpectTotalCount(
+      SupervisedUserURLFilter::GetWebFilterTypeHistogramNameForTest(),
       /*expected_count=*/3);
 }
 
 TEST_F(FamilyUserParentalControlMetricsTest, ManagedSiteListTypeMetric) {
-  base::HistogramTester histogram_tester;
-
+  // Overriding the value of prefs::kSupervisedUserSafeSites and
+  // prefs::kDefaultSupervisedUserFilteringBehavior in default storage is
+  // needed, otherwise no report could be triggered by policies change or
+  // OnNewDay(). Since the default values are the same of override values, the
+  // WebFilterType doesn't change and no report here.
   GetPrefs()->SetInteger(prefs::kDefaultSupervisedUserFilteringBehavior,
                          SupervisedUserURLFilter::ALLOW);
-  GetPrefs()->Set(prefs::kSupervisedUserManualHosts, base::DictionaryValue());
-  GetPrefs()->Set(prefs::kSupervisedUserManualURLs, base::DictionaryValue());
+  GetPrefs()->SetBoolean(prefs::kSupervisedUserSafeSites, true);
 
+  // Tests daily report.
   OnNewDay();
-  histogram_tester.ExpectBucketCount(
-      FamilyUserParentalControlMetrics::
-          GetManagedSiteListHistogramNameForTest(),
+  histogram_tester_.ExpectUniqueSample(
+      SupervisedUserURLFilter::GetManagedSiteListHistogramNameForTest(),
       /*sample=*/
       SupervisedUserURLFilter::ManagedSiteList::kEmpty,
       /*expected_count=*/1);
@@ -355,10 +383,9 @@
     base::DictionaryValue* hosts = hosts_update.Get();
     hosts->SetKey(kExampleHost0, base::Value(false));
   }
-  OnNewDay();
-  histogram_tester.ExpectBucketCount(
-      FamilyUserParentalControlMetrics::
-          GetManagedSiteListHistogramNameForTest(),
+
+  histogram_tester_.ExpectBucketCount(
+      SupervisedUserURLFilter::GetManagedSiteListHistogramNameForTest(),
       /*sample=*/
       SupervisedUserURLFilter::ManagedSiteList::kBlockedListOnly,
       /*expected_count=*/1);
@@ -370,10 +397,9 @@
     base::DictionaryValue* hosts = hosts_update.Get();
     hosts->SetKey(kExampleHost0, base::Value(true));
   }
-  OnNewDay();
-  histogram_tester.ExpectBucketCount(
-      FamilyUserParentalControlMetrics::
-          GetManagedSiteListHistogramNameForTest(),
+
+  histogram_tester_.ExpectBucketCount(
+      SupervisedUserURLFilter::GetManagedSiteListHistogramNameForTest(),
       /*sample=*/
       SupervisedUserURLFilter::ManagedSiteList::kApprovedListOnly,
       /*expected_count=*/1);
@@ -385,17 +411,15 @@
     base::DictionaryValue* urls = urls_update.Get();
     urls->SetKey(kExampleURL1, base::Value(false));
   }
-  OnNewDay();
-  histogram_tester.ExpectBucketCount(
-      FamilyUserParentalControlMetrics::
-          GetManagedSiteListHistogramNameForTest(),
+
+  histogram_tester_.ExpectBucketCount(
+      SupervisedUserURLFilter::GetManagedSiteListHistogramNameForTest(),
       /*sample=*/
       SupervisedUserURLFilter::ManagedSiteList::kBoth,
       /*expected_count=*/1);
 
-  histogram_tester.ExpectTotalCount(
-      FamilyUserParentalControlMetrics::
-          GetManagedSiteListHistogramNameForTest(),
+  histogram_tester_.ExpectTotalCount(
+      SupervisedUserURLFilter::GetManagedSiteListHistogramNameForTest(),
       /*expected_count=*/4);
 }
 
diff --git a/chrome/browser/ash/child_accounts/time_limits/app_time_controller.cc b/chrome/browser/ash/child_accounts/time_limits/app_time_controller.cc
index 6f11804..7aa748e 100644
--- a/chrome/browser/ash/child_accounts/time_limits/app_time_controller.cc
+++ b/chrome/browser/ash/child_accounts/time_limits/app_time_controller.cc
@@ -209,7 +209,9 @@
   registry->RegisterDictionaryPref(prefs::kPerAppTimeLimitsAllowlistPolicy);
 }
 
-AppTimeController::AppTimeController(Profile* profile)
+AppTimeController::AppTimeController(
+    Profile* profile,
+    base::RepeatingClosure on_policy_updated_callback)
     : profile_(profile),
       app_service_wrapper_(std::make_unique<AppServiceWrapper>(profile)),
       app_registry_(
@@ -218,7 +220,8 @@
                                                 profile->GetPrefs())),
       web_time_activity_provider_(std::make_unique<WebTimeActivityProvider>(
           this,
-          app_service_wrapper_.get())) {
+          app_service_wrapper_.get())),
+      on_policy_updated_callback_(on_policy_updated_callback) {
   DCHECK(profile);
 
   if (WebTimeLimitEnforcer::IsEnabled())
@@ -388,6 +391,8 @@
             .size();
 
     base::UmaHistogramCounts1000(kBlockedAppsCountMetric, blocked_apps);
+
+    on_policy_updated_callback_.Run();
   }
 }
 
diff --git a/chrome/browser/ash/child_accounts/time_limits/app_time_controller.h b/chrome/browser/ash/child_accounts/time_limits/app_time_controller.h
index fbd5406..b8fc174 100644
--- a/chrome/browser/ash/child_accounts/time_limits/app_time_controller.h
+++ b/chrome/browser/ash/child_accounts/time_limits/app_time_controller.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include "base/callback_forward.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/default_tick_clock.h"
 #include "base/time/time.h"
@@ -70,7 +71,8 @@
   // Registers preferences
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
-  explicit AppTimeController(Profile* profile);
+  AppTimeController(Profile* profile,
+                    base::RepeatingClosure on_policy_updated_callback);
   AppTimeController(const AppTimeController&) = delete;
   AppTimeController& operator=(const AppTimeController&) = delete;
   ~AppTimeController() override;
@@ -180,6 +182,8 @@
   int patl_policy_update_count_ = 0;
   int apps_with_limit_ = 0;
 
+  base::RepeatingClosure on_policy_updated_callback_;
+
   base::WeakPtrFactory<AppTimeController> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ash/child_accounts/time_limits/app_time_controller_unittest.cc b/chrome/browser/ash/child_accounts/time_limits/app_time_controller_unittest.cc
index 9e492f9..c8cef63c6 100644
--- a/chrome/browser/ash/child_accounts/time_limits/app_time_controller_unittest.cc
+++ b/chrome/browser/ash/child_accounts/time_limits/app_time_controller_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ash/child_accounts/time_limits/app_time_controller.h"
 
+#include "base/bind.h"
+#include "base/callback_helpers.h"
 #include "base/strings/strcat.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
@@ -278,7 +280,8 @@
 }
 
 void AppTimeControllerTest::InstantiateController() {
-  controller_ = std::make_unique<AppTimeController>(&profile_);
+  controller_ = std::make_unique<AppTimeController>(
+      &profile_, base::DoNothing::Repeatedly());
   test_api_ = std::make_unique<AppTimeController::TestApi>(controller_.get());
 }
 
diff --git a/chrome/browser/ash/child_accounts/usage_time_limit_processor.cc b/chrome/browser/ash/child_accounts/usage_time_limit_processor.cc
index 18eba28..caa50b37 100644
--- a/chrome/browser/ash/child_accounts/usage_time_limit_processor.cc
+++ b/chrome/browser/ash/child_accounts/usage_time_limit_processor.cc
@@ -1333,25 +1333,21 @@
   return updated_policies;
 }
 
-void GetEnabledTimeLimitPolicies(
-    std::set<FamilyUserParentalControlMetrics::TimeLimitPolicyType>*
-        enabled_policies,
+std::set<PolicyType> GetEnabledTimeLimitPolicies(
     const base::Value& time_limit_prefs) {
   DCHECK(time_limit_prefs.is_dict());
-  if (!enabled_policies)
-    return;
+  std::set<PolicyType> enabled_policies;
+
   absl::optional<internal::TimeWindowLimit> time_window_limit =
       TimeWindowLimitFromPolicy(time_limit_prefs);
   if (time_window_limit && !time_window_limit->entries.empty()) {
-    enabled_policies->insert(
-        FamilyUserParentalControlMetrics::TimeLimitPolicyType::kBedTimeLimit);
+    enabled_policies.insert(PolicyType::kFixedLimit);
   }
 
   absl::optional<internal::TimeUsageLimit> time_usage_limit =
       TimeUsageLimitFromPolicy(time_limit_prefs);
   if (time_usage_limit && !time_usage_limit->entries.empty()) {
-    enabled_policies->insert(FamilyUserParentalControlMetrics::
-                                 TimeLimitPolicyType::kScreenTimeLimit);
+    enabled_policies.insert(PolicyType::kUsageLimit);
   }
 
   absl::optional<TimeLimitOverride> time_limit_override =
@@ -1360,9 +1356,10 @@
   // Ignores the override time limit that is not created within 1 day.
   if (time_limit_override && now > time_limit_override->created_at() &&
       now - time_limit_override->created_at() < base::TimeDelta::FromDays(1)) {
-    enabled_policies->insert(FamilyUserParentalControlMetrics::
-                                 TimeLimitPolicyType::kOverrideTimeLimit);
+    enabled_policies.insert(PolicyType::kOverride);
   }
+
+  return enabled_policies;
 }
 
 }  // namespace usage_time_limit
diff --git a/chrome/browser/ash/child_accounts/usage_time_limit_processor.h b/chrome/browser/ash/child_accounts/usage_time_limit_processor.h
index cf9e795..e55d3fb 100644
--- a/chrome/browser/ash/child_accounts/usage_time_limit_processor.h
+++ b/chrome/browser/ash/child_accounts/usage_time_limit_processor.h
@@ -15,7 +15,6 @@
 
 #include "base/macros.h"
 #include "base/time/time.h"
-#include "chrome/browser/ash/child_accounts/family_user_parental_control_metrics.h"
 #include "chromeos/settings/timezone_settings.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -203,12 +202,10 @@
 std::set<PolicyType> UpdatedPolicyTypes(const base::Value& old_policy,
                                         const base::Value& new_policy);
 
-// Retrieves the the time limit polices in time_limit_prefs and inserts them to
-// `enabled_policies`. `time_limit_prefs` is the value of prefs::kUsageTimeLimit
-// which stores the usage time limit preference of a user.
-void GetEnabledTimeLimitPolicies(
-    std::set<FamilyUserParentalControlMetrics::TimeLimitPolicyType>*
-        enabled_policies,
+// Returns the active time limit polices in `time_limit_prefs`.
+// `time_limit_prefs` is the value of prefs::kUsageTimeLimit which stores the
+// usage time limit preference of a user.
+std::set<PolicyType> GetEnabledTimeLimitPolicies(
     const base::Value& time_limit_prefs);
 
 }  // namespace usage_time_limit
diff --git a/chrome/browser/ash/crosapi/BUILD.gn b/chrome/browser/ash/crosapi/BUILD.gn
index b6b6349..6dc87d3 100644
--- a/chrome/browser/ash/crosapi/BUILD.gn
+++ b/chrome/browser/ash/crosapi/BUILD.gn
@@ -65,6 +65,8 @@
     "metrics_reporting_ash.h",
     "prefs_ash.cc",
     "prefs_ash.h",
+    "remoting_ash.cc",
+    "remoting_ash.h",
     "screen_manager_ash.cc",
     "screen_manager_ash.h",
     "select_file_ash.cc",
@@ -129,6 +131,7 @@
     "//extensions/common",
     "//printing:printing",
     "//printing:printing_base",
+    "//remoting/host/chromeos:remoting_service",
     "//ui/message_center",
     "//ui/message_center/public/cpp",
     "//ui/shell_dialogs",
diff --git a/chrome/browser/ash/crosapi/DEPS b/chrome/browser/ash/crosapi/DEPS
index 37178cb..e8cc89ba 100644
--- a/chrome/browser/ash/crosapi/DEPS
+++ b/chrome/browser/ash/crosapi/DEPS
@@ -6,6 +6,11 @@
     "+chrome/browser/ash/crosapi",
     "+ui/message_center/message_center.h",
   ],
+  "remoting_ash\.cc": [
+    # For remote support functionality.
+    "+remoting/host/chromeos/remote_support_host_ash.h",
+    "+remoting/host/chromeos/remoting_service.h",
+  ],
   "screen_manager_ash\.cc": [
     # For window parenting.
     "+ash/shell.h",
diff --git a/chrome/browser/ash/crosapi/browser_util.cc b/chrome/browser/ash/crosapi/browser_util.cc
index 72b4dd29..1581ea7e 100644
--- a/chrome/browser/ash/crosapi/browser_util.cc
+++ b/chrome/browser/ash/crosapi/browser_util.cc
@@ -55,6 +55,7 @@
 #include "chromeos/crosapi/mojom/message_center.mojom.h"
 #include "chromeos/crosapi/mojom/metrics_reporting.mojom.h"
 #include "chromeos/crosapi/mojom/prefs.mojom.h"
+#include "chromeos/crosapi/mojom/remoting.mojom.h"
 #include "chromeos/crosapi/mojom/screen_manager.mojom.h"
 #include "chromeos/crosapi/mojom/system_display.mojom.h"
 #include "chromeos/crosapi/mojom/task_manager.mojom.h"
@@ -233,6 +234,7 @@
     MakeInterfaceVersionEntry<crosapi::mojom::MessageCenter>(),
     MakeInterfaceVersionEntry<crosapi::mojom::MetricsReporting>(),
     MakeInterfaceVersionEntry<crosapi::mojom::Prefs>(),
+    MakeInterfaceVersionEntry<crosapi::mojom::Remoting>(),
     MakeInterfaceVersionEntry<crosapi::mojom::ScreenManager>(),
     MakeInterfaceVersionEntry<crosapi::mojom::SnapshotCapturer>(),
     MakeInterfaceVersionEntry<crosapi::mojom::SystemDisplay>(),
@@ -261,7 +263,7 @@
 }
 
 static_assert(
-    crosapi::mojom::Crosapi::Version_ == 29,
+    crosapi::mojom::Crosapi::Version_ == 32,
     "if you add a new crosapi, please add it to kInterfaceVersionEntries");
 static_assert(!HasDuplicatedUuid(),
               "Each Crosapi Mojom interface should have unique UUID.");
diff --git a/chrome/browser/ash/crosapi/crosapi_ash.cc b/chrome/browser/ash/crosapi/crosapi_ash.cc
index 68c8843..7fda5a14 100644
--- a/chrome/browser/ash/crosapi/crosapi_ash.cc
+++ b/chrome/browser/ash/crosapi/crosapi_ash.cc
@@ -32,6 +32,7 @@
 #include "chrome/browser/ash/crosapi/message_center_ash.h"
 #include "chrome/browser/ash/crosapi/metrics_reporting_ash.h"
 #include "chrome/browser/ash/crosapi/prefs_ash.h"
+#include "chrome/browser/ash/crosapi/remoting_ash.h"
 #include "chrome/browser/ash/crosapi/screen_manager_ash.h"
 #include "chrome/browser/ash/crosapi/select_file_ash.h"
 #include "chrome/browser/ash/crosapi/system_display_ash.h"
@@ -106,6 +107,7 @@
       prefs_ash_(
           std::make_unique<PrefsAsh>(g_browser_process->profile_manager(),
                                      g_browser_process->local_state())),
+      remoting_ash_(std::make_unique<RemotingAsh>()),
       screen_manager_ash_(std::make_unique<ScreenManagerAsh>()),
       select_file_ash_(std::make_unique<SelectFileAsh>()),
       system_display_ash_(std::make_unique<SystemDisplayAsh>()),
@@ -297,6 +299,10 @@
   prefs_ash_->BindReceiver(std::move(receiver));
 }
 
+void CrosapiAsh::BindRemoting(mojo::PendingReceiver<mojom::Remoting> receiver) {
+  remoting_ash_->BindReceiver(std::move(receiver));
+}
+
 void CrosapiAsh::BindUrlHandler(
     mojo::PendingReceiver<mojom::UrlHandler> receiver) {
   url_handler_ash_->BindReceiver(std::move(receiver));
diff --git a/chrome/browser/ash/crosapi/crosapi_ash.h b/chrome/browser/ash/crosapi/crosapi_ash.h
index 2b29d8d..d815f20c 100644
--- a/chrome/browser/ash/crosapi/crosapi_ash.h
+++ b/chrome/browser/ash/crosapi/crosapi_ash.h
@@ -35,6 +35,7 @@
 class MessageCenterAsh;
 class MetricsReportingAsh;
 class PrefsAsh;
+class RemotingAsh;
 class ScreenManagerAsh;
 class SelectFileAsh;
 class SystemDisplayAsh;
@@ -93,6 +94,7 @@
   void BindMetricsReporting(
       mojo::PendingReceiver<mojom::MetricsReporting> receiver) override;
   void BindPrefs(mojo::PendingReceiver<mojom::Prefs> receiver) override;
+  void BindRemoting(mojo::PendingReceiver<mojom::Remoting> receiver) override;
   void BindScreenManager(
       mojo::PendingReceiver<mojom::ScreenManager> receiver) override;
   void BindSelectFile(
@@ -168,6 +170,7 @@
   std::unique_ptr<MessageCenterAsh> message_center_ash_;
   std::unique_ptr<MetricsReportingAsh> metrics_reporting_ash_;
   std::unique_ptr<PrefsAsh> prefs_ash_;
+  std::unique_ptr<RemotingAsh> remoting_ash_;
   std::unique_ptr<ScreenManagerAsh> screen_manager_ash_;
   std::unique_ptr<SelectFileAsh> select_file_ash_;
   std::unique_ptr<SystemDisplayAsh> system_display_ash_;
diff --git a/chrome/browser/ash/crosapi/remoting_ash.cc b/chrome/browser/ash/crosapi/remoting_ash.cc
new file mode 100644
index 0000000..2009041b
--- /dev/null
+++ b/chrome/browser/ash/crosapi/remoting_ash.cc
@@ -0,0 +1,45 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/crosapi/remoting_ash.h"
+
+#include <utility>
+
+#include "chromeos/crosapi/mojom/remoting.mojom.h"
+#include "content/public/browser/browser_thread.h"
+#include "remoting/host/chromeos/remote_support_host_ash.h"
+#include "remoting/host/chromeos/remoting_service.h"
+
+namespace crosapi {
+
+RemotingAsh::RemotingAsh() = default;
+RemotingAsh::~RemotingAsh() = default;
+
+void RemotingAsh::BindReceiver(
+    mojo::PendingReceiver<mojom::Remoting> receiver) {
+  receivers_.Add(this, std::move(receiver));
+}
+
+void RemotingAsh::GetSupportHostDetails(
+    GetSupportHostDetailsCallback callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  std::move(callback).Run(remoting::RemoteSupportHostAsh::GetHostDetails());
+}
+
+void RemotingAsh::StartSupportSession(
+    remoting::mojom::SupportSessionParamsPtr params,
+    StartSupportSessionCallback callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  remoting::RemotingService::Get().GetSupportHost().StartSession(
+      std::move(params),
+      base::BindOnce(
+          [](StartSupportSessionCallback callback,
+             remoting::mojom::StartSupportSessionResponsePtr response) {
+            std::move(callback).Run(std::move(response));
+          },
+          std::move(callback)));
+}
+
+}  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/remoting_ash.h b/chrome/browser/ash/crosapi/remoting_ash.h
new file mode 100644
index 0000000..fc9f0a5
--- /dev/null
+++ b/chrome/browser/ash/crosapi/remoting_ash.h
@@ -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.
+
+#ifndef CHROME_BROWSER_ASH_CROSAPI_REMOTING_ASH_H_
+#define CHROME_BROWSER_ASH_CROSAPI_REMOTING_ASH_H_
+
+#include "chromeos/crosapi/mojom/remoting.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+
+namespace crosapi {
+
+// The ash-chrome implementation of the Remoting crosapi interface.  This class
+// allows callers to create remote support sessions in ash-chrome from another
+// process (e.g. Lacros). This class is affine to the Main/UI thread.
+class RemotingAsh : public mojom::Remoting {
+ public:
+  RemotingAsh();
+  RemotingAsh(const RemotingAsh&) = delete;
+  RemotingAsh& operator=(const RemotingAsh&) = delete;
+  ~RemotingAsh() override;
+
+  void BindReceiver(mojo::PendingReceiver<mojom::Remoting> receiver);
+
+  // mojom::Remoting implementation.
+  void GetSupportHostDetails(GetSupportHostDetailsCallback callback) override;
+  void StartSupportSession(remoting::mojom::SupportSessionParamsPtr params,
+                           StartSupportSessionCallback callback) override;
+
+ private:
+  // Support any number of connections.
+  mojo::ReceiverSet<mojom::Remoting> receivers_;
+};
+
+}  // namespace crosapi
+
+#endif  // CHROME_BROWSER_ASH_CROSAPI_REMOTING_ASH_H_
diff --git a/chrome/browser/autofill/android/personal_data_manager_android.cc b/chrome/browser/autofill/android/personal_data_manager_android.cc
index a870bd9e..df401a6 100644
--- a/chrome/browser/autofill/android/personal_data_manager_android.cc
+++ b/chrome/browser/autofill/android/personal_data_manager_android.cc
@@ -72,11 +72,27 @@
   return GetProfile()->GetPrefs();
 }
 
-void MaybeSetRawInfo(AutofillProfile* profile,
-                     autofill::ServerFieldType type,
-                     const base::android::JavaRef<jstring>& jstr) {
-  if (!jstr.is_null())
-    profile->SetRawInfo(type, ConvertJavaStringToUTF16(jstr));
+void MaybeSetRawInfoWithVerificationStatus(
+    AutofillProfile* profile,
+    autofill::ServerFieldType type,
+    const base::android::JavaRef<jstring>& value,
+    jint status) {
+  if (value)
+    profile->SetRawInfoWithVerificationStatus(
+        type, ConvertJavaStringToUTF16(value),
+        static_cast<structured_address::VerificationStatus>(status));
+}
+
+void MaybeSetInfoWithVerificationStatus(
+    AutofillProfile* profile,
+    autofill::ServerFieldType type,
+    const base::android::JavaRef<jstring>& value,
+    jint status) {
+  if (value)
+    profile->SetInfoWithVerificationStatus(
+        type, ConvertJavaStringToUTF16(value),
+        g_browser_process->GetApplicationLocale(),
+        static_cast<structured_address::VerificationStatus>(status));
 }
 
 // Self-deleting requester of full card details, including full PAN and the CVC
@@ -289,23 +305,38 @@
       ConvertUTF16ToJavaString(
           env, profile.GetInfo(AutofillType(NAME_HONORIFIC_PREFIX),
                                g_browser_process->GetApplicationLocale())),
+      static_cast<jint>(profile.GetVerificationStatus(NAME_HONORIFIC_PREFIX)),
       ConvertUTF16ToJavaString(
           env, profile.GetInfo(AutofillType(NAME_FULL),
                                g_browser_process->GetApplicationLocale())),
+      static_cast<jint>(profile.GetVerificationStatus(NAME_FULL)),
       ConvertUTF16ToJavaString(env, profile.GetRawInfo(COMPANY_NAME)),
+      static_cast<jint>(profile.GetVerificationStatus(COMPANY_NAME)),
       ConvertUTF16ToJavaString(env,
                                profile.GetRawInfo(ADDRESS_HOME_STREET_ADDRESS)),
+      static_cast<jint>(
+          profile.GetVerificationStatus(ADDRESS_HOME_STREET_ADDRESS)),
       ConvertUTF16ToJavaString(env, profile.GetRawInfo(ADDRESS_HOME_STATE)),
+      static_cast<jint>(profile.GetVerificationStatus(ADDRESS_HOME_STATE)),
       ConvertUTF16ToJavaString(env, profile.GetRawInfo(ADDRESS_HOME_CITY)),
+      static_cast<jint>(profile.GetVerificationStatus(ADDRESS_HOME_CITY)),
       ConvertUTF16ToJavaString(
           env, profile.GetRawInfo(ADDRESS_HOME_DEPENDENT_LOCALITY)),
+      static_cast<jint>(
+          profile.GetVerificationStatus(ADDRESS_HOME_DEPENDENT_LOCALITY)),
       ConvertUTF16ToJavaString(env, profile.GetRawInfo(ADDRESS_HOME_ZIP)),
+      static_cast<jint>(profile.GetVerificationStatus(ADDRESS_HOME_ZIP)),
       ConvertUTF16ToJavaString(env,
                                profile.GetRawInfo(ADDRESS_HOME_SORTING_CODE)),
+      static_cast<jint>(
+          profile.GetVerificationStatus(ADDRESS_HOME_SORTING_CODE)),
       ConvertUTF16ToJavaString(env, profile.GetRawInfo(ADDRESS_HOME_COUNTRY)),
+      static_cast<jint>(profile.GetVerificationStatus(ADDRESS_HOME_COUNTRY)),
       ConvertUTF16ToJavaString(env,
                                profile.GetRawInfo(PHONE_HOME_WHOLE_NUMBER)),
+      static_cast<jint>(profile.GetVerificationStatus(PHONE_HOME_WHOLE_NUMBER)),
       ConvertUTF16ToJavaString(env, profile.GetRawInfo(EMAIL_ADDRESS)),
+      static_cast<jint>(profile.GetVerificationStatus(EMAIL_ADDRESS)),
       ConvertUTF8ToJavaString(env, profile.language_code()));
 }
 
@@ -323,37 +354,52 @@
 
   profile->set_origin(
       ConvertJavaStringToUTF8(Java_AutofillProfile_getOrigin(env, jprofile)));
-  profile->SetInfo(
-      AutofillType(NAME_FULL),
-      ConvertJavaStringToUTF16(Java_AutofillProfile_getFullName(env, jprofile)),
-      g_browser_process->GetApplicationLocale());
-  MaybeSetRawInfo(profile, autofill::NAME_HONORIFIC_PREFIX,
-                  Java_AutofillProfile_getHonorificPrefix(env, jprofile));
-  MaybeSetRawInfo(profile, autofill::COMPANY_NAME,
-                  Java_AutofillProfile_getCompanyName(env, jprofile));
-  MaybeSetRawInfo(profile, autofill::ADDRESS_HOME_STREET_ADDRESS,
-                  Java_AutofillProfile_getStreetAddress(env, jprofile));
-  MaybeSetRawInfo(profile, autofill::ADDRESS_HOME_STATE,
-                  Java_AutofillProfile_getRegion(env, jprofile));
-  MaybeSetRawInfo(profile, autofill::ADDRESS_HOME_CITY,
-                  Java_AutofillProfile_getLocality(env, jprofile));
-  MaybeSetRawInfo(profile, autofill::ADDRESS_HOME_DEPENDENT_LOCALITY,
-                  Java_AutofillProfile_getDependentLocality(env, jprofile));
-  MaybeSetRawInfo(profile, autofill::ADDRESS_HOME_ZIP,
-                  Java_AutofillProfile_getPostalCode(env, jprofile));
-  MaybeSetRawInfo(profile, autofill::ADDRESS_HOME_SORTING_CODE,
-                  Java_AutofillProfile_getSortingCode(env, jprofile));
-  ScopedJavaLocalRef<jstring> country_code =
-      Java_AutofillProfile_getCountryCode(env, jprofile);
-  if (!country_code.is_null()) {
-    profile->SetInfo(AutofillType(ADDRESS_HOME_COUNTRY),
-                     ConvertJavaStringToUTF16(country_code),
-                     g_browser_process->GetApplicationLocale());
-  }
-  MaybeSetRawInfo(profile, autofill::PHONE_HOME_WHOLE_NUMBER,
-                  Java_AutofillProfile_getPhoneNumber(env, jprofile));
-  MaybeSetRawInfo(profile, autofill::EMAIL_ADDRESS,
-                  Java_AutofillProfile_getEmailAddress(env, jprofile));
+  MaybeSetInfoWithVerificationStatus(
+      profile, NAME_FULL, Java_AutofillProfile_getFullName(env, jprofile),
+      Java_AutofillProfile_getFullNameStatus(env, jprofile));
+  MaybeSetRawInfoWithVerificationStatus(
+      profile, NAME_HONORIFIC_PREFIX,
+      Java_AutofillProfile_getHonorificPrefix(env, jprofile),
+      Java_AutofillProfile_getHonorificPrefixStatus(env, jprofile));
+  MaybeSetRawInfoWithVerificationStatus(
+      profile, COMPANY_NAME, Java_AutofillProfile_getCompanyName(env, jprofile),
+      Java_AutofillProfile_getCompanyNameStatus(env, jprofile));
+  MaybeSetRawInfoWithVerificationStatus(
+      profile, ADDRESS_HOME_STREET_ADDRESS,
+      Java_AutofillProfile_getStreetAddress(env, jprofile),
+      Java_AutofillProfile_getStreetAddressStatus(env, jprofile));
+  MaybeSetRawInfoWithVerificationStatus(
+      profile, ADDRESS_HOME_STATE,
+      Java_AutofillProfile_getRegion(env, jprofile),
+      Java_AutofillProfile_getRegionStatus(env, jprofile));
+  MaybeSetRawInfoWithVerificationStatus(
+      profile, ADDRESS_HOME_CITY,
+      Java_AutofillProfile_getLocality(env, jprofile),
+      Java_AutofillProfile_getLocalityStatus(env, jprofile));
+  MaybeSetRawInfoWithVerificationStatus(
+      profile, ADDRESS_HOME_DEPENDENT_LOCALITY,
+      Java_AutofillProfile_getDependentLocality(env, jprofile),
+      Java_AutofillProfile_getDependentLocalityStatus(env, jprofile));
+  MaybeSetRawInfoWithVerificationStatus(
+      profile, ADDRESS_HOME_ZIP,
+      Java_AutofillProfile_getPostalCode(env, jprofile),
+      Java_AutofillProfile_getPostalCodeStatus(env, jprofile));
+  MaybeSetRawInfoWithVerificationStatus(
+      profile, ADDRESS_HOME_SORTING_CODE,
+      Java_AutofillProfile_getSortingCode(env, jprofile),
+      Java_AutofillProfile_getSortingCodeStatus(env, jprofile));
+  MaybeSetInfoWithVerificationStatus(
+      profile, ADDRESS_HOME_COUNTRY,
+      Java_AutofillProfile_getCountryCode(env, jprofile),
+      Java_AutofillProfile_getCountryCodeStatus(env, jprofile));
+  MaybeSetRawInfoWithVerificationStatus(
+      profile, autofill::PHONE_HOME_WHOLE_NUMBER,
+      Java_AutofillProfile_getPhoneNumber(env, jprofile),
+      Java_AutofillProfile_getPhoneNumberStatus(env, jprofile));
+  MaybeSetRawInfoWithVerificationStatus(
+      profile, autofill::EMAIL_ADDRESS,
+      Java_AutofillProfile_getEmailAddress(env, jprofile),
+      Java_AutofillProfile_getEmailAddressStatus(env, jprofile));
   profile->set_language_code(ConvertJavaStringToUTF8(
       Java_AutofillProfile_getLanguageCode(env, jprofile)));
   profile->FinalizeAfterImport();
diff --git a/chrome/browser/autofill/android/save_address_profile_prompt_controller_unittest.cc b/chrome/browser/autofill/android/save_address_profile_prompt_controller_unittest.cc
index 4eeb29b..630c900 100644
--- a/chrome/browser/autofill/android/save_address_profile_prompt_controller_unittest.cc
+++ b/chrome/browser/autofill/android/save_address_profile_prompt_controller_unittest.cc
@@ -51,15 +51,17 @@
     CountryNames::SetLocaleString(GetLocale());
   }
 
-  // Profile with raw data as it is returned from Java.
-  AutofillProfile GetFullProfileNoStatus() {
+  // Profile with verified data as it is returned from Java.
+  AutofillProfile GetFullProfileWithVerifiedData() {
     AutofillProfile profile(base::GenerateGUID(), test::kEmptyOrigin);
-    profile.SetRawInfo(NAME_FULL, u"Mona J. Liza");
+    profile.SetRawInfoWithVerificationStatus(
+        NAME_FULL, u"Mona J. Liza",
+        structured_address::VerificationStatus::kUserVerified);
     test::SetProfileInfo(&profile, "", "", "", "email@example.com",
                          "Company Inc.", "33 Narrow Street", "Apt 42",
                          "Playa Vista", "LA", "12345", "US", "13105551234",
                          /*finalize=*/true,
-                         structured_address::VerificationStatus::kNoStatus);
+                         structured_address::VerificationStatus::kUserVerified);
     return profile;
   }
 
@@ -139,7 +141,7 @@
        ShouldInvokeSaveCallbackWhenUserEditsProfile) {
   controller_->DisplayPrompt();
 
-  AutofillProfile edited_profile = GetFullProfileNoStatus();
+  AutofillProfile edited_profile = GetFullProfileWithVerifiedData();
   EXPECT_CALL(
       decision_callback_,
       Run(AutofillClient::SaveAddressProfileOfferUserDecision::kEditAccepted,
diff --git a/chrome/browser/cart/cart_service.cc b/chrome/browser/cart/cart_service.cc
index c16dea46..2bd8530 100644
--- a/chrome/browser/cart/cart_service.cc
+++ b/chrome/browser/cart/cart_service.cc
@@ -12,9 +12,12 @@
 #include "chrome/browser/cart/cart_db_content.pb.h"
 #include "chrome/browser/cart/fetch_discount_worker.h"
 #include "chrome/browser/history/history_service_factory.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/browser_resources.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/optimization_guide/proto/hints.pb.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
 #include "components/search/ntp_features.h"
@@ -111,6 +114,12 @@
   if (IsCartDiscountEnabled()) {
     StartGettingDiscount();
   }
+  optimization_guide_decider_ =
+      OptimizationGuideKeyedServiceFactory::GetForProfile(profile);
+  if (optimization_guide_decider_) {
+    optimization_guide_decider_->RegisterOptimizationTypes(
+        {optimization_guide::proto::SHOPPING_PAGE_PREDICTOR});
+  }
 }
 
 CartService::~CartService() = default;
@@ -495,6 +504,17 @@
   }
 }
 
+bool CartService::ShouldSkip(const GURL& url) {
+  if (!optimization_guide_decider_) {
+    return false;
+  }
+  optimization_guide::OptimizationMetadata metadata;
+  auto decision = optimization_guide_decider_->CanApplyOptimization(
+      url, optimization_guide::proto::SHOPPING_PAGE_PREDICTOR, &metadata);
+  DVLOG(1) << "SHOPPING_PAGE_PREDICTOR = " << static_cast<int>(decision);
+  return optimization_guide::OptimizationGuideDecision::kFalse == decision;
+}
+
 void CartService::OnLoadCarts(CartDB::LoadCallback callback,
                               bool success,
                               std::vector<CartDB::KeyAndValue> proto_pairs) {
@@ -502,19 +522,23 @@
     std::move(callback).Run(success, {});
     return;
   }
-  std::set<std::string> expired_merchants;
+  std::set<std::string> merchants_to_erase;
   for (CartDB::KeyAndValue kv : proto_pairs) {
-    if (IsExpired(kv.second)) {
-      DeleteCart(kv.second.key());
-      expired_merchants.emplace(kv.second.key());
+    if (IsExpired(kv.second) ||
+        ShouldSkip(GURL(kv.second.merchant_cart_url()))) {
+      // Removed carts should remain removed.
+      if (!kv.second.is_removed()) {
+        DeleteCart(kv.second.key());
+      }
+      merchants_to_erase.emplace(kv.second.key());
     }
   }
   proto_pairs.erase(
       std::remove_if(proto_pairs.begin(), proto_pairs.end(),
-                     [expired_merchants](CartDB::KeyAndValue kv) {
+                     [merchants_to_erase](CartDB::KeyAndValue kv) {
                        return kv.second.is_hidden() || kv.second.is_removed() ||
-                              expired_merchants.find(kv.second.key()) !=
-                                  expired_merchants.end();
+                              merchants_to_erase.find(kv.second.key()) !=
+                                  merchants_to_erase.end();
                      }),
       proto_pairs.end());
   // Sort items in timestamp descending order.
diff --git a/chrome/browser/cart/cart_service.h b/chrome/browser/cart/cart_service.h
index 5038cbc..03ee7b9 100644
--- a/chrome/browser/cart/cart_service.h
+++ b/chrome/browser/cart/cart_service.h
@@ -16,6 +16,7 @@
 #include "components/history/core/browser/history_service.h"
 #include "components/history/core/browser/history_service_observer.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/optimization_guide/content/browser/optimization_guide_decider.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -163,7 +164,8 @@
   // Set discount_link_fetcher_ for testing purpose.
   void SetCartDiscountLinkFetcherForTesting(
       std::unique_ptr<CartDiscountLinkFetcher> discount_link_fetcher);
-
+  // Returns whether a URL should be skipped based on server-side bloom filter.
+  bool ShouldSkip(const GURL& url);
   void CacheUsedDiscounts(const cart_db::ChromeCartContentProto& proto);
   void CleanUpDiscounts(cart_db::ChromeCartContentProto proto);
 
@@ -176,6 +178,7 @@
   absl::optional<base::Value> domain_cart_url_mapping_;
   std::unique_ptr<FetchDiscountWorker> fetch_discount_worker_;
   std::unique_ptr<CartDiscountLinkFetcher> discount_link_fetcher_;
+  optimization_guide::OptimizationGuideDecider* optimization_guide_decider_;
   base::WeakPtrFactory<CartService> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/cart/cart_service_browsertest.cc b/chrome/browser/cart/cart_service_browsertest.cc
new file mode 100644
index 0000000..b325d428
--- /dev/null
+++ b/chrome/browser/cart/cart_service_browsertest.cc
@@ -0,0 +1,127 @@
+// 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/test/scoped_feature_list.h"
+#include "chrome/browser/cart/cart_db_content.pb.h"
+#include "chrome/browser/cart/cart_service.h"
+#include "chrome/browser/persisted_state_db/profile_proto_db.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/search/ntp_features.h"
+#include "content/public/test/browser_test.h"
+
+namespace {
+cart_db::ChromeCartContentProto BuildProto(const char* domain,
+                                           const char* merchant_url) {
+  cart_db::ChromeCartContentProto proto;
+  proto.set_key(domain);
+  proto.set_merchant_cart_url(merchant_url);
+  proto.set_timestamp(base::Time::Now().ToDoubleT());
+  return proto;
+}
+
+const char kMockMerchant[] = "walmart.com";
+const char kMockMerchantURL[] = "https://www.walmart.com";
+using ShoppingCarts =
+    std::vector<ProfileProtoDB<cart_db::ChromeCartContentProto>::KeyAndValue>;
+}  // namespace
+
+// Tests CartService.
+class CartServiceBrowserTest : public InProcessBrowserTest {
+ public:
+  void SetUpInProcessBrowserTestFixture() override {
+    scoped_feature_list_.InitWithFeatures(
+        {ntp_features::kNtpChromeCartModule,
+         optimization_guide::features::kOptimizationHints},
+        {});
+  }
+
+  void SetUpOnMainThread() override {
+    InProcessBrowserTest::SetUpOnMainThread();
+    Profile* profile =
+        Profile::FromBrowserContext(web_contents()->GetBrowserContext());
+    service_ = CartServiceFactory::GetForProfile(profile);
+  }
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    // This bloom filter rejects "walmart.com" as a shopping site.
+    command_line->AppendSwitchASCII("optimization_guide_hints_override",
+                                    "Eg8IDxILCBsQJxoFiUzKeE4=");
+  }
+
+  void OperationEvaluation(base::OnceClosure closure,
+                           bool expected_success,
+                           bool actual_success) {
+    EXPECT_EQ(expected_success, actual_success);
+    std::move(closure).Run();
+  }
+
+  void GetEvaluationURL(base::OnceClosure closure,
+                        ShoppingCarts expected,
+                        bool result,
+                        ShoppingCarts found) {
+    EXPECT_EQ(found.size(), expected.size());
+    for (size_t i = 0; i < expected.size(); i++) {
+      EXPECT_EQ(found[i].first, expected[i].first);
+      EXPECT_EQ(found[i].second.merchant_cart_url(),
+                expected[i].second.merchant_cart_url());
+      for (int j = 0; j < expected[i].second.product_image_urls().size(); j++) {
+        EXPECT_EQ(expected[i].second.product_image_urls()[j],
+                  found[i].second.product_image_urls()[j]);
+      }
+    }
+    std::move(closure).Run();
+  }
+
+ protected:
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+  CartService* service_;
+};
+
+IN_PROC_BROWSER_TEST_F(CartServiceBrowserTest, TestNotShowSkippedMerchants) {
+  CartDB* cart_db_ = service_->GetDB();
+  base::RunLoop run_loop[6];
+  cart_db::ChromeCartContentProto merchant_proto =
+      BuildProto(kMockMerchant, kMockMerchantURL);
+  ShoppingCarts merchant_res = {{kMockMerchant, merchant_proto}};
+  ShoppingCarts empty_res = {};
+
+  cart_db_->AddCart(
+      kMockMerchant, merchant_proto,
+      base::BindOnce(&CartServiceBrowserTest::OperationEvaluation,
+                     base::Unretained(this), run_loop[0].QuitClosure(), true));
+  run_loop[0].Run();
+
+  service_->LoadAllActiveCarts(base::BindOnce(
+      &CartServiceBrowserTest::GetEvaluationURL, base::Unretained(this),
+      run_loop[1].QuitClosure(), empty_res));
+  run_loop[1].Run();
+
+  cart_db_->LoadAllCarts(base::BindOnce(
+      &CartServiceBrowserTest::GetEvaluationURL, base::Unretained(this),
+      run_loop[2].QuitClosure(), empty_res));
+  run_loop[2].Run();
+
+  merchant_proto.set_is_removed(true);
+  cart_db_->AddCart(
+      kMockMerchant, merchant_proto,
+      base::BindOnce(&CartServiceBrowserTest::OperationEvaluation,
+                     base::Unretained(this), run_loop[3].QuitClosure(), true));
+  run_loop[3].Run();
+
+  service_->LoadAllActiveCarts(base::BindOnce(
+      &CartServiceBrowserTest::GetEvaluationURL, base::Unretained(this),
+      run_loop[4].QuitClosure(), empty_res));
+  run_loop[4].Run();
+
+  cart_db_->LoadAllCarts(base::BindOnce(
+      &CartServiceBrowserTest::GetEvaluationURL, base::Unretained(this),
+      run_loop[5].QuitClosure(), merchant_res));
+  run_loop[5].Run();
+}
diff --git a/chrome/browser/cart/cart_service_unittest.cc b/chrome/browser/cart/cart_service_unittest.cc
index b7a14cbab..114b62e 100644
--- a/chrome/browser/cart/cart_service_unittest.cc
+++ b/chrome/browser/cart/cart_service_unittest.cc
@@ -1041,9 +1041,10 @@
 
 // Tests expired entries are deleted when data is loaded.
 TEST_F(CartServiceTest, TestExpiredDataDeleted) {
-  base::RunLoop run_loop[3];
+  base::RunLoop run_loop[6];
   cart_db::ChromeCartContentProto merchant_proto =
       BuildProto(kMockMerchantA, kMockMerchantURLA);
+  const ShoppingCarts result = {{kMockMerchantA, merchant_proto}};
 
   merchant_proto.set_timestamp(
       (base::Time::Now() - base::TimeDelta::FromDays(16)).ToDoubleT());
@@ -1063,16 +1064,37 @@
                      run_loop[1].QuitClosure(), kEmptyExpected));
   run_loop[1].Run();
 
-  merchant_proto.set_timestamp(
-      (base::Time::Now() - base::TimeDelta::FromDays(13)).ToDoubleT());
+  // If the cart is removed, the expired entry is deleted in load results but is
+  // kept in database.
+  merchant_proto.set_is_removed(true);
   service_->AddCart(kMockMerchantA, absl::nullopt, merchant_proto);
   task_environment_.RunUntilIdle();
 
-  const ShoppingCarts result = {{kMockMerchantA, merchant_proto}};
   service_->LoadAllActiveCarts(
       base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
-                     run_loop[2].QuitClosure(), result));
+                     run_loop[2].QuitClosure(), kEmptyExpected));
   run_loop[2].Run();
+
+  service_->LoadCart(
+      kMockMerchantA,
+      base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
+                     run_loop[3].QuitClosure(), result));
+  run_loop[3].Run();
+
+  merchant_proto.set_timestamp(
+      (base::Time::Now() - base::TimeDelta::FromDays(13)).ToDoubleT());
+  merchant_proto.set_is_removed(false);
+  service_->GetDB()->AddCart(
+      kMockMerchantA, merchant_proto,
+      base::BindOnce(&CartServiceTest::OperationEvaluation,
+                     base::Unretained(this), run_loop[4].QuitClosure(), true));
+  run_loop[4].Run();
+
+  service_->LoadCart(
+      kMockMerchantA,
+      base::BindOnce(&CartServiceTest::GetEvaluationCartRemovedStatus,
+                     base::Unretained(this), run_loop[5].QuitClosure(), false));
+  run_loop[5].Run();
 }
 
 // Tests cart-related actions would reshow hidden module.
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 2d58c48..d43fff1 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -418,7 +418,7 @@
     "//components/webapps/browser",
     "//printing",
     "//printing/mojom",
-    "//remoting/host/it2me:chrome_os_host",
+    "//remoting/host/chromeos:remoting_service",
     "//services/audio/public/cpp",
     "//services/data_decoder/public/cpp",
     "//services/device/public/cpp/usb",
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
index 5142012a..cc90cd7 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
@@ -1344,9 +1344,6 @@
   // Cleans up dbus services depending on ash.
   dbus_services_->PreAshShutdown();
 
-  // Destroy the SystemTokenCertDbStorage global instance.
-  SystemTokenCertDbStorage::Shutdown();
-
   // NOTE: Closes ash and destroys ash::Shell.
   ChromeBrowserMainPartsLinux::PostMainMessageLoopRun();
 
@@ -1406,7 +1403,7 @@
 
   // The cert database initializer must be shut down before DBus services are
   // destroyed.
-  system_token_certdb_initializer_->ShutDown();
+  system_token_certdb_initializer_.reset();
 
   // Destroy DBus services immediately after threads are stopped.
   dbus_services_.reset();
@@ -1417,9 +1414,9 @@
 
   ShutdownDBus();
 
-  // Reset SystemTokenCertDBInitializer after DBus services because it should
-  // outlive NetworkCertLoader.
-  system_token_certdb_initializer_.reset();
+  // Destroy the SystemTokenCertDbStorage global instance which should outlive
+  // NetworkCertLoader and |system_token_certdb_initializer_|.
+  SystemTokenCertDbStorage::Shutdown();
 
   ChromeBrowserMainPartsLinux::PostDestroyThreads();
 
diff --git a/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc b/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc
index ef849940..8c6b158 100644
--- a/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc
+++ b/chrome/browser/chromeos/dbus/vm_applications_service_provider.cc
@@ -83,11 +83,14 @@
   }
 
   Profile* profile = ProfileManager::GetPrimaryUserProfile();
+  // Borealis checks Allowed() rather than Enabled() as we need to update the
+  // borealis applications list before it is considered Enabled (i.e. failure to
+  // update the list implies failure to enable).
   if (crostini::CrostiniFeatures::Get()->IsEnabled(profile) ||
       plugin_vm::PluginVmFeatures::Get()->IsEnabled(profile) ||
       borealis::BorealisService::GetForProfile(profile)
           ->Features()
-          .IsEnabled()) {
+          .IsAllowed()) {
     auto* registry_service =
         guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile);
     registry_service->UpdateApplicationList(request);
diff --git a/chrome/browser/chromeos/input_method/ui/grammar_suggestion_window.cc b/chrome/browser/chromeos/input_method/ui/grammar_suggestion_window.cc
index e96379a..89bd2a5 100644
--- a/chrome/browser/chromeos/input_method/ui/grammar_suggestion_window.cc
+++ b/chrome/browser/chromeos/input_method/ui/grammar_suggestion_window.cc
@@ -4,9 +4,8 @@
 
 #include "chrome/browser/chromeos/input_method/ui/grammar_suggestion_window.h"
 
-// #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/chromeos/input_method/ui/border_factory.h"
-#include "components/vector_icons/vector_icons.h"
+#include "chrome/browser/chromeos/input_method/ui/suggestion_details.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/paint_vector_icon.h"
@@ -14,15 +13,20 @@
 #include "ui/views/bubble/bubble_frame_view.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/layout_provider.h"
+#include "ui/views/vector_icons.h"
 #include "ui/wm/core/window_animations.h"
 
 namespace ui {
 namespace ime {
 
 namespace {
-constexpr SkColor kButtonHighlightColor =
-    SkColorSetA(SK_ColorBLACK, 0x0F);  // 6% Black.
 constexpr SkColor kSecondaryIconColor = gfx::kGoogleGrey500;
+
+bool ShouldHighlight(const views::Button& button) {
+  return button.GetState() == views::Button::STATE_HOVERED ||
+         button.GetState() == views::Button::STATE_PRESSED;
+}
+
 }  // namespace
 
 GrammarSuggestionWindow::GrammarSuggestionWindow(gfx::NativeView parent,
@@ -39,7 +43,7 @@
       views::BoxLayout::Orientation::kHorizontal));
 
   suggestion_button_ =
-      AddChildView(std::make_unique<views::LabelButton>(base::BindRepeating(
+      AddChildView(std::make_unique<SuggestionView>(base::BindRepeating(
           &AssistiveDelegate::AssistiveWindowButtonClicked,
           base::Unretained(delegate_),
           AssistiveWindowButton{
@@ -61,7 +65,24 @@
           })));
   ignore_button_->SetImageHorizontalAlignment(views::ImageButton::ALIGN_CENTER);
   ignore_button_->SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE);
+  ignore_button_->SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
   ignore_button_->SetVisible(true);
+
+  // Highlights buttons when they are hovered or pressed.
+  const auto update_button_highlight = [](views::Button* button) {
+    button->SetBackground(
+        ShouldHighlight(*button)
+            ? views::CreateSolidBackground(kButtonHighlightColor)
+            : nullptr);
+  };
+  subscriptions_.insert(
+      {suggestion_button_,
+       suggestion_button_->AddStateChangedCallback(base::BindRepeating(
+           update_button_highlight, base::Unretained(suggestion_button_)))});
+  subscriptions_.insert(
+      {ignore_button_,
+       ignore_button_->AddStateChangedCallback(base::BindRepeating(
+           update_button_highlight, base::Unretained(ignore_button_)))});
 }
 
 GrammarSuggestionWindow::~GrammarSuggestionWindow() = default;
@@ -73,7 +94,7 @@
 
   ignore_button_->SetImage(
       views::Button::ButtonState::STATE_NORMAL,
-      gfx::CreateVectorIcon(vector_icons::kCloseIcon, kSecondaryIconColor));
+      gfx::CreateVectorIcon(views::kCloseIcon, kSecondaryIconColor));
 
   BubbleDialogDelegateView::OnThemeChanged();
 }
@@ -99,7 +120,7 @@
 }
 
 void GrammarSuggestionWindow::SetSuggestion(const std::u16string& suggestion) {
-  suggestion_button_->SetText(suggestion);
+  suggestion_button_->SetView(SuggestionDetails{.text = suggestion});
 }
 
 void GrammarSuggestionWindow::SetButtonHighlighted(
@@ -128,7 +149,7 @@
   }
 }
 
-views::LabelButton* GrammarSuggestionWindow::GetSuggestionButtonForTesting() {
+SuggestionView* GrammarSuggestionWindow::GetSuggestionButtonForTesting() {
   return suggestion_button_;
 }
 
diff --git a/chrome/browser/chromeos/input_method/ui/grammar_suggestion_window.h b/chrome/browser/chromeos/input_method/ui/grammar_suggestion_window.h
index de680eb..8aff1b8 100644
--- a/chrome/browser/chromeos/input_method/ui/grammar_suggestion_window.h
+++ b/chrome/browser/chromeos/input_method/ui/grammar_suggestion_window.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_CHROMEOS_INPUT_METHOD_UI_GRAMMAR_SUGGESTION_WINDOW_H_
 
 #include "chrome/browser/chromeos/input_method/ui/assistive_delegate.h"
+#include "chrome/browser/chromeos/input_method/ui/suggestion_view.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/chromeos/ui_chromeos_export.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
@@ -36,7 +37,7 @@
   void SetButtonHighlighted(const AssistiveWindowButton& button,
                             bool highlighted);
 
-  views::LabelButton* GetSuggestionButtonForTesting();
+  SuggestionView* GetSuggestionButtonForTesting();
   views::Button* GetIgnoreButtonForTesting();
 
  protected:
@@ -44,10 +45,12 @@
 
  private:
   AssistiveDelegate* delegate_;
-  views::LabelButton* suggestion_button_;
+  SuggestionView* suggestion_button_;
   views::ImageButton* ignore_button_;
 
   ButtonId current_highlighted_button_id_ = ButtonId::kNone;
+
+  base::flat_map<views::View*, base::CallbackListSubscription> subscriptions_;
 };
 
 BEGIN_VIEW_BUILDER(UI_CHROMEOS_EXPORT,
diff --git a/chrome/browser/chromeos/input_method/ui/grammar_suggestion_window_unittest.cc b/chrome/browser/chromeos/input_method/ui/grammar_suggestion_window_unittest.cc
index d519cc56..067f695 100644
--- a/chrome/browser/chromeos/input_method/ui/grammar_suggestion_window_unittest.cc
+++ b/chrome/browser/chromeos/input_method/ui/grammar_suggestion_window_unittest.cc
@@ -129,9 +129,9 @@
   grammar_suggestion_window_->Show();
   grammar_suggestion_window_->SetSuggestion(test_suggestion);
 
-  EXPECT_EQ(
-      grammar_suggestion_window_->GetSuggestionButtonForTesting()->GetText(),
-      test_suggestion);
+  EXPECT_EQ(grammar_suggestion_window_->GetSuggestionButtonForTesting()
+                ->GetSuggestionForTesting(),
+            test_suggestion);
 }
 
 }  // namespace ime
diff --git a/chrome/browser/chromeos/input_method/ui/suggestion_view.cc b/chrome/browser/chromeos/input_method/ui/suggestion_view.cc
index 48950fd7..d6b5f5b 100644
--- a/chrome/browser/chromeos/input_method/ui/suggestion_view.cc
+++ b/chrome/browser/chromeos/input_method/ui/suggestion_view.cc
@@ -276,6 +276,10 @@
   min_width_ = min_width;
 }
 
+std::u16string SuggestionView::GetSuggestionForTesting() {
+  return suggestion_label_->GetText();
+}
+
 BEGIN_METADATA(SuggestionView, views::Button)
 END_METADATA
 
diff --git a/chrome/browser/chromeos/input_method/ui/suggestion_view.h b/chrome/browser/chromeos/input_method/ui/suggestion_view.h
index 2b11832..dd30b3c 100644
--- a/chrome/browser/chromeos/input_method/ui/suggestion_view.h
+++ b/chrome/browser/chromeos/input_method/ui/suggestion_view.h
@@ -58,6 +58,8 @@
   void SetHighlighted(bool highlighted);
   void SetMinWidth(int width);
 
+  std::u16string GetSuggestionForTesting();
+
  private:
   friend class SuggestionWindowViewTest;
   FRIEND_TEST_ALL_PREFIXES(SuggestionWindowViewTest, ShortcutSettingTest);
diff --git a/chrome/browser/chromeos/printing/print_servers_manager_unittest.cc b/chrome/browser/chromeos/printing/print_servers_manager_unittest.cc
index 78264c5..5f8d3b1 100644
--- a/chrome/browser/chromeos/printing/print_servers_manager_unittest.cc
+++ b/chrome/browser/chromeos/printing/print_servers_manager_unittest.cc
@@ -8,11 +8,9 @@
 #include <memory>
 #include <string>
 
-#include "ash/constants/ash_features.h"
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
 #include "base/sequenced_task_runner.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "chrome/browser/chromeos/printing/print_servers_provider.h"
@@ -89,10 +87,6 @@
 class PrintServersManagerTest : public testing::Test,
                                 public PrintServersManager::Observer {
  public:
-  void SetUp() override {
-    scoped_feature_list_.InitWithFeatures(
-        {chromeos::features::kPrintServerScaling}, {});
-  }
   PrintServersManagerTest() {
     auto server_printers_provider =
         std::make_unique<FakeServerPrintersProvider>();
@@ -124,8 +118,6 @@
   // Everything from PrintServersProvider must be called on Chrome_UIThread
   content::BrowserTaskEnvironment task_environment_;
 
-  base::test::ScopedFeatureList scoped_feature_list_;
-
   // Captured printer lists from observer callbacks.
   base::flat_map<PrinterClass, std::vector<Printer>> observed_printers_;
 
diff --git a/chrome/browser/chromeos/printing/print_servers_policy_provider.cc b/chrome/browser/chromeos/printing/print_servers_policy_provider.cc
index 932eb8d..d68f2e6 100644
--- a/chrome/browser/chromeos/printing/print_servers_policy_provider.cc
+++ b/chrome/browser/chromeos/printing/print_servers_policy_provider.cc
@@ -4,9 +4,7 @@
 
 #include "chrome/browser/chromeos/printing/print_servers_policy_provider.h"
 
-#include "ash/constants/ash_features.h"
 #include "base/bind.h"
-#include "base/feature_list.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/printing/print_servers_provider.h"
 #include "chrome/browser/chromeos/printing/print_servers_provider_factory.h"
@@ -87,9 +85,6 @@
 
 ServerPrintersFetchingMode PrintServersPolicyProvider::GetFetchingMode(
     const std::map<GURL, PrintServer>& all_servers) {
-  if (!base::FeatureList::IsEnabled(chromeos::features::kPrintServerScaling)) {
-    return ServerPrintersFetchingMode::kStandard;
-  }
   return all_servers.size() <= kMaxRecords
              ? ServerPrintersFetchingMode::kStandard
              : ServerPrintersFetchingMode::kSingleServerOnly;
diff --git a/chrome/browser/chromeos/system_token_cert_db_initializer.cc b/chrome/browser/chromeos/system_token_cert_db_initializer.cc
index 1bf7454..cdc8e6e 100644
--- a/chrome/browser/chromeos/system_token_cert_db_initializer.cc
+++ b/chrome/browser/chromeos/system_token_cert_db_initializer.cc
@@ -120,17 +120,19 @@
 
 SystemTokenCertDBInitializer::~SystemTokenCertDBInitializer() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-}
-
-void SystemTokenCertDBInitializer::ShutDown() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   // Note that the observer could potentially not be added yet, but
   // the operation is a no-op in that case.
   TpmManagerClient::Get()->RemoveObserver(this);
 
-  // Cancel any in-progress initialization sequence.
-  weak_ptr_factory_.InvalidateWeakPtrs();
+  // Notify consumers of SystemTokenCertDbStorage that the database is not
+  // usable anymore.
+  SystemTokenCertDbStorage::Get()->ResetDatabase();
+
+  // Destroy the NSSCertDatabase on the IO thread because consumers could be
+  // accessing it there.
+  content::GetIOThreadTaskRunner({})->DeleteSoon(
+      FROM_HERE, std::move(system_token_cert_database_));
 }
 
 void SystemTokenCertDBInitializer::OnOwnershipTaken() {
@@ -243,10 +245,11 @@
       /*public_slot=*/std::move(system_slot),
       /*private_slot=*/crypto::ScopedPK11Slot());
   database->SetSystemSlot(std::move(system_slot_copy));
+  system_token_cert_database_ = std::move(database);
 
   auto* system_token_cert_db_storage = SystemTokenCertDbStorage::Get();
   DCHECK(system_token_cert_db_storage);
-  system_token_cert_db_storage->SetDatabase(std::move(database));
+  system_token_cert_db_storage->SetDatabase(system_token_cert_database_.get());
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/system_token_cert_db_initializer.h b/chrome/browser/chromeos/system_token_cert_db_initializer.h
index 4ab1016..7ee9125 100644
--- a/chrome/browser/chromeos/system_token_cert_db_initializer.h
+++ b/chrome/browser/chromeos/system_token_cert_db_initializer.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_CHROMEOS_SYSTEM_TOKEN_CERT_DB_INITIALIZER_H_
 #define CHROME_BROWSER_CHROMEOS_SYSTEM_TOKEN_CERT_DB_INITIALIZER_H_
 
+#include <memory>
+
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
@@ -38,9 +40,6 @@
   SystemTokenCertDBInitializer();
   ~SystemTokenCertDBInitializer() override;
 
-  // Stops making new requests to D-Bus services.
-  void ShutDown();
-
   // TpmManagerClient::Observer overrides.
   void OnOwnershipTaken() override;
 
@@ -88,6 +87,9 @@
   // The flag that determines if the system slot can use software fallback.
   bool is_system_slot_software_fallback_allowed_;
 
+  // Global NSSCertDatabase which sees the system token.
+  std::unique_ptr<net::NSSCertDatabase> system_token_cert_database_;
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   base::WeakPtrFactory<SystemTokenCertDBInitializer> weak_ptr_factory_{this};
diff --git a/chrome/browser/chromeos/system_token_cert_db_initializer_unittest.cc b/chrome/browser/chromeos/system_token_cert_db_initializer_unittest.cc
index c6c0621..5f3d35e 100644
--- a/chrome/browser/chromeos/system_token_cert_db_initializer_unittest.cc
+++ b/chrome/browser/chromeos/system_token_cert_db_initializer_unittest.cc
@@ -42,6 +42,8 @@
       const SystemTokenCertDbInitializerTest& other) = delete;
 
   ~SystemTokenCertDbInitializerTest() override {
+    system_token_cert_db_initializer_.reset();
+
     TpmManagerClient::Shutdown();
     NetworkCertLoader::Shutdown();
     SystemTokenCertDbStorage::Shutdown();
diff --git a/chrome/browser/content_creation/notes/internal/android/BUILD.gn b/chrome/browser/content_creation/notes/internal/android/BUILD.gn
index 8c6d6e5..d3fd175 100644
--- a/chrome/browser/content_creation/notes/internal/android/BUILD.gn
+++ b/chrome/browser/content_creation/notes/internal/android/BUILD.gn
@@ -57,6 +57,7 @@
     "java/res/layout/carousel_item.xml",
     "java/res/layout/creation_dialog.xml",
     "java/res/layout/top_bar.xml",
+    "java/res/values/dimens.xml",
   ]
 }
 
diff --git a/chrome/browser/content_creation/notes/internal/android/java/res/layout/carousel_item.xml b/chrome/browser/content_creation/notes/internal/android/java/res/layout/carousel_item.xml
index 95254c1..b2dcf73b 100644
--- a/chrome/browser/content_creation/notes/internal/android/java/res/layout/carousel_item.xml
+++ b/chrome/browser/content_creation/notes/internal/android/java/res/layout/carousel_item.xml
@@ -12,32 +12,18 @@
         android:id="@+id/item"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:paddingTop="60dp"
-        android:paddingEnd="24dp"
         android:layout_gravity="center_horizontal"
-        android:orientation="vertical"
-        android:background="@color/modern_grey_200">
-
-        <TextView
-            android:id="@+id/title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:paddingTop="4dp"
-            android:paddingBottom="4dp"
-            android:paddingLeft="18dp"
-            android:paddingRight="18dp"
-            android:layout_marginBottom="17dp"
-            android:layout_gravity="center_horizontal"
-            android:background="@drawable/note_title_outline"
-            android:textAppearance="@style/TextAppearance.TextSmall" />
+        android:background="@color/modern_grey_200"
+        android:orientation="vertical">
 
         <LinearLayout
             android:id="@+id/background"
-            android:layout_width="280dp"
+            android:layout_width="@dimen/note_width"
             android:layout_height="240dp"
             android:gravity="center_horizontal"
             android:background="@drawable/note_background_outline"
-            android:orientation="vertical">
+            android:orientation="vertical"
+            tools:ignore="UselessParent">
 
             <TextView
                 android:id="@+id/text"
diff --git a/chrome/browser/content_creation/notes/internal/android/java/res/layout/creation_dialog.xml b/chrome/browser/content_creation/notes/internal/android/java/res/layout/creation_dialog.xml
index d27d570..9646b97 100644
--- a/chrome/browser/content_creation/notes/internal/android/java/res/layout/creation_dialog.xml
+++ b/chrome/browser/content_creation/notes/internal/android/java/res/layout/creation_dialog.xml
@@ -14,14 +14,29 @@
     <include layout="@layout/top_bar"/>
 
     <LinearLayout
-	    android:layout_width="wrap_content"
-	    android:layout_height="wrap_content"
-	    android:orientation="vertical">
+	    android:layout_width="match_parent"
+	    android:layout_height="match_parent"
+	    android:orientation="vertical"
+	    android:background="@color/modern_grey_200">
+	    
+	    <TextView
+            android:id="@+id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="60dp"
+            android:paddingTop="4dp"
+            android:paddingBottom="4dp"
+            android:paddingLeft="18dp"
+            android:paddingRight="18dp"
+            android:layout_marginBottom="17dp"
+            android:layout_gravity="center_horizontal"
+            android:background="@drawable/note_title_outline"
+            android:textAppearance="@style/TextAppearance.TextSmall" />
 
 	    <androidx.recyclerview.widget.RecyclerView
 	      android:id="@+id/note_carousel"
-	      android:layout_width="wrap_content"
-	      android:layout_height="wrap_content"
+	      android:layout_width="match_parent"
+	      android:layout_height="match_parent"
 	      android:orientation="horizontal"/>
 	</LinearLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/chrome/browser/content_creation/notes/internal/android/java/res/values/dimens.xml b/chrome/browser/content_creation/notes/internal/android/java/res/values/dimens.xml
new file mode 100644
index 0000000..bebaac7
--- /dev/null
+++ b/chrome/browser/content_creation/notes/internal/android/java/res/values/dimens.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<resources>
+    <dimen name="note_width">280dp</dimen>
+</resources>
diff --git a/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationDialog.java b/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationDialog.java
index c6a66946..fcbc8bb 100644
--- a/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationDialog.java
+++ b/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationDialog.java
@@ -15,6 +15,7 @@
 import androidx.fragment.app.DialogFragment;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
 
 import org.chromium.chrome.browser.content_creation.internal.R;
 import org.chromium.components.content_creation.notes.models.NoteTemplate;
@@ -29,6 +30,9 @@
  * Dialog for the note creation.
  */
 public class NoteCreationDialog extends DialogFragment {
+    private static final float FIRST_NOTE_PADDING_RATIO = 0.5f;
+    private static final float NOTE_PADDING_RATIO = 0.25f;
+
     private View mContentView;
     private String mSelectedText;
 
@@ -70,6 +74,20 @@
         LinearLayoutManager layoutManager =
                 new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false);
         noteCarousel.setLayoutManager(layoutManager);
+
+        noteCarousel.addOnScrollListener(new OnScrollListener() {
+            @Override
+            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                LinearLayoutManager layoutManager =
+                        (LinearLayoutManager) recyclerView.getLayoutManager();
+                if (layoutManager.findFirstCompletelyVisibleItemPosition() < 0) return;
+                int selectedItem = layoutManager.findFirstCompletelyVisibleItemPosition();
+                ((TextView) mContentView.findViewById(R.id.title))
+                        .setText(carouselItems.get(selectedItem)
+                                         .model.get(NoteProperties.TEMPLATE)
+                                         .localizedName);
+            }
+        });
     }
 
     private void bindCarouselItem(PropertyModel model, ViewGroup parent, PropertyKey propertyKey) {
@@ -79,13 +97,27 @@
         View background = parent.findViewById(R.id.background);
         template.mainBackground.apply(background);
         background.setClipToOutline(true);
-        ((TextView) parent.findViewById(R.id.title)).setText(template.localizedName);
-
         TextView noteText = (TextView) parent.findViewById(R.id.text);
         noteText.setText(mSelectedText);
         noteText.setTextColor(template.textStyle.fontColor);
         noteText.setAllCaps(template.textStyle.allCaps);
         noteText.setGravity(TextAlignment.toGravity(template.textStyle.alignment));
         noteText.setTypeface(typeface);
+
+        setLeftPadding(model.get(NoteProperties.IS_FIRST), parent.findViewById(R.id.item));
+    }
+
+    // Adjust the left padding for carousel items, so that the first item is centered and the
+    // following item is slightlight peaking from the right. For that, set left padding exactly
+    // what is needed to push the first item to the center, but set a smaller padding for the
+    // following items.
+    private void setLeftPadding(boolean is_first, View itemView) {
+        int dialogWidth = mContentView.getWidth();
+        int templateWidth = getActivity().getResources().getDimensionPixelSize(R.dimen.note_width);
+        int paddingLeft = (int) ((dialogWidth - templateWidth)
+                        * (is_first ? FIRST_NOTE_PADDING_RATIO : NOTE_PADDING_RATIO)
+                + 0.5f);
+        itemView.setPadding(paddingLeft, itemView.getPaddingTop(), itemView.getPaddingRight(),
+                itemView.getPaddingBottom());
     }
 }
\ No newline at end of file
diff --git a/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationMediator.java b/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationMediator.java
index 8ff5052..5921e5a1 100644
--- a/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationMediator.java
+++ b/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteCreationMediator.java
@@ -86,14 +86,15 @@
                 continue;
             }
 
-            ListItem listItem = new ListItem(
-                    NoteProperties.NOTE_VIEW_TYPE, buildModel(tuple.template, response.typeface));
+            ListItem listItem = new ListItem(NoteProperties.NOTE_VIEW_TYPE,
+                    buildModel(mListModel.size() == 0, tuple.template, response.typeface));
             mListModel.add(listItem);
         }
     }
 
-    private PropertyModel buildModel(NoteTemplate template, Typeface typeface) {
+    private PropertyModel buildModel(boolean is_first, NoteTemplate template, Typeface typeface) {
         PropertyModel.Builder builder = new PropertyModel.Builder(NoteProperties.ALL_KEYS)
+                                                .with(NoteProperties.IS_FIRST, is_first)
                                                 .with(NoteProperties.TEMPLATE, template)
                                                 .with(NoteProperties.TYPEFACE, typeface);
 
diff --git a/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteProperties.java b/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteProperties.java
index 5f6b80b..115408a 100644
--- a/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteProperties.java
+++ b/chrome/browser/content_creation/notes/internal/android/java/src/org/chromium/chrome/browser/content_creation/notes/NoteProperties.java
@@ -15,11 +15,14 @@
     public static final int NOTE_VIEW_TYPE = 1;
 
     /** The template definition.*/
+    static final WritableObjectPropertyKey<Boolean> IS_FIRST = new WritableObjectPropertyKey<>();
+
+    /** The template definition.*/
     static final WritableObjectPropertyKey<NoteTemplate> TEMPLATE =
             new WritableObjectPropertyKey<>();
 
     /** The Typeface instance that has been loaded for the associated template. */
     static final WritableObjectPropertyKey<Typeface> TYPEFACE = new WritableObjectPropertyKey<>();
 
-    static final PropertyKey[] ALL_KEYS = new PropertyKey[] {TEMPLATE, TYPEFACE};
+    static final PropertyKey[] ALL_KEYS = new PropertyKey[] {IS_FIRST, TEMPLATE, TYPEFACE};
 }
\ No newline at end of file
diff --git a/chrome/browser/data_use_measurement/chrome_data_use_measurement_browsertest.cc b/chrome/browser/data_use_measurement/chrome_data_use_measurement_browsertest.cc
index 0f1c2a6..019cf42a 100644
--- a/chrome/browser/data_use_measurement/chrome_data_use_measurement_browsertest.cc
+++ b/chrome/browser/data_use_measurement/chrome_data_use_measurement_browsertest.cc
@@ -9,6 +9,7 @@
 #include "base/strings/string_util.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_factory.h"
 #include "chrome/browser/profiles/profile.h"
@@ -81,8 +82,8 @@
   }
 };
 
-// Flaky on Linux (and Linux MSAN): https://crbug.com/1141975.
-#if defined(OS_LINUX)
+// Flaky on Linux (and Linux MSAN) and ChromeOS: https://crbug.com/1141975.
+#if defined(OS_LINUX) || defined(OS_CHROMEOS)
 #define MAYBE_DataUseTrackerPrefsUpdated DISABLED_DataUseTrackerPrefsUpdated
 #else
 #define MAYBE_DataUseTrackerPrefsUpdated DataUseTrackerPrefsUpdated
diff --git a/chrome/browser/enterprise/connectors/device_trust/device_trust_key_pair.cc b/chrome/browser/enterprise/connectors/device_trust/device_trust_key_pair.cc
index 634e8bf..17fb2fd 100644
--- a/chrome/browser/enterprise/connectors/device_trust/device_trust_key_pair.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/device_trust_key_pair.cc
@@ -175,6 +175,10 @@
   std::string raw_public_key;
   if (!key_pair_->ExportRawPublicKey(&raw_public_key))
     return std::string();
+  // This is intentionally using a non-standard format for the uncompressed
+  // point length, so we trim the leading 0x04 byte. See
+  // https://crbug.com/1212786
+  raw_public_key.erase(0, 1);
   std::string public_key;
   if (!CreatePEMKey(raw_public_key, KeyType::PUBLIC_KEY, public_key))
     return std::string();
diff --git a/chrome/browser/enterprise/connectors/file_system/access_token_fetcher.cc b/chrome/browser/enterprise/connectors/file_system/access_token_fetcher.cc
index acf2220c1f..27ea027 100644
--- a/chrome/browser/enterprise/connectors/file_system/access_token_fetcher.cc
+++ b/chrome/browser/enterprise/connectors/file_system/access_token_fetcher.cc
@@ -21,7 +21,6 @@
       "enterprise_connectors.file_system.%s.access_token";
 constexpr char kRefreshTokenPrefPathTemplate[] =
       "enterprise_connectors.file_system.%s.refresh_token";
-constexpr char kBoxProviderName[] = "box";
 
 // Traffic annotation strings must be fully defined at compile time.  They
 // can't be dynamically built at runtime based on the |service_provider|.
diff --git a/chrome/browser/enterprise/connectors/file_system/access_token_fetcher.h b/chrome/browser/enterprise/connectors/file_system/access_token_fetcher.h
index 1d90cbd..89ed87a 100644
--- a/chrome/browser/enterprise/connectors/file_system/access_token_fetcher.h
+++ b/chrome/browser/enterprise/connectors/file_system/access_token_fetcher.h
@@ -15,6 +15,11 @@
 
 namespace enterprise_connectors {
 
+// The unique key for the Box service provider.  This is used to generate the
+// correct network annotation tag as well as possible parameters in the
+// access token consent URL.
+constexpr char kBoxProviderName[] = "box";
+
 // Helper class to retrieve an access token for a file system service provider.
 class AccessTokenFetcher : public OAuth2AccessTokenFetcherImpl,
                            public OAuth2AccessTokenConsumer {
diff --git a/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.cc b/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.cc
index 0e776db..5af4616 100644
--- a/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.cc
+++ b/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.cc
@@ -19,6 +19,7 @@
 #include "google_apis/gaia/oauth2_access_token_consumer.h"
 #include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
 #include "google_apis/gaia/oauth2_api_call_flow.h"
+#include "net/base/escape.h"
 #include "net/base/url_util.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -76,6 +77,10 @@
 
   std::string query = base::StringPrintf("client_id=%s&response_type=code",
                                          settings_.client_id.c_str());
+  std::string extra_params = GetProviderSpecificUrlParameters();
+  if (!extra_params.empty())
+    base::StringAppendF(&query, "&%s", extra_params.c_str());
+
   url::Replacements<char> replacements;
   replacements.SetQuery(query.c_str(), url::Component(0, query.length()));
   GURL url = settings_.authorization_endpoint.ReplaceComponents(replacements);
@@ -192,6 +197,24 @@
   GetWidget()->Close();
 }
 
+std::string FileSystemSigninDialogDelegate::GetProviderSpecificUrlParameters() {
+  // If an email domain is specified, use it as a hint in the box authn URL.
+  // Make sure the domain has an @ prefix.
+  if (settings_.service_provider == kBoxProviderName) {
+    if (!settings_.email_domain.empty()) {
+      // If the domain does not already start with an @ sign, prepend the
+      // escaped version of it.
+      return base::StringPrintf(
+          "box_login=%s%s", settings_.email_domain[0] == '@' ? "" : "%40",
+          net::EscapeQueryParamValue(settings_.email_domain, true).c_str());
+    }
+  } else {
+    NOTREACHED() << "Unknown service provider: " << settings_.service_provider;
+  }
+
+  return std::string();
+}
+
 BEGIN_METADATA(FileSystemSigninDialogDelegate, views::DialogDelegateView)
 END_METADATA
 
diff --git a/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.h b/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.h
index ccb19760..25375c3 100644
--- a/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.h
+++ b/chrome/browser/enterprise/connectors/file_system/signin_dialog_delegate.h
@@ -75,6 +75,10 @@
                         const std::string& access_token,
                         const std::string& refresh_token);
 
+  // Return extra URL parameters that are specific to a given service provider.
+  // May return the empty string if there are none.
+  std::string GetProviderSpecificUrlParameters();
+
   const FileSystemSettings settings_;
   std::unique_ptr<views::WebView> web_view_;
   std::unique_ptr<OAuth2AccessTokenFetcherImpl> token_fetcher_;
diff --git a/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
index a979865..75e582e0 100644
--- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -297,6 +297,19 @@
   return seen;
 }
 
+void WaitForExtraHeadersListener(base::WaitableEvent* event,
+                                 content::BrowserContext* browser_context) {
+  if (BrowserContextKeyedAPIFactory<WebRequestAPI>::Get(browser_context)
+          ->HasExtraHeadersListenerForTesting()) {
+    event->Signal();
+    return;
+  }
+
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&WaitForExtraHeadersListener, event, browser_context));
+}
+
 }  // namespace
 
 class ExtensionWebRequestApiTest : public ExtensionApiTest {
@@ -2405,6 +2418,87 @@
             redirect_successful_listener.extension_id_for_message());
 }
 
+// Regression test for http://crbug.com/1074282.
+IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, StaleHeadersAfterRedirect) {
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(R"({
+        "name": "Web Request Stale Headers Test",
+        "manifest_version": 2,
+        "version": "0.1",
+        "background": { "scripts": ["background.js"] },
+        "permissions": ["<all_urls>", "webRequest", "webRequestBlocking"]
+      })");
+  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"(
+        window.locationCount = 0;
+        window.requestCount = 0;
+        chrome.test.sendMessage('ready', function(reply) {
+          chrome.webRequest.onResponseStarted.addListener(function(details) {
+              window.requestCount++;
+              var headers = details.responseHeaders;
+              for (var i = 0; i < headers.length; i++) {
+                if (headers[i].name === 'Location') {
+                  window.locationCount++;
+                }
+              }
+            },
+            {urls: ['<all_urls>'], types: ['xmlhttprequest']},
+            ['responseHeaders', 'extraHeaders']
+          );
+        });
+      )");
+
+  ExtensionTestMessageListener listener("ready", true);
+  const Extension* extension = LoadExtension(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+  EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+  auto task_runner = base::SequencedTaskRunnerHandle::Get();
+  embedded_test_server()->RegisterRequestHandler(base::BindLambdaForTesting(
+      [&](const net::test_server::HttpRequest& request)
+          -> std::unique_ptr<net::test_server::HttpResponse> {
+        if (request.relative_url != "/redirect-and-wait")
+          return nullptr;
+
+        // Wait for the listener to be installed before proceeding.
+        base::WaitableEvent unblock(
+            base::WaitableEvent::ResetPolicy::AUTOMATIC,
+            base::WaitableEvent::InitialState::NOT_SIGNALED);
+        // Post a task to the UI thread since the request handler runs on a
+        // background thread.
+        task_runner->PostTask(FROM_HERE, base::BindLambdaForTesting([&] {
+                                listener.Reply("");
+                                WaitForExtraHeadersListener(
+                                    &unblock, browser()->profile());
+                              }));
+        unblock.Wait();
+
+        auto http_response =
+            std::make_unique<net::test_server::BasicHttpResponse>();
+        http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
+        http_response->AddCustomHeader(
+            "Location", embedded_test_server()->GetURL("/echo").spec());
+        return http_response;
+      }));
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  // Navigate to a basic page so XHR requests work.
+  ui_test_utils::NavigateToURL(browser(),
+                               embedded_test_server()->GetURL("/echo"));
+
+  // Make a XHR request which redirects. The final response should not include
+  // the Location header.
+  auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents();
+  PerformXhrInFrame(web_contents->GetMainFrame(),
+                    embedded_test_server()->host_port_pair().host(),
+                    embedded_test_server()->port(), "redirect-and-wait");
+  EXPECT_EQ(
+      GetCountFromBackgroundPage(extension, profile(), "window.requestCount"),
+      1);
+  EXPECT_EQ(
+      GetCountFromBackgroundPage(extension, profile(), "window.locationCount"),
+      0);
+}
+
 IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, ChangeHeaderUMAs) {
   using RequestHeaderType =
       extension_web_request_api_helpers::RequestHeaderType;
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index ea14f698..12314e1 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -2432,6 +2432,45 @@
       mojom::APIPermissionID::kStorage));
 }
 
+// Tests that a Manifest V3 extension's service worker can't be used to relax
+// the extension CSP.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerBasedBackgroundTest,
+                       ExtensionCSPModification_MV3) {
+  ExtensionTestMessageListener worker_listener("ready", false);
+  const Extension* extension = LoadExtension(test_data_dir_.AppendASCII(
+      "service_worker/worker_based_background/extension_csp_modification"));
+  ASSERT_TRUE(extension);
+  const ExtensionId extension_id = extension->id();
+  ASSERT_TRUE(worker_listener.WaitUntilSatisfied());
+
+  ExtensionTestMessageListener csp_modified_listener(
+      "script-src 'self'; object-src 'self';", false);
+  csp_modified_listener.set_extension_id(extension_id);
+  ui_test_utils::NavigateToURL(
+      browser(), extension->GetResourceURL("extension_page.html"));
+  EXPECT_TRUE(csp_modified_listener.WaitUntilSatisfied());
+
+  // Ensure the inline script is not executed because we ensure that the
+  // extension's CSP is applied in the renderer (even though the service worker
+  // removed it).
+  constexpr char kScript[] = R"(
+    (() => {
+      try {
+        scriptExecuted;
+        window.domAutomationController.send('FAIL');
+      } catch (e) {
+        const result = e.message.includes('scriptExecuted is not defined')
+          ? 'PASS' : 'FAIL';
+        window.domAutomationController.send(result);
+      }
+    })();
+  )";
+  std::string result;
+  ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+      browser()->tab_strip_model()->GetActiveWebContents(), kScript, &result));
+  EXPECT_EQ("PASS", result);
+}
+
 // Tests that console messages logged by extension service workers, both via
 // the typical console.* methods and via our custom bindings console, are
 // passed through the normal ServiceWorker console messaging and are
diff --git a/chrome/browser/extensions/window_open_interactive_apitest.cc b/chrome/browser/extensions/window_open_interactive_apitest.cc
index 71d3890..6a4e56f 100644
--- a/chrome/browser/extensions/window_open_interactive_apitest.cc
+++ b/chrome/browser/extensions/window_open_interactive_apitest.cc
@@ -9,7 +9,13 @@
 
 namespace extensions {
 
-IN_PROC_BROWSER_TEST_F(ExtensionApiTest, WindowOpenFocus) {
+// Fails flakily on Mac. https://crbug.com/1216102
+#if defined(OS_MAC)
+#define MAYBE_WindowOpenFocus DISABLED_WindowOpenFocus
+#else
+#define MAYBE_WindowOpenFocus WindowOpenFocus
+#endif
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MAYBE_WindowOpenFocus) {
   ASSERT_TRUE(RunExtensionTest("window_open/focus")) << message_;
 }
 
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 7c09172..5e6f2e8 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -4238,21 +4238,11 @@
     "expiry_milestone": 88
   },
   {
-    "name": "print-server-scaling",
-    "owners": [ "mattme" ],
-    "expiry_milestone": 92
-  },
-  {
     "name": "print-with-reduced-rasterization",
     "owners": [ "thestig" ],
     "expiry_milestone": 96
   },
   {
-    "name": "printer-status-dialog",
-    "owners": [ "gavinwill", "cros-peripherals@google.com" ],
-    "expiry_milestone": 91
-  },
-  {
     "name": "privacy-advisor",
     "owners": [
       "harrisonsean",
@@ -5182,6 +5172,11 @@
     "expiry_milestone": 98
   },
   {
+    "name": "use-passthrough-command-decoder",
+    "owners": [ "//third_party/angle/OWNERS" ],
+    "expiry_milestone": 100
+  },
+  {
     "name": "use-search-click-for-right-click",
     "owners": [ "zentaro", "cros-peripherals@google.com" ],
     "expiry_milestone": 95
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 82d780c6..48b5ce9 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1996,10 +1996,6 @@
     "Enables the new prerenderer implementation for <link rel=prerender> "
     "instead of NoStatePrefetch.";
 
-const char kPrintServerScalingName[] = "Print Server Scaling";
-const char kPrintServerScalingDescription[] =
-    "Allows print servers to be selected when beyond a specified limit.";
-
 const char kPrivacyAdvisorName[] = "Privacy Advisor";
 const char kPrivacyAdvisorDescription[] =
     "Provides contextual entry points for adjusting privacy settings";
@@ -2106,12 +2102,6 @@
     "page that has been hidden for 5 minutes. For additional details, see "
     "https://www.chromestatus.com/feature/4718288976216064.";
 
-const char kPrinterStatusDialogName[] =
-    "Show printer status on destination dialog";
-const char kPrinterStatusDialogDescription[] =
-    "Enables printer status icons and labels for saved printers on the Print "
-    "Preview destination dialog";
-
 const char kSafetyTipName[] =
     "Show Safety Tip UI when visiting low-reputation websites";
 const char kSafetyTipDescription[] =
@@ -2736,6 +2726,12 @@
 const char kSanitizerApiDescription[] =
     "Enable the Sanitizer API. See: https://github.com/WICG/sanitizer-api";
 
+const char kUsePassthroughCommandDecoderName[] =
+    "Use passthrough command decoder";
+const char kUsePassthroughCommandDecoderDescription[] =
+    "Use chrome passthrough command decoder instead of validating command "
+    "decoder.";
+
 // Android ---------------------------------------------------------------------
 
 #if defined(OS_ANDROID)
@@ -4147,11 +4143,6 @@
     "Enables the embedded Assistant UI in the app list. Requires Assistant to "
     "be enabled.";
 
-const char kEnableAssistantMediaSessionIntegrationName[] =
-    "Assistant Media Session integration";
-const char kEnableAssistantMediaSessionIntegrationDescription[] =
-    "Enable Assistant Media Session Integration.";
-
 const char kEnableAssistantRoutinesName[] = "Assistant Routines";
 const char kEnableAssistantRoutinesDescription[] = "Enable Assistant Routines.";
 
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 1e30edbd..eea1a5a 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1145,9 +1145,6 @@
 extern const char kPrerender2Name[];
 extern const char kPrerender2Description[];
 
-extern const char kPrintServerScalingName[];
-extern const char kPrintServerScalingDescription[];
-
 extern const char kPrivacyAdvisorName[];
 extern const char kPrivacyAdvisorDescription[];
 
@@ -1213,9 +1210,6 @@
 extern const char kIntensiveWakeUpThrottlingName[];
 extern const char kIntensiveWakeUpThrottlingDescription[];
 
-extern const char kPrinterStatusDialogName[];
-extern const char kPrinterStatusDialogDescription[];
-
 extern const char kSafetyTipName[];
 extern const char kSafetyTipDescription[];
 
@@ -1580,6 +1574,9 @@
 extern const char kSanitizerApiName[];
 extern const char kSanitizerApiDescription[];
 
+extern const char kUsePassthroughCommandDecoderName[];
+extern const char kUsePassthroughCommandDecoderDescription[];
+
 // Android --------------------------------------------------------------------
 
 #if defined(OS_ANDROID)
@@ -2392,9 +2389,6 @@
 extern const char kEnableAssistantLauncherUIName[];
 extern const char kEnableAssistantLauncherUIDescription[];
 
-extern const char kEnableAssistantMediaSessionIntegrationName[];
-extern const char kEnableAssistantMediaSessionIntegrationDescription[];
-
 extern const char kEnableAssistantRoutinesName[];
 extern const char kEnableAssistantRoutinesDescription[];
 
diff --git a/chrome/browser/metrics/perf/cpu_identity.cc b/chrome/browser/metrics/perf/cpu_identity.cc
index b5f21ba..8cc21ea 100644
--- a/chrome/browser/metrics/perf/cpu_identity.cc
+++ b/chrome/browser/metrics/perf/cpu_identity.cc
@@ -12,6 +12,8 @@
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/system/sys_info.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 
 namespace metrics {
 
@@ -101,7 +103,14 @@
 CPUIdentity GetCPUIdentity() {
   CPUIdentity result = {};
   result.arch = base::SysInfo::OperatingSystemArchitecture();
-  result.release = base::SysInfo::OperatingSystemVersion();
+  result.release =
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+      base::SysInfo::KernelVersion();
+#elif defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
+      base::SysInfo::OperatingSystemVersion();
+#else
+#error "Unsupported configuration"
+#endif
   base::CPU cpuid;
   result.vendor = cpuid.vendor_name();
   result.family = cpuid.family();
diff --git a/chrome/browser/net/chrome_network_delegate.cc b/chrome/browser/net/chrome_network_delegate.cc
index 2c7672a..aa82d6d 100644
--- a/chrome/browser/net/chrome_network_delegate.cc
+++ b/chrome/browser/net/chrome_network_delegate.cc
@@ -65,8 +65,9 @@
   static const base::FilePath::CharType* const kLocalAccessAllowList[] = {
       "/home/chronos/user/Downloads",
       "/home/chronos/user/MyFiles",
-      "/home/chronos/user/log",
       "/home/chronos/user/WebRTC Logs",
+      "/home/chronos/user/google-assistant-library/log",
+      "/home/chronos/user/log",
       "/media",
       "/opt/oem",
       "/run/arc/sdcard/write/emulated/0",
diff --git a/chrome/browser/net/chrome_network_delegate_unittest.cc b/chrome/browser/net/chrome_network_delegate_unittest.cc
index 02d4f86..1ddff91 100644
--- a/chrome/browser/net/chrome_network_delegate_unittest.cc
+++ b/chrome/browser/net/chrome_network_delegate_unittest.cc
@@ -59,8 +59,10 @@
   EXPECT_TRUE(IsAccessAllowed("/home/chronos/user/Downloads", ""));
   EXPECT_TRUE(IsAccessAllowed("/home/chronos/user/MyFiles", ""));
   EXPECT_TRUE(IsAccessAllowed("/home/chronos/user/MyFiles/file.pdf", ""));
-  EXPECT_TRUE(IsAccessAllowed("/home/chronos/user/log", ""));
   EXPECT_TRUE(IsAccessAllowed("/home/chronos/user/WebRTC Logs", ""));
+  EXPECT_TRUE(
+      IsAccessAllowed("/home/chronos/user/google-assistant-library/log", ""));
+  EXPECT_TRUE(IsAccessAllowed("/home/chronos/user/log", ""));
   EXPECT_TRUE(IsAccessAllowed("/media", ""));
   EXPECT_TRUE(IsAccessAllowed("/opt/oem", ""));
   EXPECT_TRUE(IsAccessAllowed("/usr/share/chromeos-assets", ""));
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 71ea49e..1049fc0 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
@@ -3054,8 +3054,9 @@
                                       1);
 }
 
+// Flaky on all platforms: https://crbug.com/1211028.
 // Tests that a portal activation records metrics.
-IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PortalActivation) {
+IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, DISABLED_PortalActivation) {
   // We only record metrics for portals when the time is consistent across
   // processes.
   if (!base::TimeTicks::IsConsistentAcrossProcesses())
diff --git a/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc b/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc
index 3a430ee..072c7c4 100644
--- a/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc
+++ b/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc
@@ -45,10 +45,6 @@
     return nullptr;
   }
   void ShowProfileCustomizationBubble(Browser* browser) override {}
-  void ShowEnterpriseProfileInterceptionDialog(
-      const std::string& email,
-      base::OnceCallback<void(bool)> callback,
-      Browser* browser) override {}
 };
 
 class TestPasswordManagerClient
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service.cc b/chrome/browser/policy/cloud/user_policy_signin_service.cc
index bddcf11d..92f2902 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service.cc
@@ -17,10 +17,8 @@
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/signin/account_id_from_account_info.h"
 #include "chrome/browser/signin/signin_util.h"
-#include "chrome/common/pref_names.h"
 #include "components/policy/core/common/cloud/cloud_policy_client_registration_helper.h"
 #include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
-#include "components/prefs/pref_change_registrar.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/signin/public/identity_manager/consent_level.h"
 #include "content/public/browser/notification_details.h"
@@ -51,13 +49,6 @@
   // happens in the background after PKS initialization - so this service
   // should always be created before the oauth token is available.
   DCHECK(!CanApplyPoliciesForSignedInUser(/*check_for_refresh_token=*/true));
-
-  profile_pref_change_registrar_.Init(profile->GetPrefs());
-  profile_pref_change_registrar_.Add(
-      prefs::kUserAcceptedAccountManagement,
-      base::BindRepeating(
-          &UserPolicySigninService::OnAccountManagementPrefChange,
-          base::Unretained(this)));
 }
 
 UserPolicySigninService::~UserPolicySigninService() {
@@ -155,8 +146,6 @@
     return;
   }
 
-  profile_pref_change_registrar_.RemoveAll();
-
   InitializeForSignedInUser(
       AccountIdFromAccountInfo(
           identity_manager()->GetPrimaryAccountInfo(consent_level())),
@@ -165,11 +154,6 @@
           ->GetURLLoaderFactoryForBrowserProcess());
 }
 
-void UserPolicySigninService::OnAccountManagementPrefChange() {
-  if (CanApplyPoliciesForSignedInUser(/*check_for_refresh_token=*/true))
-    TryInitializeForSignedInUser();
-}
-
 void UserPolicySigninService::InitializeUserCloudPolicyManager(
     const AccountId& account_id,
     std::unique_ptr<CloudPolicyClient> client) {
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service.h b/chrome/browser/policy/cloud/user_policy_signin_service.h
index 8a8b83c..6d9c042 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service.h
+++ b/chrome/browser/policy/cloud/user_policy_signin_service.h
@@ -12,7 +12,6 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "chrome/browser/policy/cloud/user_policy_signin_service_base.h"
-#include "components/prefs/pref_change_registrar.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 
 class AccountId;
@@ -87,9 +86,6 @@
   // cloud policy.
   void ProhibitSignoutIfNeeded();
 
-  // Called when the value of the `profile.account_management` pref changes.
-  void OnAccountManagementPrefChange();
-
   // Helper method that attempts calls |InitializeForSignedInUser| only if
   // |policy_manager| is not-nul. Expects that there is a refresh token for
   // the primary account.
@@ -100,7 +96,6 @@
                                       PolicyRegistrationCallback callback);
 
   std::unique_ptr<CloudPolicyClientRegistrationHelper> registration_helper_;
-  PrefChangeRegistrar profile_pref_change_registrar_;
 
   DISALLOW_COPY_AND_ASSIGN(UserPolicySigninService);
 };
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
index 5d919e4..038f37b 100644
--- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
+++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
@@ -124,6 +124,9 @@
      */
     public static final String CHROME_DEFAULT_BROWSER = "applink.chrome_default_browser";
 
+    /** Number of attempts that have been made to download a survey. */
+    public static final KeyPrefix CHROME_SURVEY_DOWNLOAD_ATTEMPTS =
+            new KeyPrefix("Chrome.Survey.DownloadAttempts.*");
     /**
      * Key prefix used to indicate the timestamps when the survey info bar is displayed for a
      * certain survey.
@@ -993,6 +996,7 @@
                 APP_LAUNCH_LAST_KNOWN_ACTIVE_TAB_STATE,
                 APP_LAUNCH_SEARCH_ENGINE_HAD_LOGO,
                 APPLICATION_OVERRIDE_LANGUAGE,
+                CHROME_SURVEY_DOWNLOAD_ATTEMPTS.pattern(),
                 CHROME_SURVEY_PROMPT_DISPLAYED_TIMESTAMP.pattern(),
                 CLIPBOARD_SHARED_URI,
                 CLIPBOARD_SHARED_URI_TIMESTAMP,
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
index b82ee9c..118ff64 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
@@ -3074,9 +3074,13 @@
   const site = `<a tabindex=0></a><p>start</p><a tabindex=0></a><p>end</p>`;
   this.runWithLoadedTree(site, function(root) {
     assertEquals(
-        RoleType.ANCHOR, ChromeVoxState.instance.currentRange.start.node.role);
+        RoleType.STATIC_TEXT,
+        ChromeVoxState.instance.currentRange.start.node.role);
 
-    mockFeedback.call(doCmd('readFromHere'))
+    // "start" is uttered twice, once for the initial focus as the page loads,
+    // and once during the 'read from here' command.
+    mockFeedback.expectSpeech('start')
+        .call(doCmd('readFromHere'))
         .expectSpeech('start', 'end')
         .replay();
   });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/braille/bluetooth_braille_display_manager.js b/chrome/browser/resources/chromeos/accessibility/chromevox/braille/bluetooth_braille_display_manager.js
index d4ee205..8a6512a 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/braille/bluetooth_braille_display_manager.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/braille/bluetooth_braille_display_manager.js
@@ -52,16 +52,52 @@
      * utilize Brltty (e.g. BrailleBack).
      * @private {!Array<string|RegExp>}
      */
-    this.displayNames_ = [
-      '"EL12-', 'Esys-', 'Focus 14 BT', 'Focus 40 BT', 'Brailliant BI',
-      /Hansone|HansoneXL|BrailleSense|BrailleEDGE|SmartBeetle/,
-      'Refreshabraille', 'Orbit', 'Baum SuperVario', 'VarioConnect',
-      'VarioUltra', 'HWG Brailliant', 'braillex trio', /Alva BC/i, 'TSM', 'TS5',
-      new RegExp(
-          '(Actilino.*|Active Star.*|Braille Wave( BRW)?|Braillino( BL2)?' +
-          '|Braille Star 40( BS4)?|Easy Braille( EBR)?|Active Braille( AB4)?' +
-          '|Basic Braille BB[3,4,6]?)\\/[a-zA-Z][0-9]-[0-9]{5}'),
-      new RegExp('(BRW|BL2|BS4|EBR|AB4|BB(3|4|6)?)\\/[a-zA-Z][0-9]-[0-9]{5}')
+    this.displayNamePrefixes_ = [
+      'Actilino ALO',
+      'Activator AC4',
+      'Active Braille AB',
+      'Active Star AS',
+      'ALVA BC',
+      'APH Chameleon',
+      'APH Mantis',
+      'Basic Braille BB',
+      'Basic Braille Plus BP',
+      'BAUM Conny',
+      'Baum PocketVario',
+      'Baum SuperVario',
+      'Baum SVario',
+      'BrailleConnect',
+      'BrailleEDGE',
+      'BrailleMe',
+      'BMpk',
+      'BMsmart',
+      'BM32',
+      'BrailleNote Touch',
+      'BrailleSense',
+      'Braille Star',
+      'Braillex',
+      'Brailliant BI',
+      'Brailliant 14',
+      'Brailliant 80',
+      'Braillino BL',
+      'B2G',
+      'Conny',
+      'Easy Braille EBR',
+      'EL12-',
+      'Esys-',
+      'Focus',
+      'Humanware BrailleOne',
+      'HWG Brailliant',
+      'MB248',
+      'NLS eReader',
+      'Orbit Reader',
+      'Pronto!',
+      'Refreshabraille',
+      'SmartBeetle',
+      'SuperVario',
+      'TSM',
+      'VarioConnect',
+      'VarioUltra'
     ];
 
     /**
@@ -187,7 +223,7 @@
     handleDevicesChanged(opt_device) {
       chrome.bluetooth.getDevices((devices) => {
         const displayList = devices.filter((device) => {
-          return this.displayNames_.some((name) => {
+          return this.displayNamePrefixes_.some((name) => {
             return device.name && device.name.search(name) === 0;
           });
         });
diff --git a/chrome/browser/resources/extensions/BUILD.gn b/chrome/browser/resources/extensions/BUILD.gn
index 397a1b6..eafc30c 100644
--- a/chrome/browser/resources/extensions/BUILD.gn
+++ b/chrome/browser/resources/extensions/BUILD.gn
@@ -61,18 +61,18 @@
   out_folder = "$target_gen_dir/$preprocess_folder"
   out_manifest = "$target_gen_dir/$preprocess_manifest"
   in_files = [
-    "drag_and_drop_handler.js",
-    "extensions.js",
+    "drag_and_drop_handler.ts",
+    "extensions.ts",
     "item_behavior.js",
-    "item_util.js",
-    "keyboard_shortcut_delegate.js",
-    "navigation_helper.js",
-    "service.js",
-    "shortcut_util.js",
+    "item_util.ts",
+    "keyboard_shortcut_delegate.ts",
+    "navigation_helper.ts",
+    "service.ts",
+    "shortcut_util.ts",
   ]
 
   if (is_chromeos_ash) {
-    in_files += [ "kiosk_browser_proxy.js" ]
+    in_files += [ "kiosk_browser_proxy.ts" ]
   }
 }
 
@@ -93,7 +93,7 @@
     "drop_overlay.js",
     "error_page.js",
     "host_permissions_toggle_list.js",
-    "icons.js",
+    "icons.ts",
     "install_warnings_dialog.js",
     "item.js",
     "item_list.js",
@@ -105,8 +105,8 @@
     "pack_dialog.js",
     "runtime_host_permissions.js",
     "runtime_hosts_dialog.js",
-    "shared_style.js",
-    "shared_vars.js",
+    "shared_style.ts",
+    "shared_vars.ts",
     "shortcut_input.js",
     "sidebar.js",
     "toggle_row.js",
@@ -133,7 +133,7 @@
     "drop_overlay.js",
     "error_page.js",
     "host_permissions_toggle_list.js",
-    "icons.js",
+    "icons.ts",
     "install_warnings_dialog.js",
     "item.js",
     "item_list.js",
@@ -145,8 +145,8 @@
     "pack_dialog_alert.js",
     "runtime_host_permissions.js",
     "runtime_hosts_dialog.js",
-    "shared_style.js",
-    "shared_vars.js",
+    "shared_style.ts",
+    "shared_vars.ts",
     "shortcut_input.js",
     "sidebar.js",
     "toggle_row.js",
@@ -188,32 +188,32 @@
     "checkup.js",
     "code_section.js",
     "detail_view.js",
-    "drag_and_drop_handler.js",
+    "drag_and_drop_handler.ts",
     "drop_overlay.js",
     "error_page.js",
-    "extensions.js",
+    "extensions.ts",
     "host_permissions_toggle_list.js",
-    "icons.js",
+    "icons.ts",
     "install_warnings_dialog.js",
     "item_behavior.js",
     "item.js",
     "item_list.js",
-    "item_util.js",
-    "keyboard_shortcut_delegate.js",
+    "item_util.ts",
+    "keyboard_shortcut_delegate.ts",
     "keyboard_shortcuts.js",
     "load_error.js",
     "manager.js",
-    "navigation_helper.js",
+    "navigation_helper.ts",
     "options_dialog.js",
     "pack_dialog_alert.js",
     "pack_dialog.js",
     "runtime_host_permissions.js",
     "runtime_hosts_dialog.js",
-    "service.js",
-    "shared_style.js",
-    "shared_vars.js",
+    "service.ts",
+    "shared_style.ts",
+    "shared_vars.ts",
     "shortcut_input.js",
-    "shortcut_util.js",
+    "shortcut_util.ts",
     "sidebar.js",
     "toggle_row.js",
     "toolbar.js",
@@ -221,12 +221,14 @@
   definitions = [
     "//tools/typescript/definitions/activity_log_private.d.ts",
     "//tools/typescript/definitions/developer_private.d.ts",
+    "//tools/typescript/definitions/management.d.ts",
     "//tools/typescript/definitions/metrics_private.d.ts",
+    "//tools/typescript/definitions/runtime.d.ts",
   ]
 
   if (is_chromeos_ash) {
     in_files += [
-      "kiosk_browser_proxy.js",
+      "kiosk_browser_proxy.ts",
       "kiosk_dialog.js",
     ]
     definitions += [ "//tools/typescript/definitions/chrome_send.d.ts" ]
diff --git a/chrome/browser/resources/extensions/drag_and_drop_handler.js b/chrome/browser/resources/extensions/drag_and_drop_handler.ts
similarity index 72%
rename from chrome/browser/resources/extensions/drag_and_drop_handler.js
rename to chrome/browser/resources/extensions/drag_and_drop_handler.ts
index 8930b614..76d9ce64 100644
--- a/chrome/browser/resources/extensions/drag_and_drop_handler.js
+++ b/chrome/browser/resources/extensions/drag_and_drop_handler.ts
@@ -7,21 +7,16 @@
 import {Service} from './service.js';
 
 
-/** @implements DragWrapperDelegate */
-export class DragAndDropHandler {
-  /**
-   * @param {boolean} dragEnabled
-   * @param {!EventTarget} target
-   */
-  constructor(dragEnabled, target) {
-    this.dragEnabled = dragEnabled;
+export class DragAndDropHandler implements DragWrapperDelegate {
+  dragEnabled: boolean;
+  private eventTarget_: EventTarget;
 
-    /** @private {!EventTarget} */
+  constructor(dragEnabled: boolean, target: EventTarget) {
+    this.dragEnabled = dragEnabled;
     this.eventTarget_ = target;
   }
 
-  /** @override */
-  shouldAcceptDrag(e) {
+  shouldAcceptDrag(e: DragEvent): boolean {
     // External Extension installation can be disabled globally, e.g. while a
     // different overlay is already showing.
     if (!this.dragEnabled) {
@@ -32,29 +27,26 @@
     // wait until 'drop' to decide whether to do something with the file or
     // not.
     // See: http://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#concept-dnd-p
-    return !!e.dataTransfer.types && e.dataTransfer.types.indexOf('Files') > -1;
+    return !!e.dataTransfer!.types &&
+        e.dataTransfer!.types.indexOf('Files') > -1;
   }
 
-  /** @override */
   doDragEnter() {
     Service.getInstance().notifyDragInstallInProgress();
     this.eventTarget_.dispatchEvent(new CustomEvent('extension-drag-started'));
   }
 
-  /** @override */
   doDragLeave() {
     this.fireDragEnded_();
   }
 
-  /** @override */
-  doDragOver(e) {
+  doDragOver(e: DragEvent) {
     e.preventDefault();
   }
 
-  /** @override */
-  doDrop(e) {
+  doDrop(e: DragEvent) {
     this.fireDragEnded_();
-    if (e.dataTransfer.files.length !== 1) {
+    if (e.dataTransfer!.files.length !== 1) {
       return;
     }
 
@@ -62,11 +54,11 @@
 
     // Files lack a check if they're a directory, but we can find out through
     // its item entry.
-    const item = e.dataTransfer.items[0];
+    const item = e.dataTransfer!.items[0];
     if (item.kind === 'file' && item.webkitGetAsEntry().isDirectory) {
       handled = true;
       this.handleDirectoryDrop_();
-    } else if (/\.(crx|user\.js|zip)$/i.test(e.dataTransfer.files[0].name)) {
+    } else if (/\.(crx|user\.js|zip)$/i.test(e.dataTransfer!.files[0].name)) {
       // Only process files that look like extensions. Other files should
       // navigate the browser normally.
       handled = true;
@@ -80,25 +72,22 @@
 
   /**
    * Handles a dropped file.
-   * @private
    */
-  handleFileDrop_() {
+  private handleFileDrop_() {
     Service.getInstance().installDroppedFile();
   }
 
   /**
    * Handles a dropped directory.
-   * @private
    */
-  handleDirectoryDrop_() {
+  private handleDirectoryDrop_() {
     Service.getInstance().loadUnpackedFromDrag().catch(loadError => {
       this.eventTarget_.dispatchEvent(
           new CustomEvent('drag-and-drop-load-error', {detail: loadError}));
     });
   }
 
-  /** @private */
-  fireDragEnded_() {
+  private fireDragEnded_() {
     this.eventTarget_.dispatchEvent(new CustomEvent('extension-drag-ended'));
   }
 }
diff --git a/chrome/browser/resources/extensions/extensions.js b/chrome/browser/resources/extensions/extensions.ts
similarity index 100%
rename from chrome/browser/resources/extensions/extensions.js
rename to chrome/browser/resources/extensions/extensions.ts
diff --git a/chrome/browser/resources/extensions/icons.js b/chrome/browser/resources/extensions/icons.ts
similarity index 100%
rename from chrome/browser/resources/extensions/icons.js
rename to chrome/browser/resources/extensions/icons.ts
diff --git a/chrome/browser/resources/extensions/item_util.js b/chrome/browser/resources/extensions/item_util.ts
similarity index 68%
rename from chrome/browser/resources/extensions/item_util.js
rename to chrome/browser/resources/extensions/item_util.ts
index b6ac452..97192bf8 100644
--- a/chrome/browser/resources/extensions/item_util.js
+++ b/chrome/browser/resources/extensions/item_util.ts
@@ -7,41 +7,36 @@
 import {assertNotReached} from 'chrome://resources/js/assert.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
+export enum SourceType {
+  WEBSTORE = 'webstore',
+  POLICY = 'policy',
+  SIDELOADED = 'sideloaded',
+  UNPACKED = 'unpacked',
+  UNKNOWN = 'unknown',
+}
 
-// Closure compiler won't let this be declared inside cr.define().
-/** @enum {string} */
-export const SourceType = {
-  WEBSTORE: 'webstore',
-  POLICY: 'policy',
-  SIDELOADED: 'sideloaded',
-  UNPACKED: 'unpacked',
-  UNKNOWN: 'unknown',
-};
-
-/** @enum {string} */
-export const EnableControl = {
-  RELOAD: 'RELOAD',
-  REPAIR: 'REPAIR',
-  ENABLE_TOGGLE: 'ENABLE_TOGGLE',
-};
+export enum EnableControl {
+  RELOAD = 'RELOAD',
+  REPAIR = 'REPAIR',
+  ENABLE_TOGGLE = 'ENABLE_TOGGLE',
+}
 
 // TODO(tjudkins): This should be extracted to a shared metrics module.
 /** @enum {string} */
-export const UserAction = {
-  ALL_TOGGLED_ON: 'Extensions.Settings.HostList.AllHostsToggledOn',
-  ALL_TOGGLED_OFF: 'Extensions.Settings.HostList.AllHostsToggledOff',
-  SPECIFIC_TOGGLED_ON: 'Extensions.Settings.HostList.SpecificHostToggledOn',
-  SPECIFIC_TOGGLED_OFF: 'Extensions.Settings.HostList.SpecificHostToggledOff',
-  LEARN_MORE: 'Extensions.Settings.HostList.LearnMoreActivated',
-};
+export enum UserAction {
+  ALL_TOGGLED_ON = 'Extensions.Settings.HostList.AllHostsToggledOn',
+  ALL_TOGGLED_OFF = 'Extensions.Settings.HostList.AllHostsToggledOff',
+  SPECIFIC_TOGGLED_ON = 'Extensions.Settings.HostList.SpecificHostToggledOn',
+  SPECIFIC_TOGGLED_OFF = 'Extensions.Settings.HostList.SpecificHostToggledOff',
+  LEARN_MORE = 'Extensions.Settings.HostList.LearnMoreActivated',
+}
 
 /**
  * Returns true if the extension is enabled, including terminated
  * extensions.
- * @param {!chrome.developerPrivate.ExtensionState} state
- * @return {boolean}
  */
-export function isEnabled(state) {
+export function isEnabled(state: chrome.developerPrivate.ExtensionState):
+    boolean {
   switch (state) {
     case chrome.developerPrivate.ExtensionState.ENABLED:
     case chrome.developerPrivate.ExtensionState.TERMINATED:
@@ -51,15 +46,15 @@
       return false;
   }
   assertNotReached();
+  return false;
 }
 
 /**
- * Returns true if the user can change whether or not the extension is
- * enabled.
- * @param {!chrome.developerPrivate.ExtensionInfo} item
- * @return {boolean}
+ * @return {boolean} Whether the user can change whether or not the extension is
+ *     enabled.
  */
-export function userCanChangeEnablement(item) {
+export function userCanChangeEnablement(
+    item: chrome.developerPrivate.ExtensionInfo): boolean {
   // User doesn't have permission.
   if (!item.userMayModify) {
     return false;
@@ -84,11 +79,8 @@
   return true;
 }
 
-/**
- * @param {!chrome.developerPrivate.ExtensionInfo} item
- * @return {SourceType}
- */
-export function getItemSource(item) {
+export function getItemSource(item: chrome.developerPrivate.ExtensionInfo):
+    SourceType {
   if (item.controlledInfo) {
     return SourceType.POLICY;
   }
@@ -107,11 +99,7 @@
   assertNotReached(item.location);
 }
 
-/**
- * @param {SourceType} source
- * @return {string}
- */
-export function getItemSourceString(source) {
+export function getItemSourceString(source: SourceType): string {
   switch (source) {
     case SourceType.POLICY:
       return loadTimeData.getString('itemSourcePolicy');
@@ -127,14 +115,14 @@
       return '';
   }
   assertNotReached();
+  return '';
 }
 
 /**
  * Computes the human-facing label for the given inspectable view.
- * @param {!chrome.developerPrivate.ExtensionView} view
- * @return {string}
  */
-export function computeInspectableViewLabel(view) {
+export function computeInspectableViewLabel(
+    view: chrome.developerPrivate.ExtensionView): string {
   // Trim the "chrome-extension://<id>/".
   const url = new URL(view.url);
   let label = view.url;
@@ -162,21 +150,17 @@
 }
 
 /**
- * Returns true if the extension is in the terminated state.
- * @param {!chrome.developerPrivate.ExtensionState} state
- * @return {boolean}
- * @private
+ * @return Whether the extension is in the terminated state.
  */
-function isTerminated_(state) {
+function isTerminated_(state: chrome.developerPrivate.ExtensionState): boolean {
   return state === chrome.developerPrivate.ExtensionState.TERMINATED;
 }
 
 /**
  * Determines which enable control to display for a given extension.
- * @param {!chrome.developerPrivate.ExtensionInfo} data
- * @return {EnableControl}
  */
-export function getEnableControl(data) {
+export function getEnableControl(data: chrome.developerPrivate.ExtensionInfo):
+    EnableControl {
   if (isTerminated_(data.state)) {
     return EnableControl.RELOAD;
   }
diff --git a/chrome/browser/resources/extensions/keyboard_shortcut_delegate.js b/chrome/browser/resources/extensions/keyboard_shortcut_delegate.js
deleted file mode 100644
index 4281304e..0000000
--- a/chrome/browser/resources/extensions/keyboard_shortcut_delegate.js
+++ /dev/null
@@ -1,35 +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.
-
-'use strict';
-
-/** @interface */
-export class KeyboardShortcutDelegate {
-  /**
-   * Called when shortcut capturing changes in order to suspend or re-enable
-   * global shortcut handling. This is important so that the shortcuts aren't
-   * processed normally as the user types them.
-   * TODO(devlin): From very brief experimentation, it looks like preventing
-   * the default handling on the event also does this. Investigate more in the
-   * future.
-   * @param {boolean} isCapturing
-   */
-  setShortcutHandlingSuspended(isCapturing) {}
-
-  /**
-   * Updates an extension command's keybinding.
-   * @param {string} extensionId
-   * @param {string} commandName
-   * @param {string} keybinding
-   */
-  updateExtensionCommandKeybinding(extensionId, commandName, keybinding) {}
-
-  /**
-   * Updates an extension command's scope.
-   * @param {string} extensionId
-   * @param {string} commandName
-   * @param {chrome.developerPrivate.CommandScope} scope
-   */
-  updateExtensionCommandScope(extensionId, commandName, scope) {}
-}
diff --git a/chrome/browser/resources/extensions/keyboard_shortcut_delegate.ts b/chrome/browser/resources/extensions/keyboard_shortcut_delegate.ts
new file mode 100644
index 0000000..c76bf5ff
--- /dev/null
+++ b/chrome/browser/resources/extensions/keyboard_shortcut_delegate.ts
@@ -0,0 +1,28 @@
+// 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.
+
+export interface KeyboardShortcutDelegate {
+  /**
+   * Called when shortcut capturing changes in order to suspend or re-enable
+   * global shortcut handling. This is important so that the shortcuts aren't
+   * processed normally as the user types them.
+   * TODO(devlin): From very brief experimentation, it looks like preventing
+   * the default handling on the event also does this. Investigate more in the
+   * future.
+   */
+  setShortcutHandlingSuspended(isCapturing: boolean): void;
+
+  /**
+   * Updates an extension command's keybinding.
+   */
+  updateExtensionCommandKeybinding(
+      extensionId: string, commandName: string, keybinding: string): void;
+
+  /**
+   * Updates an extension command's scope.
+   */
+  updateExtensionCommandScope(
+      extensionId: string, commandName: string,
+      scope: chrome.developerPrivate.CommandScope): void;
+}
diff --git a/chrome/browser/resources/extensions/kiosk_browser_proxy.js b/chrome/browser/resources/extensions/kiosk_browser_proxy.js
deleted file mode 100644
index 79f1dcb..0000000
--- a/chrome/browser/resources/extensions/kiosk_browser_proxy.js
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview A helper object used from the "Kiosk" dialog to interact with
- * the browser.
- */
-
-import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
-
-/**
- * @typedef {{
- *   kioskEnabled: boolean,
- *   autoLaunchEnabled: boolean
- * }}
- */
-let KioskSettings;
-
-/**
- * @typedef {{
- *   id: string,
- *   name: string,
- *   iconURL: string,
- *   autoLaunch: boolean,
- *   isLoading: boolean
- * }}
- */
-export let KioskApp;
-
-/**
- * @typedef {{
- *   apps: !Array<!KioskApp>,
- *   disableBailout: boolean,
- *   hasAutoLaunchApp: boolean
- * }}
- */
-export let KioskAppSettings;
-
-/** @interface */
-export class KioskBrowserProxy {
-  /** @param {string} appId */
-  addKioskApp(appId) {}
-
-  /** @param {string} appId */
-  disableKioskAutoLaunch(appId) {}
-
-  /** @param {string} appId */
-  enableKioskAutoLaunch(appId) {}
-
-  /** @return {!Promise<!KioskAppSettings>} */
-  getKioskAppSettings() {}
-
-  /** @return {!Promise<!KioskSettings>} */
-  initializeKioskAppSettings() {}
-
-  /** @param {string} appId */
-  removeKioskApp(appId) {}
-
-  /** @param {boolean} disableBailout */
-  setDisableBailoutShortcut(disableBailout) {}
-}
-
-/** @implements {KioskBrowserProxy} */
-export class KioskBrowserProxyImpl {
-  /** @override */
-  initializeKioskAppSettings() {
-    return sendWithPromise('initializeKioskAppSettings');
-  }
-
-  /** @override */
-  getKioskAppSettings() {
-    return sendWithPromise('getKioskAppSettings');
-  }
-
-  /** @override */
-  addKioskApp(appId) {
-    chrome.send('addKioskApp', [appId]);
-  }
-
-  /** @override */
-  disableKioskAutoLaunch(appId) {
-    chrome.send('disableKioskAutoLaunch', [appId]);
-  }
-
-  /** @override */
-  enableKioskAutoLaunch(appId) {
-    chrome.send('enableKioskAutoLaunch', [appId]);
-  }
-
-  /** @override */
-  removeKioskApp(appId) {
-    chrome.send('removeKioskApp', [appId]);
-  }
-
-  /** @override */
-  setDisableBailoutShortcut(disableBailout) {
-    chrome.send('setDisableBailoutShortcut', [disableBailout]);
-  }
-
-  /** @return {!KioskBrowserProxy} */
-  static getInstance() {
-    return instance || (instance = new KioskBrowserProxyImpl());
-  }
-
-  /** @param {!KioskBrowserProxy} obj */
-  static setInstance(obj) {
-    instance = obj;
-  }
-}
-
-/** @type {?KioskBrowserProxy} */
-let instance = null;
diff --git a/chrome/browser/resources/extensions/kiosk_browser_proxy.ts b/chrome/browser/resources/extensions/kiosk_browser_proxy.ts
new file mode 100644
index 0000000..83f1daf
--- /dev/null
+++ b/chrome/browser/resources/extensions/kiosk_browser_proxy.ts
@@ -0,0 +1,80 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview A helper object used from the "Kiosk" dialog to interact with
+ * the browser.
+ */
+
+import {sendWithPromise} from 'chrome://resources/js/cr.m.js';
+
+type KioskSettings = {
+  kioskEnabled: boolean,
+  autoLaunchEnabled: boolean,
+};
+
+export type KioskApp = {
+  id: string,
+  name: string,
+  iconURL: string,
+  autoLaunch: boolean,
+  isLoading: boolean,
+};
+
+export type KioskAppSettings = {
+  apps: Array<KioskApp>,
+  disableBailout: boolean,
+  hasAutoLaunchApp: boolean,
+};
+
+/** @interface */
+export interface KioskBrowserProxy {
+  addKioskApp(appId: string): void;
+  disableKioskAutoLaunch(appId: string): void;
+  enableKioskAutoLaunch(appId: string): void;
+  getKioskAppSettings(): Promise<KioskAppSettings>;
+  initializeKioskAppSettings(): Promise<KioskSettings>;
+  removeKioskApp(appId: string): void;
+  setDisableBailoutShortcut(disableBailout: boolean): void;
+}
+
+export class KioskBrowserProxyImpl implements KioskBrowserProxy {
+  initializeKioskAppSettings() {
+    return sendWithPromise('initializeKioskAppSettings');
+  }
+
+  getKioskAppSettings() {
+    return sendWithPromise('getKioskAppSettings');
+  }
+
+  addKioskApp(appId: string) {
+    chrome.send('addKioskApp', [appId]);
+  }
+
+  disableKioskAutoLaunch(appId: string) {
+    chrome.send('disableKioskAutoLaunch', [appId]);
+  }
+
+  enableKioskAutoLaunch(appId: string) {
+    chrome.send('enableKioskAutoLaunch', [appId]);
+  }
+
+  removeKioskApp(appId: string) {
+    chrome.send('removeKioskApp', [appId]);
+  }
+
+  setDisableBailoutShortcut(disableBailout: boolean) {
+    chrome.send('setDisableBailoutShortcut', [disableBailout]);
+  }
+
+  static getInstance(): KioskBrowserProxy {
+    return instance || (instance = new KioskBrowserProxyImpl());
+  }
+
+  static setInstance(obj: KioskBrowserProxy) {
+    instance = obj;
+  }
+}
+
+let instance: KioskBrowserProxy|null = null;
diff --git a/chrome/browser/resources/extensions/navigation_helper.js b/chrome/browser/resources/extensions/navigation_helper.ts
similarity index 73%
rename from chrome/browser/resources/extensions/navigation_helper.js
rename to chrome/browser/resources/extensions/navigation_helper.ts
index 53de02b..9ed4800 100644
--- a/chrome/browser/resources/extensions/navigation_helper.js
+++ b/chrome/browser/resources/extensions/navigation_helper.ts
@@ -8,34 +8,29 @@
 /**
  * The different pages that can be shown at a time.
  * Note: This must remain in sync with the page ids in manager.html!
- * @enum {string}
  */
-export const Page = {
-  LIST: 'items-list',
-  DETAILS: 'details-view',
-  ACTIVITY_LOG: 'activity-log',
-  SHORTCUTS: 'keyboard-shortcuts',
-  ERRORS: 'error-page',
+export enum Page {
+  LIST = 'items-list',
+  DETAILS = 'details-view',
+  ACTIVITY_LOG = 'activity-log',
+  SHORTCUTS = 'keyboard-shortcuts',
+  ERRORS = 'error-page',
+}
+
+export enum Dialog {
+  OPTIONS = 'options',
+}
+
+export type PageState = {
+  page: Page,
+  extensionId?: string,
+  subpage?: Dialog,
 };
 
-/** @enum {string} */
-export const Dialog = {
-  OPTIONS: 'options',
-};
+type Listener = (pageState: PageState) => void;
 
-/**
- * @typedef {{page: Page,
- *            extensionId: (string|undefined),
- *            subpage: (!Dialog|undefined)}}
- */
-export let PageState;
-
-/**
- * @param {!PageState} a
- * @param {!PageState} b
- * @return {boolean} Whether a and b are equal.
- */
-function isPageStateEqual(a, b) {
+/** @return Whether a and b are equal. */
+function isPageStateEqual(a: PageState, b: PageState): boolean {
   return a.page === b.page && a.subpage === b.subpage &&
       a.extensionId === b.extensionId;
 }
@@ -43,9 +38,8 @@
 /**
  * Regular expression that captures the leading slash, the content and the
  * trailing slash in three different groups.
- * @const {!RegExp}
  */
-const CANONICAL_PATH_REGEX = /(^\/)([\/-\w]+)(\/$)/;
+const CANONICAL_PATH_REGEX: RegExp = /(^\/)([\/-\w]+)(\/$)/;
 
 /**
  * A helper object to manage in-page navigations. Since the extensions page
@@ -53,25 +47,19 @@
  * page), we use this object to manage the history and url conversions.
  */
 export class NavigationHelper {
+  private nextListenerId_: number = 1;
+  private listeners_: Map<number, Listener> = new Map();
+  private previousPage_: PageState;
+
   constructor() {
     this.processRoute_();
 
-    /** @private {number} */
-    this.nextListenerId_ = 1;
-
-    /** @private {!Map<number, function(!PageState)>} */
-    this.listeners_ = new Map();
-
-    /** @private {!PageState} */
-    this.previousPage_;
-
     window.addEventListener('popstate', () => {
       this.notifyRouteChanged_(this.getCurrentPage());
     });
   }
 
-  /** @private */
-  get currentPath_() {
+  private get currentPath_(): string {
     return location.pathname.replace(CANONICAL_PATH_REGEX, '$1$2');
   }
 
@@ -79,9 +67,8 @@
    * Going to /configureCommands and /shortcuts should land you on /shortcuts.
    * These are the only two supported routes, so all other cases will redirect
    * you to root path if not already on it.
-   * @private
    */
-  processRoute_() {
+  private processRoute_() {
     if (this.currentPath_ === '/configureCommands' ||
         this.currentPath_ === '/shortcuts') {
       window.history.replaceState(
@@ -92,10 +79,9 @@
   }
 
   /**
-   * @return {!PageState} The page that should be displayed for the
-   *     current URL.
+   * @return The page that should be displayed for the current URL.
    */
-  getCurrentPage() {
+  getCurrentPage(): PageState {
     const search = new URLSearchParams(location.search);
     let id = search.get('id');
     if (id) {
@@ -124,9 +110,9 @@
   /**
    * Function to add subscribers.
    * @param {!function(!PageState)} listener
-   * @return {number} A numerical ID to be used for removing the listener.
+   * @return A numerical ID to be used for removing the listener.
    */
-  addListener(listener) {
+  addListener(listener: Listener): number {
     const nextListenerId = this.nextListenerId_++;
     this.listeners_.set(nextListenerId, listener);
     return nextListenerId;
@@ -134,28 +120,26 @@
 
   /**
    * Remove a previously registered listener.
-   * @param {number} id
-   * @return {boolean} Whether a listener with the given ID was actually found
-   *     and removed.
+   * @return Whether a listener with the given ID was actually found and
+   *   removed.
    */
-  removeListener(id) {
+  removeListener(id: number): boolean {
     return this.listeners_.delete(id);
   }
 
   /**
    * Function to notify subscribers.
-   * @private
    */
-  notifyRouteChanged_(newPage) {
-    this.listeners_.forEach((listener, id) => {
+  private notifyRouteChanged_(newPage: PageState) {
+    for (const listener of this.listeners_.values()) {
       listener(newPage);
-    });
+    }
   }
 
   /**
-   * @param {!PageState} newPage the page to navigate to.
+   * @param newPage the page to navigate to.
    */
-  navigateTo(newPage) {
+  navigateTo(newPage: PageState) {
     const currentPage = this.getCurrentPage();
     if (currentPage && isPageStateEqual(currentPage, newPage)) {
       return;
@@ -166,10 +150,9 @@
   }
 
   /**
-   * @param {!PageState} newPage the page to replace the current
-   *     page with.
+   * @param newPage the page to replace the current page with.
    */
-  replaceWith(newPage) {
+  replaceWith(newPage: PageState) {
     this.updateHistory(newPage, true /* replaceState */);
     if (this.previousPage_ && isPageStateEqual(this.previousPage_, newPage)) {
       // Skip the duplicate history entry.
@@ -181,10 +164,8 @@
 
   /**
    * Called when a page changes, and pushes state to history to reflect it.
-   * @param {!PageState} entry
-   * @param {boolean} replaceState
    */
-  updateHistory(entry, replaceState) {
+  updateHistory(entry: PageState, replaceState: boolean) {
     let path;
     switch (entry.page) {
       case Page.LIST:
diff --git a/chrome/browser/resources/extensions/service.js b/chrome/browser/resources/extensions/service.ts
similarity index 73%
rename from chrome/browser/resources/extensions/service.js
rename to chrome/browser/resources/extensions/service.ts
index 03bb069..85438ac 100644
--- a/chrome/browser/resources/extensions/service.js
+++ b/chrome/browser/resources/extensions/service.ts
@@ -20,22 +20,16 @@
  * @implements {ActivityLogEventDelegate}
  * @implements {ErrorPageDelegate}
  * @implements {ItemDelegate}
- * @implements {KeyboardShortcutDelegate}
  * @implements {LoadErrorDelegate}
  * @implements {PackDialogDelegate}
  * @implements {ToolbarDelegate}
  */
-export class Service {
-  constructor() {
-    /** @private {boolean} */
-    this.isDeleting_ = false;
-
-    /** @private {!Set<string>} */
-    this.eventsToIgnoreOnce_ = new Set();
-  }
+export class Service implements KeyboardShortcutDelegate {
+  private isDeleting_: boolean = false;
+  private eventsToIgnoreOnce_: Set<string> = new Set();
 
   getProfileConfiguration() {
-    return new Promise(function(resolve, reject) {
+    return new Promise(function(resolve) {
       chrome.developerPrivate.getProfileConfiguration(resolve);
     });
   }
@@ -44,20 +38,14 @@
     return chrome.developerPrivate.onItemStateChanged;
   }
 
-  /**
-   * @param {string} extensionId
-   * @param {!chrome.developerPrivate.EventType} eventType
-   * @return {boolean}
-   */
-  shouldIgnoreUpdate(extensionId, eventType) {
+  shouldIgnoreUpdate(
+      extensionId: string,
+      eventType: chrome.developerPrivate.EventType): boolean {
     return this.eventsToIgnoreOnce_.delete(`${extensionId}_${eventType}`);
   }
 
-  /**
-   * @param {string} extensionId
-   * @param {!chrome.developerPrivate.EventType} eventType
-   */
-  ignoreNextEvent(extensionId, eventType) {
+  ignoreNextEvent(
+      extensionId: string, eventType: chrome.developerPrivate.EventType): void {
     this.eventsToIgnoreOnce_.add(`${extensionId}_${eventType}`);
   }
 
@@ -66,21 +54,21 @@
   }
 
   getExtensionsInfo() {
-    return new Promise(function(resolve, reject) {
+    return new Promise(function(resolve) {
       chrome.developerPrivate.getExtensionsInfo(
           {includeDisabled: true, includeTerminated: true}, resolve);
     });
   }
 
   /** @override */
-  getExtensionSize(id) {
-    return new Promise(function(resolve, reject) {
+  getExtensionSize(id: string): Promise<string> {
+    return new Promise(function(resolve) {
       chrome.developerPrivate.getExtensionSize(id, resolve);
     });
   }
 
   /** @override */
-  addRuntimeHostPermission(id, host) {
+  addRuntimeHostPermission(id: string, host: string): Promise<void> {
     return new Promise((resolve, reject) => {
       chrome.developerPrivate.addHostPermission(id, host, () => {
         if (chrome.runtime.lastError) {
@@ -93,7 +81,7 @@
   }
 
   /** @override */
-  removeRuntimeHostPermission(id, host) {
+  removeRuntimeHostPermission(id: string, host: string): Promise<void> {
     return new Promise((resolve, reject) => {
       chrome.developerPrivate.removeHostPermission(id, host, () => {
         if (chrome.runtime.lastError) {
@@ -106,18 +94,17 @@
   }
 
   /** @override */
-  recordUserAction(metricName) {
+  recordUserAction(metricName: string): void {
     chrome.metricsPrivate.recordUserAction(metricName);
   }
 
   /**
    * Opens a file browser dialog for the user to select a file (or directory).
-   * @param {chrome.developerPrivate.SelectType} selectType
-   * @param {chrome.developerPrivate.FileType} fileType
-   * @return {Promise<string>} The promise to be resolved with the selected
-   *     path.
+   * @return The promise to be resolved with the selected path.
    */
-  chooseFilePath_(selectType, fileType) {
+  chooseFilePath_(
+      selectType: chrome.developerPrivate.SelectType,
+      fileType: chrome.developerPrivate.FileType): Promise<string> {
     return new Promise(function(resolve, reject) {
       chrome.developerPrivate.choosePath(selectType, fileType, function(path) {
         if (chrome.runtime.lastError &&
@@ -132,7 +119,8 @@
   }
 
   /** @override */
-  updateExtensionCommandKeybinding(extensionId, commandName, keybinding) {
+  updateExtensionCommandKeybinding(
+      extensionId: string, commandName: string, keybinding: string) {
     chrome.developerPrivate.updateExtensionCommand({
       extensionId: extensionId,
       commandName: commandName,
@@ -141,7 +129,9 @@
   }
 
   /** @override */
-  updateExtensionCommandScope(extensionId, commandName, scope) {
+  updateExtensionCommandScope(
+      extensionId: string, commandName: string,
+      scope: chrome.developerPrivate.CommandScope): void {
     // The COMMAND_REMOVED event needs to be ignored since it is sent before
     // the command is added back with the updated scope but can be handled
     // after the COMMAND_ADDED event.
@@ -156,17 +146,16 @@
 
 
   /** @override */
-  setShortcutHandlingSuspended(isCapturing) {
+  setShortcutHandlingSuspended(isCapturing: boolean) {
     chrome.developerPrivate.setShortcutHandlingSuspended(isCapturing);
   }
 
   /**
-   * @param {chrome.developerPrivate.LoadUnpackedOptions=} opt_options
-   * @return {!Promise} A signal that loading finished, rejected if any error
-   *     occurred.
-   * @private
+   * @return A signal that loading finished, rejected if any error occurred.
    */
-  loadUnpackedHelper_(opt_options) {
+  private loadUnpackedHelper_(opt_options?:
+                                  chrome.developerPrivate.LoadUnpackedOptions):
+      Promise<boolean> {
     return new Promise(function(resolve, reject) {
       const options = Object.assign(
           {
@@ -193,7 +182,7 @@
   }
 
   /** @override */
-  deleteItem(id) {
+  deleteItem(id: string) {
     if (this.isDeleting_) {
       return;
     }
@@ -209,7 +198,7 @@
   }
 
   /** @override */
-  setItemEnabled(id, isEnabled) {
+  setItemEnabled(id: string, isEnabled: boolean) {
     chrome.metricsPrivate.recordUserAction(
         isEnabled ? 'Extensions.ExtensionEnabled' :
                     'Extensions.ExtensionDisabled');
@@ -217,7 +206,7 @@
   }
 
   /** @override */
-  setItemAllowedIncognito(id, isAllowedIncognito) {
+  setItemAllowedIncognito(id: string, isAllowedIncognito: boolean) {
     chrome.developerPrivate.updateExtensionConfiguration({
       extensionId: id,
       incognitoAccess: isAllowedIncognito,
@@ -225,7 +214,7 @@
   }
 
   /** @override */
-  setItemAllowedOnFileUrls(id, isAllowedOnFileUrls) {
+  setItemAllowedOnFileUrls(id: string, isAllowedOnFileUrls: boolean) {
     chrome.developerPrivate.updateExtensionConfiguration({
       extensionId: id,
       fileAccess: isAllowedOnFileUrls,
@@ -233,7 +222,8 @@
   }
 
   /** @override */
-  setItemHostAccess(id, hostAccess) {
+  setItemHostAccess(id: string, hostAccess: chrome.developerPrivate.HostAccess):
+      void {
     chrome.developerPrivate.updateExtensionConfiguration({
       extensionId: id,
       hostAccess: hostAccess,
@@ -241,7 +231,7 @@
   }
 
   /** @override */
-  setItemCollectsErrors(id, collectsErrors) {
+  setItemCollectsErrors(id: string, collectsErrors: boolean): void {
     chrome.developerPrivate.updateExtensionConfiguration({
       extensionId: id,
       errorCollection: collectsErrors,
@@ -249,7 +239,8 @@
   }
 
   /** @override */
-  inspectItemView(id, view) {
+  inspectItemView(id: string, view: chrome.developerPrivate.ExtensionView):
+      void {
     chrome.developerPrivate.openDevTools({
       extensionId: id,
       renderProcessId: view.renderProcessId,
@@ -259,16 +250,12 @@
     });
   }
 
-  /**
-   * @param {string} url
-   * @override
-   */
-  openUrl(url) {
+  openUrl(url: string): void {
     window.open(url);
   }
 
   /** @override */
-  reloadItem(id) {
+  reloadItem(id: string): Promise<void> {
     return new Promise(function(resolve, reject) {
       chrome.developerPrivate.reload(
           id, {failQuietly: true, populateErrorForUnpacked: true},
@@ -284,14 +271,14 @@
   }
 
   /** @override */
-  repairItem(id) {
+  repairItem(id: string): void {
     chrome.developerPrivate.repairExtension(id);
   }
 
   /** @override */
-  showItemOptionsPage(extension) {
+  showItemOptionsPage(extension: chrome.developerPrivate.ExtensionInfo): void {
     assert(extension && extension.optionsPage);
-    if (extension.optionsPage.openInTab) {
+    if (extension.optionsPage!.openInTab) {
       chrome.developerPrivate.showOptions(extension.id);
     } else {
       navigation.navigateTo({
@@ -303,51 +290,56 @@
   }
 
   /** @override */
-  setProfileInDevMode(inDevMode) {
+  setProfileInDevMode(inDevMode: boolean) {
     chrome.developerPrivate.updateProfileConfiguration(
         {inDeveloperMode: inDevMode});
   }
 
   /** @override */
-  loadUnpacked() {
+  loadUnpacked(): Promise<boolean> {
     return this.loadUnpackedHelper_();
   }
 
   /** @override */
-  retryLoadUnpacked(retryGuid) {
+  retryLoadUnpacked(retryGuid: string): Promise<boolean> {
     // Attempt to load an unpacked extension, optionally as another attempt at
     // a previously-specified load.
     return this.loadUnpackedHelper_({retryGuid: retryGuid});
   }
 
   /** @override */
-  choosePackRootDirectory() {
+  choosePackRootDirectory(): Promise<string> {
     return this.chooseFilePath_(
         chrome.developerPrivate.SelectType.FOLDER,
         chrome.developerPrivate.FileType.LOAD);
   }
 
   /** @override */
-  choosePrivateKeyPath() {
+  choosePrivateKeyPath(): Promise<string> {
     return this.chooseFilePath_(
         chrome.developerPrivate.SelectType.FILE,
         chrome.developerPrivate.FileType.PEM);
   }
 
   /** @override */
-  packExtension(rootPath, keyPath, flag, callback) {
+  packExtension(
+      rootPath: string, keyPath: string, flag?: number,
+      callback?:
+          (response: chrome.developerPrivate.PackDirectoryResponse) => void):
+      void {
     chrome.developerPrivate.packDirectory(rootPath, keyPath, flag, callback);
   }
 
   /** @override */
-  updateAllExtensions(extensions) {
+  updateAllExtensions(extensions: chrome.developerPrivate.ExtensionInfo[]):
+      Promise<string> {
     /**
      * Attempt to reload local extensions. If an extension fails to load, the
      * user is prompted to try updating the broken extension using loadUnpacked
      * and we skip reloading the remaining local extensions.
      */
-    return new Promise((resolve) => {
-             chrome.developerPrivate.autoUpdate(resolve);
+    return new Promise<void>((resolve) => {
+             chrome.developerPrivate.autoUpdate(() => resolve());
              chrome.metricsPrivate.recordUserAction('Options_UpdateExtensions');
            })
         .then(() => {
@@ -371,7 +363,9 @@
   }
 
   /** @override */
-  deleteErrors(extensionId, errorIds, type) {
+  deleteErrors(
+      extensionId: string, errorIds?: number[],
+      type?: chrome.developerPrivate.ErrorType) {
     chrome.developerPrivate.deleteExtensionErrors({
       extensionId: extensionId,
       errorIds: errorIds,
@@ -380,20 +374,21 @@
   }
 
   /** @override */
-  requestFileSource(args) {
-    return new Promise(function(resolve, reject) {
+  requestFileSource(args: chrome.developerPrivate.RequestFileSourceProperties):
+      Promise<chrome.developerPrivate.RequestFileSourceResponse> {
+    return new Promise(function(resolve) {
       chrome.developerPrivate.requestFileSource(args, resolve);
     });
   }
 
   /** @override */
-  showInFolder(id) {
+  showInFolder(id: string) {
     chrome.developerPrivate.showPath(id);
   }
 
   /** @override */
-  getExtensionActivityLog(extensionId) {
-    return new Promise(function(resolve, reject) {
+  getExtensionActivityLog(extensionId: string) {
+    return new Promise(function(resolve) {
       chrome.activityLogPrivate.getExtensionActivities(
           {
             activityType: chrome.activityLogPrivate.ExtensionActivityFilter.ANY,
@@ -404,7 +399,7 @@
   }
 
   /** @override */
-  getFilteredExtensionActivityLog(extensionId, searchTerm) {
+  getFilteredExtensionActivityLog(extensionId: string, searchTerm: string) {
     const anyType = chrome.activityLogPrivate.ExtensionActivityFilter.ANY;
 
     // Construct one filter for each API call we will make: one for substring
@@ -428,10 +423,13 @@
       }
     ];
 
-    const promises = activityLogFilters.map(
-        filter => new Promise(function(resolve, reject) {
-          chrome.activityLogPrivate.getExtensionActivities(filter, resolve);
-        }));
+    const promises:
+        Array<Promise<chrome.activityLogPrivate.ActivityResultSet>> =
+            activityLogFilters.map(
+                filter => new Promise(function(resolve) {
+                  chrome.activityLogPrivate.getExtensionActivities(
+                      filter, resolve);
+                }));
 
     return Promise.all(promises).then(results => {
       // We may have results that are present in one or more searches, so
@@ -449,15 +447,15 @@
   }
 
   /** @override */
-  deleteActivitiesById(activityIds) {
-    return new Promise(function(resolve, reject) {
+  deleteActivitiesById(activityIds: string[]): Promise<void> {
+    return new Promise(function(resolve) {
       chrome.activityLogPrivate.deleteActivities(activityIds, resolve);
     });
   }
 
   /** @override */
-  deleteActivitiesFromExtension(extensionId) {
-    return new Promise(function(resolve, reject) {
+  deleteActivitiesFromExtension(extensionId: string): Promise<void> {
+    return new Promise(function(resolve) {
       chrome.activityLogPrivate.deleteActivitiesByExtension(
           extensionId, resolve);
     });
@@ -469,7 +467,7 @@
   }
 
   /** @override */
-  downloadActivities(rawActivityData, fileName) {
+  downloadActivities(rawActivityData: string, fileName: string) {
     const blob = new Blob([rawActivityData], {type: 'application/json'});
     const url = URL.createObjectURL(blob);
     const a = document.createElement('a');
@@ -494,16 +492,13 @@
     chrome.developerPrivate.notifyDragInstallInProgress();
   }
 
-  /** @return {!Service} */
-  static getInstance() {
+  static getInstance(): Service {
     return instance || (instance = new Service());
   }
 
-  /** @param {!Service} obj */
-  static setInstance(obj) {
+  static setInstance(obj: Service) {
     instance = obj;
   }
 }
 
-/** @type {?Service} */
-let instance = null;
+let instance: Service|null = null;
diff --git a/chrome/browser/resources/extensions/shared_style.js b/chrome/browser/resources/extensions/shared_style.ts
similarity index 100%
rename from chrome/browser/resources/extensions/shared_style.js
rename to chrome/browser/resources/extensions/shared_style.ts
diff --git a/chrome/browser/resources/extensions/shared_vars.js b/chrome/browser/resources/extensions/shared_vars.ts
similarity index 100%
rename from chrome/browser/resources/extensions/shared_vars.js
rename to chrome/browser/resources/extensions/shared_vars.ts
diff --git a/chrome/browser/resources/extensions/shortcut_util.js b/chrome/browser/resources/extensions/shortcut_util.ts
similarity index 77%
rename from chrome/browser/resources/extensions/shortcut_util.js
rename to chrome/browser/resources/extensions/shortcut_util.ts
index 6c20b5b..fb56f74 100644
--- a/chrome/browser/resources/extensions/shortcut_util.js
+++ b/chrome/browser/resources/extensions/shortcut_util.ts
@@ -6,45 +6,41 @@
 import {isChromeOS, isMac} from 'chrome://resources/js/cr.m.js';
 
 
-/** @enum {number} */
-export const Key = {
-  Comma: 188,
-  Del: 46,
-  Down: 40,
-  End: 35,
-  Escape: 27,
-  Home: 36,
-  Ins: 45,
-  Left: 37,
-  MediaNextTrack: 176,
-  MediaPlayPause: 179,
-  MediaPrevTrack: 177,
-  MediaStop: 178,
-  PageDown: 34,
-  PageUp: 33,
-  Period: 190,
-  Right: 39,
-  Space: 32,
-  Tab: 9,
-  Up: 38,
-};
+export enum Key {
+  Comma = 188,
+  Del = 46,
+  Down = 40,
+  End = 35,
+  Escape = 27,
+  Home = 36,
+  Ins = 45,
+  Left = 37,
+  MediaNextTrack = 176,
+  MediaPlayPause = 179,
+  MediaPrevTrack = 177,
+  MediaStop = 178,
+  PageDown = 34,
+  PageUp = 33,
+  Period = 190,
+  Right = 39,
+  Space = 32,
+  Tab = 9,
+  Up = 38,
+}
 
 /**
  * Enum for whether we require modifiers of a keycode.
- * @enum {number}
  */
-const ModifierPolicy = {
-  NOT_ALLOWED: 0,
-  REQUIRED: 1
-};
+enum ModifierPolicy {
+  NOT_ALLOWED = 0,
+  REQUIRED = 1
+}
 
 /**
  * Gets the ModifierPolicy. Currently only "MediaNextTrack", "MediaPrevTrack",
  * "MediaStop", "MediaPlayPause" are required to be used without any modifier.
- * @param {number} keyCode
- * @return {ModifierPolicy}
  */
-function getModifierPolicy(keyCode) {
+function getModifierPolicy(keyCode: number): ModifierPolicy {
   switch (keyCode) {
     case Key.MediaNextTrack:
     case Key.MediaPlayPause:
@@ -59,12 +55,11 @@
 /**
  * Returns whether the keyboard event has a key modifier, which could affect
  * how it's handled.
- * @param {!KeyboardEvent} e
- * @param {boolean} countShiftAsModifier Whether the 'Shift' key should be
- *     counted as modifier.
- * @return {boolean} True if the event has any modifiers.
+ * @param countShiftAsModifier Whether the 'Shift' key should be counted as
+ *     modifier.
+ * @return Whether the event has any modifiers.
  */
-function hasModifier(e, countShiftAsModifier) {
+function hasModifier(e: KeyboardEvent, countShiftAsModifier: boolean): boolean {
   return e.ctrlKey || e.altKey ||
       // Meta key is only relevant on Mac and CrOS, where we treat Command
       // and Search (respectively) as modifiers.
@@ -74,15 +69,14 @@
 
 /**
  * Checks whether the passed in |keyCode| is a valid extension command key.
- * @param {number} keyCode
- * @return {boolean} Whether the key is valid.
+ * @return Whether the key is valid.
  */
-export function isValidKeyCode(keyCode) {
+export function isValidKeyCode(keyCode: number): boolean {
   if (keyCode === Key.Escape) {
     return false;
   }
   for (const k in Key) {
-    if (Key[k] === keyCode) {
+    if (Key[k as keyof typeof Key] === keyCode) {
       return true;
     }
   }
@@ -93,10 +87,8 @@
 /**
  * Converts a keystroke event to string form, ignoring invalid extension
  * commands.
- * @param {!KeyboardEvent} e
- * @return {string} The keystroke as a string.
  */
-export function keystrokeToString(e) {
+export function keystrokeToString(e: KeyboardEvent): string {
   const output = [];
   // TODO(devlin): Should this be i18n'd?
   if (isMac && e.metaKey) {
@@ -185,10 +177,10 @@
 
 /**
  * Returns true if the event has valid modifiers.
- * @param {!KeyboardEvent} e The keyboard event to consider.
- * @return {boolean} True if the event is valid.
+ * @param e The keyboard event to consider.
+ * @return Wether the event is valid.
  */
-export function hasValidModifiers(e) {
+export function hasValidModifiers(e: KeyboardEvent): boolean {
   switch (getModifierPolicy(e.keyCode)) {
     case ModifierPolicy.REQUIRED:
       return hasModifier(e, false);
diff --git a/chrome/browser/resources/new_tab_page/modules/modules.html b/chrome/browser/resources/new_tab_page/modules/modules.html
index bedde0a..c37b569 100644
--- a/chrome/browser/resources/new_tab_page/modules/modules.html
+++ b/chrome/browser/resources/new_tab_page/modules/modules.html
@@ -10,6 +10,10 @@
   ntp-module-wrapper + ntp-module-wrapper {
     margin-top: 16px;
   }
+
+  #removeModuleToastMessage {
+    flex-grow: 1;
+  }
 </style>
 <template is="dom-repeat" items="[[modules_]]" id="modules"
     on-dom-change="onModulesLoaded_">
diff --git a/chrome/browser/resources/pdf/viewport.js b/chrome/browser/resources/pdf/viewport.js
index 09774dc..e73048a 100644
--- a/chrome/browser/resources/pdf/viewport.js
+++ b/chrome/browser/resources/pdf/viewport.js
@@ -570,7 +570,7 @@
    * Sets the zoom of the viewport.
    * Same as setZoomInternal_ but for pinch zoom we have some more operations.
    * @param {number} scaleDelta The zoom delta.
-   * @param {!Point} center The pinch center in content coordinates.
+   * @param {!Point} center The pinch center in plugin coordinates.
    * @private
    */
   setPinchZoomInternal_(scaleDelta, center) {
@@ -580,11 +580,10 @@
             'Viewport.mightZoom_.');
     this.internalZoom_ = this.clampZoom_(this.internalZoom_ * scaleDelta);
 
-    const newCenterInContent = this.frameToContent_(center);
-    const delta = {
-      x: (newCenterInContent.x - this.oldCenterInContent_.x),
-      y: (newCenterInContent.y - this.oldCenterInContent_.y)
-    };
+    assert(this.oldCenterInContent_);
+    const delta = vectorDelta(
+        /** @type {!Point} */ (this.oldCenterInContent_),
+        this.pluginToContent_(center));
 
     // Record the scroll position (relative to the pinch center).
     const zoom = this.getZoom();
@@ -599,18 +598,18 @@
   }
 
   /**
-   *  Converts a point from frame to content coordinates.
-   *  @param {!Point} framePoint The frame coordinates.
+   *  Converts a point from plugin to content coordinates.
+   *  @param {!Point} pluginPoint The plugin coordinates.
    *  @return {!Point} The content coordinates.
    *  @private
    */
-  frameToContent_(framePoint) {
+  pluginToContent_(pluginPoint) {
     // TODO(mcnee) Add a helper Point class to avoid duplicating operations
     // on plain {x,y} objects.
     const zoom = this.getZoom();
     return {
-      x: (framePoint.x + this.position.x) / zoom,
-      y: (framePoint.y + this.position.y) / zoom
+      x: (pluginPoint.x + this.position.x) / zoom,
+      y: (pluginPoint.y + this.position.y) / zoom
     };
   }
 
@@ -1425,7 +1424,7 @@
             y: this.window_.offsetHeight / 2
           };
         } else if (this.keepContentCentered_) {
-          this.oldCenterInContent_ = this.frameToContent_(this.pinchCenter_);
+          this.oldCenterInContent_ = this.pluginToContent_(this.pinchCenter_);
           this.keepContentCentered_ = false;
         }
 
@@ -1481,7 +1480,7 @@
       this.pinchPhase_ = PinchPhase.START;
       this.prevScale_ = 1;
       this.oldCenterInContent_ =
-          this.frameToContent_(this.frameToPluginCoordinate_(e.detail.center));
+          this.pluginToContent_(this.frameToPluginCoordinate_(e.detail.center));
 
       const needsScrollbars = this.documentNeedsScrollbars(this.getZoom());
       this.keepContentCentered_ = !needsScrollbars.horizontal;
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog_cros.html b/chrome/browser/resources/print_preview/ui/destination_dialog_cros.html
index 47f41d2..c94f4cb 100644
--- a/chrome/browser/resources/print_preview/ui/destination_dialog_cros.html
+++ b/chrome/browser/resources/print_preview/ui/destination_dialog_cros.html
@@ -32,8 +32,7 @@
           <option value="">$i18n{addAccountTitle}</option>
         </select>
       </div>
-      <div hidden$="[[!printServerScalingFlagEnabled_]]"
-          class="server-search-box-container">
+      <div class="server-search-box-container">
         <!-- TODO(crbug.com/1013408): Uses deprecated iron-dropdown. -->
         <cr-searchable-drop-down class="server-search-box-input"
             hidden$="[[!isSingleServerFetchingMode_]]"
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog_cros.js b/chrome/browser/resources/print_preview/ui/destination_dialog_cros.js
index 6242ca3..4822040 100644
--- a/chrome/browser/resources/print_preview/ui/destination_dialog_cros.js
+++ b/chrome/browser/resources/print_preview/ui/destination_dialog_cros.js
@@ -105,15 +105,6 @@
       },
     },
 
-    /** @private */
-    printServerScalingFlagEnabled_: {
-      type: Boolean,
-      value() {
-        return loadTimeData.getBoolean('printServerScaling');
-      },
-      readOnly: true,
-    },
-
     /** @private {boolean} */
     loadingServerPrinters_: {
       type: Boolean,
@@ -151,9 +142,6 @@
 
   /** @override */
   ready() {
-    if (!this.printServerScalingFlagEnabled_) {
-      return;
-    }
     this.printServerStore_ = new PrintServerStore(
         (/** string */ eventName, /** !Function */ callback) =>
             void this.addWebUIListener(eventName, callback));
@@ -362,7 +350,7 @@
    * @private
    */
   onPrintServerSelected_(printServerName) {
-    if (!this.printServerScalingFlagEnabled_ || !this.printServerStore_) {
+    if (!this.printServerStore_) {
       return;
     }
     this.printServerStore_.choosePrintServers(printServerName);
diff --git a/chrome/browser/resources/print_preview/ui/destination_list_item.js b/chrome/browser/resources/print_preview/ui/destination_list_item.js
index 7a91ca67..8b5e7df 100644
--- a/chrome/browser/resources/print_preview/ui/destination_list_item.js
+++ b/chrome/browser/resources/print_preview/ui/destination_list_item.js
@@ -96,15 +96,6 @@
       type: Object,
       value: DestinationConfigStatus,
     },
-
-    /** @private */
-    printerStatusFlagEnabled_: {
-      type: Boolean,
-      value() {
-        return loadTimeData.getBoolean('showPrinterStatusInDialog');
-      },
-      readOnly: true,
-    },
     // </if>
   },
 
@@ -209,8 +200,7 @@
     }
 
     // <if expr="chromeos or lacros">
-    if (this.printerStatusFlagEnabled_ &&
-        this.destination.origin === DestinationOrigin.CROS) {
+    if (this.destination.origin === DestinationOrigin.CROS) {
       // Don't show status text when destination is configuring.
       if (this.configurationStatus_ !== DestinationConfigStatus.IDLE) {
         return '';
@@ -243,8 +233,7 @@
     }
 
     // <if expr="chromeos or lacros">
-    if (this.printerStatusFlagEnabled_ &&
-        this.destination.origin === DestinationOrigin.CROS) {
+    if (this.destination.origin === DestinationOrigin.CROS) {
       return getPrinterStatusIcon(
           this.destination.printerStatusReason,
           this.destination.isEnterprisePrinter);
@@ -261,20 +250,12 @@
    * @private
    */
   computeIsDestinationCrosLocal_: function() {
-    if (!this.printerStatusFlagEnabled_) {
-      return false;
-    }
-
     return this.destination &&
         this.destination.origin === DestinationOrigin.CROS;
   },
 
   /** @private */
   requestPrinterStatus_() {
-    if (!this.printerStatusFlagEnabled_) {
-      return;
-    }
-
     // Requesting printer status only allowed for local CrOS printers.
     if (this.destination.origin !== DestinationOrigin.CROS) {
       return;
diff --git a/chrome/browser/resources/read_later/side_panel/bookmark_folder.html b/chrome/browser/resources/read_later/side_panel/bookmark_folder.html
index 680db7f..1a8f441 100644
--- a/chrome/browser/resources/read_later/side_panel/bookmark_folder.html
+++ b/chrome/browser/resources/read_later/side_panel/bookmark_folder.html
@@ -56,7 +56,9 @@
 
   .title {
     grid-area: title;
+    overflow: hidden;
     padding: 0 10px;
+    text-overflow: ellipsis;
   }
 
   .bookmark {
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/cellular_setup_dialog.html b/chrome/browser/resources/settings/chromeos/internet_page/cellular_setup_dialog.html
index 117bdbe..68d73dc 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/cellular_setup_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/internet_page/cellular_setup_dialog.html
@@ -25,6 +25,7 @@
       }
 
       :host {
+        --cr-dialog-body-padding-horizontal: 24px;
         --cr-dialog-title-slot-padding-bottom: 0;
         --cr-dialog-title-slot-padding-end: 0;
         --cr-dialog-title-slot-padding-start: 0;
@@ -32,10 +33,10 @@
       }
 
       #header {
-        padding-bottom: 16px;
-        padding-inline-end:  20px;
-        padding-inline-start: 20px;
-        padding-top: 20px;
+        padding-bottom: 8px;
+        padding-inline-end:  24px;
+        padding-inline-start: 24px;
+        padding-top: 24px;
       }
 
       #title {
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.js b/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.js
index d52d266..6c13a2e 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/network_summary_item.js
@@ -225,10 +225,13 @@
     }
 
     const {pSimSlots, eSimSlots} = getSimSlotCount(deviceState);
-    if (this.isUpdatedCellularUiEnabled_ && eSimSlots > 0) {
-      // Do not show simInfo if |updatedCellularActivationUi| flag is enabled
-      // and if we are using an eSIM enabled device.
-      return false;
+    if (this.isUpdatedCellularUiEnabled_) {
+      if (eSimSlots > 0) {
+        // Do not show simInfo if |updatedCellularActivationUi| flag is enabled
+        // and if we are using an eSIM enabled device.
+        return false;
+      }
+      return this.simLocked_(deviceState);
     }
     return this.simLockedOrAbsent_(deviceState);
   },
@@ -277,6 +280,18 @@
     if (deviceState.simAbsent) {
       return true;
     }
+    return this.simLocked_(deviceState);
+  },
+
+  /**
+   * @param {!OncMojo.DeviceStateProperties|undefined} deviceState
+   * @return {boolean}
+   * @private
+   */
+  simLocked_(deviceState) {
+    if (!deviceState) {
+      return false;
+    }
     if (!deviceState.simLockStatus) {
       return false;
     }
@@ -314,16 +329,21 @@
       case mojom.NetworkType.kEthernet:
       case mojom.NetworkType.kVPN:
         return false;
+
       case mojom.NetworkType.kTether:
         return true;
+
       case mojom.NetworkType.kWiFi:
         return deviceState.deviceState !== mojom.DeviceStateType.kUninitialized;
+
       case mojom.NetworkType.kCellular:
-        return (deviceState.deviceState !==
-                    mojom.DeviceStateType.kUninitialized &&
-                !this.simLockedOrAbsent_(deviceState)) ||
-            (this.isUpdatedCellularUiEnabled_ &&
-             this.simLockedOrAbsent_(deviceState));
+        if (deviceState.deviceState === mojom.DeviceStateType.kUninitialized) {
+          return false;
+        }
+
+        // Toggle should be shown as long as we are not also showing the UI for
+        // unlocking the SIM.
+        return !this.showSimInfo_(deviceState);
     }
     assertNotReached();
     return false;
diff --git a/chrome/browser/resources/tab_search/BUILD.gn b/chrome/browser/resources/tab_search/BUILD.gn
index 90c3c1b..a9e6db9 100644
--- a/chrome/browser/resources/tab_search/BUILD.gn
+++ b/chrome/browser/resources/tab_search/BUILD.gn
@@ -16,6 +16,7 @@
 preprocess_web_components_manifest = "preprocessed_gen_manifest.json"
 preprocess_fuse_manifest = "preprocessed_fuse_manifest.json"
 preprocess_mojo_manifest = "preprocessed_mojo_manifest.json"
+preprocess_mojo_tab_groups_manifest = "preprocess_mojo_tab_groups.json"
 
 if (optimize_webui) {
   build_manifest = "build_manifest.json"
@@ -31,12 +32,14 @@
       "chrome://resources/js/cr.m.js",
       "chrome://resources/mojo/mojo/public/js/bindings.js",
       "chrome://resources/mojo/mojo/public/mojom/base/time.mojom-webui.js",
+      "chrome://resources/mojo/mojo/public/mojom/base/token.mojom-webui.js",
       "fuse.js",
     ]
 
     deps = [
       ":preprocess",
       ":preprocess_mojo",
+      ":preprocess_mojo_tab_groups",
       ":preprocess_web_components",
       "../../../../ui/webui/resources:preprocess",
     ]
@@ -65,12 +68,14 @@
       ":preprocess",
       ":preprocess_fuse",
       ":preprocess_mojo",
+      ":preprocess_mojo_tab_groups",
       ":preprocess_web_components",
     ]
     manifest_files = [
       "$target_gen_dir/$preprocess_manifest",
       "$target_gen_dir/$preprocess_fuse_manifest",
       "$target_gen_dir/$preprocess_mojo_manifest",
+      "$target_gen_dir/$preprocess_mojo_tab_groups_manifest",
       "$target_gen_dir/$preprocess_web_components_manifest",
     ]
   }
@@ -84,6 +89,7 @@
     "bimap.js",
     "fuzzy_search.js",
     "tab_data.js",
+    "tab_group_color_helper.js",
     "tab_search.js",
     "tab_search_api_proxy.js",
     "title_item.js",
@@ -98,6 +104,7 @@
   in_files = [
     "app.js",
     "infinite_list.js",
+    "tab_group_shared_vars.js",
     "tab_search_item.js",
     "tab_search_search_field.js",
   ]
@@ -110,6 +117,14 @@
   in_files = [ "fuse.basic.esm.min.js" ]
 }
 
+preprocess_if_expr("preprocess_mojo_tab_groups") {
+  deps = [ "//components/tab_groups/public/mojom:mojo_bindings_webui_js" ]
+  in_folder = "$root_gen_dir/mojom-webui/components/tab_groups/public/mojom/"
+  out_folder = "$target_gen_dir/$preprocess_folder"
+  out_manifest = "$target_gen_dir/$preprocess_mojo_tab_groups_manifest"
+  in_files = [ "tab_group_types.mojom-webui.js" ]
+}
+
 preprocess_if_expr("preprocess_mojo") {
   deps = [ "//chrome/browser/ui/webui/tab_search:mojo_bindings_webui_js" ]
   in_folder = "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/tab_search/"
@@ -143,6 +158,9 @@
         "js_module_root=" + rebase_path(
                 "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/tab_search",
                 root_build_dir),
+        "js_module_root=" + rebase_path(
+                "$root_gen_dir/mojom-webui/components/tab_groups/public/mojom",
+                root_build_dir),
       ]
   deps = [
     ":app",
@@ -199,6 +217,10 @@
   deps = []
 }
 
+js_library("tab_group_color_helper") {
+  deps = [ "//components/tab_groups/public/mojom:mojo_bindings_webui_js" ]
+}
+
 js_library("tab_search") {
   deps = [ ":app" ]
 }
@@ -214,6 +236,7 @@
 js_library("tab_search_item") {
   deps = [
     ":tab_data",
+    ":tab_group_color_helper",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements:mouse_hoverable_mixin",
     "//ui/webui/resources/cr_elements/cr_icon_button:cr_icon_button.m",
@@ -237,6 +260,7 @@
   js_files = [
     "app.js",
     "infinite_list.js",
+    "tab_group_shared_vars.js",
     "tab_search_item.js",
     "tab_search_search_field.js",
   ]
diff --git a/chrome/browser/resources/tab_search/app.js b/chrome/browser/resources/tab_search/app.js
index 31cbdc3..3360330d 100644
--- a/chrome/browser/resources/tab_search/app.js
+++ b/chrome/browser/resources/tab_search/app.js
@@ -20,8 +20,8 @@
 
 import {fuzzySearch} from './fuzzy_search.js';
 import {InfiniteList, NO_SELECTION, selectorNavigationKeys} from './infinite_list.js';
-import {ariaLabel, TabData, TabItemType} from './tab_data.js';
-import {Tab, Window} from './tab_search.mojom-webui.js';
+import {ariaLabel, TabData, TabItemType, tokenToString} from './tab_data.js';
+import {Tab, TabGroup, Window} from './tab_search.mojom-webui.js';
 import {TabSearchApiProxy, TabSearchApiProxyImpl} from './tab_search_api_proxy.js';
 import {TitleItem} from './title_item.js';
 
@@ -126,6 +126,9 @@
     /** @private {!Array<number>} */
     this.listenerIds_ = [];
 
+    /** @private {!Map<string, !TabGroup>} */
+    this.tabGroupsMap_ = new Map();
+
     /** @private {!Function} */
     this.visibilityChangedListener_ = () => {
       // Refresh Tab Search's tab data when transitioning into a visible state.
@@ -181,7 +184,8 @@
     this.listenerIds_.push(
         callbackRouter.tabsChanged.addListener(
             profileData => this.tabsChanged_(
-                profileData.windows, profileData.recentlyClosedTabs)),
+                profileData.windows, profileData.recentlyClosedTabs,
+                profileData.tabGroups)),
         callbackRouter.tabUpdated.addListener(tab => this.onTabUpdated_(tab)),
         callbackRouter.tabsRemoved.addListener(
             tabIds => this.onTabsRemoved_(tabIds)));
@@ -256,7 +260,9 @@
       });
 
       this.availableHeight_ = profileData.windows.find((t) => t.active).height;
-      this.tabsChanged_(profileData.windows, profileData.recentlyClosedTabs);
+      this.tabsChanged_(
+          profileData.windows, profileData.recentlyClosedTabs,
+          profileData.tabGroups);
     });
   }
 
@@ -269,7 +275,8 @@
     for (let i = 0; i < this.openTabs_.length; ++i) {
       if (this.openTabs_[i].tab.tabId === updatedTab.tabId) {
         this.openTabs_[i] = this.tabData_(
-            updatedTab, this.openTabs_[i].inActiveWindow, TabItemType.OPEN);
+            updatedTab, this.openTabs_[i].inActiveWindow, TabItemType.OPEN,
+            this.tabGroupsMap_);
         this.updateFilteredTabs_(this.openTabs_, this.recentlyClosedTabs_);
         return;
       }
@@ -408,15 +415,22 @@
   /**
    * @param {!Array<!Window>} newOpenWindows
    * @param {!Array<!Tab>} recentlyClosedTabs
+   * @param {!Array<!TabGroup>} tabGroups
    * @private
    */
-  tabsChanged_(newOpenWindows, recentlyClosedTabs) {
+  tabsChanged_(newOpenWindows, recentlyClosedTabs, tabGroups) {
+    this.tabGroupsMap_ = tabGroups.reduce((map, tabGroup) => {
+      map.set(tokenToString(tabGroup.id), tabGroup);
+      return map;
+    }, new Map());
     this.openTabs_ = newOpenWindows.reduce(
-        (acc, {active, tabs}) => acc.concat(
-            tabs.map(tab => this.tabData_(tab, active, TabItemType.OPEN))),
+        (acc, {active, tabs}) => acc.concat(tabs.map(
+            tab => this.tabData_(
+                tab, active, TabItemType.OPEN, this.tabGroupsMap_))),
         []);
     this.recentlyClosedTabs_ = recentlyClosedTabs.map(
-        tab => this.tabData_(tab, false, TabItemType.RECENTLY_CLOSED));
+        tab => this.tabData_(
+            tab, false, TabItemType.RECENTLY_CLOSED, this.tabGroupsMap_));
     this.updateFilteredTabs_(this.openTabs_, this.recentlyClosedTabs_);
 
     // If there was no previously selected index, set the first item as
@@ -556,10 +570,11 @@
    * @param {!Tab} tab
    * @param {boolean} inActiveWindow
    * @param {!TabItemType} type
+   * @param {!Map<string, !TabGroup>} tabGroupsMap
    * @return {!TabData}
    * @private
    */
-  tabData_(tab, inActiveWindow, type) {
+  tabData_(tab, inActiveWindow, type, tabGroupsMap) {
     const tabData = new TabData();
     try {
       tabData.hostname = new URL(tab.url).hostname;
@@ -570,6 +585,11 @@
     }
     tabData.inActiveWindow = inActiveWindow;
     tabData.tab = tab;
+
+    if (tab.groupId) {
+      tabData.tabGroup = tabGroupsMap.get(tokenToString(tab.groupId));
+    }
+
     tabData.type = type;
     tabData.a11yTypeText = loadTimeData.getStringF(
         type === TabItemType.OPEN ? 'a11yOpenTab' : 'a11yRecentlyClosedTab');
diff --git a/chrome/browser/resources/tab_search/fuzzy_search.js b/chrome/browser/resources/tab_search/fuzzy_search.js
index 4f6c3b4..72c385a 100644
--- a/chrome/browser/resources/tab_search/fuzzy_search.js
+++ b/chrome/browser/resources/tab_search/fuzzy_search.js
@@ -7,6 +7,9 @@
 import Fuse from './fuse.js';
 import {TabData} from './tab_data.js';
 
+// TODO(crbug.com/215325): Add support for fuzzy search matching on tab group
+// title.
+
 /**
  * @param {string} input
  * @param {!Array<!TabData>} records
diff --git a/chrome/browser/resources/tab_search/tab_data.js b/chrome/browser/resources/tab_search/tab_data.js
index 410bb38..256c8dc 100644
--- a/chrome/browser/resources/tab_search/tab_data.js
+++ b/chrome/browser/resources/tab_search/tab_data.js
@@ -2,7 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {Tab} from './tab_search.mojom-webui.js';
+import {Token} from 'chrome://resources/mojo/mojo/public/mojom/base/token.mojom-webui.js';
+
+import {Tab, TabGroup} from './tab_search.mojom-webui.js';
 
 /** @enum {number} */
 export const TabItemType = {
@@ -37,14 +39,28 @@
 
     /** @type {string} */
     this.a11yTypeText;
+
+    /** @type {?TabGroup} */
+    this.tabGroup;
   }
 }
 
 /**
+ * Converts a token to a string by combining the high and low values as strings
+ * with a hashtag as the separator.
+ * @param {!Token} token
+ * @return {string}
+ */
+export function tokenToString(token) {
+  return `${token.high.toString()}#${token.low.toString()}`;
+}
+
+/**
  * @param {!TabData} tabData
  * @return {string}
  */
 export function ariaLabel(tabData) {
-  return `${tabData.tab.title} ${tabData.hostname} ${
+  const groupTitleOrEmpty = tabData.tabGroup ? tabData.tabGroup.title : '';
+  return `${tabData.tab.title} ${groupTitleOrEmpty} ${tabData.hostname} ${
       tabData.tab.lastActiveElapsedText} ${tabData.a11yTypeText}`;
 }
diff --git a/chrome/browser/resources/tab_search/tab_group_color_helper.js b/chrome/browser/resources/tab_search/tab_group_color_helper.js
new file mode 100644
index 0000000..a880bd6a
--- /dev/null
+++ b/chrome/browser/resources/tab_search/tab_group_color_helper.js
@@ -0,0 +1,32 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import './tab_group_shared_vars.js';
+
+import {Color} from './tab_group_types.mojom-webui.js';
+
+/** @type Map<Color, string> */
+const colorMap = new Map([
+  [Color.kGrey, 'grey'],
+  [Color.kBlue, 'blue'],
+  [Color.kRed, 'red'],
+  [Color.kYellow, 'yellow'],
+  [Color.kGreen, 'green'],
+  [Color.kPink, 'pink'],
+  [Color.kPurple, 'purple'],
+  [Color.kCyan, 'cyan'],
+]);
+
+/**
+ * @param {Color} color
+ * @return {string}
+ * @throws {Error}
+ */
+export function colorName(color) {
+  if (!colorMap.has(color)) {
+    throw Error('Undefined color id');
+  }
+
+  return colorMap.get(color);
+}
diff --git a/chrome/browser/resources/tab_search/tab_group_shared_vars.html b/chrome/browser/resources/tab_search/tab_group_shared_vars.html
new file mode 100644
index 0000000..9f09068
--- /dev/null
+++ b/chrome/browser/resources/tab_search/tab_group_shared_vars.html
@@ -0,0 +1,61 @@
+<custom-style>
+  <style>
+    /*
+     * TODO(romanarora): Move to cr_elements folder when another component needs
+     * to reference the file.
+     */
+
+    /* Matches colors specified in ThemeHelper::GetTabGroupColors. */
+    html {
+      /* TODO(romanarora): Consider moving colors to shared_vars_css.html. Only
+       * adds colors not already present in shared_vars_css.html.
+       */
+      --google-blue-300-rgb: 123, 170, 247;
+      --google-blue-300: rgb(var(--google-blue-300-rgb));
+      --google-cyan-300-rgb: 120, 217, 236; /* #78d9ec */
+      --google-cyan-300: rgb(var(--google-cyan-300-rgb));
+      --google-cyan-900-rgb: 0, 123, 131; /* #007b83 */
+      --google-cyan-900: rgb(var(--google-cyan-900-rgb));
+      --google-green-300-rgb: 87, 187, 138;  /* #57bb8a */
+      --google-green-300: rgb(var(--google-green-300-rgb));
+      --google-green-600-rgb: 30, 142, 62; /* #1e8e3e */
+      --google-green-600: rgb(var(--google-green-600-rgb));
+      --google-pink-300-rgb: 255, 139, 203; /* #ff8bcb */
+      --google-pink-300: rgb(var(--google-pink-300-rgb));
+      --google-pink-700-rgb: 208, 24, 132; /* #d01884 */
+      --google-pink-700: rgb(var(--google-pink-700-rgb));
+      --google-purple-200-rgb: 215, 174, 251; /* #d7aefb */
+      --google-purple-200: rgb(var(--google-purple-200-rgb));
+      --google-purple-600-rgb: 147, 52, 230; /* #9334e6 */
+      --google-purple-600: rgb(var(--google-purple-600-rgb));
+      --google-red-300-rgb: 230, 124, 115;  /* #e67c73 */
+      --google-red-300: rgb(var(--google-red-300-rgb));
+      --google-yellow-300-rgb: 247, 203, 77;  /* #f7cb4d */
+      --google-yellow-300: rgb(var(--google-yellow-300-rgb));
+      --google-yellow-900-rgb: 227, 116, 0; /* #e37400 */
+      --google-yellow-900: rgb(var(--google-yellow-900-rgb));
+
+      --tab-group-color-grey: var(--google-grey-700);
+      --tab-group-color-blue: var(--google-blue-600);
+      --tab-group-color-red: var(--google-red-600);
+      --tab-group-color-yellow: var(--google-yellow-900);
+      --tab-group-color-green: var(--google-green-600);
+      --tab-group-color-pink: var(--google-pink-700);
+      --tab-group-color-purple: var(--google-purple-600);
+      --tab-group-color-cyan: var(--google-cyan-900);
+    }
+
+    @media (prefers-color-scheme: dark) {
+      html {
+        --tab-group-color-grey: var(--google-grey-400);
+        --tab-group-color-blue: var(--google-blue-300);
+        --tab-group-color-red: var(--google-red-300);
+        --tab-group-color-yellow: var(--google-yellow-300);
+        --tab-group-color-green: var(--google-green-300);
+        --tab-group-color-pink: var(--google-pink-300);
+        --tab-group-color-purple: var(--google-purple-200);
+        --tab-group-color-cyan: var(--google-cyan-300);
+      }
+    }
+  </style>
+</custom-style>
diff --git a/chrome/browser/resources/tab_search/tab_group_shared_vars.js b/chrome/browser/resources/tab_search/tab_group_shared_vars.js
new file mode 100644
index 0000000..2bd30f5
--- /dev/null
+++ b/chrome/browser/resources/tab_search/tab_group_shared_vars.js
@@ -0,0 +1,7 @@
+// 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.
+
+const $_documentContainer = document.createElement('template');
+$_documentContainer.innerHTML = `{__html_template__}`;
+document.head.appendChild($_documentContainer.content);
diff --git a/chrome/browser/resources/tab_search/tab_search.js b/chrome/browser/resources/tab_search/tab_search.js
index ea25330..6a3289a 100644
--- a/chrome/browser/resources/tab_search/tab_search.js
+++ b/chrome/browser/resources/tab_search/tab_search.js
@@ -9,7 +9,8 @@
 export {fuzzySearch} from './fuzzy_search.js';
 export {InfiniteList} from './infinite_list.js';
 export {TabData, TabItemType} from './tab_data.js';
-export {PageCallbackRouter, PageRemote, ProfileData, Tab} from './tab_search.mojom-webui.js';
+export {Color as TabGroupColor} from './tab_group_types.mojom-webui.js';
+export {PageCallbackRouter, PageRemote, ProfileData, Tab, TabGroup} from './tab_search.mojom-webui.js';
 export {TabSearchApiProxy, TabSearchApiProxyImpl} from './tab_search_api_proxy.js';
 export {TabSearchItem} from './tab_search_item.js';
 export {TabSearchSearchField} from './tab_search_search_field.js';
diff --git a/chrome/browser/resources/tab_search/tab_search_item.html b/chrome/browser/resources/tab_search/tab_search_item.html
index 7c32528..7b978d9 100644
--- a/chrome/browser/resources/tab_search/tab_search_item.html
+++ b/chrome/browser/resources/tab_search/tab_search_item.html
@@ -45,6 +45,7 @@
     user-select: none;
   }
 
+  #groupTitle,
   #primaryText,
   #secondaryText {
     overflow: hidden;
@@ -59,6 +60,7 @@
   }
 
   #secondaryContainer {
+    align-items: center;
     color: var(--cr-secondary-text-color);
     display: flex;
     font-size: var(--mwb-secondary-text-font-size);
@@ -74,7 +76,7 @@
     position: fixed;
   }
 
-  #separator {
+  .separator {
     margin-inline-end: 4px;
     margin-inline-start: 4px;
   }
@@ -89,6 +91,17 @@
   .search-highlight-hit {
     font-weight: bold;
   }
+
+  #groupSvg {
+    flex-shrink: 0;
+    height: 8px;
+    margin-inline-end: 6px;
+    width: 8px;
+  }
+
+  #groupDot {
+    fill: var(--group-dot-color);
+  }
 </style>
 
 <div class="favicon" style="background-image:[[faviconUrl_(data.tab)]]"></div>
@@ -96,8 +109,16 @@
 <div class="text-container" aria-hidden="true">
   <div id="primaryText" title="[[data.tab.title]]"></div>
   <div id="secondaryContainer">
+    <template is="dom-if" if="[[data.tabGroup]]">
+      <svg id="groupSvg" viewBox="-5 -5 10 10"
+          xmlns="http://www.w3.org/2000/svg">
+        <circle id= "groupDot" cx="0" cy="0" r="4">
+      </svg>
+      <div id="groupTitle">[[data.tabGroup.title]]</div>
+      <div class="separator">•</div>
+    </template>
     <div id="secondaryText"></div>
-    <div id="separator" hidden="[[!data.hostname]]">•</div>
+    <div class="separator" hidden="[[!data.hostname]]">•</div>
     <div id="secondaryTimestamp">
       [[data.tab.lastActiveElapsedText]]
     </div>
diff --git a/chrome/browser/resources/tab_search/tab_search_item.js b/chrome/browser/resources/tab_search/tab_search_item.js
index bdb93fd..a2c9a4c 100644
--- a/chrome/browser/resources/tab_search/tab_search_item.js
+++ b/chrome/browser/resources/tab_search/tab_search_item.js
@@ -14,8 +14,8 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {highlight} from 'chrome://resources/js/search_highlight_utils.js';
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
 import {ariaLabel, TabData, TabItemType} from './tab_data.js';
+import {colorName} from './tab_group_color_helper.js';
 import {Tab} from './tab_search.mojom-webui.js';
 
 /**
@@ -84,9 +84,7 @@
             tab.isDefaultFavicon ? 'chrome://newtab' : tab.url, false);
   }
 
-  /**
-   * @private
-   */
+  /** @private */
   dataChanged_() {
     this.highlightText_(
         /** @type {!HTMLElement} */ (this.$.primaryText), this.data.tab.title,
@@ -110,13 +108,19 @@
           .prepend(document.createTextNode('chrome://'));
       secondaryLabel = `chrome://${secondaryLabel}`;
     }
+
+    if (this.data.tabGroup) {
+      this.style.setProperty(
+          '--group-dot-color',
+          `var(--tab-group-color-${colorName(this.data.tabGroup.color)})`);
+    }
   }
 
   /**
-   *
    * @param {!HTMLElement} container
    * @param {string} text
    * @param {!Array<!{start:number, length:number}>|undefined} ranges
+   * @private
    */
   highlightText_(container, text, ranges) {
     container.textContent = '';
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc
index 6e14228..ebf1754 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc
@@ -218,9 +218,12 @@
 
   // Reports the SwReporter run time with UMA both as reported by the tool via
   // the registry and as measured by |ReporterRunner|.
-  void ReportRuntime(const base::TimeDelta& reporter_running_time) const {
+  void ReportRuntime(const base::TimeDelta& reporter_running_time,
+                     const base::TimeDelta& running_time_without_sleep) const {
     RecordLongTimesHistogram("SoftwareReporter.RunningTimeAccordingToChrome",
                              reporter_running_time);
+    RecordLongTimesHistogram("SoftwareReporter.RunningTimeWithoutSleep",
+                             running_time_without_sleep);
 
     // TODO(b/641081): This should only have KEY_QUERY_VALUE and KEY_SET_VALUE.
     base::win::RegKey reporter_key;
@@ -468,6 +471,10 @@
   }
 
  private:
+  // The type returned by QueryUnbiasedInterruptTime, which does not include
+  // time spent in sleep or hibernation.
+  using TimestampWithoutSleep = ULONGLONG;
+
   // Keeps track of last and upcoming reporter runs and logs uploading.
   //
   // Periodic runs are allowed to start if the last time the reporter ran was
@@ -554,11 +561,15 @@
     base::TaskRunner* task_runner =
         g_testing_delegate_ ? g_testing_delegate_->BlockingTaskRunner()
                             : blocking_task_runner_.get();
+
+    TimestampWithoutSleep now_without_sleep;
+    ::QueryUnbiasedInterruptTime(&now_without_sleep);
+
     auto launch_and_wait =
         base::BindOnce(&LaunchAndWaitForExit, next_invocation);
     auto reporter_done =
         base::BindOnce(&ReporterRunner::ReporterDone, base::Unretained(this),
-                       Now(), next_invocation);
+                       Now(), now_without_sleep, next_invocation);
     base::PostTaskAndReplyWithResult(task_runner, FROM_HERE,
                                      std::move(launch_and_wait),
                                      std::move(reporter_done));
@@ -568,6 +579,7 @@
   // has completed. This is run as a task posted from an interruptible worker
   // thread so should be resilient to unexpected shutdown.
   void ReporterDone(const base::Time& reporter_start_time,
+                    TimestampWithoutSleep start_time_without_sleep,
                     SwReporterInvocation finished_invocation,
                     int exit_code) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -590,9 +602,18 @@
       return;
     }
 
+    TimestampWithoutSleep now_without_sleep;
+    ::QueryUnbiasedInterruptTime(&now_without_sleep);
+
     base::Time now = Now();
     base::TimeDelta reporter_running_time = now - reporter_start_time;
 
+    // QueryUnbiasedInterruptTime returns units of 100 nanoseconds. See
+    // https://docs.microsoft.com/en-us/windows/win32/api/realtimeapiset/nf-realtimeapiset-queryunbiasedinterrupttime
+    base::TimeDelta running_time_without_sleep =
+        base::TimeDelta::FromNanoseconds(
+            100 * (now_without_sleep - start_time_without_sleep));
+
     // Tries to run the next invocation in the queue.
     if (!invocations_.container().empty()) {
       // If there are other invocations to start, then we shouldn't finalize
@@ -616,7 +637,7 @@
       local_state->SetInt64(prefs::kSwReporterLastTimeTriggered,
                             now.ToInternalValue());
     }
-    uma.ReportRuntime(reporter_running_time);
+    uma.ReportRuntime(reporter_running_time, running_time_without_sleep);
     uma.ReportMemoryUsage();
     if (finished_invocation.reporter_logs_upload_enabled())
       uma.RecordLogsUploadResult();
diff --git a/chrome/browser/sessions/session_restore_browsertest_chromeos.cc b/chrome/browser/sessions/session_restore_browsertest_chromeos.cc
index 78a18b93..29319ea 100644
--- a/chrome/browser/sessions/session_restore_browsertest_chromeos.cc
+++ b/chrome/browser/sessions/session_restore_browsertest_chromeos.cc
@@ -181,7 +181,7 @@
 
 // Assigns a browser window to all desks.
 IN_PROC_BROWSER_TEST_F(SessionRestoreTestChromeOS,
-                       DISABLED_PRE_RestoreAllDesksBrowserWindow) {
+                       PRE_RestoreAllDesksBrowserWindow) {
   // Create two desks so we have three in total.
   ash::AutotestDesksApi().CreateNewDesk();
   ash::AutotestDesksApi().CreateNewDesk();
@@ -212,7 +212,7 @@
 
 // Verifies that the visible on all desks browser window is restore properly.
 IN_PROC_BROWSER_TEST_F(SessionRestoreTestChromeOS,
-                       DISABLED_RestoreAllDesksBrowserWindow) {
+                       RestoreAllDesksBrowserWindow) {
   // There should be two browsers restored, the default browser and the all
   // desks browser.
   auto* browser_list = BrowserList::GetInstance();
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QrCodeCoordinator.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QrCodeCoordinator.java
index 772c98a..ee9effa 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QrCodeCoordinator.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QrCodeCoordinator.java
@@ -7,6 +7,8 @@
 import android.app.Activity;
 import android.app.FragmentManager;
 
+import org.chromium.ui.base.AndroidPermissionDelegate;
+
 /**
  * Creates and represents the QrCode main UI.
  */
@@ -14,8 +16,15 @@
     private final QrCodeDialog mDialog;
     private final FragmentManager mFragmentManager;
 
-    public QrCodeCoordinator(Activity activity, String url) {
-        mDialog = QrCodeDialog.newInstance(url);
+    /**
+     * Constructor.
+     * @param activity The android activity.
+     * @param url the url to be shared.
+     * @param windowAndroid the AndroidPermissionDelegate to access system permissions.
+     */
+    public QrCodeCoordinator(
+            Activity activity, String url, AndroidPermissionDelegate windowAndroid) {
+        mDialog = QrCodeDialog.newInstance(url, windowAndroid);
 
         mFragmentManager = activity.getFragmentManager();
     }
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QrCodeDialog.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QrCodeDialog.java
index 775bbd62..93a5fd8c 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QrCodeDialog.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/QrCodeDialog.java
@@ -18,6 +18,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.share.qrcode.scan_tab.QrCodeScanCoordinator;
 import org.chromium.chrome.browser.share.qrcode.share_tab.QrCodeShareCoordinator;
+import org.chromium.ui.base.AndroidPermissionDelegate;
 import org.chromium.ui.widget.ChromeImageButton;
 
 import java.util.ArrayList;
@@ -29,17 +30,21 @@
     // Used to pass the URL in the bundle.
     public static String URL_KEY = "url_key";
 
+    private AndroidPermissionDelegate mWindowAndroid;
     private ArrayList<QrCodeDialogTab> mTabs;
     private TabLayoutPageListener mTabLayoutPageListener;
 
     /**
      * Create a new instance of {@link QrCodeDialog} and set the URL.
+     * @param windowAndroid The AndroidPermissionDelegate to be query for download permissions.
      */
-    static QrCodeDialog newInstance(String url) {
+    static QrCodeDialog newInstance(String url, AndroidPermissionDelegate windowAndroid) {
+        assert windowAndroid != null;
         QrCodeDialog qrCodeDialog = new QrCodeDialog();
         Bundle args = new Bundle();
         args.putString(URL_KEY, url);
         qrCodeDialog.setArguments(args);
+        qrCodeDialog.setAndroidPermissionDelegate(windowAndroid);
         return qrCodeDialog;
     }
 
@@ -71,6 +76,15 @@
             tab.onDestroy();
         }
         mTabs.clear();
+        mWindowAndroid = null;
+    }
+
+    /**
+     * Setter for the current WindowAndroid.
+     * @param windowAndroid The windowAndroid to set.
+     */
+    public void setAndroidPermissionDelegate(AndroidPermissionDelegate windowAndroid) {
+        mWindowAndroid = windowAndroid;
     }
 
     private View getDialogView() {
@@ -99,7 +113,7 @@
         Context context = getActivity();
 
         QrCodeShareCoordinator shareCoordinator = new QrCodeShareCoordinator(
-                context, this::dismiss, getArguments().getString(URL_KEY));
+                context, this::dismiss, getArguments().getString(URL_KEY), mWindowAndroid);
         QrCodeScanCoordinator scanCoordinator = new QrCodeScanCoordinator(context, this::dismiss);
 
         mTabs = new ArrayList<>();
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/share_tab/QrCodeShareCoordinator.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/share_tab/QrCodeShareCoordinator.java
index 641f50a..a59d476 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/share_tab/QrCodeShareCoordinator.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/share_tab/QrCodeShareCoordinator.java
@@ -9,6 +9,7 @@
 
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.browser.share.qrcode.QrCodeDialogTab;
+import org.chromium.ui.base.AndroidPermissionDelegate;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
@@ -19,9 +20,11 @@
     private final QrCodeShareView mShareView;
     private final QrCodeShareMediator mMediator;
 
-    public QrCodeShareCoordinator(Context context, Runnable closeDialog, String url) {
+    public QrCodeShareCoordinator(Context context, Runnable closeDialog, String url,
+            AndroidPermissionDelegate windowAndroid) {
         PropertyModel shareViewModel = new PropertyModel(QrCodeShareViewProperties.ALL_KEYS);
-        mMediator = new QrCodeShareMediator(context, shareViewModel, closeDialog, url);
+        mMediator =
+                new QrCodeShareMediator(context, shareViewModel, closeDialog, url, windowAndroid);
         mShareView = new QrCodeShareView(context, mMediator::downloadQrCode);
         PropertyModelChangeProcessor.create(
                 shareViewModel, mShareView, new QrCodeShareViewBinder());
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/share_tab/QrCodeShareMediator.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/share_tab/QrCodeShareMediator.java
index c19de19..f2e0d26 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/share_tab/QrCodeShareMediator.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/qrcode/share_tab/QrCodeShareMediator.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.share.qrcode.share_tab;
 
 import android.Manifest.permission;
-import android.app.Activity;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
@@ -24,12 +23,9 @@
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
 import org.chromium.chrome.browser.share.BitmapDownloadRequest;
 import org.chromium.chrome.browser.share.qrcode.QRCodeGenerationRequest;
-import org.chromium.ui.base.ActivityAndroidPermissionDelegate;
 import org.chromium.ui.base.AndroidPermissionDelegate;
 import org.chromium.ui.modelutil.PropertyModel;
 
-import java.lang.ref.WeakReference;
-
 /**
  * QrCodeShareMediator is in charge of calculating and setting values for QrCodeShareViewProperties.
  */
@@ -53,17 +49,17 @@
      * @param propertyModel The property model to use to communicate with views.
      * @param closeDialog The {@link Runnable} to close the dialog.
      * @param url The url to create the QRCode.
+     * @param permissionDelegate The delegate to help with downloading QRCode.
      */
-    QrCodeShareMediator(
-            Context context, PropertyModel propertyModel, Runnable closeDialog, String url) {
+    QrCodeShareMediator(Context context, PropertyModel propertyModel, Runnable closeDialog,
+            String url, AndroidPermissionDelegate permissionDelegate) {
         mContext = context;
         mPropertyModel = propertyModel;
         mCloseDialog = closeDialog;
         mUrl = url;
         ChromeBrowserInitializer.getInstance().runNowOrAfterFullBrowserStarted(
                 () -> refreshQrCode(mUrl));
-        mPermissionDelegate = new ActivityAndroidPermissionDelegate(
-                new WeakReference<Activity>((Activity) mContext));
+        mPermissionDelegate = permissionDelegate;
         updatePermissionSettings();
     }
 
@@ -105,9 +101,8 @@
         logDownload();
         Bitmap qrcodeBitmap = mPropertyModel.get(QrCodeShareViewProperties.QRCODE_BITMAP);
         if (qrcodeBitmap != null && !mIsDownloadInProgress) {
-            // TODO(crbug/1209228): Pass the window android.
             DownloadController.requestFileAccessPermission(
-                    null, this::finishDownloadWithPermission);
+                    mPermissionDelegate, this::finishDownloadWithPermission);
             return;
         }
     }
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
index 6e13206..d0779d0 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
@@ -410,7 +410,8 @@
                 .setIcon(R.drawable.qr_code, R.string.qr_code_share_icon_label)
                 .setFeatureNameForMetrics("SharingHubAndroid.QRCodeSelected")
                 .setOnClickCallback((view) -> {
-                    QrCodeCoordinator qrCodeCoordinator = new QrCodeCoordinator(mActivity, mUrl);
+                    QrCodeCoordinator qrCodeCoordinator = new QrCodeCoordinator(
+                            mActivity, mUrl, mTabProvider.get().getWindowAndroid());
                     qrCodeCoordinator.show();
                 })
                 .build();
diff --git a/chrome/browser/signin/dice_web_signin_interceptor.cc b/chrome/browser/signin/dice_web_signin_interceptor.cc
index eb582d3..22f7d14f 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor.cc
+++ b/chrome/browser/signin/dice_web_signin_interceptor.cc
@@ -18,7 +18,6 @@
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
-#include "chrome/browser/policy/profile_policy_connector.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
 #include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
@@ -42,16 +41,9 @@
 #include "chrome/common/themes/autogenerated_theme_util.h"
 #include "components/password_manager/core/browser/password_manager.h"
 #include "components/password_manager/core/common/password_manager_ui.h"
-#include "components/policy/core/browser/browser_policy_connector.h"
-#include "components/policy/core/common/policy_map.h"
-#include "components/policy/core/common/policy_namespace.h"
-#include "components/policy/core/common/policy_service.h"
-#include "components/policy/policy_constants.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
-#include "components/signin/public/base/signin_pref_names.h"
-#include "components/signin/public/identity_manager/accounts_mutator.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -182,16 +174,6 @@
     // interception is missed.
     return SigninInterceptionHeuristicOutcome::kAbortSyncSignin;
   }
-
-  // If the email is an not an customer email and enterprise profile separation
-  // is mandatory, return `absl::nullopt` so that more information on the
-  // account is fetched.
-  if (!policy::BrowserPolicyConnector::IsNonEnterpriseUser(email) &&
-      profile_->GetPrefs()->HasPrefPath(
-          prefs::kManagedAccountsSigninRestriction)) {
-    return absl::nullopt;
-  }
-
   if (!is_new_account) {
     // Do not intercept reauth.
     return SigninInterceptionHeuristicOutcome::kAbortAccountNotNew;
@@ -289,7 +271,6 @@
                           &entry);
   account_id_ = account_id;
   is_interception_in_progress_ = true;
-  new_account_interception_ = is_new_account;
   Observe(web_contents);
 
   if (heuristic_outcome) {
@@ -334,13 +315,9 @@
     CoreAccountId account_id,
     content::WebContents* intercepted_contents,
     std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle> bubble_handle,
-    bool managed_account_interception,
     bool is_new_profile) {
   DCHECK(!session_startup_helper_);
-  DCHECK(bubble_handle || managed_account_interception);
-  DCHECK_EQ(managed_account_interception, !bubble_handle)
-      << "There should be no handle for managed accout interception";
-  managed_account_interception_ = managed_account_interception;
+  DCHECK(bubble_handle);
   interception_bubble_handle_ = std::move(bubble_handle);
   session_startup_helper_ =
       std::make_unique<DiceInterceptedSessionStartupHelper>(
@@ -364,9 +341,6 @@
   on_account_info_update_timeout_.Cancel();
   is_interception_in_progress_ = false;
   account_id_ = CoreAccountId();
-  new_account_interception_ = false;
-  managed_account_interception_ = false;
-  intercepted_account_management_accepted_ = false;
   dice_signed_in_profile_creator_.reset();
   was_interception_ui_displayed_ = false;
   account_info_fetch_start_time_ = base::TimeTicks();
@@ -392,34 +366,6 @@
   return nullptr;
 }
 
-bool DiceWebSigninInterceptor::ShouldEnforceEnterpriseProfileSeparation(
-    const AccountInfo& intercepted_account_info) const {
-  DCHECK(intercepted_account_info.IsValid());
-
-  CoreAccountInfo primary_core_account_info =
-      identity_manager_->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
-  // In case of re-auth, do not show the enterprise separation dialog if the
-  // user already consented to enterprise management.
-  if (!new_account_interception_ && primary_core_account_info.account_id ==
-                                        intercepted_account_info.account_id) {
-    return base::FeatureList::IsEnabled(kAccountPoliciesLoadedWithoutSync) &&
-           !profile_->GetPrefs()->GetBoolean(
-               prefs::kUserAcceptedAccountManagement);
-  }
-
-  std::string account_restriction =
-      profile_->GetPrefs()->GetString(prefs::kManagedAccountsSigninRestriction);
-  if ((!account_restriction.empty() &&
-       profile_->GetPrefs()->GetBoolean(
-           prefs::kManagedAccountsSigninRestrictionScopeMachine)) ||
-      account_restriction == "primary_account_strict") {
-    return intercepted_account_info.IsManaged();
-  }
-
-  // TODO(crbug/1163117) Look for the policy value for the intercepted account.
-  return false;
-}
-
 bool DiceWebSigninInterceptor::ShouldShowEnterpriseBubble(
     const AccountInfo& intercepted_account_info) const {
   DCHECK(intercepted_account_info.IsValid());
@@ -478,47 +424,6 @@
 
   absl::optional<SigninInterceptionType> interception_type;
 
-  ProfileAttributesEntry* entry =
-      g_browser_process->profile_manager()
-          ->GetProfileAttributesStorage()
-          .GetProfileAttributesWithPath(profile_->GetPath());
-  SkColor profile_color = GenerateNewProfileColor(entry).color;
-
-  if (ShouldEnforceEnterpriseProfileSeparation(info)) {
-    // TODO (crbug/1163117): Create a new SigninInterceptionType and use
-    // interception_bubble_handle_.
-    DCHECK(base::FeatureList::IsEnabled(kAccountPoliciesLoadedWithoutSync) ||
-           !profile_->GetPrefs()
-                ->GetString(prefs::kManagedAccountsSigninRestriction)
-                .empty());
-    managed_account_interception_ = true;
-    // Immediately switch to an already existing profile.
-    const ProfileAttributesEntry* switch_to_entry =
-        ShouldShowProfileSwitchBubble(info.email,
-                                      &g_browser_process->profile_manager()
-                                           ->GetProfileAttributesStorage());
-    if (switch_to_entry) {
-      // TODO (crbug/1163117): There should be a UI notifying the user of the
-      // profile switch.
-      RecordSigninInterceptionHeuristicOutcome(
-          SigninInterceptionHeuristicOutcome::
-              kInterceptEnterpriseForcedProfileSwitch);
-      OnProfileSwitchChoice(info.email, switch_to_entry->GetPath(),
-                            SigninInterceptionResult::kAccepted);
-    } else {
-      RecordSigninInterceptionHeuristicOutcome(
-          SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);
-      Browser* browser = chrome::FindBrowserWithProfile(profile_);
-      DCHECK(browser);
-      delegate_->ShowEnterpriseProfileInterceptionDialog(
-          info.email,
-          base::BindOnce(
-              &DiceWebSigninInterceptor::OnEnterpriseProfileCreationResult,
-              base::Unretained(this), info, profile_color),
-          browser);
-    }
-    return;
-  }
   if (ShouldShowEnterpriseBubble(info))
     interception_type = SigninInterceptionType::kEnterprise;
   else if (ShouldShowMultiUserBubble(info))
@@ -532,6 +437,11 @@
     return;
   }
 
+  ProfileAttributesEntry* entry =
+      g_browser_process->profile_manager()
+          ->GetProfileAttributesStorage()
+          .GetProfileAttributesWithPath(profile_->GetPath());
+  SkColor profile_color = GenerateNewProfileColor(entry).color;
   auto guest_option_availability = GetGuestOptionAvailablity();
   Delegate::BubbleParameters bubble_parameters{
       *interception_type, info, GetPrimaryAccountInfo(identity_manager_),
@@ -568,7 +478,7 @@
     return;
   }
 
-  DCHECK(interception_bubble_handle_ || managed_account_interception_);
+  DCHECK(interception_bubble_handle_);
   profile_creation_start_time_ = base::TimeTicks::Now();
   std::u16string profile_name;
   profile_name = profiles::GetDefaultNameForNewSignedInProfile(account_info);
@@ -595,7 +505,7 @@
     return;
   }
 
-  DCHECK(interception_bubble_handle_ || managed_account_interception_);
+  DCHECK(interception_bubble_handle_);
   DCHECK(!dice_signed_in_profile_creator_);
   profile_creation_start_time_ = base::TimeTicks::Now();
   // Unretained is fine because the profile creator is owned by this.
@@ -639,49 +549,17 @@
         base::TimeTicks::Now() - profile_creation_start_time_);
   }
 
-  new_profile->GetPrefs()->SetBoolean(prefs::kUserAcceptedAccountManagement,
-                                      intercepted_account_management_accepted_);
-
   // Work is done in this profile, the flow continues in the
   // DiceWebSigninInterceptor that is attached to the new profile.
   DiceWebSigninInterceptorFactory::GetForProfile(new_profile)
       ->CreateBrowserAfterSigninInterception(
           account_id_, web_contents(), std::move(interception_bubble_handle_),
-          managed_account_interception_, is_new_profile);
+          is_new_profile);
   Reset();
 }
 
-void DiceWebSigninInterceptor::OnEnterpriseProfileCreationResult(
-    const AccountInfo& account_info,
-    SkColor profile_color,
-    bool create) {
-  intercepted_account_management_accepted_ = create;
-  if (create) {
-    // In case of a reauth if there was no consent for management, do not create
-    // a new profile.
-    if (!new_account_interception_ &&
-        GetPrimaryAccountInfo(identity_manager_).account_id ==
-            account_info.account_id) {
-      profile_->GetPrefs()->SetBoolean(
-          prefs::kUserAcceptedAccountManagement,
-          intercepted_account_management_accepted_);
-    } else {
-      OnProfileCreationChoice(account_info, profile_color,
-                              SigninInterceptionResult::kAccepted);
-    }
-  } else {
-    OnProfileCreationChoice(account_info, profile_color,
-                            SigninInterceptionResult::kDeclined);
-    auto* accounts_mutator = identity_manager_->GetAccountsMutator();
-    accounts_mutator->RemoveAccount(
-        account_info.account_id,
-        signin_metrics::SourceForRefreshTokenOperation::
-            kDiceTurnOnSyncHelper_Abort);
-  }
-}
-
 void DiceWebSigninInterceptor::OnNewBrowserCreated(bool is_new_profile) {
-  DCHECK(interception_bubble_handle_ || managed_account_interception_);
+  DCHECK(interception_bubble_handle_);
   interception_bubble_handle_.reset();  // Close the bubble now.
   session_startup_helper_.reset();
 
diff --git a/chrome/browser/signin/dice_web_signin_interceptor.h b/chrome/browser/signin/dice_web_signin_interceptor.h
index 86fed40..22978f7 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor.h
+++ b/chrome/browser/signin/dice_web_signin_interceptor.h
@@ -13,7 +13,6 @@
 #include "base/gtest_prod_util.h"
 #include "base/scoped_observation.h"
 #include "base/time/time.h"
-#include "chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -82,11 +81,7 @@
   // Signin interception is disabled by the SigninInterceptionEnabled policy.
   kAbortInterceptionDisabled = 15,
 
-  // Interception succeeded when enteprise account separation is mandatory.
-  kInterceptEnterpriseForced = 16,
-  kInterceptEnterpriseForcedProfileSwitch = 17,
-
-  kMaxValue = kInterceptEnterpriseForcedProfileSwitch,
+  kMaxValue = kAbortInterceptionDisabled,
 };
 
 // User selection in the interception bubble.
@@ -177,14 +172,6 @@
         const BubbleParameters& bubble_parameters,
         base::OnceCallback<void(SigninInterceptionResult)> callback) = 0;
 
-    // Shows the enterprise profile confirmation dialog to notify the user that
-    // a managed profile will be created or that their account needs a new
-    // profile to be created.
-    virtual void ShowEnterpriseProfileInterceptionDialog(
-        const std::string& email,
-        base::OnceCallback<void(bool)> callback,
-        Browser* browser) = 0;
-
     // Shows the profile customization bubble.
     virtual void ShowProfileCustomizationBubble(Browser* browser) = 0;
   };
@@ -224,7 +211,6 @@
       content::WebContents* intercepted_contents,
       std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
           bubble_handle,
-      bool managed_account_interception,
       bool is_new_profile);
 
   // Returns the outcome of the interception heuristic.
@@ -258,15 +244,7 @@
                            ShouldShowEnterpriseBubbleWithoutUPA);
   FRIEND_TEST_ALL_PREFIXES(DiceWebSigninInterceptorTest,
                            ShouldShowMultiUserBubble);
-  FRIEND_TEST_ALL_PREFIXES(DiceWebSigninInterceptorTest,
-                           ShouldEnforceEnterpriseProfileSeparation);
-  FRIEND_TEST_ALL_PREFIXES(DiceWebSigninInterceptorTest,
-                           ShouldEnforceEnterpriseProfileSeparationWithoutUPA);
-  FRIEND_TEST_ALL_PREFIXES(DiceWebSigninInterceptorTest,
-                           ShouldEnforceEnterpriseProfileSeparationReauth);
   FRIEND_TEST_ALL_PREFIXES(DiceWebSigninInterceptorTest, PersistentHash);
-  FRIEND_TEST_ALL_PREFIXES(DiceWebSigninInterceptorTest,
-                           EnforceManagedAccountAsPrimary);
 
   // Cancels any current signin interception and resets the interceptor to its
   // initial state.
@@ -276,8 +254,6 @@
   const ProfileAttributesEntry* ShouldShowProfileSwitchBubble(
       const std::string& intercepted_email,
       ProfileAttributesStorage* profile_attribute_storage) const;
-  bool ShouldEnforceEnterpriseProfileSeparation(
-      const AccountInfo& intercepted_account_info) const;
   bool ShouldShowEnterpriseBubble(
       const AccountInfo& intercepted_account_info) const;
   bool ShouldShowMultiUserBubble(
@@ -305,13 +281,6 @@
   void OnNewSignedInProfileCreated(absl::optional<SkColor> profile_color,
                                    Profile* new_profile);
 
-  // Called after the user choses whether the session should continue in a new
-  // work profile or not. If the user choses not to continue in a work profile,
-  // the account is signed out.
-  void OnEnterpriseProfileCreationResult(const AccountInfo& account_info,
-                                         SkColor profile_color,
-                                         bool create);
-
   // Called when the new browser is created after interception. Passed as
   // callback to `session_startup_helper_`.
   void OnNewBrowserCreated(bool is_new_profile);
@@ -346,9 +315,6 @@
   // Members below are related to the interception in progress.
   bool is_interception_in_progress_ = false;
   CoreAccountId account_id_;
-  bool new_account_interception_ = false;
-  bool managed_account_interception_ = false;
-  bool intercepted_account_management_accepted_ = false;
   base::ScopedObservation<signin::IdentityManager,
                           signin::IdentityManager::Observer>
       account_info_update_observation_{this};
diff --git a/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc b/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc
index df5525fe..feefd78a 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc
+++ b/chrome/browser/signin/dice_web_signin_interceptor_browsertest.cc
@@ -32,13 +32,11 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/common/chrome_features.h"
-#include "chrome/common/pref_names.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/profile_waiter.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/account_id/account_id.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
-#include "components/prefs/pref_service.h"
 #include "components/signin/public/identity_manager/accounts_mutator.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/identity_test_environment.h"
@@ -100,13 +98,6 @@
     return bubble_handle;
   }
 
-  void ShowEnterpriseProfileInterceptionDialog(
-      const std::string& email,
-      base::OnceCallback<void(bool)> callback,
-      Browser* browser) override {
-    std::move(callback).Run(expected_enteprise_confirmation_result_);
-  }
-
   void ShowProfileCustomizationBubble(Browser* browser) override {
     EXPECT_FALSE(customized_browser_)
         << "Customization must be shown only once.";
@@ -124,10 +115,6 @@
     expected_interception_result_ = result;
   }
 
-  void set_expected_enteprise_confirmation_result(bool result) {
-    expected_enteprise_confirmation_result_ = result;
-  }
-
   bool intercept_bubble_shown() const { return weak_bubble_handle_.get(); }
 
   bool intercept_bubble_destroyed() const {
@@ -140,7 +127,6 @@
       DiceWebSigninInterceptor::SigninInterceptionType::kMultiUser;
   SigninInterceptionResult expected_interception_result_ =
       SigninInterceptionResult::kAccepted;
-  bool expected_enteprise_confirmation_result_ = false;
   base::WeakPtr<FakeBubbleHandle> weak_bubble_handle_;
 };
 
@@ -186,22 +172,15 @@
                      SigninInterceptionHeuristicOutcome outcome,
                      bool intercept_to_guest = false) {
   int profile_switch_count =
-      outcome == SigninInterceptionHeuristicOutcome::kInterceptProfileSwitch ||
-              outcome == SigninInterceptionHeuristicOutcome::
-                             kInterceptEnterpriseForcedProfileSwitch
+      outcome == SigninInterceptionHeuristicOutcome::kInterceptProfileSwitch
           ? 1
           : 0;
   int profile_creation_count = 1 - profile_switch_count;
-  int fetched_account_count =
-      outcome == SigninInterceptionHeuristicOutcome::
-                     kInterceptEnterpriseForcedProfileSwitch
-          ? 1
-          : profile_creation_count;
 
   histogram_tester.ExpectUniqueSample("Signin.Intercept.HeuristicOutcome",
                                       outcome, 1);
   histogram_tester.ExpectTotalCount("Signin.Intercept.AccountInfoFetchDuration",
-                                    fetched_account_count);
+                                    profile_creation_count);
   histogram_tester.ExpectTotalCount("Signin.Intercept.ProfileCreationDuration",
                                     profile_creation_count);
   histogram_tester.ExpectTotalCount("Signin.Intercept.ProfileSwitchDuration",
@@ -261,9 +240,6 @@
     return interceptor_delegate;
   }
 
- protected:
-  base::test::ScopedFeatureList feature_list_;
-
  private:
   // InProcessBrowserTest:
   void SetUpOnMainThread() override {
@@ -311,6 +287,7 @@
         Profile::FromBrowserContext(context), std::move(fake_delegate));
   }
 
+  base::test::ScopedFeatureList feature_list_;
   network::TestURLLoaderFactory test_url_loader_factory_;
   std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
       identity_test_env_profile_adaptor_;
@@ -408,100 +385,6 @@
   EXPECT_EQ(source_interceptor_delegate->customized_browser(), nullptr);
 }
 
-// Tests the complete interception flow including profile and browser creation.
-IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest,
-                       ForcedEnterpriseInterceptionTest) {
-  base::HistogramTester histogram_tester;
-  AccountInfo account_info =
-      identity_test_env()->MakeAccountAvailable("alice@example.com");
-  // Fill the account info, in particular for the hosted_domain field.
-  account_info.full_name = "fullname";
-  account_info.given_name = "givenname";
-  account_info.hosted_domain = "example.com";
-  account_info.locale = "en";
-  account_info.picture_url = "https://example.com";
-  account_info.is_child_account = false;
-  DCHECK(account_info.IsValid());
-  identity_test_env()->UpdateAccountInfoForAccount(account_info);
-
-  // Enforce enterprise profile sepatation.
-  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
-                                   "primary_account_strict");
-
-  // Instantly return from Gaia calls, to avoid timing out when injecting the
-  // account in the new profile.
-  network::TestURLLoaderFactory* loader_factory = test_url_loader_factory();
-  loader_factory->SetInterceptor(base::BindLambdaForTesting(
-      [loader_factory](const network::ResourceRequest& request) {
-        std::string path = request.url.path();
-        if (path == "/ListAccounts" || path == "/GetCheckConnectionInfo") {
-          loader_factory->AddResponse(request.url.spec(), std::string());
-          return;
-        }
-        if (path == "/oauth/multilogin") {
-          loader_factory->AddResponse(request.url.spec(),
-                                      kMultiloginSuccessResponse);
-          return;
-        }
-      }));
-
-  // Add a tab.
-  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
-  content::WebContents* web_contents = AddTab(intercepted_url);
-  int original_tab_count = browser()->tab_strip_model()->count();
-
-  // Do the signin interception.
-  EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
-  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
-      GetInterceptorDelegate(profile());
-  source_interceptor_delegate->set_expected_enteprise_confirmation_result(true);
-  Profile* new_profile =
-      InterceptAndWaitProfileCreation(web_contents, account_info.account_id);
-  EXPECT_TRUE(new_profile->GetPrefs()->GetBoolean(
-      prefs::kUserAcceptedAccountManagement));
-  ASSERT_TRUE(new_profile);
-  EXPECT_FALSE(source_interceptor_delegate->intercept_bubble_shown());
-  signin::IdentityManager* new_identity_manager =
-      IdentityManagerFactory::GetForProfile(new_profile);
-  EXPECT_TRUE(new_identity_manager->HasAccountWithRefreshToken(
-      account_info.account_id));
-
-  IdentityTestEnvironmentProfileAdaptor adaptor(new_profile);
-  adaptor.identity_test_env()->SetAutomaticIssueOfAccessTokens(true);
-
-  // Check the profile name.
-  ProfileAttributesStorage& storage =
-      g_browser_process->profile_manager()->GetProfileAttributesStorage();
-  ProfileAttributesEntry* entry =
-      storage.GetProfileAttributesWithPath(new_profile->GetPath());
-  ASSERT_TRUE(entry);
-  EXPECT_EQ("example.com", base::UTF16ToUTF8(entry->GetLocalProfileName()));
-  // Check the profile color.
-  EXPECT_TRUE(ThemeServiceFactory::GetForProfile(new_profile)
-                  ->UsingAutogeneratedTheme());
-
-  // A browser has been created for the new profile and the tab was moved there.
-  Browser* added_browser = ui_test_utils::WaitForBrowserToOpen();
-  ASSERT_TRUE(added_browser);
-  ASSERT_EQ(BrowserList::GetInstance()->size(), 2u);
-  EXPECT_EQ(added_browser->profile(), new_profile);
-  EXPECT_EQ(browser()->tab_strip_model()->count(), original_tab_count - 1);
-  EXPECT_EQ(added_browser->tab_strip_model()->GetActiveWebContents()->GetURL(),
-            intercepted_url);
-
-  CheckHistograms(
-      histogram_tester,
-      SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);
-  // Interception bubble is destroyed in the source profile, and was not shown
-  // in the new profile.
-  FakeDiceWebSigninInterceptorDelegate* new_interceptor_delegate =
-      GetInterceptorDelegate(new_profile);
-
-  // Profile customization UI was shown exactly once in the new profile.
-  EXPECT_EQ(new_interceptor_delegate->customized_browser(), added_browser);
-  EXPECT_EQ(source_interceptor_delegate->customized_browser(), nullptr);
-}
-
 // Tests the complete profile switch flow when the profile is not loaded.
 IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest, SwitchAndLoad) {
   base::HistogramTester histogram_tester;
@@ -773,180 +656,3 @@
                   SigninInterceptionHeuristicOutcome::kInterceptMultiUser,
                   /*intercept_to_guest =*/true);
 }
-
-class DiceWebSigninInterceptorEnterpriseSwitchBrowserTest
-    : public DiceWebSigninInterceptorBrowserTest {
- public:
-  DiceWebSigninInterceptorEnterpriseSwitchBrowserTest() {
-    enterprise_feature_list_.InitAndEnableFeature(
-        kAccountPoliciesLoadedWithoutSync);
-  }
-
- private:
-  base::test::ScopedFeatureList enterprise_feature_list_;
-};
-
-// Tests the complete profile switch flow when the profile is not loaded.
-IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorEnterpriseSwitchBrowserTest,
-                       EnterpriseSwitchAndLoad) {
-  base::HistogramTester histogram_tester;
-  // Enforce enterprise profile sepatation.
-  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
-                                   "primary_account_strict");
-  AccountInfo account_info =
-      identity_test_env()->MakeAccountAvailable("alice@example.com");
-  // Fill the account info, in particular for the hosted_domain field.
-  account_info.full_name = "fullname";
-  account_info.given_name = "givenname";
-  account_info.hosted_domain = "example.com";
-  account_info.locale = "en";
-  account_info.picture_url = "https://example.com";
-  account_info.is_child_account = false;
-  DCHECK(account_info.IsValid());
-  identity_test_env()->UpdateAccountInfoForAccount(account_info);
-
-  // Add a profile in the cache (simulate the profile on disk).
-  ProfileManager* profile_manager = g_browser_process->profile_manager();
-  ProfileAttributesStorage* profile_storage =
-      &profile_manager->GetProfileAttributesStorage();
-  const base::FilePath profile_path =
-      profile_manager->GenerateNextProfileDirectoryPath();
-  ProfileAttributesInitParams params;
-  params.profile_path = profile_path;
-  params.profile_name = u"TestProfileName";
-  params.gaia_id = account_info.gaia;
-  params.user_name = base::UTF8ToUTF16(account_info.email);
-  profile_storage->AddProfile(std::move(params));
-  ProfileAttributesEntry* entry =
-      profile_storage->GetProfileAttributesWithPath(profile_path);
-  ASSERT_TRUE(entry);
-  ASSERT_EQ(entry->GetGAIAId(), account_info.gaia);
-
-  // Add a tab.
-  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
-  content::WebContents* web_contents = AddTab(intercepted_url);
-  int original_tab_count = browser()->tab_strip_model()->count();
-
-  // Do the signin interception.
-  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
-      GetInterceptorDelegate(profile());
-  source_interceptor_delegate->set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch);
-  Profile* new_profile =
-      InterceptAndWaitProfileCreation(web_contents, account_info.account_id);
-  ASSERT_TRUE(new_profile);
-  EXPECT_FALSE(source_interceptor_delegate->intercept_bubble_shown());
-  signin::IdentityManager* new_identity_manager =
-      IdentityManagerFactory::GetForProfile(new_profile);
-  EXPECT_TRUE(new_identity_manager->HasAccountWithRefreshToken(
-      account_info.account_id));
-
-  // Check that the right profile was opened.
-  EXPECT_EQ(new_profile->GetPath(), profile_path);
-
-  // Add the account to the cookies (simulates the account reconcilor).
-  EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
-  signin::SetCookieAccounts(new_identity_manager, test_url_loader_factory(),
-                            {{account_info.email, account_info.gaia}});
-
-  // A browser has been created for the new profile and the tab was moved there.
-  ASSERT_EQ(BrowserList::GetInstance()->size(), 2u);
-  Browser* added_browser = BrowserList::GetInstance()->get(1);
-  ASSERT_TRUE(added_browser);
-  EXPECT_EQ(added_browser->profile(), new_profile);
-  EXPECT_EQ(browser()->tab_strip_model()->count(), original_tab_count - 1);
-  EXPECT_EQ(added_browser->tab_strip_model()->GetActiveWebContents()->GetURL(),
-            intercepted_url);
-
-  CheckHistograms(histogram_tester,
-                  SigninInterceptionHeuristicOutcome::
-                      kInterceptEnterpriseForcedProfileSwitch);
-
-  // Profile customization was not shown.
-  EXPECT_EQ(GetInterceptorDelegate(new_profile)->customized_browser(), nullptr);
-  EXPECT_EQ(source_interceptor_delegate->customized_browser(), nullptr);
-}
-
-// Tests the complete profile switch flow when the profile is already loaded.
-IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorEnterpriseSwitchBrowserTest,
-                       EnterpriseSwitchAlreadyOpen) {
-  base::HistogramTester histogram_tester;
-  // Enforce enterprise profile sepatation.
-  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
-                                   "primary_account_strict");
-  AccountInfo account_info =
-      identity_test_env()->MakeAccountAvailable("alice@example.com");
-  // Fill the account info, in particular for the hosted_domain field.
-  account_info.full_name = "fullname";
-  account_info.given_name = "givenname";
-  account_info.hosted_domain = "example.com";
-  account_info.locale = "en";
-  account_info.picture_url = "https://example.com";
-  account_info.is_child_account = false;
-  DCHECK(account_info.IsValid());
-  identity_test_env()->UpdateAccountInfoForAccount(account_info);
-  // Create another profile with a browser window.
-  ProfileManager* profile_manager = g_browser_process->profile_manager();
-  const base::FilePath profile_path =
-      profile_manager->GenerateNextProfileDirectoryPath();
-  base::RunLoop loop;
-  Profile* other_profile = nullptr;
-  ProfileManager::CreateCallback callback = base::BindLambdaForTesting(
-      [&other_profile, &loop](Profile* profile, Profile::CreateStatus status) {
-        DCHECK_EQ(status, Profile::CREATE_STATUS_INITIALIZED);
-        other_profile = profile;
-        loop.Quit();
-      });
-  profiles::SwitchToProfile(profile_path, /*always_create=*/true,
-                            std::move(callback));
-  loop.Run();
-  ASSERT_TRUE(other_profile);
-  ASSERT_EQ(BrowserList::GetInstance()->size(), 2u);
-  Browser* other_browser = BrowserList::GetInstance()->get(1);
-  ASSERT_TRUE(other_browser);
-  ASSERT_EQ(other_browser->profile(), other_profile);
-  // Add the account to the other profile.
-  signin::IdentityManager* other_identity_manager =
-      IdentityManagerFactory::GetForProfile(other_profile);
-  other_identity_manager->GetAccountsMutator()->AddOrUpdateAccount(
-      account_info.gaia, account_info.email, "dummy_refresh_token",
-      /*is_under_advanced_protection=*/false,
-      signin_metrics::SourceForRefreshTokenOperation::kUnknown);
-  other_identity_manager->GetPrimaryAccountMutator()->SetPrimaryAccount(
-      account_info.account_id, signin::ConsentLevel::kSync);
-
-  // Add a tab.
-  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
-  content::WebContents* web_contents = AddTab(intercepted_url);
-  int original_tab_count = browser()->tab_strip_model()->count();
-  int other_original_tab_count = other_browser->tab_strip_model()->count();
-
-  // Start the interception.
-  GetInterceptorDelegate(profile())->set_expected_interception_type(
-      DiceWebSigninInterceptor::SigninInterceptionType::kProfileSwitch);
-  DiceWebSigninInterceptor* interceptor =
-      DiceWebSigninInterceptorFactory::GetForProfile(profile());
-  interceptor->MaybeInterceptWebSignin(web_contents, account_info.account_id,
-                                       /*is_new_account=*/true,
-                                       /*is_sync_signin=*/false);
-
-  // Add the account to the cookies (simulates the account reconcilor).
-  signin::SetCookieAccounts(other_identity_manager, test_url_loader_factory(),
-                            {{account_info.email, account_info.gaia}});
-
-  // The tab was moved to the new browser.
-  ASSERT_EQ(BrowserList::GetInstance()->size(), 2u);
-  EXPECT_EQ(browser()->tab_strip_model()->count(), original_tab_count - 1);
-  EXPECT_EQ(other_browser->tab_strip_model()->count(),
-            other_original_tab_count + 1);
-  EXPECT_EQ(other_browser->tab_strip_model()->GetActiveWebContents()->GetURL(),
-            intercepted_url);
-
-  CheckHistograms(histogram_tester,
-                  SigninInterceptionHeuristicOutcome::
-                      kInterceptEnterpriseForcedProfileSwitch);
-  // Profile customization was not shown.
-  EXPECT_EQ(GetInterceptorDelegate(other_profile)->customized_browser(),
-            nullptr);
-  EXPECT_EQ(GetInterceptorDelegate(profile())->customized_browser(), nullptr);
-}
diff --git a/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc b/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc
index 08ee2a9..b499455 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc
+++ b/chrome/browser/signin/dice_web_signin_interceptor_unittest.cc
@@ -26,13 +26,11 @@
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/prefs/pref_service.h"
-#include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/signin/public/identity_manager/identity_test_environment.h"
 #include "services/network/test/test_url_loader_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 
 namespace {
@@ -46,12 +44,6 @@
                const BubbleParameters& bubble_parameters,
                base::OnceCallback<void(SigninInterceptionResult)> callback),
               (override));
-  MOCK_METHOD(void,
-              ShowEnterpriseProfileInterceptionDialog,
-              (const std::string& email,
-               base::OnceCallback<void(bool)> callback,
-               Browser* browser),
-              (override));
   void ShowProfileCustomizationBubble(Browser* browser) override {}
 };
 
@@ -152,29 +144,6 @@
               SigninInterceptionHeuristicOutcomeIsSuccess(expected_outcome));
   }
 
-  // Calls MaybeInterceptWebSignin and verifies the heuristic outcome and the
-  // histograms.
-  // This function only works if the interception decision cannot be made
-  // synchronously (GetHeuristicOutcome() returns no value).
-  void TestAsynchronousInterception(
-      AccountInfo account_info,
-      bool is_new_account,
-      bool is_sync_signin,
-      SigninInterceptionHeuristicOutcome expected_outcome) {
-    ASSERT_EQ(interceptor()->GetHeuristicOutcome(is_new_account, is_sync_signin,
-                                                 account_info.email,
-                                                 /*entry=*/nullptr),
-              absl::nullopt);
-    base::HistogramTester histogram_tester;
-    interceptor()->MaybeInterceptWebSignin(web_contents(),
-                                           account_info.account_id,
-                                           is_new_account, is_sync_signin);
-    testing::Mock::VerifyAndClearExpectations(mock_delegate());
-    histogram_tester.ExpectUniqueSample("Signin.Intercept.HeuristicOutcome",
-                                        expected_outcome, 1);
-    EXPECT_TRUE(interceptor()->is_interception_in_progress());
-  }
-
  private:
   // testing::Test:
   void SetUp() override {
@@ -319,161 +288,6 @@
   EXPECT_TRUE(interceptor()->ShouldShowEnterpriseBubble(account_info));
 }
 
-TEST_F(DiceWebSigninInterceptorTest, ShouldEnforceEnterpriseProfileSeparation) {
-  profile()->GetPrefs()->SetBoolean(
-      prefs::kManagedAccountsSigninRestrictionScopeMachine, true);
-  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
-                                   "primary_account_strict");
-
-  // Setup 3 accounts in the profile:
-  // - primary account
-  // - other enterprise account that is not primary (should be ignored)
-  // - intercepted account.
-  AccountInfo primary_account_info =
-      identity_test_env()->MakeUnconsentedPrimaryAccountAvailable(
-          "alice@gmail.com");
-
-  AccountInfo other_account_info =
-      identity_test_env()->MakeAccountAvailable("dummy@example.com");
-  MakeValidAccountInfo(&other_account_info);
-  other_account_info.hosted_domain = "example.com";
-  identity_test_env()->UpdateAccountInfoForAccount(other_account_info);
-  AccountInfo account_info =
-      identity_test_env()->MakeAccountAvailable("bob@example.com");
-  MakeValidAccountInfo(&account_info);
-  identity_test_env()->UpdateAccountInfoForAccount(account_info);
-  ASSERT_EQ(identity_test_env()->identity_manager()->GetPrimaryAccountId(
-                signin::ConsentLevel::kSignin),
-            primary_account_info.account_id);
-  // Consumer account not intercepted.
-  EXPECT_FALSE(
-      interceptor()->ShouldEnforceEnterpriseProfileSeparation(account_info));
-  account_info.hosted_domain = "example.com";
-  identity_test_env()->UpdateAccountInfoForAccount(account_info);
-  // Managed account intercepted.
-  EXPECT_TRUE(
-      interceptor()->ShouldEnforceEnterpriseProfileSeparation(account_info));
-}
-
-TEST_F(DiceWebSigninInterceptorTest,
-       ShouldEnforceEnterpriseProfileSeparationWithoutUPA) {
-  profile()->GetPrefs()->SetBoolean(
-      prefs::kManagedAccountsSigninRestrictionScopeMachine, true);
-  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
-                                   "primary_account_strict");
-  AccountInfo account_info_1 =
-      identity_test_env()->MakeAccountAvailable("bob@example.com");
-  MakeValidAccountInfo(&account_info_1);
-  account_info_1.hosted_domain = "example.com";
-  identity_test_env()->UpdateAccountInfoForAccount(account_info_1);
-
-  // Primary account is not set.
-  ASSERT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount(
-      signin::ConsentLevel::kSignin));
-  EXPECT_TRUE(
-      interceptor()->ShouldEnforceEnterpriseProfileSeparation(account_info_1));
-}
-
-TEST_F(DiceWebSigninInterceptorTest,
-       ShouldEnforceEnterpriseProfileSeparationReauth) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(kAccountPoliciesLoadedWithoutSync);
-  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
-                                   "primary_account_strict");
-  AccountInfo primary_account_info =
-      identity_test_env()->MakeUnconsentedPrimaryAccountAvailable(
-          "alice@example.com");
-  MakeValidAccountInfo(&primary_account_info);
-  primary_account_info.hosted_domain = "example.com";
-  identity_test_env()->UpdateAccountInfoForAccount(primary_account_info);
-
-  // Primary account is set.
-  ASSERT_TRUE(identity_test_env()->identity_manager()->HasPrimaryAccount(
-      signin::ConsentLevel::kSignin));
-  EXPECT_TRUE(interceptor()->ShouldEnforceEnterpriseProfileSeparation(
-      primary_account_info));
-
-  profile()->GetPrefs()->SetBoolean(prefs::kUserAcceptedAccountManagement,
-                                    true);
-  EXPECT_FALSE(interceptor()->ShouldEnforceEnterpriseProfileSeparation(
-      primary_account_info));
-}
-
-TEST_F(DiceWebSigninInterceptorTest, EnforceManagedAccountAsPrimaryReauth) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(kAccountPoliciesLoadedWithoutSync);
-  profile()->GetPrefs()->SetBoolean(
-      prefs::kManagedAccountsSigninRestrictionScopeMachine, true);
-  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
-                                   "primary_account");
-
-  // Reauth intercepted if enterprise confirmation not shown yet for forced
-  // managed separation.
-  AccountInfo account_info =
-      identity_test_env()->MakeUnconsentedPrimaryAccountAvailable(
-          "alice@example.com");
-  MakeValidAccountInfo(&account_info);
-  account_info.hosted_domain = "example.com";
-  identity_test_env()->UpdateAccountInfoForAccount(account_info);
-  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
-                                   "primary_account");
-  EXPECT_CALL(*mock_delegate(), ShowEnterpriseProfileInterceptionDialog(
-                                    testing::_, testing::_, testing::_))
-      .WillOnce(testing::Invoke(
-          [](const std::string& email, base::OnceCallback<void(bool)> callback,
-             Browser* browser) { std::move(callback).Run(true); }));
-  TestAsynchronousInterception(
-      account_info, /*is_new_account=*/false, /*is_sync_signin=*/false,
-      SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);
-}
-
-TEST_F(DiceWebSigninInterceptorTest, EnforceManagedAccountAsPrimaryManaged) {
-  AccountInfo account_info =
-      identity_test_env()->MakeAccountAvailable("alice@example.com");
-  MakeValidAccountInfo(&account_info);
-  account_info.hosted_domain = "example.com";
-  identity_test_env()->UpdateAccountInfoForAccount(account_info);
-
-  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
-                                   "primary_account_strict");
-
-  EXPECT_CALL(*mock_delegate(), ShowEnterpriseProfileInterceptionDialog(
-                                    testing::_, testing::_, testing::_))
-      .WillOnce(testing::Invoke(
-          [](const std::string& email, base::OnceCallback<void(bool)> callback,
-             Browser* browser) { std::move(callback).Run(true); }));
-  TestAsynchronousInterception(
-      account_info, /*is_new_account=*/true, /*is_sync_signin=*/false,
-      SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);
-}
-
-TEST_F(DiceWebSigninInterceptorTest,
-       EnforceManagedAccountAsPrimaryProfileSwitch) {
-  AccountInfo account_info =
-      identity_test_env()->MakeAccountAvailable("alice@example.com");
-  MakeValidAccountInfo(&account_info);
-  account_info.hosted_domain = "example.com";
-  identity_test_env()->UpdateAccountInfoForAccount(account_info);
-
-  profile()->GetPrefs()->SetBoolean(
-      prefs::kManagedAccountsSigninRestrictionScopeMachine, true);
-  profile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
-                                   "primary_account_strict");
-
-  // Setup for profile switch interception.
-  Profile* profile_2 = CreateTestingProfile("Profile 2");
-  ProfileAttributesEntry* entry =
-      profile_attributes_storage()->GetProfileAttributesWithPath(
-          profile_2->GetPath());
-  ASSERT_NE(entry, nullptr);
-  entry->SetAuthInfo(account_info.gaia, base::UTF8ToUTF16(account_info.email),
-                     /*is_consented_primary_account=*/false);
-  TestAsynchronousInterception(account_info, /*is_new_account=*/true,
-                               /*is_sync_signin=*/false,
-                               SigninInterceptionHeuristicOutcome::
-                                   kInterceptEnterpriseForcedProfileSwitch);
-}
-
 TEST_F(DiceWebSigninInterceptorTest, ShouldShowEnterpriseBubbleWithoutUPA) {
   AccountInfo account_info_1 =
       identity_test_env()->MakeAccountAvailable("bob@example.com");
diff --git a/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc b/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc
index 47fbdd15..d362eba 100644
--- a/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc
+++ b/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc
@@ -50,14 +50,6 @@
     std::move(callback).Run(SigninInterceptionResult::kDeclined);
     return nullptr;
   }
-
-  void ShowEnterpriseProfileInterceptionDialog(
-      const std::string& email,
-      base::OnceCallback<void(bool)> callback,
-      Browser* browser) override {
-    std::move(callback).Run(false);
-  }
-
   void ShowProfileCustomizationBubble(Browser* browser) override {}
 };
 
diff --git a/chrome/browser/speech/fake_speech_recognition_service.cc b/chrome/browser/speech/fake_speech_recognition_service.cc
index ca08c4f3..2bc1564 100644
--- a/chrome/browser/speech/fake_speech_recognition_service.cc
+++ b/chrome/browser/speech/fake_speech_recognition_service.cc
@@ -69,14 +69,13 @@
 }
 
 void FakeSpeechRecognitionService::SendSpeechRecognitionResult(
-    media::mojom::SpeechRecognitionResultPtr result) {
+    const media::SpeechRecognitionResult& result) {
   ASSERT_TRUE(recognizer_client_remote_.is_bound());
   EXPECT_TRUE(capturing_audio_ || has_received_audio_);
   recognizer_client_remote_->OnSpeechRecognitionRecognitionEvent(
-      std::move(result),
-      base::BindOnce(&FakeSpeechRecognitionService::
-                         OnSpeechRecognitionRecognitionEventCallback,
-                     base::Unretained(this)));
+      result, base::BindOnce(&FakeSpeechRecognitionService::
+                                 OnSpeechRecognitionRecognitionEventCallback,
+                             base::Unretained(this)));
 }
 
 void FakeSpeechRecognitionService::OnSpeechRecognitionRecognitionEventCallback(
diff --git a/chrome/browser/speech/fake_speech_recognition_service.h b/chrome/browser/speech/fake_speech_recognition_service.h
index f8d4855..75ac0ec 100644
--- a/chrome/browser/speech/fake_speech_recognition_service.h
+++ b/chrome/browser/speech/fake_speech_recognition_service.h
@@ -61,7 +61,7 @@
 
   // Methods for testing plumbing to SpeechRecognitionRecognizerClient.
   void SendSpeechRecognitionResult(
-      media::mojom::SpeechRecognitionResultPtr result);
+      const media::SpeechRecognitionResult& result);
   void SendSpeechRecognitionError();
 
   void WaitForRecognitionStarted();
diff --git a/chrome/browser/speech/network_speech_recognizer.cc b/chrome/browser/speech/network_speech_recognizer.cc
index fdb1cc5..238e7c4 100644
--- a/chrome/browser/speech/network_speech_recognizer.cc
+++ b/chrome/browser/speech/network_speech_recognizer.cc
@@ -225,7 +225,7 @@
       FROM_HERE,
       base::BindOnce(&SpeechRecognizerDelegate::OnSpeechResult, delegate_,
                      result_str, final_count == results.size(),
-                     absl::nullopt /* word offsets */));
+                     /* full_result = */ absl::nullopt));
 
   last_result_str_ = result_str;
 }
diff --git a/chrome/browser/speech/network_speech_recognizer_browsertest.cc b/chrome/browser/speech/network_speech_recognizer_browsertest.cc
index 4ddb855..f4f457b 100644
--- a/chrome/browser/speech/network_speech_recognizer_browsertest.cc
+++ b/chrome/browser/speech/network_speech_recognizer_browsertest.cc
@@ -24,6 +24,7 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/fake_speech_recognition_manager.h"
 #include "content/public/test/test_utils.h"
+#include "media/mojo/mojom/speech_recognition_service.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"
@@ -43,8 +44,7 @@
       OnSpeechResult,
       void(const std::u16string& text,
            bool is_final,
-           const absl::optional<SpeechRecognizerDelegate::TranscriptTiming>&
-               timing));
+           const absl::optional<media::SpeechRecognitionResult>& timing));
   MOCK_METHOD1(OnSpeechSoundLevelChanged, void(int16_t));
   MOCK_METHOD1(OnSpeechRecognitionStateChanged, void(SpeechRecognizerStatus));
 
diff --git a/chrome/browser/speech/on_device_speech_recognizer.cc b/chrome/browser/speech/on_device_speech_recognizer.cc
index afd8cf1..f42a521 100644
--- a/chrome/browser/speech/on_device_speech_recognizer.cc
+++ b/chrome/browser/speech/on_device_speech_recognizer.cc
@@ -131,18 +131,18 @@
 }
 
 void OnDeviceSpeechRecognizer::OnSpeechRecognitionRecognitionEvent(
-    media::mojom::SpeechRecognitionResultPtr result,
+    const media::SpeechRecognitionResult& result,
     OnSpeechRecognitionRecognitionEventCallback reply) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 
   // Returning true ensures the speech recognition continues.
   std::move(reply).Run(true);
 
-  if (!result->transcription.size())
+  if (!result.transcription.size())
     return;
   UpdateStatus(SpeechRecognizerStatus::SPEECH_RECOGNIZER_IN_SPEECH);
-  delegate()->OnSpeechResult(base::UTF8ToUTF16(result->transcription),
-                             result->is_final, absl::nullopt);
+  delegate()->OnSpeechResult(base::UTF8ToUTF16(result.transcription),
+                             result.is_final, result);
 }
 
 void OnDeviceSpeechRecognizer::OnSpeechRecognitionError() {
diff --git a/chrome/browser/speech/on_device_speech_recognizer.h b/chrome/browser/speech/on_device_speech_recognizer.h
index 0ae7e29..8b871f1 100644
--- a/chrome/browser/speech/on_device_speech_recognizer.h
+++ b/chrome/browser/speech/on_device_speech_recognizer.h
@@ -56,7 +56,7 @@
 
   // media::mojom::SpeechRecognitionRecognizerClient:
   void OnSpeechRecognitionRecognitionEvent(
-      media::mojom::SpeechRecognitionResultPtr result,
+      const media::SpeechRecognitionResult& result,
       OnSpeechRecognitionRecognitionEventCallback reply) override;
   void OnSpeechRecognitionError() override;
   void OnLanguageIdentificationEvent(
diff --git a/chrome/browser/speech/on_device_speech_recognizer_browsertest.cc b/chrome/browser/speech/on_device_speech_recognizer_browsertest.cc
index 55d88b8..3f5de01 100644
--- a/chrome/browser/speech/on_device_speech_recognizer_browsertest.cc
+++ b/chrome/browser/speech/on_device_speech_recognizer_browsertest.cc
@@ -18,6 +18,7 @@
 #include "content/public/test/browser_test.h"
 #include "media/audio/audio_system.h"
 #include "media/base/audio_parameters.h"
+#include "media/mojo/mojom/speech_recognition_service.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -41,8 +42,7 @@
       OnSpeechResult,
       void(const std::u16string& text,
            bool is_final,
-           const absl::optional<SpeechRecognizerDelegate::TranscriptTiming>&
-               timing));
+           const absl::optional<media::SpeechRecognitionResult>& timing));
   MOCK_METHOD1(OnSpeechSoundLevelChanged, void(int16_t));
   MOCK_METHOD1(OnSpeechRecognitionStateChanged, void(SpeechRecognizerStatus));
 
@@ -206,8 +206,7 @@
       .Times(1)
       .RetiresOnSaturation();
   fake_service_->SendSpeechRecognitionResult(
-      media::mojom::SpeechRecognitionResult::New("All mammals have hair",
-                                                 false));
+      media::SpeechRecognitionResult("All mammals have hair", false));
   base::RunLoop().RunUntilIdle();
 
   EXPECT_CALL(*mock_speech_delegate_,
@@ -216,9 +215,8 @@
                   true, testing::_))
       .Times(1)
       .RetiresOnSaturation();
-  fake_service_->SendSpeechRecognitionResult(
-      media::mojom::SpeechRecognitionResult::New(
-          "All mammals drink milk from their mothers", true));
+  fake_service_->SendSpeechRecognitionResult(media::SpeechRecognitionResult(
+      "All mammals drink milk from their mothers", true));
   base::RunLoop().RunUntilIdle();
 }
 
diff --git a/chrome/browser/speech/speech_recognizer_delegate.cc b/chrome/browser/speech/speech_recognizer_delegate.cc
deleted file mode 100644
index aa8101a..0000000
--- a/chrome/browser/speech/speech_recognizer_delegate.cc
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/speech/speech_recognizer_delegate.h"
-
-SpeechRecognizerDelegate::TranscriptTiming::TranscriptTiming() = default;
-
-SpeechRecognizerDelegate::TranscriptTiming::~TranscriptTiming() = default;
diff --git a/chrome/browser/speech/speech_recognizer_delegate.h b/chrome/browser/speech/speech_recognizer_delegate.h
index 84f3db9..f8fb236 100644
--- a/chrome/browser/speech/speech_recognizer_delegate.h
+++ b/chrome/browser/speech/speech_recognizer_delegate.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/time/time.h"
+#include "media/mojo/mojom/speech_recognition_service.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 // Requires cleanup. See crbug.com/800374.
@@ -28,38 +29,15 @@
 // which this delegate was constructed.
 class SpeechRecognizerDelegate {
  public:
-  // The timing information for the recognized speech text.
-  struct TranscriptTiming {
-    TranscriptTiming();
-    TranscriptTiming(const TranscriptTiming&) = delete;
-    TranscriptTiming& operator=(const TranscriptTiming&) = delete;
-    ~TranscriptTiming();
-
-    // Describes the time that elapsed between the start of speech recognition
-    // session and the first audio input that corresponds to the transcription
-    // result.
-    base::TimeDelta audio_start_time;
-
-    // Describes the time that elapsed between the start of speech recognition
-    // and the last audio input that corresponds to the transcription result.
-    base::TimeDelta audio_end_time;
-
-    // Describes the time that elapsed between the start of speech recognition
-    // and the audio input corresponding to each word in the transcription
-    // result. The vector will have the same number of TimeDeltas as the
-    // transcription result.
-    std::vector<base::TimeDelta> word_offsets;
-  };
-
   // Receive a speech recognition result. |is_final| indicated whether the
   // result is an intermediate or final result. If |is_final| is true, then the
   // recognizer stops and no more results will be returned.
-  // May include word timing information in |timing| if the speech recognizer is
-  // an on device speech recognizer.
+  // May include word timing information in |full_result| if the speech
+  // recognizer is an on device speech recognizer.
   virtual void OnSpeechResult(
       const std::u16string& text,
       bool is_final,
-      const absl::optional<TranscriptTiming>& timing) = 0;
+      const absl::optional<media::SpeechRecognitionResult>& full_result) = 0;
 
   // Invoked regularly to indicate the average sound volume.
   virtual void OnSpeechSoundLevelChanged(int16_t level) = 0;
diff --git a/chrome/browser/ssl/typed_navigation_upgrade_throttle_browsertest.cc b/chrome/browser/ssl/typed_navigation_upgrade_throttle_browsertest.cc
index ed215c18..bb36f68 100644
--- a/chrome/browser/ssl/typed_navigation_upgrade_throttle_browsertest.cc
+++ b/chrome/browser/ssl/typed_navigation_upgrade_throttle_browsertest.cc
@@ -7,6 +7,7 @@
 #include "base/containers/contains.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/history/history_test_utils.h"
@@ -533,32 +534,63 @@
                               0);
 }
 
+// Flaky on Mac. See https://crbug.com/1216086
+#if defined(OS_MAC)
+#define MAYBE_SearchQuery_ShouldNotUpgrade DISABLED_SearchQuery_ShouldNotUpgrade
+#else
+#define MAYBE_SearchQuery_ShouldNotUpgrade SearchQuery_ShouldNotUpgrade
+#endif
 // Test the case when the user types a search keyword. The keyword may or may
 // not be a non-unique hostname. The navigation should always result in a
 // search and we should never upgrade it to https.
 IN_PROC_BROWSER_TEST_P(TypedNavigationUpgradeThrottleBrowserTest,
-                       SearchQuery_ShouldNotUpgrade) {
+                       MAYBE_SearchQuery_ShouldNotUpgrade) {
   TypeUrlAndExpectNoUpgrade("testpage", /*expect_search_query=*/true);
 }
 
+// Flaky on Mac. See https://crbug.com/1216086
+#if defined(OS_MAC)
+#define MAYBE_SearchQuery_TwoWords_ShouldNotUpgrade \
+  DISABLED_SearchQuery_TwoWords_ShouldNotUpgrade
+#else
+#define MAYBE_SearchQuery_TwoWords_ShouldNotUpgrade \
+  SearchQuery_TwoWords_ShouldNotUpgrade
+#endif
 // Same as SearchQuery_ShouldNotUpgrade but with two words. This is a definite
 // search query, and can never be a hostname.
 IN_PROC_BROWSER_TEST_P(TypedNavigationUpgradeThrottleBrowserTest,
-                       SearchQuery_TwoWords_ShouldNotUpgrade) {
+                       MAYBE_SearchQuery_TwoWords_ShouldNotUpgrade) {
   TypeUrlAndExpectNoUpgrade("test page", /*expect_search_query=*/true);
 }
 
+// Flaky on Mac. See https://crbug.com/1216086
+#if defined(OS_MAC)
+#define MAYBE_NonUniqueHostnameTypedWithoutScheme_ShouldNotUpgrade \
+  DISABLED_NonUniqueHostnameTypedWithoutScheme_ShouldNotUpgrade
+#else
+#define MAYBE_NonUniqueHostnameTypedWithoutScheme_ShouldNotUpgrade \
+  NonUniqueHostnameTypedWithoutScheme_ShouldNotUpgrade
+#endif
 // Test the case when the user types a non-unique hostname. We shouldn't upgrade
 // it to https.
-IN_PROC_BROWSER_TEST_P(TypedNavigationUpgradeThrottleBrowserTest,
-                       NonUniqueHostnameTypedWithoutScheme_ShouldNotUpgrade) {
+IN_PROC_BROWSER_TEST_P(
+    TypedNavigationUpgradeThrottleBrowserTest,
+    MAYBE_NonUniqueHostnameTypedWithoutScheme_ShouldNotUpgrade) {
   TypeUrlAndExpectNoUpgrade(kNonUniqueHostname2, /*expect_search_query=*/false);
 }
 
+// Flaky on Mac. See https://crbug.com/1216086
+#if defined(OS_MAC)
+#define MAYBE_IPAddressTypedWithoutScheme_ShouldNotUpgrade \
+  DISABLED_IPAddressTypedWithoutScheme_ShouldNotUpgrade
+#else
+#define MAYBE_IPAddressTypedWithoutScheme_ShouldNotUpgrade \
+  IPAddressTypedWithoutScheme_ShouldNotUpgrade
+#endif
 // Test the case when the user types an IP address. We shouldn't upgrade it to
 // https.
 IN_PROC_BROWSER_TEST_P(TypedNavigationUpgradeThrottleBrowserTest,
-                       IPAddressTypedWithoutScheme_ShouldNotUpgrade) {
+                       MAYBE_IPAddressTypedWithoutScheme_ShouldNotUpgrade) {
   TypeUrlAndExpectNoUpgrade("127.0.0.1", /*expect_search_query=*/false);
 }
 
diff --git a/chrome/browser/supervised_user/supervised_user_service.cc b/chrome/browser/supervised_user/supervised_user_service.cc
index a584fba..cf29bf72 100644
--- a/chrome/browser/supervised_user/supervised_user_service.cc
+++ b/chrome/browser/supervised_user/supervised_user_service.cc
@@ -124,6 +124,17 @@
   return denylist_dir.AppendASCII(kDenylistFilename);
 }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+bool AreWebFilterPrefsDefault(PrefService* pref_service) {
+  return pref_service
+             ->FindPreference(prefs::kDefaultSupervisedUserFilteringBehavior)
+             ->IsDefaultValue() ||
+         pref_service->FindPreference(prefs::kSupervisedUserSafeSites)
+             ->IsDefaultValue();
+}
+
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 }  // namespace
 
 SupervisedUserService::~SupervisedUserService() {
@@ -205,8 +216,8 @@
 }
 
 std::string SupervisedUserService::GetCustodianEmailAddress() const {
-  std::string email = profile_->GetPrefs()->GetString(
-      prefs::kSupervisedUserCustodianEmail);
+  std::string email =
+      profile_->GetPrefs()->GetString(prefs::kSupervisedUserCustodianEmail);
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   // |GetActiveUser()| can return null in unit tests.
   if (email.empty() && !!user_manager::UserManager::Get()->GetActiveUser()) {
@@ -227,8 +238,8 @@
 }
 
 std::string SupervisedUserService::GetCustodianName() const {
-  std::string name = profile_->GetPrefs()->GetString(
-      prefs::kSupervisedUserCustodianName);
+  std::string name =
+      profile_->GetPrefs()->GetString(prefs::kSupervisedUserCustodianName);
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   // |GetActiveUser()| can return null in unit tests.
   if (name.empty() && !!user_manager::UserManager::Get()->GetActiveUser()) {
@@ -401,11 +412,15 @@
 }
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
 
-bool SupervisedUserService::IsFilteringBehaviorPrefDefault() const {
-  return profile_->GetPrefs()
-      ->FindPreference(prefs::kDefaultSupervisedUserFilteringBehavior)
-      ->IsDefaultValue();
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+void SupervisedUserService::ReportNonDefaultWebFilterValue() const {
+  if (AreWebFilterPrefsDefault(profile_->GetPrefs()))
+    return;
+
+  url_filter_.ReportManagedSiteListMetrics();
+  url_filter_.ReportWebFilterTypeMetrics();
 }
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 void SupervisedUserService::SetActive(bool active) {
   if (active_ == active)
@@ -471,6 +486,11 @@
     UpdateManualHosts();
     UpdateManualURLs();
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+    GetURLFilter()->SetFilterInitialized(true);
+    current_web_filter_type_ = url_filter_.GetWebFilterType();
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 #if BUILDFLAG(ENABLE_EXTENSIONS)
     RefreshApprovedExtensionsFromPrefs();
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
@@ -576,6 +596,16 @@
 
   for (SupervisedUserServiceObserver& observer : observer_list_)
     observer.OnURLFilterChanged();
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  SupervisedUserURLFilter::WebFilterType filter_type =
+      url_filter_.GetWebFilterType();
+  if (!AreWebFilterPrefsDefault(profile_->GetPrefs()) &&
+      current_web_filter_type_ != filter_type) {
+    url_filter_.ReportWebFilterTypeMetrics();
+    current_web_filter_type_ = filter_type;
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 void SupervisedUserService::OnSafeSitesSettingChanged() {
@@ -593,6 +623,16 @@
   }
 
   UpdateAsyncUrlChecker();
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  SupervisedUserURLFilter::WebFilterType filter_type =
+      url_filter_.GetWebFilterType();
+  if (!AreWebFilterPrefsDefault(profile_->GetPrefs()) &&
+      current_web_filter_type_ != filter_type) {
+    url_filter_.ReportWebFilterTypeMetrics();
+    current_web_filter_type_ = filter_type;
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 void SupervisedUserService::UpdateAsyncUrlChecker() {
@@ -726,6 +766,11 @@
 
   for (SupervisedUserServiceObserver& observer : observer_list_)
     observer.OnURLFilterChanged();
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  if (!AreWebFilterPrefsDefault(profile_->GetPrefs()))
+    url_filter_.ReportManagedSiteListMetrics();
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 void SupervisedUserService::UpdateManualURLs() {
@@ -740,6 +785,11 @@
 
   for (SupervisedUserServiceObserver& observer : observer_list_)
     observer.OnURLFilterChanged();
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  if (!AreWebFilterPrefsDefault(profile_->GetPrefs()))
+    url_filter_.ReportManagedSiteListMetrics();
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 void SupervisedUserService::Shutdown() {
diff --git a/chrome/browser/supervised_user/supervised_user_service.h b/chrome/browser/supervised_user/supervised_user_service.h
index d106990..4faab14 100644
--- a/chrome/browser/supervised_user/supervised_user_service.h
+++ b/chrome/browser/supervised_user/supervised_user_service.h
@@ -18,6 +18,7 @@
 #include "base/observer_list.h"
 #include "base/scoped_observation.h"
 #include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/net/file_downloader.h"
 #include "chrome/browser/supervised_user/supervised_user_denylist.h"
 #include "chrome/browser/supervised_user/supervised_user_url_filter.h"
@@ -220,9 +221,11 @@
   void RecordExtensionEnablementUmaMetrics(bool enabled) const;
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
 
-  // Returns true if prefs::kDefaultSupervisedUserFilteringBehavior is set to
-  // default value.
-  bool IsFilteringBehaviorPrefDefault() const;
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Reports FamilyUser.WebFilterType and FamilyUser.ManagedSiteList metrics.
+  // Igores reporting when AreWebFilterPrefsDefault() is true.
+  void ReportNonDefaultWebFilterValue() const;
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
  private:
   friend class SupervisedUserServiceExtensionTestBase;
@@ -409,6 +412,16 @@
   bool signout_required_after_supervision_enabled_ = false;
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // When there is change between WebFilterType::kTryToBlockMatureSites and
+  // WebFilterType::kCertainSites, both
+  // prefs::kDefaultSupervisedUserFilteringBehavior and
+  // prefs::kSupervisedUserSafeSites change. Uses this member to avoid duplicate
+  // reports. Initialized in the SetActive().
+  SupervisedUserURLFilter::WebFilterType current_web_filter_type_ =
+      SupervisedUserURLFilter::WebFilterType::kMaxValue;
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
   base::WeakPtrFactory<SupervisedUserService> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(SupervisedUserService);
diff --git a/chrome/browser/supervised_user/supervised_user_url_filter.cc b/chrome/browser/supervised_user/supervised_user_url_filter.cc
index cc1a22e2..0c60f5b 100644
--- a/chrome/browser/supervised_user/supervised_user_url_filter.cc
+++ b/chrome/browser/supervised_user/supervised_user_url_filter.cc
@@ -48,6 +48,10 @@
 #include "extensions/common/extension_urls.h"
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "base/metrics/histogram_functions.h"
+#endif
+
 using content::BrowserThread;
 using net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES;
 using net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES;
@@ -123,6 +127,18 @@
 // accounts.google.com used for login:
 const char kAccountsGoogleUrl[] = "https://accounts.google.com";
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// UMA histogram FamilyUser.WebFilterType
+// Reports WebFilterType which indicates web filter behaviour are used for
+// current Family Link user on Chrome OS.
+constexpr char kWebFilterTypeHistogramName[] = "FamilyUser.WebFilterType";
+
+// UMA histogram FamilyUser.ManualSiteListType
+// Reports ManualSiteListType which indicates approved list and blocked list
+// usage for current Family Link user on Chrome OS.
+constexpr char kManagedSiteListHistogramName[] = "FamilyUser.ManagedSiteList";
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 // This class encapsulates all the state that is required during construction of
 // a new SupervisedUserURLFilter::Contents.
 class FilterBuilder {
@@ -239,6 +255,19 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// static
+const char* SupervisedUserURLFilter::GetWebFilterTypeHistogramNameForTest() {
+  return kWebFilterTypeHistogramName;
+}
+
+// static
+const char* SupervisedUserURLFilter::GetManagedSiteListHistogramNameForTest() {
+  return kManagedSiteListHistogramName;
+}
+
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 // static
 bool SupervisedUserURLFilter::ShouldSkipParentManualAllowlistFiltering(
     content::WebContents* contents) {
@@ -681,12 +710,25 @@
                             : WebFilterType::kAllowAllSites;
 }
 
-SupervisedUserURLFilter::ManagedSiteList
-SupervisedUserURLFilter ::GetManagedSiteList() const {
+void SupervisedUserURLFilter::ReportWebFilterTypeMetrics() const {
+  if (!is_filter_initialized_)
+    return;
+
+  base::UmaHistogramEnumeration(kWebFilterTypeHistogramName,
+                                GetWebFilterType());
+}
+
+void SupervisedUserURLFilter::ReportManagedSiteListMetrics() const {
+  if (!is_filter_initialized_)
+    return;
+
   if (url_map_.empty() && host_map_.empty()) {
-    return ManagedSiteList::kEmpty;
+    base::UmaHistogramEnumeration(kManagedSiteListHistogramName,
+                                  ManagedSiteList::kEmpty);
+    return;
   }
 
+  ManagedSiteList managed_site_list = ManagedSiteList::kMaxValue;
   bool approved_list = false;
   bool blocked_list = false;
   for (const auto& it : url_map_) {
@@ -710,12 +752,19 @@
   }
 
   if (approved_list && blocked_list) {
-    return ManagedSiteList::kBoth;
+    managed_site_list = ManagedSiteList::kBoth;
   } else if (approved_list) {
-    return ManagedSiteList::kApprovedListOnly;
+    managed_site_list = ManagedSiteList::kApprovedListOnly;
   } else {
-    return ManagedSiteList::kBlockedListOnly;
+    managed_site_list = ManagedSiteList::kBlockedListOnly;
   }
+
+  base::UmaHistogramEnumeration(kManagedSiteListHistogramName,
+                                managed_site_list);
+}
+
+void SupervisedUserURLFilter::SetFilterInitialized(bool is_filter_initialized) {
+  is_filter_initialized_ = is_filter_initialized;
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
diff --git a/chrome/browser/supervised_user/supervised_user_url_filter.h b/chrome/browser/supervised_user/supervised_user_url_filter.h
index 0cbbf5c8..eb7bdf8 100644
--- a/chrome/browser/supervised_user/supervised_user_url_filter.h
+++ b/chrome/browser/supervised_user/supervised_user_url_filter.h
@@ -128,6 +128,11 @@
   SupervisedUserURLFilter();
   ~SupervisedUserURLFilter();
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  static const char* GetWebFilterTypeHistogramNameForTest();
+  static const char* GetManagedSiteListHistogramNameForTest();
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
   // Returns true if the parental allowlist/blocklist should be skipped in
   // |contents|. SafeSearch filtering is still applied to |contents|.
   static bool ShouldSkipParentManualAllowlistFiltering(
@@ -242,10 +247,18 @@
       const scoped_refptr<base::TaskRunner>& task_runner);
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  // // Returns WebFilterType for current web filter setting.
   WebFilterType GetWebFilterType() const;
-  // Returns ManagedSiteList for current web filter setting.
-  ManagedSiteList GetManagedSiteList() const;
+
+  // Reports FamilyUser.WebFilterType metrics when `is_filter_initialized_` is
+  // true.
+  void ReportWebFilterTypeMetrics() const;
+
+  // Reports FamilyUser.ManagedSiteList metrics when `is_filter_initialized_` is
+  // true.
+  void ReportManagedSiteListMetrics() const;
+
+  // Set value for `is_filter_initialized_`.
+  void SetFilterInitialized(bool is_filter_initialized);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
  private:
@@ -289,6 +302,10 @@
 
   SEQUENCE_CHECKER(sequence_checker_);
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  bool is_filter_initialized_ = false;
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
   base::WeakPtrFactory<SupervisedUserURLFilter> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(SupervisedUserURLFilter);
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index fe11d991..71c55fc4 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -4203,6 +4203,8 @@
       "views/send_tab_to_self/send_tab_to_self_bubble_view_impl.h",
       "views/send_tab_to_self/send_tab_to_self_icon_view.cc",
       "views/send_tab_to_self/send_tab_to_self_icon_view.h",
+      "views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view.cc",
+      "views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view.h",
       "views/send_tab_to_self/send_tab_to_self_toolbar_button_view.cc",
       "views/send_tab_to_self/send_tab_to_self_toolbar_button_view.h",
       "views/session_crashed_bubble_view.cc",
@@ -4546,6 +4548,15 @@
       ]
     }
 
+    if (is_win || is_mac || (is_linux && !is_chromeos_lacros)) {
+      sources += [
+        "views/web_apps/web_app_url_handler_hover_button.cc",
+        "views/web_apps/web_app_url_handler_hover_button.h",
+        "views/web_apps/web_app_url_handler_intent_picker_dialog_view.cc",
+        "views/web_apps/web_app_url_handler_intent_picker_dialog_view.h",
+      ]
+    }
+
     if (use_aura) {
       # These files can do Gtk+-based theming for builds with gtk enabled.
       if (is_linux || is_chromeos_lacros) {
diff --git a/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutStateProvider.java b/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutStateProvider.java
index bd9a9ac6b..20735887 100644
--- a/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutStateProvider.java
+++ b/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutStateProvider.java
@@ -60,6 +60,13 @@
     boolean isLayoutVisible(@LayoutType int layoutType);
 
     /**
+     * Get the type of the layout that is currently active.
+     * @return The {@link LayoutType} of the active layout.
+     */
+    @LayoutType
+    int getActiveLayoutType();
+
+    /**
      * @param listener Registers {@code listener} for all layout status changes.
      */
     void addObserver(LayoutStateObserver listener);
diff --git a/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutType.java b/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutType.java
index 212219b8..fe26a24f 100644
--- a/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutType.java
+++ b/chrome/browser/ui/android/layouts/java/src/org/chromium/chrome/browser/layouts/LayoutType.java
@@ -13,10 +13,11 @@
  * The type info of the Layout. These types are bit flags, so they can be or-ed together to test for
  * multiple.
  */
-@IntDef({LayoutType.BROWSING, LayoutType.TAB_SWITCHER, LayoutType.TOOLBAR_SWIPE,
+@IntDef({LayoutType.NONE, LayoutType.BROWSING, LayoutType.TAB_SWITCHER, LayoutType.TOOLBAR_SWIPE,
         LayoutType.SIMPLE_ANIMATION})
 @Retention(RetentionPolicy.SOURCE)
 public @interface LayoutType {
+    int NONE = 0;
     int BROWSING = 1;
     int TAB_SWITCHER = 2;
     int TOOLBAR_SWIPE = 4;
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonController.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonController.java
index b1de46c..721eb21 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonController.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonController.java
@@ -203,7 +203,7 @@
     }
 
     private boolean shouldShowVoiceButton(Tab tab) {
-        if (!FeatureList.isInitialized() || !isFeatureEnabled() || tab == null || tab.isIncognito()
+        if (!isToolbarMicEnabled() || tab == null || tab.isIncognito()
                 || !mVoiceSearchDelegate.isVoiceSearchEnabled()) {
             return false;
         }
@@ -220,7 +220,9 @@
         return UrlUtilities.isHttpOrHttps(tab.getUrl());
     }
 
-    private static boolean isFeatureEnabled() {
+    /** Returns whether the feature flags allow showing the mic icon in the toolbar. */
+    public static boolean isToolbarMicEnabled() {
+        if (!FeatureList.isInitialized()) return false;
         if (AdaptiveToolbarFeatures.isEnabled()) {
             return AdaptiveToolbarFeatures.getSingleVariantMode()
                     == AdaptiveToolbarButtonVariant.VOICE;
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerUnitTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerUnitTest.java
index cc7a0da..3094b84 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerUnitTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/VoiceToolbarButtonControllerUnitTest.java
@@ -84,8 +84,7 @@
         doReturn(mUrl).when(mTab).getUrl();
 
         doReturn(mContext).when(mTab).getContext();
-
-        CachedFeatureFlags.setForTesting(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR, false);
+        AdaptiveToolbarFeatures.clearParsedParamsForTesting();
         // clang-format off
         mVoiceToolbarButtonController = new VoiceToolbarButtonController(mContext, mDrawable,
                 () -> mTab, mActivityLifecycleDispatcher, mModalDialogManager,
@@ -97,7 +96,9 @@
     @DisableFeatures({ChromeFeatureList.TOOLBAR_MIC_IPH_ANDROID})
     @Test
     public void onConfigurationChanged_screenWidthChanged() {
+        CachedFeatureFlags.setForTesting(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR, false);
         AdaptiveToolbarFeatures.MODE_PARAM.setForTesting(AdaptiveToolbarFeatures.ALWAYS_NONE);
+
         assertTrue(mVoiceToolbarButtonController.get(mTab).canShow());
 
         // Screen width shrinks below the threshold (e.g. screen rotated).
@@ -120,6 +121,7 @@
     @Test
     public void
     testIPHCommandHelper() {
+        CachedFeatureFlags.setForTesting(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR, false);
         AdaptiveToolbarFeatures.MODE_PARAM.setForTesting(AdaptiveToolbarFeatures.ALWAYS_NONE);
         assertNull(mVoiceToolbarButtonController.get(/*tab*/ null)
                            .getButtonSpec()
@@ -128,9 +130,34 @@
         // Verify that IPHCommandBuilder is set just once;
         IPHCommandBuilder builder =
                 mVoiceToolbarButtonController.get(mTab).getButtonSpec().getIPHCommandBuilder();
+
         assertNotNull(
                 mVoiceToolbarButtonController.get(mTab).getButtonSpec().getIPHCommandBuilder());
         assertEquals(builder,
                 mVoiceToolbarButtonController.get(mTab).getButtonSpec().getIPHCommandBuilder());
     }
+
+    @Test
+    public void isToolbarMicEnabled_adaptiveButtons_nonVoice() {
+        CachedFeatureFlags.setForTesting(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR, true);
+        AdaptiveToolbarFeatures.MODE_PARAM.setForTesting(AdaptiveToolbarFeatures.ALWAYS_SHARE);
+
+        assertFalse(VoiceToolbarButtonController.isToolbarMicEnabled());
+    }
+
+    @Test
+    public void isToolbarMicEnabled_adaptiveButtons_voice() {
+        CachedFeatureFlags.setForTesting(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR, true);
+        AdaptiveToolbarFeatures.MODE_PARAM.setForTesting(AdaptiveToolbarFeatures.ALWAYS_VOICE);
+
+        assertTrue(VoiceToolbarButtonController.isToolbarMicEnabled());
+    }
+
+    @EnableFeatures({ChromeFeatureList.VOICE_BUTTON_IN_TOP_TOOLBAR})
+    @Test
+    public void isToolbarMicEnabled_toolbarMic() {
+        CachedFeatureFlags.setForTesting(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR, false);
+
+        assertTrue(VoiceToolbarButtonController.isToolbarMicEnabled());
+    }
 }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java
index 2d199ee..f4ec1fe 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java
@@ -85,6 +85,9 @@
         mModel = model;
         mIsVisibilityManuallyControlled = manualVisibilityControl;
 
+        mIsOnValidLayout = (mLayoutStateProvider.getActiveLayoutType() & layoutsToShowOn) > 0;
+        updateVisibility();
+
         mSceneChangeObserver = new LayoutStateObserver() {
             @Override
             public void onStartedShowing(@LayoutType int layout, boolean showToolbar) {
diff --git a/chrome/browser/ui/ash/projector/projector_client_impl.cc b/chrome/browser/ui/ash/projector/projector_client_impl.cc
index fc08166..e8d6403 100644
--- a/chrome/browser/ui/ash/projector/projector_client_impl.cc
+++ b/chrome/browser/ui/ash/projector/projector_client_impl.cc
@@ -73,18 +73,9 @@
 void ProjectorClientImpl::OnSpeechResult(
     const std::u16string& text,
     bool is_final,
-    const absl::optional<SpeechRecognizerDelegate::TranscriptTiming>& timing) {
-  DCHECK(timing.has_value() || ShouldUseWebSpeechFallback());
-
-  if (timing.has_value()) {
-    ash::ProjectorController::Get()->OnTranscription(
-        text, timing->audio_start_time, timing->audio_end_time,
-        timing->word_offsets, is_final);
-  } else {
-    // This is only used for development.
-    ash::ProjectorController::Get()->OnTranscription(
-        text, absl::nullopt, absl::nullopt, absl::nullopt, is_final);
-  }
+    const absl::optional<media::SpeechRecognitionResult>& full_result) {
+  DCHECK(full_result.has_value());
+  ash::ProjectorController::Get()->OnTranscription(full_result.value());
 }
 
 void ProjectorClientImpl::OnSpeechRecognitionStateChanged(
diff --git a/chrome/browser/ui/ash/projector/projector_client_impl.h b/chrome/browser/ui/ash/projector/projector_client_impl.h
index 0267575..26b26987 100644
--- a/chrome/browser/ui/ash/projector/projector_client_impl.h
+++ b/chrome/browser/ui/ash/projector/projector_client_impl.h
@@ -39,8 +39,7 @@
   void OnSpeechResult(
       const std::u16string& text,
       bool is_final,
-      const absl::optional<SpeechRecognizerDelegate::TranscriptTiming>& timing)
-      override;
+      const absl::optional<media::SpeechRecognitionResult>& timing) override;
   // This class is not utilizing the information about sound level.
   void OnSpeechSoundLevelChanged(int16_t level) override {}
   void OnSpeechRecognitionStateChanged(
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc b/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc
index fb66c63..0e5e189 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc
+++ b/chrome/browser/ui/ash/wallpaper_controller_client_impl.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/apps/app_service/launch_utils.h"
+#include "chrome/browser/ash/backdrop_wallpaper_handlers/backdrop_wallpaper.pb.h"
 #include "chrome/browser/ash/customization/customization_wallpaper_util.h"
 #include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/browser_process.h"
@@ -48,6 +49,7 @@
 #include "ui/display/screen.h"
 #include "url/gurl.h"
 
+using backdrop_wallpaper_handlers::SurpriseMeImageFetcher;
 using extension_misc::kWallpaperManagerId;
 
 namespace {
@@ -636,6 +638,16 @@
           storage_weak_factory_.GetWeakPtr(), task_runner));
 }
 
+void WallpaperControllerClientImpl::FetchDailyRefreshWallpaper(
+    const std::string& collection_id,
+    DailyWallpaperUrlFetchedCallback callback) {
+  surprise_me_image_fetcher_ = std::make_unique<SurpriseMeImageFetcher>(
+      collection_id, /*resume_token=*/std::string());
+  surprise_me_image_fetcher_->Start(
+      base::BindOnce(&WallpaperControllerClientImpl::OnDailyImageInfoFetched,
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
 bool WallpaperControllerClientImpl::ShouldShowUserNamesOnLogin() const {
   bool show_user_names = true;
   ash::CrosSettings::Get()->GetBoolean(
@@ -666,3 +678,16 @@
     const std::string& collection_id) {
   wallpaper_controller_->SetDailyRefreshCollectionId(collection_id);
 }
+
+void WallpaperControllerClientImpl::OnDailyImageInfoFetched(
+    DailyWallpaperUrlFetchedCallback callback,
+    bool success,
+    const backdrop::Image& image,
+    const std::string& next_resume_token) {
+  if (success) {
+    std::move(callback).Run(image.image_url());
+  } else {
+    std::move(callback).Run(std::string());
+  }
+  surprise_me_image_fetcher_.reset();
+}
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client_impl.h b/chrome/browser/ui/ash/wallpaper_controller_client_impl.h
index b8d8fbf..83b8705 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client_impl.h
+++ b/chrome/browser/ui/ash/wallpaper_controller_client_impl.h
@@ -11,6 +11,7 @@
 #include "ash/public/cpp/wallpaper_controller_client.h"
 #include "ash/public/cpp/wallpaper_types.h"
 #include "base/macros.h"
+#include "chrome/browser/ash/backdrop_wallpaper_handlers/backdrop_wallpaper_handlers.h"
 #include "chrome/browser/ash/settings/cros_settings.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "url/gurl.h"
@@ -53,6 +54,9 @@
   void SetDefaultWallpaper(const AccountId& account_id,
                            bool show_wallpaper) override;
   void MigrateCollectionIdFromChromeApp() override;
+  void FetchDailyRefreshWallpaper(
+      const std::string& collection_id,
+      DailyWallpaperUrlFetchedCallback callback) override;
 
   // Wrappers around the ash::WallpaperController interface.
   void SetCustomWallpaper(const AccountId& account_id,
@@ -144,6 +148,11 @@
   // Passes |collection_id| to wallpaper controller on main task runner.
   void SetDailyRefreshCollectionId(const std::string& collection_id);
 
+  void OnDailyImageInfoFetched(DailyWallpaperUrlFetchedCallback callback,
+                               bool success,
+                               const backdrop::Image& image,
+                               const std::string& next_resume_token);
+
   // WallpaperController interface in ash.
   ash::WallpaperController* wallpaper_controller_;
 
@@ -157,6 +166,9 @@
   // wallpaper should be shown.
   base::CallbackListSubscription show_user_names_on_signin_subscription_;
 
+  std::unique_ptr<backdrop_wallpaper_handlers::SurpriseMeImageFetcher>
+      surprise_me_image_fetcher_;
+
   base::WeakPtrFactory<WallpaperControllerClientImpl> weak_factory_{this};
   base::WeakPtrFactory<WallpaperControllerClientImpl> storage_weak_factory_{
       this};
diff --git a/chrome/browser/ui/browser_dialogs.h b/chrome/browser/ui/browser_dialogs.h
index 70b55231..ab3912f 100644
--- a/chrome/browser/ui/browser_dialogs.h
+++ b/chrome/browser/ui/browser_dialogs.h
@@ -22,6 +22,11 @@
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/native_widget_types.h"
 
+#if defined(OS_WIN) || defined(OS_MAC) || \
+    (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
+#include "chrome/browser/web_applications/components/web_app_id.h"
+#endif
+
 class Browser;
 class GURL;
 class LoginHandler;
@@ -70,6 +75,13 @@
 struct SelectedFileInfo;
 }  // namespace ui
 
+#if defined(OS_WIN) || defined(OS_MAC) || \
+    (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
+namespace web_app {
+struct UrlHandlerLaunchParams;
+}
+#endif
+
 namespace chrome {
 
 // Shows or hides the Task Manager. |browser| can be NULL when called from Ash.
@@ -137,6 +149,25 @@
     WebAppProtocolHandlerAcceptanceCallback close_callback);
 #endif
 
+#if defined(OS_WIN) || defined(OS_MAC) || \
+    (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
+// Callback that runs when the Web App URL Handler Intent Picker dialog is
+// closed. `accepted` is true when the dialog is accepted, false otherwise.
+// `launch_params` contains information of the app that is selected to open by
+// the user. It is null when the user selects to open the browser.
+using WebAppUrlHandlerAcceptanceCallback = base::OnceCallback<void(
+    bool accepted,
+    absl::optional<web_app::UrlHandlerLaunchParams> launch_params)>;
+
+// Shows the Web App URL Handler Intent Picker dialog and runs
+// `dialog_close_callback` on closure with the dialog acceptance status and
+// information of the user-selected app. `launch_params_list` contains
+// information of all the apps to show.
+void ShowWebAppUrlHandlerIntentPickerDialog(
+    std::vector<web_app::UrlHandlerLaunchParams> launch_params_list,
+    WebAppUrlHandlerAcceptanceCallback dialog_close_callback);
+#endif
+
 // Sets whether |ShowWebAppDialog| should accept immediately without any
 // user interaction. |auto_open_in_window| sets whether the open in window
 // checkbox is checked.
diff --git a/chrome/browser/ui/caption_bubble_controller.h b/chrome/browser/ui/caption_bubble_controller.h
index 9d79285..3780bf3b 100644
--- a/chrome/browser/ui/caption_bubble_controller.h
+++ b/chrome/browser/ui/caption_bubble_controller.h
@@ -39,7 +39,7 @@
   // Transcriptions will halt if this returns false.
   virtual bool OnTranscription(
       LiveCaptionSpeechRecognitionHost* live_caption_speech_recognition_host,
-      const media::mojom::SpeechRecognitionResultPtr& result) = 0;
+      const media::SpeechRecognitionResult& result) = 0;
 
   // Called when the speech service has an error.
   virtual void OnError(LiveCaptionSpeechRecognitionHost*
diff --git a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_toolbar_button_controller.cc b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_toolbar_button_controller.cc
index f852b6d..b66476c 100644
--- a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_toolbar_button_controller.cc
+++ b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_toolbar_button_controller.cc
@@ -16,7 +16,10 @@
 
 void SendTabToSelfToolbarButtonController::DisplayNewEntries(
     const std::vector<const SendTabToSelfEntry*>& new_entries) {
-  ShowToolbarButton();
+  // TODO(crbug/1206381): Any entries that were never shown are lost.
+  // This is consistent with current behavior and we don't have UI for
+  // showing multiple entries with this iteration.
+  ShowToolbarButton(*new_entries.at(0));
 }
 
 void SendTabToSelfToolbarButtonController::DismissEntries(
@@ -24,14 +27,12 @@
   NOTIMPLEMENTED();
 }
 
-void SendTabToSelfToolbarButtonController::ShowToolbarButton() {
+void SendTabToSelfToolbarButtonController::ShowToolbarButton(
+    const SendTabToSelfEntry& entry) {
   if (!delegate_)
     return;
 
-  if (delegate_display_state_ != DisplayState::kShown) {
-    delegate_->Show();
-    delegate_display_state_ = DisplayState::kShown;
-  }
+  delegate_->Show(entry);
 }
 
 void SendTabToSelfToolbarButtonController::SetDelegate(
@@ -39,10 +40,6 @@
   delegate_ = delegate;
 }
 
-void SendTabToSelfToolbarButtonController::UpdateToolbarButtonState() {
-  NOTIMPLEMENTED();
-}
-
 SendTabToSelfToolbarButtonController::~SendTabToSelfToolbarButtonController() =
     default;
 
diff --git a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_toolbar_button_controller.h b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_toolbar_button_controller.h
index ced31c4..c67462a 100644
--- a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_toolbar_button_controller.h
+++ b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_toolbar_button_controller.h
@@ -32,28 +32,16 @@
           new_entries) override;
   void DismissEntries(const std::vector<std::string>& guids) override;
 
-  void ShowToolbarButton();
+  void ShowToolbarButton(const SendTabToSelfEntry& entry);
 
   void SetDelegate(SendTabToSelfToolbarButtonControllerDelegate* delegate);
 
   Profile* profile() const { return profile_; }
 
  private:
-  // Tracks the current display state of the toolbar button delegate.
-  enum class DisplayState {
-    kShown,
-    kHidden,
-  };
-
-  void UpdateToolbarButtonState();
-
   Profile* profile_;
 
   SendTabToSelfToolbarButtonControllerDelegate* delegate_;
-
-  // The delegate starts hidden and isn't shown until a STTS
-  // notification is received.
-  DisplayState delegate_display_state_ = DisplayState::kHidden;
 };
 
 }  // namespace send_tab_to_self
diff --git a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_toolbar_button_controller_delegate.h b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_toolbar_button_controller_delegate.h
index 69f0643..734afa1 100644
--- a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_toolbar_button_controller_delegate.h
+++ b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_toolbar_button_controller_delegate.h
@@ -7,12 +7,13 @@
 
 namespace send_tab_to_self {
 
+class SendTabToSelfEntry;
+
 // Delegate for SendTabToSelfToolbarButtonController that is told when to show
 // by the controller.
 class SendTabToSelfToolbarButtonControllerDelegate {
  public:
-  virtual void Show() = 0;
-  virtual void Hide() = 0;
+  virtual void Show(const SendTabToSelfEntry& entry) = 0;
 
  protected:
   virtual ~SendTabToSelfToolbarButtonControllerDelegate() = default;
diff --git a/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc b/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc
index ce8f4c0..2a30ffc 100644
--- a/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc
+++ b/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.cc
@@ -8,7 +8,6 @@
 
 #include "base/callback.h"
 #include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.h"
 
 DiceWebSigninInterceptorDelegate::DiceWebSigninInterceptorDelegate() = default;
 
@@ -32,24 +31,3 @@
     Browser* browser) {
   ShowProfileCustomizationBubbleInternal(browser);
 }
-
-void DiceWebSigninInterceptorDelegate::ShowEnterpriseProfileInterceptionDialog(
-    const std::string& email,
-    base::OnceCallback<void(bool)> callback,
-    Browser* browser) {
-  // TODO (crbug/1163117): Replace this temporary solution with the spaces
-  // enterprise welcome screen inside a dialog.
-  DiceTurnSyncOnHelper::Delegate::ShowEnterpriseAccountConfirmationForBrowser(
-      email, true,
-      base::BindOnce(
-          [](base::OnceCallback<void(bool)> callback,
-             DiceTurnSyncOnHelper::SigninChoice choice) {
-            std::move(callback).Run(
-                choice == DiceTurnSyncOnHelper::SigninChoice::
-                              SIGNIN_CHOICE_CONTINUE ||
-                choice == DiceTurnSyncOnHelper::SigninChoice::
-                              SIGNIN_CHOICE_NEW_PROFILE);
-          },
-          std::move(callback)),
-      browser);
-}
diff --git a/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h b/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h
index 1b0be3bd..b796032 100644
--- a/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h
+++ b/chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h
@@ -29,11 +29,6 @@
       base::OnceCallback<void(SigninInterceptionResult)> callback) override;
   void ShowProfileCustomizationBubble(Browser* browser) override;
 
-  void ShowEnterpriseProfileInterceptionDialog(
-      const std::string& email,
-      base::OnceCallback<void(bool)> callback,
-      Browser* browser) override;
-
  private:
   // Implemented in dice_web_signin_interception_bubble_view.cc
   std::unique_ptr<ScopedDiceWebSigninInterceptionBubbleHandle>
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc
index 0d4fc22..dd91c15 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -470,6 +470,35 @@
   }
   return false;
 }
+
+#if defined(OS_WIN) || (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
+bool MaybeLaunchUrlHandlerWebAppFromCmd(
+    const base::CommandLine& command_line,
+    const base::FilePath& cur_dir,
+    bool process_startup,
+    Profile* last_used_profile,
+    const std::vector<Profile*>& last_opened_profiles) {
+  auto on_urls_unhandled_cb = base::BindOnce(
+      [](const base::CommandLine& command_line, const base::FilePath& cur_dir,
+         bool process_startup, Profile* last_used_profile,
+         const std::vector<Profile*>& last_opened_profiles) {
+        // TODO(crbug.com/1208199): Refactor StartupBrowserCreator and use the
+        // state struct here.
+        StartupBrowserCreator startup_browser_creator;
+        startup_browser_creator.LaunchBrowserForLastProfiles(
+            command_line, cur_dir, process_startup, last_used_profile,
+            last_opened_profiles);
+      },
+      command_line, cur_dir, process_startup, last_used_profile,
+      last_opened_profiles);
+
+  return web_app::startup::MaybeLaunchUrlHandlerWebAppFromCmd(
+      command_line, cur_dir, last_used_profile, std::move(on_urls_unhandled_cb),
+      base::BindOnce(&FinalizeWebAppLaunch,
+                     std::make_unique<LaunchModeRecorder>()));
+}
+#endif
+
 }  // namespace
 
 StartupBrowserCreator::StartupBrowserCreator() = default;
@@ -573,6 +602,89 @@
   return true;
 }
 
+bool StartupBrowserCreator::LaunchBrowserForLastProfiles(
+    const base::CommandLine& command_line,
+    const base::FilePath& cur_dir,
+    bool process_startup,
+    Profile* last_used_profile,
+    const Profiles& last_opened_profiles) {
+  chrome::startup::IsProcessStartup is_process_startup =
+      process_startup ? chrome::startup::IS_PROCESS_STARTUP
+                      : chrome::startup::IS_NOT_PROCESS_STARTUP;
+  chrome::startup::IsFirstRun is_first_run =
+      first_run::IsChromeFirstRun() ? chrome::startup::IS_FIRST_RUN
+                                    : chrome::startup::IS_NOT_FIRST_RUN;
+
+  // On Windows, when chrome is launched by notification activation where the
+  // kNotificationLaunchId switch is used, always use |last_used_profile| which
+  // contains the profile id extracted from the notification launch id.
+  bool was_windows_notification_launch = false;
+#if defined(OS_WIN)
+  was_windows_notification_launch =
+      command_line.HasSwitch(switches::kNotificationLaunchId);
+#endif  // defined(OS_WIN)
+
+  // TODO(crbug.com/1150326) Calling ShouldShowProfilePickerAtProcessLaunch()
+  // a second time here duplicates the logic to show the profile picker. The
+  // decision to show the picker should instead be on the previous call to
+  // ShouldShowProfilePickerAtProcessLaunch() issued from
+  // GetStartupProfilePath().
+  // Ephemeral guest is added here just for symmetry, once we use other ways to
+  // indicate that picker should get opened, we can remove both IsGuestSession()
+  // and IsEphemeralGuestProfile().
+  if (ShouldShowProfilePickerAtProcessLaunch(
+          g_browser_process->profile_manager(), command_line) &&
+      last_used_profile &&
+      (last_used_profile->IsGuestSession() ||
+       last_used_profile->IsEphemeralGuestProfile())) {
+    // The guest session is used to indicate the the profile picker should be
+    // displayed on start-up. See GetStartupProfilePath().
+    ShowProfilePicker(/*is_process_startup=*/process_startup);
+    return true;
+  }
+
+  // |last_opened_profiles| will be empty in the following circumstances:
+  // - This is the first launch. |last_used_profile| is the initial profile.
+  // - The user exited the browser by closing all windows for all profiles.
+  //   |last_used_profile| is the profile which owned the last open window.
+  // - Only incognito windows were open when the browser exited.
+  //   |last_used_profile| is the last used incognito profile. Restoring it will
+  //   create a browser window for the corresponding original profile.
+  // - All of the last opened profiles fail to initialize.
+  if (last_opened_profiles.empty() || was_windows_notification_launch) {
+    if (CanOpenProfileOnStartup(last_used_profile)) {
+      Profile* profile_to_open = last_used_profile->IsGuestSession()
+                                     ? last_used_profile->GetPrimaryOTRProfile(
+                                           /*create_if_needed=*/true)
+                                     : last_used_profile;
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+      if (process_startup) {
+        // If FullRestoreService is available for the profile (i.e. the full
+        // restore feature is enabled and the profile is a regular user
+        // profile), defer the browser launching to FullRestoreService code.
+        auto* full_restore_service =
+            chromeos::full_restore::FullRestoreService::GetForProfile(
+                profile_to_open);
+        if (full_restore_service) {
+          full_restore_service->LaunchBrowserWhenReady();
+          return true;
+        }
+      }
+#endif
+      return LaunchBrowser(command_line, profile_to_open, cur_dir,
+                           is_process_startup, is_first_run,
+                           std::make_unique<LaunchModeRecorder>());
+    }
+
+    // Show ProfilePicker if |last_used_profile| can't be auto opened.
+    ShowProfilePicker(/*is_process_startup=*/process_startup);
+    return true;
+  }
+  return ProcessLastOpenedProfiles(command_line, cur_dir, is_process_startup,
+                                   is_first_run, last_used_profile,
+                                   last_opened_profiles);
+}
+
 // static
 bool StartupBrowserCreator::WasRestarted() {
   // Stores the value of the preference kWasRestarted had when it was read.
@@ -705,18 +817,18 @@
 #endif  // defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
 }
 
+#if defined(OS_MAC)
 // static
-bool StartupBrowserCreator::MaybeHandleProfileAgnosticUrls(
-    const std::vector<GURL>& urls) {
+void StartupBrowserCreator::MaybeHandleProfileAgnosticUrls(
+    const std::vector<GURL>& urls,
+    base::OnceCallback<void()> on_urls_unhandled_cb) {
   // Web app URL handling.
-#if defined(OS_WIN) || defined(OS_MAC) || \
-    (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
-  return web_app::startup::MaybeLaunchUrlHandlerWebAppFromUrls(
-      urls, base::BindOnce(&FinalizeWebAppLaunch,
-                           std::make_unique<LaunchModeRecorder>()));
-#endif
-  return false;
+  web_app::startup::MaybeLaunchUrlHandlerWebAppFromUrls(
+      urls, std::move(on_urls_unhandled_cb),
+      base::BindOnce(&FinalizeWebAppLaunch,
+                     std::make_unique<LaunchModeRecorder>()));
 }
+#endif  // defined(OS_MAC)
 
 bool StartupBrowserCreator::ProcessCmdLineImpl(
     const base::CommandLine& command_line,
@@ -1031,12 +1143,10 @@
   }
 
   // Web app URL handling.
-#if defined(OS_WIN) || defined(OS_MAC) || \
-    (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
-  if (web_app::startup::MaybeLaunchUrlHandlerWebAppFromCmd(
-          command_line, cur_dir,
-          base::BindOnce(&FinalizeWebAppLaunch,
-                         std::make_unique<LaunchModeRecorder>()))) {
+#if defined(OS_WIN) || (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
+  if (MaybeLaunchUrlHandlerWebAppFromCmd(command_line, cur_dir, process_startup,
+                                         last_used_profile,
+                                         last_opened_profiles)) {
     return true;
   }
 #endif
@@ -1045,89 +1155,6 @@
                                       last_used_profile, last_opened_profiles);
 }
 
-bool StartupBrowserCreator::LaunchBrowserForLastProfiles(
-    const base::CommandLine& command_line,
-    const base::FilePath& cur_dir,
-    bool process_startup,
-    Profile* last_used_profile,
-    const Profiles& last_opened_profiles) {
-  chrome::startup::IsProcessStartup is_process_startup =
-      process_startup ? chrome::startup::IS_PROCESS_STARTUP
-                      : chrome::startup::IS_NOT_PROCESS_STARTUP;
-  chrome::startup::IsFirstRun is_first_run =
-      first_run::IsChromeFirstRun() ? chrome::startup::IS_FIRST_RUN
-                                    : chrome::startup::IS_NOT_FIRST_RUN;
-
-  // On Windows, when chrome is launched by notification activation where the
-  // kNotificationLaunchId switch is used, always use |last_used_profile| which
-  // contains the profile id extracted from the notification launch id.
-  bool was_windows_notification_launch = false;
-#if defined(OS_WIN)
-  was_windows_notification_launch =
-      command_line.HasSwitch(switches::kNotificationLaunchId);
-#endif  // defined(OS_WIN)
-
-  // TODO(crbug.com/1150326) Calling ShouldShowProfilePickerAtProcessLaunch()
-  // a second time here duplicates the logic to show the profile picker. The
-  // decision to show the picker should instead be on the previous call to
-  // ShouldShowProfilePickerAtProcessLaunch() issued from
-  // GetStartupProfilePath().
-  // Ephemeral guest is added here just for symmetry, once we use other ways to
-  // indicate that picker should get opened, we can remove both IsGuestSession()
-  // and IsEphemeralGuestProfile().
-  if (ShouldShowProfilePickerAtProcessLaunch(
-          g_browser_process->profile_manager(), command_line) &&
-      last_used_profile &&
-      (last_used_profile->IsGuestSession() ||
-       last_used_profile->IsEphemeralGuestProfile())) {
-    // The guest session is used to indicate the the profile picker should be
-    // displayed on start-up. See GetStartupProfilePath().
-    ShowProfilePicker(/*is_process_startup=*/process_startup);
-    return true;
-  }
-
-  // |last_opened_profiles| will be empty in the following circumstances:
-  // - This is the first launch. |last_used_profile| is the initial profile.
-  // - The user exited the browser by closing all windows for all profiles.
-  //   |last_used_profile| is the profile which owned the last open window.
-  // - Only incognito windows were open when the browser exited.
-  //   |last_used_profile| is the last used incognito profile. Restoring it will
-  //   create a browser window for the corresponding original profile.
-  // - All of the last opened profiles fail to initialize.
-  if (last_opened_profiles.empty() || was_windows_notification_launch) {
-    if (CanOpenProfileOnStartup(last_used_profile)) {
-      Profile* profile_to_open = last_used_profile->IsGuestSession()
-                                     ? last_used_profile->GetPrimaryOTRProfile(
-                                           /*create_if_needed=*/true)
-                                     : last_used_profile;
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-      if (process_startup) {
-        // If FullRestoreService is available for the profile (i.e. the full
-        // restore feature is enabled and the profile is a regular user
-        // profile), defer the browser launching to FullRestoreService code.
-        auto* full_restore_service =
-            chromeos::full_restore::FullRestoreService::GetForProfile(
-                profile_to_open);
-        if (full_restore_service) {
-          full_restore_service->LaunchBrowserWhenReady();
-          return true;
-        }
-      }
-#endif
-      return LaunchBrowser(command_line, profile_to_open, cur_dir,
-                           is_process_startup, is_first_run,
-                           std::make_unique<LaunchModeRecorder>());
-    }
-
-    // Show ProfilePicker if |last_used_profile| can't be auto opened.
-    ShowProfilePicker(/*is_process_startup=*/process_startup);
-    return true;
-  }
-  return ProcessLastOpenedProfiles(command_line, cur_dir, is_process_startup,
-                                   is_first_run, last_used_profile,
-                                   last_opened_profiles);
-}
-
 bool StartupBrowserCreator::ProcessLastOpenedProfiles(
     const base::CommandLine& command_line,
     const base::FilePath& cur_dir,
diff --git a/chrome/browser/ui/startup/startup_browser_creator.h b/chrome/browser/ui/startup/startup_browser_creator.h
index a288b33..c348a48 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.h
+++ b/chrome/browser/ui/startup/startup_browser_creator.h
@@ -10,6 +10,7 @@
 
 #include "base/files/file_path.h"
 #include "base/gtest_prod_util.h"
+#include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/prefs/session_startup_pref.h"
 #include "chrome/browser/profiles/profile.h"
@@ -93,6 +94,15 @@
                      chrome::startup::IsFirstRun is_first_run,
                      std::unique_ptr<LaunchModeRecorder> launch_mode_recorder);
 
+  // Launch browser for `last_opened_profiles` if it's not empty. Otherwise,
+  // launch browser for `last_used_profile`. Return false if any browser is
+  // failed to be launched. Otherwise, return true.
+  bool LaunchBrowserForLastProfiles(const base::CommandLine& command_line,
+                                    const base::FilePath& cur_dir,
+                                    bool process_startup,
+                                    Profile* last_used_profile,
+                                    const Profiles& last_opened_profiles);
+
   // If Incognito or Guest mode are requested by policy or command line returns
   // the appropriate private browsing profile. Otherwise returns |profile|.
   Profile* GetPrivateProfileIfRequested(const base::CommandLine& command_line,
@@ -113,8 +123,14 @@
   static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
-  // Return true if |urls| are handled, false otherwise.
-  static bool MaybeHandleProfileAgnosticUrls(const std::vector<GURL>& urls);
+#if defined(OS_MAC)
+  // Searches for web apps to handle `urls` and prompts the user to pick one.
+  // Runs `on_urls_unhandled_cb` (either synchronously or asynchronously) if no
+  // web app is found or selected to open `urls`.
+  static void MaybeHandleProfileAgnosticUrls(
+      const std::vector<GURL>& urls,
+      base::OnceClosure on_urls_unhandled_cb);
+#endif
 
  private:
   friend class CloudPrintProxyPolicyTest;
@@ -153,15 +169,6 @@
                           Profile* last_used_profile,
                           const Profiles& last_opened_profiles);
 
-  // Launch browser for |last_opened_profiles| if it's not empty. Otherwise,
-  // launch browser for |last_used_profile|. Return false if any browser is
-  // failed to be launched. Otherwise, return true.
-  bool LaunchBrowserForLastProfiles(const base::CommandLine& command_line,
-                                    const base::FilePath& cur_dir,
-                                    bool process_startup,
-                                    Profile* last_used_profile,
-                                    const Profiles& last_opened_profiles);
-
   // Launch the |last_used_profile| with the full command line, and the other
   // |last_opened_profiles| without the URLs to launch. Return false if any
   // browser is failed to be launched. Otherwise, return true.
diff --git a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
index 73711f1..e5f8a590 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_browsertest.cc
@@ -120,7 +120,11 @@
 #include "chrome/browser/web_applications/test/fake_web_app_origin_association_manager.h"
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "components/services/app_service/public/cpp/url_handler_info.h"
+#include "extensions/browser/extension_dialog_auto_confirm.h"
 #include "third_party/blink/public/common/features.h"
+#include "ui/views/test/dialog_test.h"
+#include "ui/views/widget/any_widget_observer.h"
+#include "ui/views/widget/widget.h"
 #endif
 
 using testing::Return;
@@ -269,6 +273,15 @@
     std::move(quit_closure_).Run();
 }
 
+#if defined(OS_WIN) || defined(OS_MAC) || \
+    (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
+void AutoCloseDialog(views::Widget* widget) {
+  // Call CancelDialog to close the dialog, but the actual behavior will be
+  // determined by the ScopedTestDialogAutoConfirm configs.
+  views::test::CancelDialog(widget);
+}
+#endif
+
 }  // namespace
 
 class StartupBrowserCreatorTest : public extensions::ExtensionBrowserTest {
@@ -1665,14 +1678,73 @@
 };
 
 IN_PROC_BROWSER_TEST_F(StartupBrowserWebAppUrlHandlingTest,
-                       WebAppLaunch_InScopeUrl) {
+                       DialogCancelled_NoLaunch) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       "WebAppUrlHandlerIntentPickerView");
+
   apps::UrlHandlerInfo url_handler;
   url_handler.origin = url::Origin::Create(GURL(kStartUrl));
 
   web_app::AppId app_id = InstallWebAppWithUrlHandlers({url_handler});
 
+  SetUpCommandlineAndStart(kStartUrl);
+
+  // The waiter will get the dialog when it shows up and close it.
+  waiter.WaitIfNeededAndGet()->CloseWithReason(
+      views::Widget::ClosedReason::kEscKeyPressed);
+
+  // Wait for browser launch task to complete.
+  content::RunAllTasksUntilIdle();
+
+  // When dialog is closed, nothing will happen.
+  ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile()));
+  ASSERT_FALSE(web_app::AppBrowserController::IsForWebApp(browser(), app_id));
+}
+
+IN_PROC_BROWSER_TEST_F(StartupBrowserWebAppUrlHandlingTest,
+                       DialogAccepted_BrowserLaunch) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       "WebAppUrlHandlerIntentPickerView");
+
+  apps::UrlHandlerInfo url_handler;
+  url_handler.origin = url::Origin::Create(GURL(kStartUrl));
+
+  web_app::AppId app_id = InstallWebAppWithUrlHandlers({url_handler});
+
+  // Select the first choice, which is the browser.
+  extensions::ScopedTestDialogAutoConfirm auto_confirm(
+      extensions::ScopedTestDialogAutoConfirm::ACCEPT_AND_OPTION, 0);
+  SetUpCommandlineAndStart(kStartUrl);
+  AutoCloseDialog(waiter.WaitIfNeededAndGet());
+
+  // Wait for browser launch task to complete.
+  content::RunAllTasksUntilIdle();
+
+  // When dialog is closed, URL will be launched in a browser window.
+  // Check for new browser window.
+  ASSERT_EQ(2u, chrome::GetBrowserCount(browser()->profile()));
+  ASSERT_FALSE(web_app::AppBrowserController::IsForWebApp(browser(), app_id));
+  Browser* other_browser = FindOneOtherBrowser(browser());
+  ASSERT_TRUE(other_browser);
+  ASSERT_FALSE(
+      web_app::AppBrowserController::IsForWebApp(other_browser, app_id));
+}
+
+IN_PROC_BROWSER_TEST_F(StartupBrowserWebAppUrlHandlingTest,
+                       DialogAccepted_WebAppLaunch_InScopeUrl) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       "WebAppUrlHandlerIntentPickerView");
+  apps::UrlHandlerInfo url_handler;
+  url_handler.origin = url::Origin::Create(GURL(kStartUrl));
+
+  web_app::AppId app_id = InstallWebAppWithUrlHandlers({url_handler});
+
+  // Select the second choice, which is the app.
+  extensions::ScopedTestDialogAutoConfirm auto_confirm(
+      extensions::ScopedTestDialogAutoConfirm::ACCEPT_AND_OPTION, 1);
   // kStartUrl is in app scope.
   SetUpCommandlineAndStart(kStartUrl);
+  AutoCloseDialog(waiter.WaitIfNeededAndGet());
 
   // Wait for app launch task to complete.
   content::RunAllTasksUntilIdle();
@@ -1691,13 +1763,19 @@
 }
 
 IN_PROC_BROWSER_TEST_F(StartupBrowserWebAppUrlHandlingTest,
-                       WebAppLaunch_DifferentOriginUrl) {
+                       DialogAccepted_WebAppLaunch_DifferentOriginUrl) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       "WebAppUrlHandlerIntentPickerView");
   apps::UrlHandlerInfo url_handler;
   url_handler.origin = url::Origin::Create(GURL("https://example.com"));
   web_app::AppId app_id = InstallWebAppWithUrlHandlers({url_handler});
 
+  // Select the second choice, which is the app.
+  extensions::ScopedTestDialogAutoConfirm auto_confirm(
+      extensions::ScopedTestDialogAutoConfirm::ACCEPT_AND_OPTION, 1);
   // URL is not in app scope but matches url_handlers of installed app.
   SetUpCommandlineAndStart("https://example.com/abc/def");
+  AutoCloseDialog(waiter.WaitIfNeededAndGet());
 
   // Wait for app launch task to complete.
   content::RunAllTasksUntilIdle();
diff --git a/chrome/browser/ui/startup/web_app_url_handling_startup_utils.cc b/chrome/browser/ui/startup/web_app_url_handling_startup_utils.cc
index b6f8a9b..9423419c 100644
--- a/chrome/browser/ui/startup/web_app_url_handling_startup_utils.cc
+++ b/chrome/browser/ui/startup/web_app_url_handling_startup_utils.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/startup/web_app_url_handling_startup_utils.h"
 
+#include <algorithm>
 #include <utility>
 #include <vector>
 
@@ -13,9 +14,15 @@
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/apps/app_service/browser_app_launcher.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/first_run/first_run.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/startup/startup_browser_creator.h"
+#include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
 #include "chrome/browser/web_applications/components/url_handler_launch_params.h"
 #include "chrome/browser/web_applications/components/url_handler_manager_impl.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -37,37 +44,41 @@
                               std::move(callback));
 }
 
-bool LaunchFirstValidMatch(
+void OnUrlHandlerIntentPickerDialogCompleted(
     const base::CommandLine& command_line,
     const base::FilePath& cur_dir,
-    const std::vector<web_app::UrlHandlerLaunchParams>& url_handler_matches,
-    web_app::startup::FinalizeWebAppLaunchCallback callback) {
-  // Launch the first match for which a Profile can be loaded.
-  // TODO(crbug/1072058): Use WebAppUiManagerImpl and WebAppDialogManager
-  // to display the intent picker dialog. Use the first match here for testing.
-  // TODO(crbug/1072058): Check user preferences before showing intent picker.
-  ProfileManager* profile_manager = g_browser_process->profile_manager();
-  Profile* profile = nullptr;
-  const web_app::UrlHandlerLaunchParams* found_match = nullptr;
-  for (const auto& match : url_handler_matches) {
-    // Do not load profile if profile path is not valid.
-    if (!profile_manager->GetProfileAttributesStorage()
-             .GetProfileAttributesWithPath(match.profile_path)) {
-      continue;
-    }
-    profile = profile_manager->GetProfile(match.profile_path);
-    if (profile) {
-      found_match = &match;
-      break;
-    }
-  }
+    base::OnceClosure on_urls_unhandled_cb,
+    web_app::startup::FinalizeWebAppLaunchCallback app_launched_callback,
+    bool accepted,
+    absl::optional<web_app::UrlHandlerLaunchParams> selected_match) {
+  // Dialog is not accepted. Quit the process and do nothing.
+  if (!accepted)
+    return;
 
-  if (profile && found_match) {
-    LaunchApp(found_match->profile_path, found_match->app_id, command_line,
-              cur_dir, found_match->url, std::move(callback));
-    return true;
+  if (selected_match.has_value()) {
+    // The user has selected an app to handle the URL.
+    LaunchApp(selected_match->profile_path, selected_match->app_id,
+              command_line, cur_dir, selected_match->url,
+              std::move(app_launched_callback));
+  } else {
+    // The user has selected the browser. Open the link in the browser.
+    std::move(on_urls_unhandled_cb).Run();
   }
-  return false;
+}
+
+std::vector<web_app::UrlHandlerLaunchParams> GetValidUrlHandlerMatches(
+    std::vector<web_app::UrlHandlerLaunchParams> url_handler_matches,
+    const base::FilePath& last_used_profile) {
+  // TODO(crbug.com/1200951): Save matches from all valid profiles, not just
+  // the last used profile.
+  url_handler_matches.erase(
+      std::remove_if(
+          url_handler_matches.begin(), url_handler_matches.end(),
+          [&last_used_profile](const web_app::UrlHandlerLaunchParams& match) {
+            return match.profile_path != last_used_profile;
+          }),
+      url_handler_matches.end());
+  return url_handler_matches;
 }
 
 }  // namespace
@@ -75,25 +86,51 @@
 namespace web_app {
 namespace startup {
 
-bool MaybeLaunchUrlHandlerWebAppFromCmd(const base::CommandLine& command_line,
-                                        const base::FilePath& cur_dir,
-                                        FinalizeWebAppLaunchCallback callback) {
-  return LaunchFirstValidMatch(
-      command_line, cur_dir,
+bool MaybeLaunchUrlHandlerWebAppFromCmd(
+    const base::CommandLine& command_line,
+    const base::FilePath& cur_dir,
+    Profile* last_used_profile,
+    base::OnceClosure on_urls_unhandled_cb,
+    FinalizeWebAppLaunchCallback app_launched_callback) {
+  auto valid_matches = GetValidUrlHandlerMatches(
       UrlHandlerManagerImpl::GetUrlHandlerMatches(command_line),
-      std::move(callback));
-}
-
-bool MaybeLaunchUrlHandlerWebAppFromUrls(
-    const std::vector<GURL>& urls,
-    FinalizeWebAppLaunchCallback callback) {
-  if (urls.size() != 1)
+      last_used_profile->GetPath());
+  if (valid_matches.empty())
     return false;
 
-  return LaunchFirstValidMatch(
-      base::CommandLine(base::CommandLine::NO_PROGRAM), base::FilePath(),
+  chrome::ShowWebAppUrlHandlerIntentPickerDialog(
+      std::move(valid_matches),
+      base::BindOnce(&OnUrlHandlerIntentPickerDialogCompleted, command_line,
+                     cur_dir, std::move(on_urls_unhandled_cb),
+                     std::move(app_launched_callback)));
+  return true;
+}
+
+void MaybeLaunchUrlHandlerWebAppFromUrls(
+    const std::vector<GURL>& urls,
+    base::OnceClosure on_urls_unhandled_cb,
+    FinalizeWebAppLaunchCallback app_launched_callback) {
+  if (urls.size() != 1 || !g_browser_process->profile_manager()) {
+    std::move(on_urls_unhandled_cb).Run();
+    return;
+  }
+
+  auto valid_matches = GetValidUrlHandlerMatches(
       UrlHandlerManagerImpl::GetUrlHandlerMatches(urls.front()),
-      std::move(callback));
+      g_browser_process->profile_manager()
+          ->GetLastUsedProfileAllowedByPolicy()
+          ->GetPath());
+  if (valid_matches.empty()) {
+    std::move(on_urls_unhandled_cb).Run();
+    return;
+  }
+
+  chrome::ShowWebAppUrlHandlerIntentPickerDialog(
+      std::move(valid_matches),
+      base::BindOnce(&OnUrlHandlerIntentPickerDialogCompleted,
+                     base::CommandLine(base::CommandLine::NO_PROGRAM),
+                     base::FilePath(), std::move(on_urls_unhandled_cb),
+                     std::move(app_launched_callback)));
 }
 
 }  // namespace startup
diff --git a/chrome/browser/ui/startup/web_app_url_handling_startup_utils.h b/chrome/browser/ui/startup/web_app_url_handling_startup_utils.h
index 56f383c..2d968285 100644
--- a/chrome/browser/ui/startup/web_app_url_handling_startup_utils.h
+++ b/chrome/browser/ui/startup/web_app_url_handling_startup_utils.h
@@ -17,6 +17,7 @@
 
 class Browser;
 class GURL;
+class Profile;
 
 namespace web_app {
 namespace startup {
@@ -25,18 +26,24 @@
     base::OnceCallback<void(Browser* browser,
                             apps::mojom::LaunchContainer container)>;
 
-// If |command_line| contains a single URL argument and that URL matches URL
+// If `command_line` contains a single URL argument and that URL matches URL
 // handling registration from installed web apps, show app options to user and
 // launch one if accepted.
-// Returns true if launching an app, false otherwise.
-bool MaybeLaunchUrlHandlerWebAppFromCmd(const base::CommandLine& command_line,
-                                        const base::FilePath& cur_dir,
-                                        FinalizeWebAppLaunchCallback callback);
+// Returns true if matching web apps are found, false otherwise.
+bool MaybeLaunchUrlHandlerWebAppFromCmd(
+    const base::CommandLine& command_line,
+    const base::FilePath& cur_dir,
+    Profile* last_used_profile,
+    base::OnceClosure on_urls_unhandled_cb,
+    FinalizeWebAppLaunchCallback app_launched_callback);
 
-// Same as MaybeLaunchUrlHandlerWebAppFromCmd but check if |urls| contains a
-// single URL.
-bool MaybeLaunchUrlHandlerWebAppFromUrls(const std::vector<GURL>& urls,
-                                         FinalizeWebAppLaunchCallback callback);
+// Checks if `urls` contains a single URL that can be handled by installed web
+// app URL handlers, show app options to user and launch one if accepted.
+// Otherwise, run `on_urls_unhandled_cb` to open `urls` in the browser.
+void MaybeLaunchUrlHandlerWebAppFromUrls(
+    const std::vector<GURL>& urls,
+    base::OnceClosure on_urls_unhandled_cb,
+    FinalizeWebAppLaunchCallback app_launched_callback);
 
 }  // namespace startup
 }  // namespace web_app
diff --git a/chrome/browser/ui/thumbnails/thumbnail_tab_helper_browsertest.cc b/chrome/browser/ui/thumbnails/thumbnail_tab_helper_browsertest.cc
index 2a4809e..b0292b0c 100644
--- a/chrome/browser/ui/thumbnails/thumbnail_tab_helper_browsertest.cc
+++ b/chrome/browser/ui/thumbnails/thumbnail_tab_helper_browsertest.cc
@@ -167,10 +167,17 @@
 // with ENABLE_SESSION_SERVICE.
 #if BUILDFLAG(ENABLE_SESSION_SERVICE)
 
+// Flaky on Win: https://crbug.com/1211377
+#if defined(OS_WIN)
+#define MAYBE_CapturesRestoredTabWhenRequested \
+  DISABLED_CapturesRestoredTabWhenRequested
+#else
+#define MAYBE_CapturesRestoredTabWhenRequested CapturesRestoredTabWhenRequested
+#endif
 // On browser restore, some tabs may not be loaded. Requesting a
 // thumbnail for one of these tabs should trigger load and capture.
 IN_PROC_BROWSER_TEST_F(ThumbnailTabHelperBrowserTest,
-                       CapturesRestoredTabWhenRequested) {
+                       MAYBE_CapturesRestoredTabWhenRequested) {
   ui_test_utils::NavigateToURLWithDisposition(
       browser(), url2_, WindowOpenDisposition::NEW_WINDOW,
       ui_test_utils::BROWSER_TEST_WAIT_FOR_BROWSER);
diff --git a/chrome/browser/ui/user_education/README.md b/chrome/browser/ui/user_education/README.md
new file mode 100644
index 0000000..5abb46b
--- /dev/null
+++ b/chrome/browser/ui/user_education/README.md
@@ -0,0 +1,16 @@
+# Desktop User Education
+
+This directory contains code related to user education efforts on desktop. Also
+see [//chrome/browser/ui/views/user_education](../views/user_education)
+
+Active projects:
+* In-product help (IPH): help dialogs offered by Chrome suggesting useful
+  features. These are triggered automatically based on user behavior and give a 
+  short value statement and directions to use the feature
+* New badge: pop-out label applied to new features' entry points to make them
+  more obvious
+* Tutorials: step-by-step guided walkthroughs of features. User-initiated and
+  more thorough than single-step IPH promotions
+
+TODO(https://crbug.com/1132335): add overview and thorough instructions on how
+to apply user education to features
diff --git a/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.cc b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.cc
index 54d97ad..5b39156 100644
--- a/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.cc
+++ b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.cc
@@ -47,7 +47,7 @@
 
 bool CaptionBubbleControllerViews::OnTranscription(
     LiveCaptionSpeechRecognitionHost* live_caption_speech_recognition_host,
-    const media::mojom::SpeechRecognitionResultPtr& result) {
+    const media::SpeechRecognitionResult& result) {
   if (!caption_bubble_)
     return false;
   SetActiveModel(live_caption_speech_recognition_host);
@@ -59,11 +59,11 @@
   // transcription after several seconds of no audio. This prevents the bubble
   // reappearing with a final transcription after it had disappeared due to no
   // activity.
-  if (!caption_bubble_->HasActivity() && result->is_final)
+  if (!caption_bubble_->HasActivity() && result.is_final)
     return true;
 
-  active_model_->SetPartialText(result->transcription);
-  if (result->is_final)
+  active_model_->SetPartialText(result.transcription);
+  if (result.is_final)
     active_model_->CommitPartialText();
 
   return true;
diff --git a/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.h b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.h
index 061bb2a2..911c2ee 100644
--- a/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.h
+++ b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views.h
@@ -42,7 +42,7 @@
   // Transcriptions will halt if this returns false.
   bool OnTranscription(
       LiveCaptionSpeechRecognitionHost* live_caption_speech_recognition_host,
-      const media::mojom::SpeechRecognitionResultPtr& result) override;
+      const media::SpeechRecognitionResult& result) override;
 
   // Called when the speech service has an error.
   void OnError(LiveCaptionSpeechRecognitionHost*
diff --git a/chrome/browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc
index a7e5dc8..1e02fea 100644
--- a/chrome/browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc
+++ b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc
@@ -149,7 +149,7 @@
       LiveCaptionSpeechRecognitionHost* live_caption_speech_recognition_host) {
     return GetController()->OnTranscription(
         live_caption_speech_recognition_host,
-        media::mojom::SpeechRecognitionResult::New(text, false));
+        media::SpeechRecognitionResult(text, false));
   }
 
   bool OnFinalTranscription(std::string text) {
@@ -161,7 +161,7 @@
       LiveCaptionSpeechRecognitionHost* live_caption_speech_recognition_host) {
     return GetController()->OnTranscription(
         live_caption_speech_recognition_host,
-        media::mojom::SpeechRecognitionResult::New(text, true));
+        media::SpeechRecognitionResult(text, true));
   }
 
   void OnError() { OnError(GetLiveCaptionSpeechRecognitionHost()); }
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 8b63d88..43286ac 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
@@ -615,7 +615,7 @@
                         /*vertical=*/0,
                         /*horizontal=*/
                         ChromeLayoutProvider::Get()->GetDistanceMetric(
-                            views::DISTANCE_RELATED_LABEL_HORIZONTAL)));
+                            DISTANCE_RELATED_LABEL_HORIZONTAL_LIST)));
 
     first_line_container->AddChildView(std::move(main_text_label));
     first_line_container->AddChildView(std::move(minor_text_label));
diff --git a/chrome/browser/ui/views/borealis/borealis_installer_view.cc b/chrome/browser/ui/views/borealis/borealis_installer_view.cc
index 5078f39..c776ef2 100644
--- a/chrome/browser/ui/views/borealis/borealis_installer_view.cc
+++ b/chrome/browser/ui/views/borealis/borealis_installer_view.cc
@@ -12,8 +12,10 @@
 #include "base/callback_helpers.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/ash/borealis/borealis_app_launcher.h"
 #include "chrome/browser/ash/borealis/borealis_context_manager.h"
 #include "chrome/browser/ash/borealis/borealis_installer.h"
+#include "chrome/browser/ash/borealis/borealis_metrics.h"
 #include "chrome/browser/ash/borealis/borealis_service.h"
 #include "chrome/browser/ash/borealis/borealis_util.h"
 #include "chrome/browser/ui/browser_navigator.h"
@@ -72,8 +74,8 @@
 
   METADATA_HEADER(TitleLabel);
 
-  TitleLabel() {}
-  ~TitleLabel() override {}
+  TitleLabel() = default;
+  ~TitleLabel() override = default;
 
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
     node_data->SetName(GetText());
@@ -202,9 +204,14 @@
 
   if (state_ == State::kCompleted) {
     // Launch button has been clicked.
-    borealis::BorealisService::GetForProfile(profile_)
-        ->ContextManager()
-        .StartBorealis(base::DoNothing());
+    borealis::BorealisService::GetForProfile(profile_)->AppLauncher().Launch(
+        borealis::kBorealisMainAppId,
+        base::BindOnce([](borealis::BorealisAppLauncher::LaunchResult result) {
+          if (result == borealis::BorealisAppLauncher::LaunchResult::kSuccess)
+            return;
+          LOG(ERROR) << "Failed to launch borealis after install: code="
+                     << static_cast<int>(result);
+        }));
     return true;
   }
 
@@ -216,6 +223,17 @@
 }
 
 bool BorealisInstallerView::Cancel() {
+  if (state_ == State::kCompleted) {
+    borealis::BorealisService::GetForProfile(profile_)
+        ->ContextManager()
+        .ShutDownBorealis(
+            base::BindOnce([](borealis::BorealisShutdownResult result) {
+              if (result == borealis::BorealisShutdownResult::kSuccess)
+                return;
+              LOG(ERROR) << "Failed to shutdown borealis after install: code="
+                         << static_cast<int>(result);
+            }));
+  }
   return true;
 }
 
@@ -350,6 +368,8 @@
         case borealis::BorealisInstallResult::kBorealisNotAllowed:
         case borealis::BorealisInstallResult::kDlcUnsupportedError:
         case borealis::BorealisInstallResult::kDlcNeedUpdateError:
+        case borealis::BorealisInstallResult::kStartupFailed:
+        case borealis::BorealisInstallResult::kMainAppNotPresent:
           return ui::DIALOG_BUTTON_CANCEL;
         case borealis::BorealisInstallResult::kBorealisInstallInProgress:
         case borealis::BorealisInstallResult::kDlcInternalError:
diff --git a/chrome/browser/ui/views/borealis/borealis_installer_view_browsertest.cc b/chrome/browser/ui/views/borealis/borealis_installer_view_browsertest.cc
index fa2fac8..0cdc5a0 100644
--- a/chrome/browser/ui/views/borealis/borealis_installer_view_browsertest.cc
+++ b/chrome/browser/ui/views/borealis/borealis_installer_view_browsertest.cc
@@ -4,19 +4,28 @@
 
 #include "chrome/browser/ui/views/borealis/borealis_installer_view.h"
 
+#include <memory>
+
 #include "base/bind.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ash/borealis/borealis_app_launcher.h"
 #include "chrome/browser/ash/borealis/borealis_context.h"
 #include "chrome/browser/ash/borealis/borealis_context_manager_mock.h"
+#include "chrome/browser/ash/borealis/borealis_features.h"
 #include "chrome/browser/ash/borealis/borealis_installer.h"
 #include "chrome/browser/ash/borealis/borealis_metrics.h"
+#include "chrome/browser/ash/borealis/borealis_prefs.h"
+#include "chrome/browser/ash/borealis/borealis_service.h"
 #include "chrome/browser/ash/borealis/borealis_service_fake.h"
 #include "chrome/browser/ash/borealis/borealis_task.h"
 #include "chrome/browser/ash/borealis/borealis_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/grit/generated_resources.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
+#include "components/prefs/pref_service.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -45,16 +54,23 @@
 
 class BorealisInstallerViewBrowserTest : public DialogBrowserTest {
  public:
-  BorealisInstallerViewBrowserTest() = default;
+  BorealisInstallerViewBrowserTest() {
+    feature_list_.InitAndEnableFeature(features::kBorealis);
+  }
 
   // DialogBrowserTest:
   void SetUpOnMainThread() override {
     app_name_ = l10n_util::GetStringUTF16(IDS_BOREALIS_APP_NAME);
 
+    app_launcher_ = std::make_unique<BorealisAppLauncher>(browser()->profile());
+    features_ = std::make_unique<BorealisFeatures>(browser()->profile());
+
     BorealisServiceFake* fake_service =
         BorealisServiceFake::UseFakeForTesting(browser()->profile());
     fake_service->SetContextManagerForTesting(&mock_context_manager_);
     fake_service->SetInstallerForTesting(&mock_installer_);
+    fake_service->SetAppLauncherForTesting(app_launcher_.get());
+    fake_service->SetFeaturesForTesting(features_.get());
   }
 
   void ShowUi(const std::string& name) override {
@@ -126,8 +142,11 @@
     EXPECT_TRUE(view_->GetWidget()->IsClosed());
   }
 
+  base::test::ScopedFeatureList feature_list_;
   ::testing::StrictMock<BorealisInstallerMock> mock_installer_;
   ::testing::StrictMock<BorealisContextManagerMock> mock_context_manager_;
+  std::unique_ptr<BorealisAppLauncher> app_launcher_;
+  std::unique_ptr<BorealisFeatures> features_;
   BorealisInstallerView* view_;
   std::u16string app_name_;
 
@@ -150,9 +169,13 @@
   ShowUi("default");
   AcceptInstallation();
 
+  browser()->profile()->GetPrefs()->SetBoolean(
+      prefs::kBorealisInstalledOnDevice, true);
   view_->OnInstallationEnded(InstallationResult::kSuccess);
   ExpectInstallationCompletedSucessfully();
 
+  EXPECT_CALL(mock_context_manager_, IsRunning())
+      .WillOnce(testing::Return(true));
   EXPECT_CALL(mock_context_manager_, StartBorealis(_));
   EXPECT_CALL(mock_installer_, RemoveObserver(_));
   view_->AcceptDialog();
@@ -192,9 +215,13 @@
 
   AcceptInstallation();
 
+  browser()->profile()->GetPrefs()->SetBoolean(
+      prefs::kBorealisInstalledOnDevice, true);
   view_->OnInstallationEnded(InstallationResult::kSuccess);
   ExpectInstallationCompletedSucessfully();
 
+  EXPECT_CALL(mock_context_manager_, IsRunning())
+      .WillOnce(testing::Return(true));
   EXPECT_CALL(mock_context_manager_, StartBorealis(_));
   EXPECT_CALL(mock_installer_, RemoveObserver(_));
   view_->AcceptDialog();
diff --git a/chrome/browser/ui/views/file_system_access/file_system_access_permission_view.cc b/chrome/browser/ui/views/file_system_access/file_system_access_permission_view.cc
index a7cfb60f..82b87ba 100644
--- a/chrome/browser/ui/views/file_system_access/file_system_access_permission_view.cc
+++ b/chrome/browser/ui/views/file_system_access/file_system_access_permission_view.cc
@@ -6,6 +6,7 @@
 
 #include "base/files/file_path.h"
 #include "base/memory/ptr_util.h"
+#include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/chrome_typography.h"
 #include "chrome/browser/ui/views/file_system_access/file_system_access_ui_helpers.h"
@@ -29,16 +30,30 @@
 int GetMessageText(const FileSystemAccessPermissionView::Request& request) {
   switch (request.access) {
     case AccessType::kRead:
-      return request.handle_type == HandleType::kDirectory
-                 ? IDS_FILE_SYSTEM_ACCESS_ORIGIN_SCOPED_READ_PERMISSION_DIRECTORY_TEXT
-                 : IDS_FILE_SYSTEM_ACCESS_ORIGIN_SCOPED_READ_PERMISSION_FILE_TEXT;
+      if (base::FeatureList::IsEnabled(
+              features::kFileSystemAccessPersistentPermissions)) {
+        return request.handle_type == HandleType::kDirectory
+                   ? IDS_FILE_SYSTEM_ACCESS_READ_PERMISSION_DIRECTORY_TEXT
+                   : IDS_FILE_SYSTEM_ACCESS_READ_PERMISSION_FILE_TEXT;
+      } else {
+        return request.handle_type == HandleType::kDirectory
+                   ? IDS_FILE_SYSTEM_ACCESS_ORIGIN_SCOPED_READ_PERMISSION_DIRECTORY_TEXT
+                   : IDS_FILE_SYSTEM_ACCESS_ORIGIN_SCOPED_READ_PERMISSION_FILE_TEXT;
+      }
     case AccessType::kWrite:
     case AccessType::kReadWrite:
       // Only difference between write and read-write access dialog is in button
       // label and dialog title.
-      return request.handle_type == HandleType::kDirectory
-                 ? IDS_FILE_SYSTEM_ACCESS_ORIGIN_SCOPED_WRITE_PERMISSION_DIRECTORY_TEXT
-                 : IDS_FILE_SYSTEM_ACCESS_ORIGIN_SCOPED_WRITE_PERMISSION_FILE_TEXT;
+      if (base::FeatureList::IsEnabled(
+              features::kFileSystemAccessPersistentPermissions)) {
+        return request.handle_type == HandleType::kDirectory
+                   ? IDS_FILE_SYSTEM_ACCESS_WRITE_PERMISSION_DIRECTORY_TEXT
+                   : IDS_FILE_SYSTEM_ACCESS_WRITE_PERMISSION_FILE_TEXT;
+      } else {
+        return request.handle_type == HandleType::kDirectory
+                   ? IDS_FILE_SYSTEM_ACCESS_ORIGIN_SCOPED_WRITE_PERMISSION_DIRECTORY_TEXT
+                   : IDS_FILE_SYSTEM_ACCESS_ORIGIN_SCOPED_WRITE_PERMISSION_FILE_TEXT;
+      }
   }
   NOTREACHED();
 }
diff --git a/chrome/browser/ui/views/payments/shipping_address_editor_view_controller_browsertest.cc b/chrome/browser/ui/views/payments/shipping_address_editor_view_controller_browsertest.cc
index c130356..0946535 100644
--- a/chrome/browser/ui/views/payments/shipping_address_editor_view_controller_browsertest.cc
+++ b/chrome/browser/ui/views/payments/shipping_address_editor_view_controller_browsertest.cc
@@ -621,8 +621,14 @@
             request->state()->selected_shipping_option_error_profile());
 }
 
+// Flaky on ozone: https://crbug.com/1216184
+#if defined(USE_OZONE)
+#define MAYBE_FocusFirstField_Name DISABLED_FocusFirstField_Name
+#else
+#define MAYBE_FocusFirstField_Name FocusFirstField_Name
+#endif
 IN_PROC_BROWSER_TEST_F(MAYBE_PaymentRequestShippingAddressEditorTest,
-                       FocusFirstField_Name) {
+                       MAYBE_FocusFirstField_Name) {
   NavigateTo("/payment_request_dynamic_shipping_test.html");
   InvokePaymentRequestUI();
   SetRegionDataLoader(&test_region_data_loader_);
@@ -644,8 +650,15 @@
   EXPECT_TRUE(textfield->HasFocus());
 }
 
+// Flaky on ozone: https://crbug.com/1216184
+#if defined(USE_OZONE)
+#define MAYBE_FocusFirstInvalidField_NotName \
+  DISABLED_FocusFirstInvalidField_NotName
+#else
+#define MAYBE_FocusFirstInvalidField_NotName FocusFirstInvalidField_NotName
+#endif
 IN_PROC_BROWSER_TEST_F(MAYBE_PaymentRequestShippingAddressEditorTest,
-                       FocusFirstInvalidField_NotName) {
+                       MAYBE_FocusFirstInvalidField_NotName) {
   NavigateTo("/payment_request_dynamic_shipping_test.html");
   // Add address with only the name set, so that another view takes focus.
   autofill::AutofillProfile profile;
diff --git a/chrome/browser/ui/views/profiles/profile_picker_interactive_uitest.cc b/chrome/browser/ui/views/profiles/profile_picker_interactive_uitest.cc
index 19914106..0ec690d 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_interactive_uitest.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_interactive_uitest.cc
@@ -143,8 +143,15 @@
 }
 #endif
 
+// Flaky on Mac, see https://crbug.com/1216134
+#if defined(OS_MAC)
+#define MAYBE_FullscreenWithKeyboard DISABLED_FullscreenWithKeyboard
+#else
+#define MAYBE_FullscreenWithKeyboard FullscreenWithKeyboard
+#endif
 // Checks that the main picker view can switch to full screen.
-IN_PROC_BROWSER_TEST_F(ProfilePickerInteractiveUiTest, FullscreenWithKeyboard) {
+IN_PROC_BROWSER_TEST_F(ProfilePickerInteractiveUiTest,
+                       MAYBE_FullscreenWithKeyboard) {
   // Open a new picker.
   ProfilePicker::Show(ProfilePicker::EntryPoint::kProfileMenuManageProfiles);
   WaitForLayoutWithoutToolbar();
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
index c4ffeff..e9f02d26 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
@@ -11,6 +11,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "base/util/values/values_util.h"
+#include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/feature_engagement/tracker_factory.h"
 #include "chrome/browser/policy/cloud/user_policy_signin_service.h"
@@ -939,8 +940,14 @@
             /*enable_feature=*/false) {}
 };
 
+// Flaky on Win: https://crbug.com/1215038
+#if defined(OS_WIN)
+#define MAYBE_CreateSignedInProfile DISABLED_CreateSignedInProfile
+#else
+#define MAYBE_CreateSignedInProfile CreateSignedInProfile
+#endif
 IN_PROC_BROWSER_TEST_F(ProfilePickerSeparateEnterpriseCreationFlowBrowserTest,
-                       CreateSignedInProfile) {
+                       MAYBE_CreateSignedInProfile) {
   ASSERT_EQ(1u, BrowserList::GetInstance()->size());
   Profile* profile_being_created = StartSigninFlow();
 
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view.cc b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view.cc
new file mode 100644
index 0000000..d51943b5
--- /dev/null
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view.cc
@@ -0,0 +1,117 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view.h"
+
+#include "chrome/browser/ui/browser_navigator.h"
+#include "chrome/browser/ui/browser_navigator_params.h"
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_button_view.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/send_tab_to_self/send_tab_to_self_entry.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/views/controls/button/md_text_button.h"
+#include "ui/views/layout/flex_layout.h"
+#include "ui/views/layout/flex_layout_types.h"
+
+namespace send_tab_to_self {
+
+// static
+SendTabToSelfToolbarBubbleView* SendTabToSelfToolbarBubbleView::CreateBubble(
+    Profile* profile,
+    SendTabToSelfToolbarButtonView* parent,
+    const SendTabToSelfEntry& entry,
+    base::OnceCallback<void(NavigateParams*)> navigate_callback) {
+  SendTabToSelfToolbarBubbleView* bubble_view =
+      new SendTabToSelfToolbarBubbleView(profile, parent, entry,
+                                         std::move(navigate_callback));
+  // The widget is owned by the views system.
+  views::Widget* widget =
+      views::BubbleDialogDelegateView::CreateBubble(bubble_view);
+  widget->Show();
+  return bubble_view;
+}
+
+SendTabToSelfToolbarBubbleView::~SendTabToSelfToolbarBubbleView() = default;
+
+SendTabToSelfToolbarBubbleView::SendTabToSelfToolbarBubbleView(
+    Profile* profile,
+    SendTabToSelfToolbarButtonView* parent,
+    const SendTabToSelfEntry& entry,
+    base::OnceCallback<void(NavigateParams*)> navigate_callback)
+    : views::BubbleDialogDelegateView(dynamic_cast<views::View*>(parent),
+                                      views::BubbleBorder::TOP_RIGHT),
+      toolbar_button_(parent),
+      navigate_callback_(std::move(navigate_callback)),
+      profile_(profile),
+      title_(entry.GetTitle()),
+      url_(entry.GetURL()),
+      device_name_(entry.GetDeviceName()),
+      guid_(entry.GetGUID()) {
+  SetButtons(ui::DIALOG_BUTTON_NONE);
+  SetShowCloseButton(true);
+  SetTitle(
+      l10n_util::GetStringUTF16(IDS_TOOLBAR_BUTTON_SEND_TAB_TO_SELF_TITLE));
+  SetCloseCallback(base::BindOnce(&SendTabToSelfToolbarBubbleView::Hide,
+                                  base::Unretained(this)));
+  set_fixed_width(views::LayoutProvider::Get()->GetDistanceMetric(
+      views::DISTANCE_BUBBLE_PREFERRED_WIDTH));
+  set_close_on_deactivate(false);
+
+  SetLayoutManager(std::make_unique<views::FlexLayout>())
+      ->SetOrientation(views::LayoutOrientation::kVertical)
+      .SetCrossAxisAlignment(views::LayoutAlignment::kStart);
+
+  // TODO(crbug/1206381): Check formatting against spec, fix text eliding, add
+  // metrics.
+
+  // Page title.
+  auto title = std::make_unique<views::Label>(base::UTF8ToUTF16(title_));
+  title->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
+  title->SetTextStyle(views::style::STYLE_PRIMARY);
+  title->SetElideBehavior(gfx::ELIDE_TAIL);
+  AddChildView(std::move(title));
+
+  // Page URL.
+  auto url = std::make_unique<views::Label>(base::UTF8ToUTF16(url_.spec()));
+  url->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
+  url->SetTextStyle(views::style::STYLE_SECONDARY);
+  url->SetElideBehavior(gfx::ELIDE_TAIL);
+  AddChildView(std::move(url));
+
+  // Device name.
+  auto device = std::make_unique<views::Label>(l10n_util::GetStringFUTF16(
+      IDS_TOOLBAR_BUTTON_SEND_TAB_TO_SELF_FROM_DEVICE,
+      base::UTF8ToUTF16(device_name_)));
+  device->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
+  device->SetTextStyle(views::style::STYLE_SECONDARY);
+  device->SetElideBehavior(gfx::ELIDE_TAIL);
+  AddChildView(std::move(device));
+
+  // Open in New Tab button.
+  auto button = std::make_unique<views::MdTextButton>(
+      base::BindRepeating(&SendTabToSelfToolbarBubbleView::OpenInNewTab,
+                          base::Unretained(this)),
+      l10n_util::GetStringUTF16(
+          IDS_TOOLBAR_BUTTON_SEND_TAB_TO_SELF_BUTTON_LABEL));
+  button->SetProminent(true);
+  button->SetProperty(views::kCrossAxisAlignmentKey,
+                      views::LayoutAlignment::kEnd);
+  AddChildView(std::move(button));
+}
+
+void SendTabToSelfToolbarBubbleView::OpenInNewTab() {
+  NavigateParams params(profile_, url_, ui::PAGE_TRANSITION_LINK);
+  params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
+  params.window_action = NavigateParams::SHOW_WINDOW;
+  std::move(navigate_callback_).Run(&params);
+
+  GetWidget()->Close();
+}
+
+void SendTabToSelfToolbarBubbleView::Hide() {
+  toolbar_button_->DismissEntry(guid_);
+}
+
+}  // namespace send_tab_to_self
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view.h b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view.h
new file mode 100644
index 0000000..dcbcb7d
--- /dev/null
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view.h
@@ -0,0 +1,59 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_TOOLBAR_BUBBLE_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_TOOLBAR_BUBBLE_VIEW_H_
+
+#include "ui/views/bubble/bubble_dialog_delegate_view.h"
+
+class Profile;
+struct NavigateParams;
+
+namespace send_tab_to_self {
+
+class SendTabToSelfEntry;
+class SendTabToSelfToolbarButtonView;
+
+class SendTabToSelfToolbarBubbleView : public views::BubbleDialogDelegateView {
+ public:
+  ~SendTabToSelfToolbarBubbleView() override;
+
+  // Creates and shows the bubble.
+  static SendTabToSelfToolbarBubbleView* CreateBubble(
+      Profile* profile,
+      SendTabToSelfToolbarButtonView* parent,
+      const SendTabToSelfEntry& entry,
+      base::OnceCallback<void(NavigateParams*)> navigate_callback);
+
+ private:
+  friend class SendTabToSelfToolbarBubbleViewTest;
+  FRIEND_TEST_ALL_PREFIXES(SendTabToSelfToolbarBubbleViewTest,
+                           ButtonNavigatesToPage);
+
+  SendTabToSelfToolbarBubbleView(
+      Profile* profile,
+      SendTabToSelfToolbarButtonView* parent,
+      const SendTabToSelfEntry& entry,
+      base::OnceCallback<void(NavigateParams*)> navigate_callback);
+
+  void OpenInNewTab();
+
+  void Hide();
+
+  // The button that owns |this|.
+  SendTabToSelfToolbarButtonView* toolbar_button_;
+
+  base::OnceCallback<void(NavigateParams*)> navigate_callback_;
+
+  Profile* profile_;
+
+  std::string title_;
+  GURL url_;
+  std::string device_name_;
+  std::string guid_;
+};
+
+}  // namespace send_tab_to_self
+
+#endif  // CHROME_BROWSER_UI_VIEWS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_TOOLBAR_BUBBLE_VIEW_H_
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view_unittest.cc b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view_unittest.cc
new file mode 100644
index 0000000..e3aa063
--- /dev/null
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view_unittest.cc
@@ -0,0 +1,38 @@
+// 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/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.h"
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/test/bind.h"
+#include "chrome/browser/ui/browser_navigator.h"
+#include "chrome/browser/ui/browser_navigator_params.h"
+#include "chrome/browser/ui/views/frame/test_with_browser_view.h"
+#include "chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view.h"
+#include "chrome/test/views/chrome_views_test_base.h"
+#include "components/send_tab_to_self/send_tab_to_self_entry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/views/bubble/bubble_dialog_delegate_view.h"
+
+namespace send_tab_to_self {
+
+class SendTabToSelfToolbarBubbleViewTest : public ChromeViewsTestBase {};
+
+TEST_F(SendTabToSelfToolbarBubbleViewTest, ButtonNavigatesToPage) {
+  GURL url("https://www.example.com");
+  SendTabToSelfEntry entry("guid", url, "Example", base::Time::Now(),
+                           base::Time::Now(), "Example Device", "sync_guid");
+  SendTabToSelfToolbarBubbleView bubble(
+      nullptr, nullptr, entry,
+      base::BindLambdaForTesting([&](NavigateParams* params) {
+        EXPECT_EQ("https://www.example.com", params->url.spec());
+        EXPECT_EQ(WindowOpenDisposition::NEW_FOREGROUND_TAB,
+                  params->disposition);
+        EXPECT_EQ(NavigateParams::SHOW_WINDOW, params->window_action);
+      }));
+}
+
+}  // namespace send_tab_to_self
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_button_view.cc b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_button_view.cc
index ef2ba9c..4b7a00d 100644
--- a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_button_view.cc
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_button_view.cc
@@ -7,8 +7,12 @@
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/send_tab_to_self/receiving_ui_handler_registry.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_navigator.h"
+#include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/send_tab_to_self/send_tab_to_self_toolbar_button_controller.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view.h"
+#include "components/send_tab_to_self/send_tab_to_self_entry.h"
 #include "components/vector_icons/vector_icons.h"
 #include "ui/views/controls/button/button_controller.h"
 
@@ -38,16 +42,21 @@
       ->SetDelegate(nullptr);
 }
 
-void SendTabToSelfToolbarButtonView::Show() {
+void SendTabToSelfToolbarButtonView::Show(const SendTabToSelfEntry& entry) {
+  entry_ = &entry;
   SetVisible(true);
 }
 
-void SendTabToSelfToolbarButtonView::Hide() {
-  SetVisible(false);
+void SendTabToSelfToolbarButtonView::ButtonPressed() {
+  SendTabToSelfToolbarBubbleView::CreateBubble(
+      browser_->profile(), this, *entry_, base::BindOnce(&Navigate));
 }
 
-void SendTabToSelfToolbarButtonView::ButtonPressed() {
-  NOTIMPLEMENTED();
+void SendTabToSelfToolbarButtonView::DismissEntry(std::string& guid) {
+  send_tab_to_self::ReceivingUiHandlerRegistry::GetInstance()
+      ->GetToolbarButtonControllerForProfile(browser_->profile())
+      ->DismissEntries(std::vector<std::string>({guid}));
+  SetVisible(false);
 }
 
 }  // namespace send_tab_to_self
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_button_view.h b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_button_view.h
index 16b2756..b50bebf 100644
--- a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_button_view.h
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_button_view.h
@@ -7,6 +7,7 @@
 
 #include "chrome/browser/ui/send_tab_to_self/send_tab_to_self_toolbar_button_controller_delegate.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_button.h"
+#include "components/send_tab_to_self/send_tab_to_self_entry.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 
 class Browser;
@@ -29,13 +30,16 @@
   ~SendTabToSelfToolbarButtonView() override;
 
   // SendTabToSelfToolbarButtonControllerDelegate implementation.
-  void Show() override;
-  void Hide() override;
+  void Show(const SendTabToSelfEntry& entry) override;
+
+  void DismissEntry(std::string& guid);
 
  private:
   void ButtonPressed();
 
   const Browser* const browser_;
+
+  const SendTabToSelfEntry* entry_;
 };
 
 }  // namespace send_tab_to_self
diff --git a/chrome/browser/ui/views/user_education/README.md b/chrome/browser/ui/views/user_education/README.md
new file mode 100644
index 0000000..a7ae142
--- /dev/null
+++ b/chrome/browser/ui/views/user_education/README.md
@@ -0,0 +1,2 @@
+Views-specific user education code. See
+[//chrome/browser/ui/user_education](../../user_education) for docs.
diff --git a/chrome/browser/ui/views/web_apps/web_app_hover_button.cc b/chrome/browser/ui/views/web_apps/web_app_hover_button.cc
index c0024baf..0ced78c 100644
--- a/chrome/browser/ui/views/web_apps/web_app_hover_button.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_hover_button.cc
@@ -10,12 +10,9 @@
 
 #include "base/bind.h"
 #include "base/memory/weak_ptr.h"
-#include "base/strings/string_piece.h"
-#include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/views/accessibility/non_accessible_image_view.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/web_apps/web_app_info_image_source.h"
-#include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/components/web_app_id.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
@@ -25,6 +22,7 @@
 #include "components/url_formatter/elide_url.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
 #include "ui/events/event.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/views/controls/button/button.h"
@@ -35,17 +33,16 @@
 WebAppHoverButton::WebAppHoverButton(views::Button::PressedCallback callback,
                                      const web_app::AppId& app_id,
                                      web_app::WebAppProvider* provider,
-                                     const std::string& display_name,
+                                     const std::u16string& display_name,
                                      const GURL& url)
     : HoverButton(std::move(callback),
                   std::make_unique<NonAccessibleImageView>(),
-                  base::UTF8ToUTF16(base::StringPiece(display_name)),
+                  display_name,
                   l10n_util::GetStringFUTF16(
                       IDS_PROTOCOL_HANDLER_INTENT_PICKER_APP_ORIGIN_LABEL,
-                      web_app::AppBrowserController::FormatUrlOrigin(
+                      url_formatter::FormatUrlForSecurityDisplay(
                           url,
-                          url_formatter::kFormatUrlOmitHTTP |
-                              url_formatter::kFormatUrlOmitHTTPS)),
+                          url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS)),
                   /*secondary_view=*/nullptr,
                   /*resize_row_for_secondary_view=*/false,
                   /*secondary_view_can_process_events=*/false),
@@ -63,7 +60,16 @@
   Layout();
 }
 
-inline WebAppHoverButton::~WebAppHoverButton() = default;
+WebAppHoverButton::WebAppHoverButton(views::Button::PressedCallback callback,
+                                     const gfx::ImageSkia& icon,
+                                     const std::u16string& display_name)
+    : HoverButton(std::move(callback),
+                  ui::ImageModel::FromImageSkia(icon),
+                  display_name) {
+  Layout();
+}
+
+WebAppHoverButton::~WebAppHoverButton() = default;
 
 void WebAppHoverButton::MarkAsUnselected(const ui::Event* event) {
   ink_drop()->AnimateToState(views::InkDropState::HIDDEN,
diff --git a/chrome/browser/ui/views/web_apps/web_app_hover_button.h b/chrome/browser/ui/views/web_apps/web_app_hover_button.h
index f229d9e..649d6f5 100644
--- a/chrome/browser/ui/views/web_apps/web_app_hover_button.h
+++ b/chrome/browser/ui/views/web_apps/web_app_hover_button.h
@@ -13,6 +13,7 @@
 #include "chrome/browser/web_applications/components/web_app_id.h"
 #include "chrome/browser/web_applications/components/web_application_info.h"
 #include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/image/image_skia.h"
 #include "ui/views/controls/button/button.h"
 #include "url/gurl.h"
 
@@ -39,8 +40,11 @@
   WebAppHoverButton(views::Button::PressedCallback callback,
                     const web_app::AppId& app_id,
                     web_app::WebAppProvider* provider,
-                    const std::string& display_name,
+                    const std::u16string& display_name,
                     const GURL& url);
+  WebAppHoverButton(views::Button::PressedCallback callback,
+                    const gfx::ImageSkia& icon,
+                    const std::u16string& display_name);
   WebAppHoverButton(const WebAppHoverButton&) = delete;
   WebAppHoverButton& operator=(const WebAppHoverButton&) = delete;
   ~WebAppHoverButton() override;
diff --git a/chrome/browser/ui/views/web_apps/web_app_protocol_handler_intent_picker_dialog_view.cc b/chrome/browser/ui/views/web_apps/web_app_protocol_handler_intent_picker_dialog_view.cc
index d9c8f77..25a71cb 100644
--- a/chrome/browser/ui/views/web_apps/web_app_protocol_handler_intent_picker_dialog_view.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_protocol_handler_intent_picker_dialog_view.cc
@@ -10,7 +10,9 @@
 
 #include "base/callback_forward.h"
 #include "base/check_op.h"
+#include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_keep_alive_types.h"
 #include "chrome/browser/profiles/scoped_profile_keep_alive.h"
@@ -154,7 +156,8 @@
   web_app::AppRegistrar& registrar = provider->registrar();
   auto app_button = std::make_unique<WebAppHoverButton>(
       views::Button::PressedCallback(), app_id_, provider,
-      registrar.GetAppShortName(app_id_), registrar.GetAppStartUrl(app_id_));
+      base::UTF8ToUTF16(registrar.GetAppShortName(app_id_)),
+      registrar.GetAppStartUrl(app_id_));
   app_button->set_tag(0);
   app_button->SetTooltipAndAccessibleName();
   scrollable_view->AddChildViewAt(std::move(app_button), 0);
diff --git a/chrome/browser/ui/views/web_apps/web_app_url_handler_hover_button.cc b/chrome/browser/ui/views/web_apps/web_app_url_handler_hover_button.cc
new file mode 100644
index 0000000..5e15810
--- /dev/null
+++ b/chrome/browser/ui/views/web_apps/web_app_url_handler_hover_button.cc
@@ -0,0 +1,45 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/web_apps/web_app_url_handler_hover_button.h"
+
+#include <string>
+#include <utility>
+
+#include "chrome/browser/ui/views/web_apps/web_app_hover_button.h"
+#include "chrome/browser/web_applications/components/url_handler_launch_params.h"
+#include "chrome/browser/web_applications/components/web_app_id.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/grit/chromium_strings.h"
+#include "chrome/grit/generated_resources.h"
+#include "chrome/grit/theme_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/views/controls/button/button.h"
+#include "url/gurl.h"
+
+WebAppUrlHandlerHoverButton::WebAppUrlHandlerHoverButton(
+    views::Button::PressedCallback callback,
+    const web_app::UrlHandlerLaunchParams& url_handler_launch_params,
+    web_app::WebAppProvider* provider,
+    const std::u16string& display_name,
+    const GURL& app_start_url)
+    : WebAppHoverButton(std::move(callback),
+                        url_handler_launch_params.app_id,
+                        provider,
+                        display_name,
+                        app_start_url),
+      url_handler_launch_params_(url_handler_launch_params),
+      is_app_(true) {}
+
+WebAppUrlHandlerHoverButton::WebAppUrlHandlerHoverButton(
+    views::Button::PressedCallback callback)
+    : WebAppHoverButton(
+          std::move(callback),
+          *(ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+              IDR_PRODUCT_LOGO_32)),
+          l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)),
+      is_app_(false) {}
+
+WebAppUrlHandlerHoverButton::~WebAppUrlHandlerHoverButton() = default;
diff --git a/chrome/browser/ui/views/web_apps/web_app_url_handler_hover_button.h b/chrome/browser/ui/views/web_apps/web_app_url_handler_hover_button.h
new file mode 100644
index 0000000..1903be38
--- /dev/null
+++ b/chrome/browser/ui/views/web_apps/web_app_url_handler_hover_button.h
@@ -0,0 +1,67 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_WEB_APPS_WEB_APP_URL_HANDLER_HOVER_BUTTON_H_
+#define CHROME_BROWSER_UI_VIEWS_WEB_APPS_WEB_APP_URL_HANDLER_HOVER_BUTTON_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/ui/views/web_apps/web_app_hover_button.h"
+#include "chrome/browser/web_applications/components/url_handler_launch_params.h"
+#include "ui/views/controls/button/button.h"
+
+class GURL;
+
+namespace web_app {
+class WebAppProvider;
+}
+
+// WebAppUrlHandlerHoverButton is a hoverable button with a primary left-hand
+// icon, a title and a subtitle.
+class WebAppUrlHandlerHoverButton : public WebAppHoverButton {
+ public:
+  // Creates a hoverable button with the given elements for an app, like so:
+  //
+  // +-------------------------------------------------------------------+
+  // |      |    title                                                   |
+  // | icon |                                                            |
+  // |      |    subtitle                                                |
+  // +-------------------------------------------------------------------+
+  //
+  WebAppUrlHandlerHoverButton(
+      views::Button::PressedCallback callback,
+      const web_app::UrlHandlerLaunchParams& url_handler_launch_params,
+      web_app::WebAppProvider* provider,
+      const std::u16string& display_name,
+      const GURL& app_start_url);
+
+  // Creates a hoverable button for the browser option, like so:
+  //
+  // +-------------------------------------------------------------------+
+  // |      |                                                            |
+  // | icon |    title                                                   |
+  // |      |                                                            |
+  // +-------------------------------------------------------------------+
+  //
+  explicit WebAppUrlHandlerHoverButton(views::Button::PressedCallback callback);
+  WebAppUrlHandlerHoverButton(const WebAppUrlHandlerHoverButton&) = delete;
+  WebAppUrlHandlerHoverButton& operator=(const WebAppUrlHandlerHoverButton&) =
+      delete;
+  ~WebAppUrlHandlerHoverButton() override;
+
+  const web_app::UrlHandlerLaunchParams& url_handler_launch_params() const {
+    return url_handler_launch_params_;
+  }
+
+  bool is_app() const { return is_app_; }
+
+ private:
+  const web_app::UrlHandlerLaunchParams url_handler_launch_params_;
+  // True if the current WebAppUrlHandlerHoverButton is for an app, false if
+  // it's for the browser.
+  const bool is_app_;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_WEB_APPS_WEB_APP_URL_HANDLER_HOVER_BUTTON_H_
diff --git a/chrome/browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_browsertest.cc b/chrome/browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_browsertest.cc
new file mode 100644
index 0000000..9b6e12d8
--- /dev/null
+++ b/chrome/browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_browsertest.cc
@@ -0,0 +1,237 @@
+// 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 <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback_helpers.h"
+#include "base/files/file_path.h"
+#include "base/test/bind.h"
+#include "base/test/mock_callback.h"
+#include "base/time/time.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/browser/ui/test/test_browser_dialog.h"
+#include "chrome/browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_view.h"
+#include "chrome/browser/web_applications/components/os_integration_manager.h"
+#include "chrome/browser/web_applications/components/url_handler_launch_params.h"
+#include "chrome/browser/web_applications/components/url_handler_manager.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/test/web_app_install_test_utils.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "components/keep_alive_registry/keep_alive_types.h"
+#include "components/keep_alive_registry/scoped_keep_alive.h"
+#include "content/public/test/browser_test.h"
+#include "extensions/browser/extension_dialog_auto_confirm.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/test/dialog_test.h"
+#include "ui/views/widget/any_widget_observer.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+#include "ui/views/window/dialog_delegate.h"
+#include "url/gurl.h"
+
+namespace {
+
+const char16_t kAppName[] = u"Test App";
+const char kStartUrl[] = "https://test.com";
+const char kViewClassName[] = "WebAppUrlHandlerIntentPickerView";
+
+std::vector<web_app::UrlHandlerLaunchParams> CreateUrlHandlerLaunchParams(
+    const base::FilePath& profile_path,
+    const web_app::AppId& app_id) {
+  std::vector<web_app::UrlHandlerLaunchParams> url_handler_matches;
+  url_handler_matches.emplace_back(profile_path, app_id, GURL(kStartUrl),
+                                   web_app::UrlHandlerSavedChoice::kNone,
+                                   base::Time::Now());
+  return url_handler_matches;
+}
+
+web_app::AppId InstallTestWebApp(Profile* profile) {
+  auto app_info = std::make_unique<WebApplicationInfo>();
+  app_info->start_url = GURL(kStartUrl);
+  app_info->title = kAppName;
+  app_info->open_as_window = true;
+  return web_app::test::InstallWebApp(profile, std::move(app_info));
+}
+
+views::DialogDelegate* DialogDelegateFor(views::Widget* widget) {
+  auto* delegate = widget->widget_delegate()->AsDialogDelegate();
+  return delegate;
+}
+
+void AutoCloseDialog(views::Widget* widget) {
+  // Call CancelDialog to close the dialog, but the actual behavior will be
+  // determined by the ScopedTestDialogAutoConfirm configs.
+  views::test::CancelDialog(widget);
+}
+
+}  // namespace
+
+class WebAppUrlHandlerIntentPickerDialogInProcessBrowserTest
+    : public InProcessBrowserTest {};
+
+IN_PROC_BROWSER_TEST_F(WebAppUrlHandlerIntentPickerDialogInProcessBrowserTest,
+                       ShowWebAppUrlHandlerIntentPickerDialog) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       kViewClassName);
+  web_app::AppId test_app_id = InstallTestWebApp(browser()->profile());
+
+  base::MockCallback<chrome::WebAppUrlHandlerAcceptanceCallback>
+      show_dialog_callback;
+  absl::optional<web_app::UrlHandlerLaunchParams> result_launch_params;
+  bool dialog_accepted;
+  ON_CALL(show_dialog_callback, Run)
+      .WillByDefault([&](bool accepted,
+                         absl::optional<web_app::UrlHandlerLaunchParams> data) {
+        dialog_accepted = accepted;
+        result_launch_params = data;
+      });
+  EXPECT_CALL(show_dialog_callback, Run);
+
+  auto keep_alive = std::make_unique<ScopedKeepAlive>(
+      KeepAliveOrigin::WEB_APP_INTENT_PICKER, KeepAliveRestartOption::DISABLED);
+  WebAppUrlHandlerIntentPickerView::Show(
+      CreateUrlHandlerLaunchParams(browser()->profile()->GetPath(),
+                                   test_app_id),
+      std::move(keep_alive), show_dialog_callback.Get());
+
+  waiter.WaitIfNeededAndGet()->CloseWithReason(
+      views::Widget::ClosedReason::kEscKeyPressed);
+  EXPECT_FALSE(dialog_accepted);
+  EXPECT_FALSE(result_launch_params.has_value());
+}
+
+IN_PROC_BROWSER_TEST_F(WebAppUrlHandlerIntentPickerDialogInProcessBrowserTest,
+                       OpenIsDisabledByDefault) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       kViewClassName);
+  web_app::AppId test_app_id = InstallTestWebApp(browser()->profile());
+
+  base::MockCallback<chrome::WebAppUrlHandlerAcceptanceCallback>
+      show_dialog_callback;
+  absl::optional<web_app::UrlHandlerLaunchParams> result_launch_params;
+  bool dialog_accepted;
+  ON_CALL(show_dialog_callback, Run)
+      .WillByDefault([&](bool accepted,
+                         absl::optional<web_app::UrlHandlerLaunchParams> data) {
+        dialog_accepted = accepted;
+        result_launch_params = data;
+      });
+  EXPECT_CALL(show_dialog_callback, Run);
+
+  extensions::ScopedTestDialogAutoConfirm auto_confirm(
+      extensions::ScopedTestDialogAutoConfirm::CANCEL);
+  auto keep_alive = std::make_unique<ScopedKeepAlive>(
+      KeepAliveOrigin::WEB_APP_INTENT_PICKER, KeepAliveRestartOption::DISABLED);
+  WebAppUrlHandlerIntentPickerView::Show(
+      CreateUrlHandlerLaunchParams(browser()->profile()->GetPath(),
+                                   test_app_id),
+      std::move(keep_alive), show_dialog_callback.Get());
+
+  auto* widget = waiter.WaitIfNeededAndGet();
+  auto* dialog_delegate = DialogDelegateFor(widget);
+  // Verify "Open" button is disabled by default.
+  EXPECT_FALSE(dialog_delegate->GetOkButton()->GetEnabled());
+  AutoCloseDialog(widget);
+}
+
+IN_PROC_BROWSER_TEST_F(WebAppUrlHandlerIntentPickerDialogInProcessBrowserTest,
+                       SelectBrowser) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       kViewClassName);
+  web_app::AppId test_app_id = InstallTestWebApp(browser()->profile());
+
+  base::MockCallback<chrome::WebAppUrlHandlerAcceptanceCallback>
+      show_dialog_callback;
+  absl::optional<web_app::UrlHandlerLaunchParams> result_launch_params;
+  bool dialog_accepted;
+  ON_CALL(show_dialog_callback, Run)
+      .WillByDefault([&](bool accepted,
+                         absl::optional<web_app::UrlHandlerLaunchParams> data) {
+        dialog_accepted = accepted;
+        result_launch_params = data;
+      });
+  EXPECT_CALL(show_dialog_callback, Run);
+
+  extensions::ScopedTestDialogAutoConfirm auto_confirm(
+      extensions::ScopedTestDialogAutoConfirm::ACCEPT_AND_OPTION, 0);
+  auto launch_params_list = CreateUrlHandlerLaunchParams(
+      browser()->profile()->GetPath(), test_app_id);
+  auto keep_alive = std::make_unique<ScopedKeepAlive>(
+      KeepAliveOrigin::WEB_APP_INTENT_PICKER, KeepAliveRestartOption::DISABLED);
+  WebAppUrlHandlerIntentPickerView::Show(
+      launch_params_list, std::move(keep_alive), show_dialog_callback.Get());
+
+  AutoCloseDialog(waiter.WaitIfNeededAndGet());
+  EXPECT_TRUE(dialog_accepted);
+  EXPECT_FALSE(result_launch_params.has_value());
+}
+
+IN_PROC_BROWSER_TEST_F(WebAppUrlHandlerIntentPickerDialogInProcessBrowserTest,
+                       SelectApp) {
+  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                       kViewClassName);
+  web_app::AppId test_app_id = InstallTestWebApp(browser()->profile());
+
+  base::MockCallback<chrome::WebAppUrlHandlerAcceptanceCallback>
+      show_dialog_callback;
+  absl::optional<web_app::UrlHandlerLaunchParams> result_launch_params;
+  bool dialog_accepted;
+  ON_CALL(show_dialog_callback, Run)
+      .WillByDefault([&](bool accepted,
+                         absl::optional<web_app::UrlHandlerLaunchParams> data) {
+        dialog_accepted = accepted;
+        result_launch_params = data;
+      });
+  EXPECT_CALL(show_dialog_callback, Run);
+
+  extensions::ScopedTestDialogAutoConfirm auto_confirm(
+      extensions::ScopedTestDialogAutoConfirm::ACCEPT_AND_OPTION, 1);
+  auto launch_params_list = CreateUrlHandlerLaunchParams(
+      browser()->profile()->GetPath(), test_app_id);
+  auto keep_alive = std::make_unique<ScopedKeepAlive>(
+      KeepAliveOrigin::WEB_APP_INTENT_PICKER, KeepAliveRestartOption::DISABLED);
+  WebAppUrlHandlerIntentPickerView::Show(
+      launch_params_list, std::move(keep_alive), show_dialog_callback.Get());
+
+  AutoCloseDialog(waiter.WaitIfNeededAndGet());
+  // Select the second choice - the app.
+  EXPECT_TRUE(dialog_accepted);
+  EXPECT_EQ(result_launch_params, launch_params_list[0]);
+}
+
+class WebAppUrlHandlerIntentPickerDialogInteractiveBrowserTest
+    : public DialogBrowserTest {
+ public:
+  // DialogBrowserTest:
+  void ShowUi(const std::string& name) override {
+    views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
+                                         kViewClassName);
+
+    web_app::AppId test_app_id = InstallTestWebApp(browser()->profile());
+    auto keep_alive = std::make_unique<ScopedKeepAlive>(
+        KeepAliveOrigin::WEB_APP_INTENT_PICKER,
+        KeepAliveRestartOption::DISABLED);
+    WebAppUrlHandlerIntentPickerView::Show(
+        CreateUrlHandlerLaunchParams(browser()->profile()->GetPath(),
+                                     test_app_id),
+        std::move(keep_alive), base::DoNothing());
+    waiter.WaitIfNeededAndGet()->CloseWithReason(
+        views::Widget::ClosedReason::kEscKeyPressed);
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(WebAppUrlHandlerIntentPickerDialogInteractiveBrowserTest,
+                       InvokeUi_CloseDialog) {
+  ShowAndVerifyUi();
+}
diff --git a/chrome/browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_view.cc b/chrome/browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_view.cc
new file mode 100644
index 0000000..f729c0c
--- /dev/null
+++ b/chrome/browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_view.cc
@@ -0,0 +1,354 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_view.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/check.h"
+#include "base/location.h"
+#include "base/strings/string_piece_forward.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/profiles/profiles_state.h"
+#include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "chrome/browser/ui/views/web_apps/web_app_url_handler_hover_button.h"
+#include "chrome/browser/web_applications/components/app_registrar.h"
+#include "chrome/browser/web_applications/components/url_handler_launch_params.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/keep_alive_registry/keep_alive_types.h"
+#include "components/keep_alive_registry/scoped_keep_alive.h"
+#include "extensions/browser/extension_dialog_auto_confirm.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/events/event.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/native_theme/native_theme.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/button/checkbox.h"
+#include "ui/views/controls/scroll_view.h"
+#include "ui/views/controls/separator.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/grid_layout.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/window/dialog_delegate.h"
+
+namespace {
+
+// Maximum numbers of web apps we want to show at a time in the dialog.
+// The height of the scroll in the dialog depends on how many app
+// candidates we got and how many we want to show. If there is more than
+// |KMaxAppResults| app candidates, we will show 3.5 apps to let the user
+// know there are more than |kMaxAppResults| apps accessible by scrolling
+// the list.
+constexpr size_t kMaxAppResults = 3;
+// This dialog follows the design that
+// chrome/browser/ui/views/intent_picker_bubble_view.cc created and the
+// main component sizes were also mostly copied over to share the
+// same layout.
+// Main components sizes
+constexpr int kIntentPickerCheckBoxColumnWidth = 288;
+constexpr int kMaxIntentPickerWidth = 320;
+constexpr int kRowHeight = 32;
+constexpr int kTitlePadding = 16;
+constexpr gfx::Insets kSeparatorPadding(0, 0, 16, 0);
+constexpr SkColor kSeparatorColor = SkColorSetARGB(0x1F, 0x0, 0x0, 0x0);
+
+std::unique_ptr<views::Separator> CreateHorizontalSeparator() {
+  auto separator = std::make_unique<views::Separator>();
+  separator->SetColor(kSeparatorColor);
+  separator->SetBorder(views::CreateEmptyBorder(kSeparatorPadding));
+  return separator;
+}
+
+}  // namespace
+
+void WebAppUrlHandlerIntentPickerView::Show(
+    std::vector<web_app::UrlHandlerLaunchParams> launch_params_list,
+    std::unique_ptr<ScopedKeepAlive> keep_alive,
+    chrome::WebAppUrlHandlerAcceptanceCallback dialog_close_callback) {
+  auto view = std::make_unique<WebAppUrlHandlerIntentPickerView>(
+      std::move(launch_params_list), std::move(keep_alive),
+      std::move(dialog_close_callback));
+
+  views::DialogDelegate::CreateDialogWidget(std::move(view),
+                                            /*context=*/nullptr,
+                                            /*parent=*/nullptr)
+      ->Show();
+}
+
+WebAppUrlHandlerIntentPickerView::WebAppUrlHandlerIntentPickerView(
+    std::vector<web_app::UrlHandlerLaunchParams> launch_params_list,
+    std::unique_ptr<ScopedKeepAlive> keep_alive,
+    chrome::WebAppUrlHandlerAcceptanceCallback dialog_close_callback)
+    : launch_params_list_(std::move(launch_params_list)),
+      close_callback_(std::move(dialog_close_callback)),
+      // Pass the ScopedKeepAlive into here ensures the process is alive until
+      // the dialog is closed, and initiates the shutdown at closure if there
+      // is nothing else keeping the browser alive.
+      keep_alive_(std::move(keep_alive)) {
+  SetDefaultButton(ui::DIALOG_BUTTON_OK);
+  // Disable the open button by default and enable it when the user has
+  // selected an option.
+  SetButtonEnabled(ui::DIALOG_BUTTON_OK, false);
+  SetModalType(ui::MODAL_TYPE_NONE);
+  std::u16string title =
+      l10n_util::GetStringUTF16(IDS_URL_HANDLER_INTENT_PICKER_TITLE);
+  SetTitle(title);
+  SetShowCloseButton(true);
+
+  SetButtonLabel(
+      ui::DIALOG_BUTTON_OK,
+      l10n_util::GetStringUTF16(IDS_URL_HANDLER_INTENT_PICKER_OK_BUTTON_TEXT));
+  SetButtonLabel(ui::DIALOG_BUTTON_CANCEL,
+                 l10n_util::GetStringUTF16(
+                     IDS_URL_HANDLER_INTENT_PICKER_CANCEL_BUTTON_TEXT));
+
+  SetAcceptCallback(base::BindOnce(
+      &WebAppUrlHandlerIntentPickerView::OnAccepted, base::Unretained(this)));
+
+  SetCancelCallback(base::BindOnce(
+      &WebAppUrlHandlerIntentPickerView::OnCanceled, base::Unretained(this)));
+
+  SetCloseCallback(base::BindOnce(&WebAppUrlHandlerIntentPickerView::OnClosed,
+                                  base::Unretained(this)));
+  Initialize();
+}
+
+WebAppUrlHandlerIntentPickerView::~WebAppUrlHandlerIntentPickerView() = default;
+
+gfx::Size WebAppUrlHandlerIntentPickerView::CalculatePreferredSize() const {
+  return gfx::Size(kMaxIntentPickerWidth,
+                   GetHeightForWidth(kMaxIntentPickerWidth));
+}
+
+absl::optional<web_app::UrlHandlerLaunchParams>
+WebAppUrlHandlerIntentPickerView::GetSelectedLaunchParams() const {
+  // User didn't make a choice, no launch params.
+  if (!HasUserSelectedApp())
+    return absl::nullopt;
+
+  DCHECK(IsSelectedAppValid());
+
+  if (hover_buttons_[selected_app_tag_.value()]->is_app()) {
+    return hover_buttons_[selected_app_tag_.value()]
+        ->url_handler_launch_params();
+  }
+
+  // User has selected the browser, no launch params.
+  return absl::nullopt;
+}
+
+void WebAppUrlHandlerIntentPickerView::SetSelectedAppIndex(
+    size_t index,
+    const ui::Event& event) {
+  DCHECK_GE(index, 0u);
+  DCHECK_LT(index, hover_buttons_.size());
+  if (!HasUserSelectedApp()) {
+    // User made a choice for the first time, enable the open button.
+    SetButtonEnabled(ui::DIALOG_BUTTON_OK, true);
+  } else {
+    // Unselect the previous user choice.
+    hover_buttons_[selected_app_tag_.value()]->MarkAsUnselected(nullptr);
+  }
+  selected_app_tag_ = index;
+  hover_buttons_[selected_app_tag_.value()]->MarkAsSelected(&event);
+  views::View::RequestFocus();
+}
+
+void WebAppUrlHandlerIntentPickerView::OnAccepted() {
+  RunCloseCallback(/*accepted=*/true);
+}
+
+void WebAppUrlHandlerIntentPickerView::OnCanceled() {
+  RunCloseCallback(/*accepted=*/false);
+}
+
+void WebAppUrlHandlerIntentPickerView::OnClosed() {
+  OnCanceled();
+}
+
+void WebAppUrlHandlerIntentPickerView::Initialize() {
+  views::GridLayout* layout =
+      SetLayoutManager(std::make_unique<views::GridLayout>());
+
+  // Creates a view to hold the views for each app.
+  auto scrollable_view = std::make_unique<views::View>();
+  scrollable_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kVertical));
+
+  web_app::WebAppProvider* provider;
+  // Reserve size+1 for the browser entry.
+  hover_buttons_.reserve(launch_params_list_.size() + 1);
+  // Create a WebAppUrlHandlerHoverButton to open the link in browser and
+  // list it as the first choice.
+  auto app_button =
+      std::make_unique<WebAppUrlHandlerHoverButton>(base::BindRepeating(
+          &WebAppUrlHandlerIntentPickerView::SetSelectedAppIndex,
+          base::Unretained(this), 0));
+  app_button->set_tag(0);
+  hover_buttons_.push_back(app_button.get());
+  scrollable_view->AddChildViewAt(std::move(app_button), 0);
+
+  for (const auto& launch_params : launch_params_list_) {
+    Profile* profile = g_browser_process->profile_manager()->GetProfileByPath(
+        launch_params.profile_path);
+    provider = web_app::WebAppProvider::Get(profile);
+    web_app::AppRegistrar& registrar = provider->registrar();
+
+    const std::u16string& profile_name =
+        profiles::GetAvatarNameForProfile(launch_params.profile_path);
+    const std::u16string& app_name = base::UTF8ToUTF16(
+        base::StringPiece(registrar.GetAppShortName(launch_params.app_id)));
+    const std::u16string& app_title =
+        (profile_name ==
+         l10n_util::GetStringUTF16(IDS_SINGLE_PROFILE_DISPLAY_NAME))
+            ? app_name
+            : l10n_util::GetStringFUTF16(
+                  IDS_URL_HANDLER_INTENT_PICKER_APP_TITLE, app_name,
+                  profile_name);
+
+    const size_t button_index = hover_buttons_.size();
+    // TODO(crbug.com/1072058): Make sure the UI is reasonable when
+    // |app_title| is long.
+    auto app_button = std::make_unique<WebAppUrlHandlerHoverButton>(
+        base::BindRepeating(
+            &WebAppUrlHandlerIntentPickerView::SetSelectedAppIndex,
+            base::Unretained(this), button_index),
+        launch_params, provider, app_title,
+        registrar.GetAppStartUrl(launch_params.app_id));
+    app_button->set_tag(button_index);
+    hover_buttons_.push_back(app_button.get());
+    scrollable_view->AddChildViewAt(std::move(app_button), button_index);
+  }
+
+  auto scroll_view = std::make_unique<views::ScrollView>();
+  scroll_view->SetBackgroundThemeColorId(
+      ui::NativeTheme::kColorId_BubbleBackground);
+  scroll_view->SetContents(std::move(scrollable_view));
+  // This part gives the scroll a fixed width and height. The height depends on
+  // how many app candidates we got and how many we actually want to show.
+  // The added 0.5 on the else block allow us to let the user know there are
+  // more than |kMaxAppResults| apps accessible by scrolling the list.
+  scroll_view->ClipHeightTo(kRowHeight, (kMaxAppResults + 0.5) * kRowHeight);
+
+  constexpr int kColumnSetId = 0;
+  views::ColumnSet* cs = layout->AddColumnSet(kColumnSetId);
+  cs->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
+                views::GridLayout::kFixedSize,
+                views::GridLayout::ColumnSize::kFixed, kMaxIntentPickerWidth,
+                0);
+
+  layout->StartRowWithPadding(views::GridLayout::kFixedSize, kColumnSetId,
+                              views::GridLayout::kFixedSize, kTitlePadding);
+  scroll_view_ = layout->AddView(std::move(scroll_view));
+  layout->StartRow(views::GridLayout::kFixedSize, kColumnSetId, 0);
+
+  // The checkbox allows the user to opt-in to relaxed security
+  // (i.e. skipping future prompts) for this url.
+  layout->AddView(CreateHorizontalSeparator());
+  // This second ColumnSet has a padding column in order to manipulate the
+  // Checkbox positioning freely.
+  constexpr int kColumnSetIdPadded = 2;
+  views::ColumnSet* cs_padded = layout->AddColumnSet(kColumnSetIdPadded);
+  cs_padded->AddPaddingColumn(views::GridLayout::kFixedSize, kTitlePadding);
+  cs_padded->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
+                       views::GridLayout::kFixedSize,
+                       views::GridLayout::ColumnSize::kFixed,
+                       kIntentPickerCheckBoxColumnWidth, 0);
+  layout->StartRowWithPadding(views::GridLayout::kFixedSize, kColumnSetIdPadded,
+                              views::GridLayout::kFixedSize, 0);
+
+  if (enable_remember_checkbox_) {
+    remember_selection_checkbox_ = layout->AddView(
+        std::make_unique<views::Checkbox>(l10n_util::GetStringUTF16(
+            IDS_URL_HANDLER_INTENT_PICKER_REMEMBER_SELECTION)));
+    layout->AddPaddingRow(views::GridLayout::kFixedSize, kRowHeight);
+  }
+}
+
+void WebAppUrlHandlerIntentPickerView::RunCloseCallback(bool accepted) {
+  if (!close_callback_)
+    return;
+
+  absl::optional<web_app::UrlHandlerLaunchParams> launch_params;
+  bool accepted_override = false;
+  switch (extensions::ScopedTestDialogAutoConfirm::GetAutoConfirmValue()) {
+    case extensions::ScopedTestDialogAutoConfirm::NONE:
+      accepted_override = accepted;
+      launch_params = GetSelectedLaunchParams();
+      break;
+    case extensions::ScopedTestDialogAutoConfirm::ACCEPT_AND_OPTION:
+      accepted_override = true;
+      selected_app_tag_ =
+          extensions::ScopedTestDialogAutoConfirm::GetOptionSelected();
+      launch_params = GetSelectedLaunchParams();
+      break;
+    case extensions::ScopedTestDialogAutoConfirm::ACCEPT:
+      accepted_override = true;
+      launch_params = GetSelectedLaunchParams();
+      break;
+    case extensions::ScopedTestDialogAutoConfirm::CANCEL:
+      accepted_override = false;
+      launch_params = absl::nullopt;
+      break;
+  }
+
+  if (accepted_override && enable_remember_checkbox_ &&
+      remember_selection_checkbox_->GetChecked()) {
+    // TODO(crbug.com/1072058): Save choice if the user has checked the
+    // "Remember my choice" box.
+  }
+
+  std::move(close_callback_).Run(accepted_override, std::move(launch_params));
+}
+
+bool WebAppUrlHandlerIntentPickerView::IsSelectedAppValid() const {
+  return selected_app_tag_.has_value() && selected_app_tag_.value() >= 0 &&
+         selected_app_tag_.value() < static_cast<int>(hover_buttons_.size());
+}
+
+bool WebAppUrlHandlerIntentPickerView::HasUserSelectedApp() const {
+  return selected_app_tag_.has_value();
+}
+
+BEGIN_METADATA(WebAppUrlHandlerIntentPickerView, views::DialogDelegateView)
+END_METADATA
+
+namespace chrome {
+
+// static
+void ShowWebAppUrlHandlerIntentPickerDialog(
+    std::vector<web_app::UrlHandlerLaunchParams> launch_params_list,
+    WebAppUrlHandlerAcceptanceCallback dialog_close_callback) {
+  DCHECK(dialog_close_callback);
+
+  // TODO(crbug.com/1200951): Update the following accordingly for multi-
+  // profile URL handler launch support.
+  auto* provider = web_app::WebAppProvider::Get(
+      g_browser_process->profile_manager()->GetProfileByPath(
+          launch_params_list.front().profile_path));
+  DCHECK(provider);
+  auto keep_alive = std::make_unique<ScopedKeepAlive>(
+      KeepAliveOrigin::WEB_APP_INTENT_PICKER, KeepAliveRestartOption::DISABLED);
+  provider->on_registry_ready().Post(
+      FROM_HERE,
+      base::BindOnce(&WebAppUrlHandlerIntentPickerView::Show,
+                     std::move(launch_params_list), std::move(keep_alive),
+                     std::move(dialog_close_callback)));
+}
+
+}  // namespace chrome
diff --git a/chrome/browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_view.h b/chrome/browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_view.h
new file mode 100644
index 0000000..e5a6c0a
--- /dev/null
+++ b/chrome/browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_view.h
@@ -0,0 +1,98 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_WEB_APPS_WEB_APP_URL_HANDLER_INTENT_PICKER_DIALOG_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_WEB_APPS_WEB_APP_URL_HANDLER_INTENT_PICKER_DIALOG_VIEW_H_
+
+#include <memory>
+#include <vector>
+
+#include "chrome/browser/ui/browser_dialogs.h"
+#include "chrome/browser/web_applications/components/url_handler_launch_params.h"
+#include "components/keep_alive_registry/scoped_keep_alive.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/views/window/dialog_delegate.h"
+
+class ScopedKeepAlive;
+class WebAppUrlHandlerHoverButton;
+
+namespace gfx {
+class Size;
+}
+
+namespace ui {
+class Event;
+}
+
+namespace views {
+class Checkbox;
+class ScrollView;
+}  // namespace views
+
+// The dialog's view, owned by the views framework.
+// TODO(crbug.com/1209222): Dialog should be accessible.
+class WebAppUrlHandlerIntentPickerView : public views::DialogDelegateView {
+ public:
+  METADATA_HEADER(WebAppUrlHandlerIntentPickerView);
+
+  WebAppUrlHandlerIntentPickerView(
+      std::vector<web_app::UrlHandlerLaunchParams> launch_params_list,
+      std::unique_ptr<ScopedKeepAlive> keep_alive,
+      chrome::WebAppUrlHandlerAcceptanceCallback dialog_close_callback);
+  WebAppUrlHandlerIntentPickerView(const WebAppUrlHandlerIntentPickerView&) =
+      delete;
+  WebAppUrlHandlerIntentPickerView& operator=(
+      const WebAppUrlHandlerIntentPickerView&) = delete;
+  ~WebAppUrlHandlerIntentPickerView() override;
+
+  static void Show(
+      std::vector<web_app::UrlHandlerLaunchParams> launch_params_list,
+      std::unique_ptr<ScopedKeepAlive> keep_alive,
+      chrome::WebAppUrlHandlerAcceptanceCallback dialog_close_callback);
+
+ private:
+  void Initialize();
+  // views::DialogDelegateView:
+  gfx::Size CalculatePreferredSize() const override;
+
+  // Return the UrlHandlerLaunchParams for the selected option. Null when the
+  // browser is selected.
+  absl::optional<web_app::UrlHandlerLaunchParams> GetSelectedLaunchParams()
+      const;
+
+  void OnAccepted();
+  void OnCanceled();
+  // Close callback called by DialogDeletegate. See
+  // DialogDelegate::SetCloseCallback for when it's called.
+  void OnClosed();
+
+  // Unselects the current focused app item on the list and
+  // refocus on the selected app item based on the index provided.
+  void SetSelectedAppIndex(size_t index, const ui::Event& event);
+
+  // Runs the close_callback_ provided during Show() if it exists.
+  void RunCloseCallback(bool accepted);
+
+  // Return if the |selected_app_tag_| is valid.
+  bool IsSelectedAppValid() const;
+  // Return if the user has selected an app in the dialog.
+  bool HasUserSelectedApp() const;
+
+  const std::vector<web_app::UrlHandlerLaunchParams> launch_params_list_;
+  chrome::WebAppUrlHandlerAcceptanceCallback close_callback_;
+  std::unique_ptr<ScopedKeepAlive> keep_alive_;
+
+  std::vector<WebAppUrlHandlerHoverButton*> hover_buttons_;
+  // Allow the checkbox to be enabled or disabled.
+  // TODO(crbug.com/1072058): Remove when settings are implemented.
+  const bool enable_remember_checkbox_ = true;
+  views::Checkbox* remember_selection_checkbox_ = nullptr;
+  views::ScrollView* scroll_view_ = nullptr;
+
+  // No default selection. Not null if selected by user.
+  absl::optional<int> selected_app_tag_ = absl::nullopt;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_WEB_APPS_WEB_APP_URL_HANDLER_INTENT_PICKER_DIALOG_VIEW_H_
diff --git a/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc b/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc
index 0f4363da..2c1be6e 100644
--- a/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc
+++ b/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc
@@ -123,14 +123,14 @@
 }
 
 // This test is flaky on MacOS with ASAN or DBG. https://crbug.com/1173317
+// Also flaky on MacOS in general, see https://crbug.com/1216076
 #if defined(OS_MAC)
-#if defined(ADDRESS_SANITIZER) || !defined(NDEBUG)
 #define MAYBE_AnchorLinkClick DISABLED_AnchorLinkClick
 #else
 #define MAYBE_AnchorLinkClick AnchorLinkClick
-#endif  // ADDRESS_SANITIZER || !NDEBUG
 #endif  // OS_MAC
-IN_PROC_BROWSER_TEST_P(SystemWebAppLinkCaptureBrowserTest, AnchorLinkClick) {
+IN_PROC_BROWSER_TEST_P(SystemWebAppLinkCaptureBrowserTest,
+                       MAYBE_AnchorLinkClick) {
   WaitForTestSystemAppInstall();
 
   GURL kInitiatingChromeUrl = GURL(chrome::kChromeUIAboutURL);
@@ -303,7 +303,13 @@
                                       ->GetLastCommittedURL());
 }
 
-IN_PROC_BROWSER_TEST_P(SystemWebAppLinkCaptureBrowserTest, WindowOpen) {
+// Flaky on Mac, see https://crbug.com/1216076
+#if defined(OS_MAC)
+#define MAYBE_WindowOpen DISABLED_WindowOpen
+#else
+#define MAYBE_WindowOpen WindowOpen
+#endif  // OS_MAC
+IN_PROC_BROWSER_TEST_P(SystemWebAppLinkCaptureBrowserTest, MAYBE_WindowOpen) {
   WaitForTestSystemAppInstall();
 
   GURL kInitiatingChromeUrl = GURL(chrome::kChromeUIAboutURL);
@@ -350,8 +356,14 @@
   }
 }
 
+// Flaky on Mac, see https://crbug.com/1216076
+#if defined(OS_MAC)
+#define MAYBE_WindowOpenFromOtherSWA DISABLED_WindowOpenFromOtherSWA
+#else
+#define MAYBE_WindowOpenFromOtherSWA WindowOpenFromOtherSWA
+#endif  // OS_MAC
 IN_PROC_BROWSER_TEST_P(SystemWebAppLinkCaptureBrowserTest,
-                       WindowOpenFromOtherSWA) {
+                       MAYBE_WindowOpenFromOtherSWA) {
   WaitForTestSystemAppInstall();
 
   content::WebContents* initiating_web_contents = LaunchApp(kInitiatingAppType);
diff --git a/chrome/browser/ui/web_applications/web_app_uninstall_browsertest.cc b/chrome/browser/ui/web_applications/web_app_uninstall_browsertest.cc
index b57609d3..b082a025 100644
--- a/chrome/browser/ui/web_applications/web_app_uninstall_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_uninstall_browsertest.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/web_applications/components/install_finalizer.h"
 #include "chrome/browser/web_applications/components/web_app_id.h"
 #include "chrome/browser/web_applications/components/web_app_provider_base.h"
+#include "chrome/browser/web_applications/isolation_prefs_utils.h"
 #include "chrome/browser/web_applications/web_app.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "components/services/app_service/public/mojom/types.mojom.h"
@@ -183,4 +184,28 @@
   EXPECT_FALSE(provider->registrar().IsInstalled(app_id));
 }
 
+IN_PROC_BROWSER_TEST_F(WebAppUninstallBrowserTest, PrefsRemovedAfterUninstall) {
+  const GURL app_url = GetSecureAppURL();
+  const url::Origin origin = url::Origin::Create(app_url);
+  auto web_app_info = std::make_unique<WebApplicationInfo>();
+  web_app_info->start_url = app_url;
+  web_app_info->scope = app_url.GetWithoutFilename();
+  web_app_info->is_storage_isolated = true;
+  const AppId app_id = InstallWebApp(std::move(web_app_info));
+
+  {
+    const std::string* storage_isolation_key =
+        GetStorageIsolationKey(profile()->GetPrefs(), origin);
+    EXPECT_EQ(*storage_isolation_key, app_id);
+  }
+
+  UninstallWebApp(app_id);
+
+  {
+    const std::string* storage_isolation_key =
+        GetStorageIsolationKey(profile()->GetPrefs(), origin);
+    EXPECT_EQ(storage_isolation_key, nullptr);
+  }
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/ui/webui/flags/flags_ui_handler.cc b/chrome/browser/ui/webui/flags/flags_ui_handler.cc
index b5ae7942..6284942c 100644
--- a/chrome/browser/ui/webui/flags/flags_ui_handler.cc
+++ b/chrome/browser/ui/webui/flags/flags_ui_handler.cc
@@ -23,7 +23,7 @@
 
 FlagsUIHandler::FlagsUIHandler()
     : access_(flags_ui::kGeneralAccessFlagsOnly),
-      experimental_features_requested_(false),
+      experimental_features_callback_id_(""),
       deprecated_features_only_(false) {}
 
 FlagsUIHandler::~FlagsUIHandler() {}
@@ -57,8 +57,8 @@
   flags_storage_.reset(flags_storage);
   access_ = access;
 
-  if (experimental_features_requested_)
-    HandleRequestExperimentalFeatures(nullptr);
+  if (!experimental_features_callback_id_.empty())
+    SendExperimentalFeatures();
 }
 
 void FlagsUIHandler::HandleRequestExperimentalFeatures(
@@ -66,14 +66,17 @@
   AllowJavascript();
   const base::Value& callback_id = args->GetList()[0];
 
-  experimental_features_requested_ = true;
+  experimental_features_callback_id_ = callback_id.GetString();
   // Bail out if the handler hasn't been initialized yet. The request will be
   // handled after the initialization.
   if (!flags_storage_) {
-    ResolveJavascriptCallback(callback_id, base::Value());
     return;
   }
 
+  SendExperimentalFeatures();
+}
+
+void FlagsUIHandler::SendExperimentalFeatures() {
   base::DictionaryValue results;
 
   std::unique_ptr<base::ListValue> supported_features(new base::ListValue);
@@ -108,7 +111,9 @@
   results.SetBoolean(flags_ui::kShowBetaChannelPromotion, false);
   results.SetBoolean(flags_ui::kShowDevChannelPromotion, false);
 #endif
-  ResolveJavascriptCallback(callback_id, results);
+  ResolveJavascriptCallback(base::Value(experimental_features_callback_id_),
+                            results);
+  experimental_features_callback_id_.clear();
 }
 
 void FlagsUIHandler::HandleEnableExperimentalFeatureMessage(
diff --git a/chrome/browser/ui/webui/flags/flags_ui_handler.h b/chrome/browser/ui/webui/flags/flags_ui_handler.h
index 743a601..ae061a8d 100644
--- a/chrome/browser/ui/webui/flags/flags_ui_handler.h
+++ b/chrome/browser/ui/webui/flags/flags_ui_handler.h
@@ -23,9 +23,12 @@
 
   // Initializes the UI handler with the provided flags storage and flags
   // access. If there were flags experiments requested from javascript before
-  // this was called, it calls |HandleRequestExperimentalFeatures| again.
+  // this was called, it calls |SendExperimentalFeatures|.
   void Init(flags_ui::FlagsStorage* flags_storage, flags_ui::FlagAccess access);
 
+  // Sends experimental features lists to the UI.
+  void SendExperimentalFeatures();
+
   // Configures the handler to return either all features or deprecated
   // features only.
   void set_deprecated_features_only(bool deprecatedFeaturesOnly) {
@@ -53,7 +56,7 @@
  private:
   std::unique_ptr<flags_ui::FlagsStorage> flags_storage_;
   flags_ui::FlagAccess access_;
-  bool experimental_features_requested_;
+  std::string experimental_features_callback_id_;
   bool deprecated_features_only_;
 
   DISALLOW_COPY_AND_ASSIGN(FlagsUIHandler);
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler_chromeos.cc b/chrome/browser/ui/webui/print_preview/print_preview_handler_chromeos.cc
index 40c902f..d00e3ecf 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_handler_chromeos.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_handler_chromeos.cc
@@ -31,7 +31,6 @@
 #include "content/public/browser/web_ui.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "ash/constants/ash_features.h"
 #include "chrome/browser/ash/account_manager/account_manager_util.h"
 #include "chrome/browser/chromeos/printing/cups_printers_manager.h"
 #include "chrome/browser/chromeos/printing/cups_printers_manager_factory.h"
@@ -143,8 +142,6 @@
 
 PrintPreviewHandlerChromeOS::~PrintPreviewHandlerChromeOS() {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  if (!base::FeatureList::IsEnabled(chromeos::features::kPrintServerScaling))
-    return;
   Profile* profile = Profile::FromWebUI(web_ui());
   auto* cups_manager =
       chromeos::CupsPrintersManagerFactory::GetForBrowserContext(profile);
@@ -180,10 +177,6 @@
       base::BindRepeating(
           &PrintPreviewHandlerChromeOS::HandleRequestPrinterStatusUpdate,
           base::Unretained(this)));
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  if (!base::FeatureList::IsEnabled(chromeos::features::kPrintServerScaling))
-    return;
-#endif
   web_ui()->RegisterMessageCallback(
       "choosePrintServers",
       base::BindRepeating(
@@ -198,8 +191,6 @@
 
 void PrintPreviewHandlerChromeOS::OnJavascriptAllowed() {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  if (!base::FeatureList::IsEnabled(chromeos::features::kPrintServerScaling))
-    return;
   Profile* profile = Profile::FromWebUI(web_ui());
   print_servers_manager_ =
       chromeos::CupsPrintersManagerFactory::GetForBrowserContext(profile)
@@ -221,8 +212,6 @@
   // this is necessary for refresh or navigation from the chrome://print page.
   weak_factory_.InvalidateWeakPtrs();
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  if (!base::FeatureList::IsEnabled(chromeos::features::kPrintServerScaling))
-    return;
   print_servers_manager_->RemoveObserver(this);
 #elif BUILDFLAG(IS_CHROMEOS_LACROS)
   receiver_.reset();
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler_chromeos_unittest.cc b/chrome/browser/ui/webui/print_preview/print_preview_handler_chromeos_unittest.cc
index e7e74c7..81bc6638 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_handler_chromeos_unittest.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_handler_chromeos_unittest.cc
@@ -8,7 +8,6 @@
 
 #include "base/run_loop.h"
 #include "base/test/bind.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "chrome/browser/chromeos/printing/cups_printers_manager.h"
 #include "chrome/browser/chromeos/printing/cups_printers_manager_factory.h"
@@ -22,9 +21,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "ash/constants/ash_features.h"
-#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "chromeos/crosapi/mojom/local_printer.mojom.h"
 #endif
 
@@ -192,10 +189,6 @@
   ~PrintPreviewHandlerChromeOSTest() override = default;
 
   void SetUp() override {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-    scoped_feature_list_.InitWithFeatures(
-        {chromeos::features::kPrintServerScaling}, {});
-#endif
     TestingProfile::Builder builder;
     profile_ = builder.Build();
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -271,7 +264,6 @@
 
  private:
   content::BrowserTaskEnvironment task_environment_;
-  base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<TestingProfile> profile_;
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   std::unique_ptr<TestPrintServersManager> print_servers_manager_;
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
index bf8112d..6a8d973 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
@@ -412,18 +412,6 @@
       "forceEnablePrivetPrinting",
       profile->GetPrefs()->GetBoolean(prefs::kForceEnablePrivetPrinting));
 #endif
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  source->AddBoolean(
-      "showPrinterStatusInDialog",
-      base::FeatureList::IsEnabled(chromeos::features::kPrinterStatusDialog));
-  source->AddBoolean(
-      "printServerScaling",
-      base::FeatureList::IsEnabled(chromeos::features::kPrintServerScaling));
-#elif BUILDFLAG(IS_CHROMEOS_LACROS)
-  source->AddBoolean("showPrinterStatusInDialog", true);
-  source->AddBoolean("printServerScaling", true);
-#endif
 }
 
 void SetupPrintPreviewPlugin(content::WebUIDataSource* source) {
diff --git a/chrome/browser/ui/webui/tab_search/BUILD.gn b/chrome/browser/ui/webui/tab_search/BUILD.gn
index 1ec3de6..b7ac263 100644
--- a/chrome/browser/ui/webui/tab_search/BUILD.gn
+++ b/chrome/browser/ui/webui/tab_search/BUILD.gn
@@ -6,6 +6,9 @@
 
 mojom("mojo_bindings") {
   sources = [ "tab_search.mojom" ]
-  public_deps = [ "//mojo/public/mojom/base" ]
+  public_deps = [
+    "//components/tab_groups/public/mojom:mojo_bindings",
+    "//mojo/public/mojom/base",
+  ]
   webui_module_path = "/"
 }
diff --git a/chrome/browser/ui/webui/tab_search/tab_search.mojom b/chrome/browser/ui/webui/tab_search/tab_search.mojom
index 135f7e9..9b37079 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search.mojom
+++ b/chrome/browser/ui/webui/tab_search/tab_search.mojom
@@ -5,11 +5,14 @@
 module tab_search.mojom;
 
 import "mojo/public/mojom/base/time.mojom";
+import "mojo/public/mojom/base/token.mojom";
+import "components/tab_groups/public/mojom/tab_group_types.mojom";
 
 // Collection of window details associated with a profile.
 struct ProfileData {
   array<Window> windows;
   array<RecentlyClosedTab> recently_closed_tabs;
+  array<TabGroup> tab_groups;
 };
 
 // Properties and tabs associated with a window.
@@ -31,7 +34,7 @@
   int32 tab_id;
 
   // The group identifier of the tab.
-  string? group_id;
+  mojo_base.mojom.Token? group_id;
 
   // Whether the tab is pinned.
   bool pinned;
@@ -80,15 +83,15 @@
   string last_active_elapsed_text;
 };
 
-// Collection of tab groups.
-struct TabGroups {
-  map<string, TabGroup> groups;
-};
-
 // Information about a tab group.
 struct TabGroup {
-  string color;
-  string text_color;
+  // The unique identifier of the tab group.
+  mojo_base.mojom.Token id;
+
+  // The color of the tab group.
+  tab_groups.mojom.Color color;
+
+  // The title of the tab group.
   string title;
 };
 
@@ -112,9 +115,6 @@
   // Get window and tab data for the current profile.
   GetProfileData() => (ProfileData profile_data);
 
-  // Get tab groups for the current profile.
-  GetTabGroups() => (TabGroups tab_groups);
-
   // Switch to a specific tab.
   SwitchToTab(SwitchToTabInfo switch_to_tab_info);
 
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
index 84ee293..235956a 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
+++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
@@ -17,6 +17,7 @@
 #include "base/timer/timer.h"
 #include "base/trace_event/trace_event.h"
 #include "build/chromeos_buildflags.h"
+#include "chrome/browser/extensions/api/tab_groups/tab_groups_util.h"
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/browser/favicon/favicon_utils.h"
 #include "chrome/browser/profiles/profile.h"
@@ -136,12 +137,6 @@
   return absl::nullopt;
 }
 
-void TabSearchPageHandler::GetTabGroups(GetTabGroupsCallback callback) {
-  // TODO(crbug.com/1096120): Implement this when we can get theme color from
-  // browser
-  NOTIMPLEMENTED();
-}
-
 void TabSearchPageHandler::SwitchToTab(
     tab_search::mojom::SwitchToTabInfoPtr switch_to_tab_info) {
   absl::optional<TabDetails> optional_details =
@@ -188,6 +183,7 @@
     if (!ShouldTrackBrowser(browser))
       continue;
     TabStripModel* tab_strip_model = browser->tab_strip_model();
+
     auto window = tab_search::mojom::Window::New();
     window->active = (browser == active_browser);
     window->height = browser->window()->GetContentsSize().height();
@@ -198,6 +194,19 @@
       window->tabs.push_back(std::move(tab));
     }
     profile_data->windows.push_back(std::move(window));
+
+    for (auto tab_group_id : tab_strip_model->group_model()->ListTabGroups()) {
+      const tab_groups::TabGroupVisualData* tab_group_visual_data =
+          tab_strip_model->group_model()
+              ->GetTabGroup(tab_group_id)
+              ->visual_data();
+
+      auto tab_group = tab_search::mojom::TabGroup::New();
+      tab_group->id = tab_group_id.token();
+      tab_group->title = base::UTF16ToUTF8(tab_group_visual_data->title());
+      tab_group->color = tab_group_visual_data->color();
+      profile_data->tab_groups.push_back(std::move(tab_group));
+    }
   }
 
   AddRecentlyClosedTabs(profile_data->recently_closed_tabs, tab_urls);
@@ -289,7 +298,7 @@
   const absl::optional<tab_groups::TabGroupId> group_id =
       tab_strip_model->GetTabGroupForTab(index);
   if (group_id.has_value()) {
-    tab_data->group_id = group_id.value().ToString();
+    tab_data->group_id = group_id.value().token();
   }
   TabRendererData tab_renderer_data =
       TabRendererData::FromTabInModel(tab_strip_model, index);
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.h b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.h
index adb8578..6114edd8 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.h
+++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.h
@@ -48,7 +48,6 @@
   // tab_search::mojom::PageHandler:
   void CloseTab(int32_t tab_id) override;
   void GetProfileData(GetProfileDataCallback callback) override;
-  void GetTabGroups(GetTabGroupsCallback callback) override;
   void SwitchToTab(
       tab_search::mojom::SwitchToTabInfoPtr switch_to_tab_info) override;
   void OpenRecentlyClosedTab(int32_t tab_id) override;
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_page_handler_unittest.cc b/chrome/browser/ui/webui/tab_search/tab_search_page_handler_unittest.cc
index 0308fe16..7f5f343 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler_unittest.cc
@@ -18,6 +18,9 @@
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/sessions/core/tab_restore_service_impl.h"
 #include "components/sync_preferences/pref_service_syncable.h"
+#include "components/tab_groups/tab_group_color.h"
+#include "components/tab_groups/tab_group_id.h"
+#include "components/tab_groups/tab_group_visual_data.h"
 #include "content/public/test/test_web_ui.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/gfx/color_utils.h"
@@ -289,6 +292,42 @@
   handler()->GetProfileData(std::move(callback3));
 }
 
+TEST_F(TabSearchPageHandlerTest, GetTabsAndGroups) {
+  // Add tabs to a browser.
+  AddTabWithTitle(browser1(), GURL(kTabUrl1), kTabName1);
+  AddTabWithTitle(browser1(), GURL(kTabUrl2), kTabName2);
+
+  TabStripModel* tab_strip_model = browser1()->tab_strip_model();
+  TabGroupModel* tab_group_model = tab_strip_model->group_model();
+
+  EXPECT_CALL(page_, TabUpdated(_)).Times(1);
+  EXPECT_CALL(page_, TabsRemoved(_)).Times(1);
+  // Associate a tab to a given tab group.
+  tab_groups::TabGroupId group1 = tab_strip_model->AddToNewGroup({0});
+
+  std::u16string sample_title = u"Sample title";
+  const tab_groups::TabGroupColorId sample_color =
+      tab_groups::TabGroupColorId::kGrey;
+  tab_groups::TabGroupVisualData visual_data1(sample_title, sample_color);
+  tab_group_model->GetTabGroup(group1)->SetVisualData(visual_data1);
+
+  // Get Tabs and Tab Group details.
+  tab_search::mojom::PageHandler::GetProfileDataCallback callback1 =
+      base::BindLambdaForTesting(
+          [&](tab_search::mojom::ProfileDataPtr profile_tabs) {
+            ASSERT_EQ(2u, profile_tabs->windows.size());
+            auto* window1 = profile_tabs->windows[0].get();
+            ASSERT_TRUE(window1->active);
+            ASSERT_EQ(2u, window1->tabs.size());
+
+            ASSERT_EQ(1u, profile_tabs->tab_groups.size());
+            auto* tab_group = profile_tabs->tab_groups[0].get();
+            ASSERT_EQ(sample_color, tab_group->color);
+            ASSERT_EQ(base::UTF16ToUTF8(sample_title), tab_group->title);
+          });
+  handler()->GetProfileData(std::move(callback1));
+}
+
 // Ensure that repeated tab model changes do not result in repeated calls to
 // TabsChanged() and TabsChanged() is only called when the page handler's
 // timer fires.
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 1dba26ec..b42286f 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -24,6 +24,8 @@
     "externally_managed_app_registration_task.h",
     "file_utils_wrapper.cc",
     "file_utils_wrapper.h",
+    "isolation_prefs_utils.cc",
+    "isolation_prefs_utils.h",
     "manifest_update_manager.cc",
     "manifest_update_manager.h",
     "manifest_update_task.cc",
@@ -104,6 +106,7 @@
     "//components/webapps/browser",
     "//content/public/browser",
     "//services/metrics/public/cpp:ukm_builders",
+    "//services/preferences/public/cpp",
     "//skia",
     "//ui/base/idle",
     "//ui/events/devices:devices",
@@ -263,6 +266,7 @@
   sources = [
     "daily_metrics_helper_unittest.cc",
     "externally_managed_app_manager_impl_unittest.cc",
+    "isolation_prefs_utils_unittest.cc",
     "manifest_update_task_unittest.cc",
     "preinstalled_web_app_manager_unittest.cc",
     "preinstalled_web_app_utils_unittest.cc",
diff --git a/chrome/browser/web_applications/components/url_handler_launch_params.cc b/chrome/browser/web_applications/components/url_handler_launch_params.cc
index 2a19c66..d80cee6 100644
--- a/chrome/browser/web_applications/components/url_handler_launch_params.cc
+++ b/chrome/browser/web_applications/components/url_handler_launch_params.cc
@@ -8,6 +8,8 @@
 
 namespace web_app {
 
+UrlHandlerLaunchParams::UrlHandlerLaunchParams() = default;
+
 UrlHandlerLaunchParams::UrlHandlerLaunchParams(
     const base::FilePath& profile_path,
     const AppId& app_id,
@@ -29,4 +31,14 @@
 
 UrlHandlerLaunchParams::~UrlHandlerLaunchParams() = default;
 
+bool operator==(const UrlHandlerLaunchParams& launch_params1,
+                const UrlHandlerLaunchParams& launch_params2) {
+  return launch_params1.profile_path == launch_params2.profile_path &&
+         launch_params1.app_id == launch_params2.app_id &&
+         launch_params1.url == launch_params2.url &&
+         launch_params1.saved_choice == launch_params2.saved_choice &&
+         launch_params1.saved_choice_timestamp ==
+             launch_params2.saved_choice_timestamp;
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/components/url_handler_launch_params.h b/chrome/browser/web_applications/components/url_handler_launch_params.h
index 1c6e695..8d640e3 100644
--- a/chrome/browser/web_applications/components/url_handler_launch_params.h
+++ b/chrome/browser/web_applications/components/url_handler_launch_params.h
@@ -34,6 +34,7 @@
 // |saved_choice| can be used to determine if a UI prompt needs to be shown to
 // the user before launch.
 struct UrlHandlerLaunchParams {
+  UrlHandlerLaunchParams();
   UrlHandlerLaunchParams(const base::FilePath& profile_path,
                          const AppId& app_id,
                          const GURL& url,
@@ -51,6 +52,9 @@
   base::Time saved_choice_timestamp;
 };
 
+bool operator==(const UrlHandlerLaunchParams& launch_params1,
+                const UrlHandlerLaunchParams& launch_params2);
+
 }  // namespace web_app
 
 #endif  // CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_URL_HANDLER_LAUNCH_PARAMS_H_
diff --git a/chrome/browser/web_applications/components/web_app_prefs_utils.cc b/chrome/browser/web_applications/components/web_app_prefs_utils.cc
index ad6fbf4..d373fdc 100644
--- a/chrome/browser/web_applications/components/web_app_prefs_utils.cc
+++ b/chrome/browser/web_applications/components/web_app_prefs_utils.cc
@@ -87,6 +87,12 @@
 //     since the Windows epoch, using util::TimeToValue().
 //     "IPH_last_ignore_time": "13249617864945500",
 //   },
+//   isolation_state is managed by isolation_prefs_utils
+//   "isolation_state": {
+//     "<origin>": {
+//       "storage_isolation_key": "abc123",
+//     },
+//   },
 // }
 //
 const char kWasExternalAppUninstalledByUser[] =
diff --git a/chrome/browser/web_applications/isolation_prefs_utils.cc b/chrome/browser/web_applications/isolation_prefs_utils.cc
new file mode 100644
index 0000000..cb95a7e
--- /dev/null
+++ b/chrome/browser/web_applications/isolation_prefs_utils.cc
@@ -0,0 +1,82 @@
+// 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/web_applications/isolation_prefs_utils.h"
+
+#include <memory>
+
+#include "base/values.h"
+#include "chrome/browser/web_applications/components/web_app_prefs_utils.h"
+#include "chrome/browser/web_applications/web_app.h"
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_thread.h"
+#include "services/preferences/public/cpp/dictionary_value_update.h"
+#include "services/preferences/public/cpp/scoped_pref_update.h"
+#include "url/origin.h"
+
+namespace web_app {
+
+// The stored preferences managed by this file look like:
+// "web_apps": {
+//   ... other fields managed by web_app_prefs_utils ...
+//
+//   "isolation_state": {
+//     "<origin>": {
+//       "storage_isolation_key": "abc123",
+//     },
+//   },
+// }
+
+const char kStorageIsolationKey[] = "storage_isolation_key";
+
+void IsolationPrefsUtilsRegisterProfilePrefs(PrefRegistrySimple* registry) {
+  registry->RegisterDictionaryPref(::prefs::kWebAppsIsolationState);
+}
+
+void RecordOrRemoveAppIsolationState(PrefService* pref_service,
+                                     const WebApp& web_app) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  url::Origin origin = url::Origin::Create(web_app.scope());
+
+  prefs::ScopedDictionaryPrefUpdate update(pref_service,
+                                           prefs::kWebAppsIsolationState);
+  if (web_app.IsStorageIsolated()) {
+    std::unique_ptr<prefs::DictionaryValueUpdate> origin_isolation_update;
+    if (!update->GetDictionaryWithoutPathExpansion(origin.Serialize(),
+                                                   &origin_isolation_update)) {
+      origin_isolation_update = update->SetDictionaryWithoutPathExpansion(
+          origin.Serialize(), std::make_unique<base::DictionaryValue>());
+    }
+    origin_isolation_update->SetString(kStorageIsolationKey, web_app.app_id());
+  } else {
+    update->RemoveWithoutPathExpansion(origin.Serialize(), nullptr);
+  }
+}
+
+void RemoveAppIsolationState(PrefService* pref_service,
+                             const url::Origin& origin) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  prefs::ScopedDictionaryPrefUpdate update(pref_service,
+                                           prefs::kWebAppsIsolationState);
+  update->RemoveWithoutPathExpansion(origin.Serialize(), nullptr);
+}
+
+const std::string* GetStorageIsolationKey(PrefService* pref_service,
+                                          const url::Origin& origin) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  const base::DictionaryValue* isolation_prefs =
+      pref_service->GetDictionary(prefs::kWebAppsIsolationState);
+  if (!isolation_prefs)
+    return nullptr;
+
+  const base::Value* origin_prefs =
+      isolation_prefs->FindDictKey(origin.Serialize());
+  if (!origin_prefs)
+    return nullptr;
+  return origin_prefs->FindStringKey(kStorageIsolationKey);
+}
+
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/isolation_prefs_utils.h b/chrome/browser/web_applications/isolation_prefs_utils.h
new file mode 100644
index 0000000..db7ecdf
--- /dev/null
+++ b/chrome/browser/web_applications/isolation_prefs_utils.h
@@ -0,0 +1,42 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_WEB_APPLICATIONS_ISOLATION_PREFS_UTILS_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_ISOLATION_PREFS_UTILS_H_
+
+#include <string>
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace url {
+class Origin;
+}
+
+namespace web_app {
+
+class WebApp;
+
+extern const char kStorageIsolation[];
+
+void IsolationPrefsUtilsRegisterProfilePrefs(PrefRegistrySimple* registry);
+
+// Updates |web_app|'s entry in the "isolation_state" dictionary to be in sync
+// with its current isolation state.
+void RecordOrRemoveAppIsolationState(PrefService* pref_service,
+                                     const WebApp& web_app);
+
+// Removes |web_app|'s entry in the "isolation_state" dictionary. Must be called
+// when uninstalling an app.
+void RemoveAppIsolationState(PrefService* pref_service,
+                             const url::Origin& origin);
+
+// Returns the storage isolation key to use when loading resources
+// from |origin|.
+const std::string* GetStorageIsolationKey(PrefService* pref_service,
+                                          const url::Origin& origin);
+
+}  // namespace web_app
+
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_ISOLATION_PREFS_UTILS_H_
diff --git a/chrome/browser/web_applications/isolation_prefs_utils_unittest.cc b/chrome/browser/web_applications/isolation_prefs_utils_unittest.cc
new file mode 100644
index 0000000..7fe914b
--- /dev/null
+++ b/chrome/browser/web_applications/isolation_prefs_utils_unittest.cc
@@ -0,0 +1,106 @@
+// 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/web_applications/isolation_prefs_utils.h"
+
+#include "chrome/browser/web_applications/components/web_app_id.h"
+#include "chrome/browser/web_applications/web_app.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/origin.h"
+
+namespace web_app {
+
+namespace {
+const AppId app_id = "test_app";
+const std::string scope("https://example.com/pwa");
+}  // namespace
+
+class IsolationPrefsUtilsTest : public testing::Test {
+ public:
+  IsolationPrefsUtilsTest() {
+    IsolationPrefsUtilsRegisterProfilePrefs(prefs_.registry());
+  }
+
+  sync_preferences::TestingPrefServiceSyncable* prefs() { return &prefs_; }
+
+ protected:
+  content::BrowserTaskEnvironment task_environment_;
+
+ private:
+  sync_preferences::TestingPrefServiceSyncable prefs_;
+};
+
+TEST_F(IsolationPrefsUtilsTest, TestInstallNonIsolatedApp) {
+  WebApp app(app_id);
+  app.SetScope(GURL(scope));
+
+  RecordOrRemoveAppIsolationState(prefs(), app);
+
+  const std::string* storage_isolation_key =
+      GetStorageIsolationKey(prefs(), url::Origin::Create(app.scope()));
+  EXPECT_EQ(storage_isolation_key, nullptr);
+}
+
+TEST_F(IsolationPrefsUtilsTest, TestInstallIsolatedApp) {
+  WebApp app(app_id);
+  app.SetScope(GURL(scope));
+  app.SetStorageIsolated(true);
+
+  RecordOrRemoveAppIsolationState(prefs(), app);
+
+  const std::string* storage_isolation_key =
+      GetStorageIsolationKey(prefs(), url::Origin::Create(app.scope()));
+  EXPECT_NE(storage_isolation_key, nullptr);
+  EXPECT_EQ(*storage_isolation_key, app_id);
+}
+
+TEST_F(IsolationPrefsUtilsTest, TestUpdateIsolatedApp) {
+  WebApp app(app_id);
+  app.SetScope(GURL(scope));
+  app.SetStorageIsolated(true);
+
+  {
+    RecordOrRemoveAppIsolationState(prefs(), app);
+
+    const std::string* storage_isolation_key =
+        GetStorageIsolationKey(prefs(), url::Origin::Create(app.scope()));
+    EXPECT_NE(storage_isolation_key, nullptr);
+    EXPECT_EQ(*storage_isolation_key, app_id);
+  }
+
+  {
+    app.SetStorageIsolated(false);
+    RecordOrRemoveAppIsolationState(prefs(), app);
+
+    const std::string* storage_isolation_key =
+        GetStorageIsolationKey(prefs(), url::Origin::Create(app.scope()));
+    EXPECT_EQ(storage_isolation_key, nullptr);
+  }
+}
+
+TEST_F(IsolationPrefsUtilsTest, TestUninstallIsolatedApp) {
+  WebApp app(app_id);
+  app.SetScope(GURL(scope));
+  app.SetStorageIsolated(true);
+
+  {
+    RecordOrRemoveAppIsolationState(prefs(), app);
+
+    const std::string* storage_isolation_key =
+        GetStorageIsolationKey(prefs(), url::Origin::Create(app.scope()));
+    EXPECT_NE(storage_isolation_key, nullptr);
+    EXPECT_EQ(*storage_isolation_key, app_id);
+  }
+
+  {
+    RemoveAppIsolationState(prefs(), url::Origin::Create(app.scope()));
+
+    const std::string* storage_isolation_key =
+        GetStorageIsolationKey(prefs(), url::Origin::Create(app.scope()));
+    EXPECT_EQ(storage_isolation_key, nullptr);
+  }
+}
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.cc b/chrome/browser/web_applications/web_app_install_finalizer.cc
index 0832123f..1a55816a 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/web_app_install_finalizer.cc
@@ -31,6 +31,7 @@
 #include "chrome/browser/web_applications/components/web_app_system_web_app_data.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/isolation_prefs_utils.h"
 #include "chrome/browser/web_applications/manifest_update_task.h"
 #include "chrome/browser/web_applications/web_app.h"
 #include "chrome/browser/web_applications/web_app_icon_manager.h"
@@ -480,6 +481,13 @@
     webapps::WebappUninstallSource uninstall_source,
     UninstallWebAppCallback callback,
     OsHooksResults os_hooks_info) {
+  WebAppRegistrar* web_app_registrar = registrar().AsWebAppRegistrar();
+  DCHECK(web_app_registrar);
+  const WebApp* web_app = web_app_registrar->GetAppById(app_id);
+  DCHECK(web_app);
+  RemoveAppIsolationState(profile_->GetPrefs(),
+                          url::Origin::Create(web_app->scope()));
+
   ScopedRegistryUpdate update(registry_controller().AsWebAppSyncBridge());
   update->DeleteApp(app_id);
 
@@ -570,6 +578,11 @@
     return;
   }
 
+  // Save the isolation state to prefs. On browser startup we may need access
+  // to the isolation state before WebAppDatabase has finished loading, so we
+  // duplicate this state in a pref to prevent blocking startup.
+  RecordOrRemoveAppIsolationState(profile_->GetPrefs(), *web_app);
+
   AppId app_id = web_app->app_id();
 
   std::unique_ptr<WebAppRegistryUpdate> update =
diff --git a/chrome/browser/web_applications/web_app_provider.cc b/chrome/browser/web_applications/web_app_provider.cc
index 2d123e2..aad8e6d 100644
--- a/chrome/browser/web_applications/web_app_provider.cc
+++ b/chrome/browser/web_applications/web_app_provider.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/web_applications/daily_metrics_helper.h"
 #include "chrome/browser/web_applications/externally_managed_app_manager_impl.h"
 #include "chrome/browser/web_applications/file_utils_wrapper.h"
+#include "chrome/browser/web_applications/isolation_prefs_utils.h"
 #include "chrome/browser/web_applications/manifest_update_manager.h"
 #include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
 #include "chrome/browser/web_applications/preinstalled_web_app_manager.h"
@@ -360,6 +361,7 @@
   WebAppPolicyManager::RegisterProfilePrefs(registry);
   SystemWebAppManager::RegisterProfilePrefs(registry);
   WebAppPrefsUtilsRegisterProfilePrefs(registry);
+  IsolationPrefsUtilsRegisterProfilePrefs(registry);
   RegisterInstallBounceMetricProfilePrefs(registry);
   RegisterDailyWebAppMetricsProfilePrefs(registry);
 }
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index e9da05f..45742ef 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -1979,6 +1979,10 @@
 // outlive the app installation and uninstallation.
 const char kWebAppsPreferences[] = "web_apps.web_app_ids";
 
+// Dictionary that maps the origin of a web app to other preferences related to
+// its isolation requirements.
+const char kWebAppsIsolationState[] = "web_apps.isolation_state";
+
 #if defined(OS_WIN) || defined(OS_MAC) || \
     (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
 // Dictionary that maps origins to web apps that can act as URL handlers.
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index f57cd98e..842d073 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -669,6 +669,7 @@
 extern const char kWebAppsDidMigrateDefaultChromeApps[];
 extern const char kWebAppsUninstalledDefaultChromeApps[];
 extern const char kWebAppsPreferences[];
+extern const char kWebAppsIsolationState[];
 
 #if defined(OS_WIN) || defined(OS_MAC) || \
     (defined(OS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS))
diff --git a/chrome/renderer/autofill/form_control_click_detection_browsertest.cc b/chrome/renderer/autofill/form_control_click_detection_browsertest.cc
index b684ece..0bf79e0 100644
--- a/chrome/renderer/autofill/form_control_click_detection_browsertest.cc
+++ b/chrome/renderer/autofill/form_control_click_detection_browsertest.cc
@@ -18,6 +18,18 @@
 
 namespace autofill {
 
+namespace {
+
+FieldRendererId GetFieldRendererId(blink::WebElement element) {
+  blink::WebFormControlElement field =
+      element.To<blink::WebFormControlElement>();
+  if (field.IsNull())
+    return {};
+  return FieldRendererId(field.UniqueRendererFormControlId());
+}
+
+}  // namespace
+
 class FormControlClickDetectionTest : public ChromeRenderViewTest {
  protected:
   void SetUp() override {
@@ -54,14 +66,12 @@
   }
 
   void ClearAutofillAgentTestState() {
-    autofill_agent_->last_clicked_form_control_element_for_testing_ =
-        blink::WebFormControlElement();
+    autofill_agent_->last_clicked_form_control_element_for_testing_ = {};
     autofill_agent_
         ->last_clicked_form_control_element_was_focused_for_testing_ = false;
   }
 
-  const blink::WebFormControlElement& last_clicked_form_control_element()
-      const {
+  FieldRendererId last_clicked_form_control_element() const {
     return autofill_agent_->last_clicked_form_control_element_for_testing_;
   }
 
@@ -71,7 +81,7 @@
   }
 
   bool form_control_element_clicked_called() const {
-    return !last_clicked_form_control_element().IsNull();
+    return !last_clicked_form_control_element().is_null();
   }
 
   blink::WebElement text_;
@@ -86,7 +96,7 @@
   EXPECT_TRUE(SimulateElementClick("text_1"));
   EXPECT_TRUE(form_control_element_clicked_called());
   EXPECT_FALSE(last_clicked_form_control_element_was_focused());
-  EXPECT_EQ(text_, last_clicked_form_control_element());
+  EXPECT_EQ(GetFieldRendererId(text_), last_clicked_form_control_element());
   ClearAutofillAgentTestState();
 
   // Click the text field again and verify that AutofillAgent knows about its
@@ -94,7 +104,7 @@
   EXPECT_TRUE(SimulateElementClick("text_1"));
   EXPECT_TRUE(form_control_element_clicked_called());
   EXPECT_TRUE(last_clicked_form_control_element_was_focused());
-  EXPECT_EQ(text_, last_clicked_form_control_element());
+  EXPECT_EQ(GetFieldRendererId(text_), last_clicked_form_control_element());
   ClearAutofillAgentTestState();
 
   // Click the button, no notification should happen (this is not a text-input).
@@ -110,7 +120,7 @@
   EXPECT_TRUE(SimulateElementRightClick("text_1"));
   EXPECT_FALSE(form_control_element_clicked_called());
   EXPECT_FALSE(last_clicked_form_control_element_was_focused());
-  EXPECT_NE(text_, last_clicked_form_control_element());
+  EXPECT_NE(GetFieldRendererId(text_), last_clicked_form_control_element());
 }
 
 TEST_F(FormControlClickDetectionTest, InputFocusedAndClicked) {
@@ -124,7 +134,7 @@
   EXPECT_TRUE(SimulateElementClick("text_1"));
   EXPECT_TRUE(form_control_element_clicked_called());
   EXPECT_TRUE(last_clicked_form_control_element_was_focused());
-  EXPECT_EQ(text_, last_clicked_form_control_element());
+  EXPECT_EQ(GetFieldRendererId(text_), last_clicked_form_control_element());
 }
 
 // Tests that AutofillAgent accepts form clicks for a textarea element which is
@@ -135,7 +145,7 @@
   EXPECT_TRUE(SimulateElementClick("textarea_1"));
   EXPECT_TRUE(form_control_element_clicked_called());
   EXPECT_FALSE(last_clicked_form_control_element_was_focused());
-  EXPECT_EQ(textarea_, last_clicked_form_control_element());
+  EXPECT_EQ(GetFieldRendererId(textarea_), last_clicked_form_control_element());
   ClearAutofillAgentTestState();
 
   // Click the text field again and verify that AutofillAgent knows about its
@@ -143,7 +153,7 @@
   EXPECT_TRUE(SimulateElementClick("textarea_1"));
   EXPECT_TRUE(form_control_element_clicked_called());
   EXPECT_TRUE(last_clicked_form_control_element_was_focused());
-  EXPECT_EQ(textarea_, last_clicked_form_control_element());
+  EXPECT_EQ(GetFieldRendererId(textarea_), last_clicked_form_control_element());
   ClearAutofillAgentTestState();
 
   // Click the button, no notification should happen (this is not a text-input).
@@ -163,7 +173,7 @@
   EXPECT_TRUE(SimulateElementClick("textarea_1"));
   EXPECT_TRUE(form_control_element_clicked_called());
   EXPECT_TRUE(last_clicked_form_control_element_was_focused());
-  EXPECT_EQ(textarea_, last_clicked_form_control_element());
+  EXPECT_EQ(GetFieldRendererId(textarea_), last_clicked_form_control_element());
   ClearAutofillAgentTestState();
 }
 
@@ -177,7 +187,7 @@
   SimulatePointClick(gfx::Point(30, 30));
   EXPECT_TRUE(form_control_element_clicked_called());
   EXPECT_FALSE(last_clicked_form_control_element_was_focused());
-  EXPECT_EQ(textarea_, last_clicked_form_control_element());
+  EXPECT_EQ(GetFieldRendererId(textarea_), last_clicked_form_control_element());
 }
 
 TEST_F(FormControlClickDetectionTest, ScaledTextareaTapped) {
@@ -190,7 +200,7 @@
   SimulateRectTap(gfx::Rect(30, 30, 30, 30));
   EXPECT_TRUE(form_control_element_clicked_called());
   EXPECT_FALSE(last_clicked_form_control_element_was_focused());
-  EXPECT_EQ(textarea_, last_clicked_form_control_element());
+  EXPECT_EQ(GetFieldRendererId(textarea_), last_clicked_form_control_element());
 }
 
 TEST_F(FormControlClickDetectionTest, DisabledInputClickedNoEvent) {
@@ -200,7 +210,7 @@
   EXPECT_TRUE(SimulateElementClick("text_1"));
   EXPECT_TRUE(form_control_element_clicked_called());
   EXPECT_FALSE(last_clicked_form_control_element_was_focused());
-  EXPECT_EQ(text_, last_clicked_form_control_element());
+  EXPECT_EQ(GetFieldRendererId(text_), last_clicked_form_control_element());
   ClearAutofillAgentTestState();
 
   // Click the disabled element.
@@ -215,7 +225,7 @@
   EXPECT_TRUE(SimulateElementClick("text_1"));
   EXPECT_TRUE(form_control_element_clicked_called());
   EXPECT_FALSE(last_clicked_form_control_element_was_focused());
-  EXPECT_EQ(text_, last_clicked_form_control_element());
+  EXPECT_EQ(GetFieldRendererId(text_), last_clicked_form_control_element());
   ClearAutofillAgentTestState();
 
   // Click the disabled element and focus should change.
@@ -228,7 +238,7 @@
   EXPECT_TRUE(SimulateElementClick("text_1"));
   EXPECT_TRUE(form_control_element_clicked_called());
   EXPECT_FALSE(last_clicked_form_control_element_was_focused());
-  EXPECT_EQ(text_, last_clicked_form_control_element());
+  EXPECT_EQ(GetFieldRendererId(text_), last_clicked_form_control_element());
 }
 
 TEST_F(FormControlClickDetectionTest, TapNearEdgeIsPageClick) {
@@ -239,7 +249,7 @@
                   gfx::Vector2d(element_bounds.width() / 2 + 1, 0));
   EXPECT_TRUE(form_control_element_clicked_called());
   EXPECT_FALSE(last_clicked_form_control_element_was_focused());
-  EXPECT_EQ(text_, last_clicked_form_control_element());
+  EXPECT_EQ(GetFieldRendererId(text_), last_clicked_form_control_element());
 }
 
 }  // namespace autofill
diff --git a/chrome/renderer/cart/commerce_hint_agent.cc b/chrome/renderer/cart/commerce_hint_agent.cc
index 2a4add8..882a07e 100644
--- a/chrome/renderer/cart/commerce_hint_agent.cc
+++ b/chrome/renderer/cart/commerce_hint_agent.cc
@@ -379,6 +379,28 @@
     return;
   if (navigation_url.DomainIs(kElectronicExpressDomain))
     return;
+  if (IsCartHeuristicsImprovementEnabled()) {
+    if (navigation_url.DomainIs("abebooks.com"))
+      return;
+    if (navigation_url.DomainIs("abercrombie.com"))
+      return;
+    if (navigation_url.DomainIs(kAmazonDomain) &&
+        url.host() != "fls-na.amazon.com")
+      return;
+    if (navigation_url.DomainIs("bestbuy.com"))
+      return;
+    if (navigation_url.DomainIs("containerstore.com"))
+      return;
+    if (navigation_url.DomainIs("gap.com") && url.DomainIs("granify.com"))
+      return;
+    if (navigation_url.DomainIs("kohls.com"))
+      return;
+    if (navigation_url.DomainIs("officedepot.com") &&
+        url.DomainIs("chatid.com"))
+      return;
+    if (navigation_url.DomainIs("pier1.com"))
+      return;
+  }
 
   blink::WebHTTPBody body = request.HttpBody();
   if (body.IsNull())
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index 21bc479..0302b4e 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -142,8 +142,10 @@
 #include "third_party/blink/public/platform/scheduler/web_renderer_process_type.h"
 #include "third_party/blink/public/platform/url_conversion.h"
 #include "third_party/blink/public/platform/web_cache.h"
+#include "third_party/blink/public/platform/web_content_security_policy_struct.h"
 #include "third_party/blink/public/platform/web_runtime_features.h"
 #include "third_party/blink/public/platform/web_security_origin.h"
+#include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/public/platform/web_url.h"
 #include "third_party/blink/public/platform/web_url_error.h"
 #include "third_party/blink/public/platform/web_url_request.h"
@@ -195,6 +197,7 @@
 #include "chrome/renderer/extensions/chrome_extensions_renderer_client.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension_urls.h"
+#include "extensions/common/manifest_handlers/csp_info.h"
 #include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
 #include "extensions/common/switches.h"
 #include "extensions/renderer/dispatcher.h"
@@ -1657,3 +1660,28 @@
   printing::SetAgent(user_agent);
 #endif
 }
+
+void ChromeContentRendererClient::AppendContentSecurityPolicy(
+    const blink::WebURL& url,
+    blink::WebVector<blink::WebContentSecurityPolicyHeader>* csp) {
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+  DCHECK(csp);
+  GURL gurl(url);
+  const extensions::Extension* extension =
+      extensions::RendererExtensionRegistry::Get()->GetExtensionOrAppByURL(
+          gurl);
+  if (!extension || !extension->is_extension() ||
+      extension->manifest_version() < 3) {
+    return;
+  }
+
+  // Append the default extension pages CSP to ensure the extension can't relax
+  // the default applied CSP through means like Service Worker.
+  const std::string& default_csp =
+      extensions::CSPInfo::GetDefaultMV3ExtensionPagesCSP();
+
+  csp->push_back({blink::WebString::FromUTF8(default_csp),
+                  network::mojom::ContentSecurityPolicyType::kEnforce,
+                  network::mojom::ContentSecurityPolicySource::kHTTP});
+#endif
+}
diff --git a/chrome/renderer/chrome_content_renderer_client.h b/chrome/renderer/chrome_content_renderer_client.h
index 4fb3926..b9b1841 100644
--- a/chrome/renderer/chrome_content_renderer_client.h
+++ b/chrome/renderer/chrome_content_renderer_client.h
@@ -55,6 +55,7 @@
 namespace blink {
 class WebServiceWorkerContextProxy;
 enum class ProtocolHandlerSecurityLevel;
+struct WebContentSecurityPolicyHeader;
 }
 
 namespace chrome {
@@ -190,6 +191,9 @@
                              const std::string& name) override;
   bool IsSafeRedirectTarget(const GURL& url) override;
   void DidSetUserAgent(const std::string& user_agent) override;
+  void AppendContentSecurityPolicy(
+      const blink::WebURL& url,
+      blink::WebVector<blink::WebContentSecurityPolicyHeader>* csp) override;
 
 #if BUILDFLAG(ENABLE_PLUGINS)
   static mojo::AssociatedRemote<chrome::mojom::PluginInfoHost>&
diff --git a/chrome/services/speech/cloud_speech_recognition_client.cc b/chrome/services/speech/cloud_speech_recognition_client.cc
index bbbf509..3824981 100644
--- a/chrome/services/speech/cloud_speech_recognition_client.cc
+++ b/chrome/services/speech/cloud_speech_recognition_client.cc
@@ -115,7 +115,8 @@
       result = previous_result_;
 
     previous_result_ = result;
-    recognition_event_callback().Run(result, is_final);
+    recognition_event_callback().Run(
+        media::SpeechRecognitionResult(result, is_final));
   }
 }
 
diff --git a/chrome/services/speech/cloud_speech_recognition_client.h b/chrome/services/speech/cloud_speech_recognition_client.h
index 814a68d9..bd82c5f 100644
--- a/chrome/services/speech/cloud_speech_recognition_client.h
+++ b/chrome/services/speech/cloud_speech_recognition_client.h
@@ -16,6 +16,7 @@
 #include "components/speech/downstream_loader_client.h"
 #include "components/speech/upstream_loader.h"
 #include "components/speech/upstream_loader_client.h"
+#include "media/mojo/mojom/speech_recognition_service.mojom.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 
 namespace speech {
@@ -38,8 +39,7 @@
                                      public speech::DownstreamLoaderClient {
  public:
   using OnRecognitionEventCallback =
-      base::RepeatingCallback<void(const std::string& result,
-                                   const bool is_final)>;
+      base::RepeatingCallback<void(media::SpeechRecognitionResult)>;
 
   explicit CloudSpeechRecognitionClient(
       OnRecognitionEventCallback callback,
diff --git a/chrome/services/speech/cloud_speech_recognition_client_unittest.cc b/chrome/services/speech/cloud_speech_recognition_client_unittest.cc
index e02cbf30..7ab83a8b 100644
--- a/chrome/services/speech/cloud_speech_recognition_client_unittest.cc
+++ b/chrome/services/speech/cloud_speech_recognition_client_unittest.cc
@@ -68,7 +68,7 @@
   void SetUp() override;
 
  protected:
-  void OnRecognitionEvent(const std::string& result, const bool is_final);
+  void OnRecognitionEvent(media::SpeechRecognitionResult result);
 
   void InjectDummyAudio();
 
@@ -164,10 +164,9 @@
 }
 
 void CloudSpeechRecognitionClientUnitTest::OnRecognitionEvent(
-    const std::string& result,
-    const bool is_final) {
-  results_.push(result);
-  is_final_ = is_final;
+    media::SpeechRecognitionResult result) {
+  results_.push(result.transcription);
+  is_final_ = result.is_final;
 }
 
 void CloudSpeechRecognitionClientUnitTest::InjectDummyAudio() {
diff --git a/chrome/services/speech/soda/BUILD.gn b/chrome/services/speech/soda/BUILD.gn
index 04249ac..f5770429 100644
--- a/chrome/services/speech/soda/BUILD.gn
+++ b/chrome/services/speech/soda/BUILD.gn
@@ -20,6 +20,7 @@
   deps = [
     ":soda_api_proto",
     "//base",
+    "//media/mojo/mojom",
   ]
 
   if (is_chromeos_ash) {
diff --git a/chrome/services/speech/soda/cros_soda_client.cc b/chrome/services/speech/soda/cros_soda_client.cc
index 4e32d9e..d04353b 100644
--- a/chrome/services/speech/soda/cros_soda_client.cc
+++ b/chrome/services/speech/soda/cros_soda_client.cc
@@ -8,6 +8,30 @@
 #include "chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
+namespace {
+
+media::SpeechRecognitionResult GetSpeechRecognitionResultFromFinalEvent(
+    const chromeos::machine_learning::mojom::FinalResultPtr& final_event) {
+  media::SpeechRecognitionResult result;
+  result.transcription = final_event->final_hypotheses.front();
+  result.is_final = true;
+
+  if (!final_event->timing_event || !final_event->hypothesis_part)
+    return result;
+
+  const auto& timing_event = final_event->timing_event;
+  media::TimingInformation timing;
+  timing.audio_start_time = timing_event->audio_start_time;
+  timing.audio_end_time = timing_event->event_end_time;
+  timing.hypothesis_parts = std::vector<media::HypothesisParts>();
+
+  for (const auto& part : final_event->hypothesis_part.value())
+    timing.hypothesis_parts->emplace_back(part->text, part->alignment);
+
+  return result;
+}
+
+}  // namespace
 namespace soda {
 CrosSodaClient::CrosSodaClient() : soda_client_(this) {}
 CrosSodaClient::~CrosSodaClient() = default;
@@ -30,7 +54,7 @@
 
 void CrosSodaClient::Reset(
     chromeos::machine_learning::mojom::SodaConfigPtr soda_config,
-    base::RepeatingCallback<void(const std::string&, bool)> callback) {
+    CrosSodaClient::TranscriptionResultCallback callback) {
   sample_rate_ = soda_config->sample_rate;
   channel_count_ = soda_config->channel_count;
   if (is_initialized_) {
@@ -67,15 +91,13 @@
     chromeos::machine_learning::mojom::SpeechRecognizerEventPtr event) {
   if (event->is_final_result()) {
     auto& final_result = event->get_final_result();
-    if (!final_result->final_hypotheses.empty()) {
-      const std::string final_hyp = final_result->final_hypotheses.front();
-      callback_.Run(final_hyp, true);
-    }
+    if (!final_result->final_hypotheses.empty())
+      callback_.Run(GetSpeechRecognitionResultFromFinalEvent(final_result));
   } else if (event->is_partial_result()) {
     auto& partial_result = event->get_partial_result();
     if (!partial_result->partial_text.empty()) {
       const std::string partial_hyp = partial_result->partial_text.front();
-      callback_.Run(partial_hyp, false);
+      callback_.Run(media::SpeechRecognitionResult(partial_hyp, false));
     }
   } else if (!event->is_endpointer_event() || !event->is_audio_event()) {
     LOG(ERROR) << "Some kind of other soda event, ignoring completely. Tag is '"
diff --git a/chrome/services/speech/soda/cros_soda_client.h b/chrome/services/speech/soda/cros_soda_client.h
index bfd0f3a..1762628 100644
--- a/chrome/services/speech/soda/cros_soda_client.h
+++ b/chrome/services/speech/soda/cros_soda_client.h
@@ -5,12 +5,10 @@
 #ifndef CHROME_SERVICES_SPEECH_SODA_CROS_SODA_CLIENT_H_
 #define CHROME_SERVICES_SPEECH_SODA_CROS_SODA_CLIENT_H_
 
-#include <memory>
-#include <string>
-
 #include "base/callback.h"
 #include "chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom.h"
 #include "chromeos/services/machine_learning/public/mojom/soda.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"
 
@@ -24,6 +22,9 @@
   CrosSodaClient();
   ~CrosSodaClient() override;
 
+  using TranscriptionResultCallback =
+      base::RepeatingCallback<void(media::SpeechRecognitionResult event)>;
+
   // Adds audio to this soda client. Only makes sense when initialized.
   // Eventually, asynchronous callbacks to the ::SodaClient overrides below are
   // executed.
@@ -42,12 +43,12 @@
   // Reset this client with the provided configuration, and send recognition
   // callbacks of (text, is_final) to the given callback.
   void Reset(chromeos::machine_learning::mojom::SodaConfigPtr soda_config,
-             base::RepeatingCallback<void(const std::string&, bool)> callback);
+             TranscriptionResultCallback callback);
 
  private:
-  // This callback is called with (text, is_final) whenever soda responds
-  // appropriately.
-  base::RepeatingCallback<void(const std::string&, bool)> callback_;
+  // This callback is called with (media::mojom::SpeechRecognitionResult)
+  // whenever soda responds appropriately.
+  TranscriptionResultCallback callback_;
   bool is_initialized_ = false;
   int sample_rate_ = 0;
   int channel_count_ = 0;
diff --git a/chrome/services/speech/speech_recognition_recognizer_impl.cc b/chrome/services/speech/speech_recognition_recognizer_impl.cc
index 6e5d7a7..807484a 100644
--- a/chrome/services/speech/speech_recognition_recognizer_impl.cc
+++ b/chrome/services/speech/speech_recognition_recognizer_impl.cc
@@ -63,9 +63,10 @@
     DCHECK(result.hypothesis_size());
     static_cast<SpeechRecognitionRecognizerImpl*>(callback_handle)
         ->recognition_event_callback()
-        .Run(
-            std::string(result.hypothesis(0)),
-            result.result_type() == soda::chrome::SodaRecognitionResult::FINAL);
+        .Run(media::SpeechRecognitionResult(
+            result.hypothesis(0),
+            result.result_type() ==
+                soda::chrome::SodaRecognitionResult::FINAL));
   }
 
   if (response.soda_type() == soda::chrome::SodaResponse::LANGID) {
@@ -129,12 +130,12 @@
 }
 
 void SpeechRecognitionRecognizerImpl::OnRecognitionEvent(
-    const std::string& result,
-    const bool is_final) {
+    media::SpeechRecognitionResult event) {
   if (!client_remote_.is_bound())
     return;
+
   client_remote_->OnSpeechRecognitionRecognitionEvent(
-      media::mojom::SpeechRecognitionResult::New(result, is_final),
+      std::move(event),
       base::BindOnce(&SpeechRecognitionRecognizerImpl::
                          OnSpeechRecognitionRecognitionEventCallback,
                      weak_factory_.GetWeakPtr()));
diff --git a/chrome/services/speech/speech_recognition_recognizer_impl.h b/chrome/services/speech/speech_recognition_recognizer_impl.h
index 1c8c5d5..2fa6ea5 100644
--- a/chrome/services/speech/speech_recognition_recognizer_impl.h
+++ b/chrome/services/speech/speech_recognition_recognizer_impl.h
@@ -26,11 +26,12 @@
     : public media::mojom::SpeechRecognitionRecognizer {
  public:
   using OnRecognitionEventCallback =
-      base::RepeatingCallback<void(const std::string& result,
-                                   const bool is_final)>;
+      base::RepeatingCallback<void(media::SpeechRecognitionResult event)>;
+
   using OnLanguageIdentificationEventCallback = base::RepeatingCallback<void(
       const std::string& language,
       const media::mojom::ConfidenceLevel confidence_level)>;
+
   SpeechRecognitionRecognizerImpl(
       mojo::PendingRemote<media::mojom::SpeechRecognitionRecognizerClient>
           remote,
@@ -78,7 +79,8 @@
 
   // Return the transcribed audio from the recognition event back to the caller
   // via the recognition event client.
-  void OnRecognitionEvent(const std::string& result, const bool is_final);
+  void OnRecognitionEvent(media::SpeechRecognitionResult event);
+
   void OnLanguageIdentificationEvent(
       const std::string& language,
       const media::mojom::ConfidenceLevel confidence_level);
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 9dac70d..d55f00d 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -593,6 +593,27 @@
   ]
 }
 
+if (is_android) {
+  template("telemetry_gpu_integration_test_android_template") {
+    forward_variables_from(invoker, [ "telemetry_target_suffix" ])
+    group(target_name) {
+      testonly = true
+      data_deps = [
+        "//content/test:telemetry_gpu_integration_test_support",
+        "//tools/perf/chrome_telemetry_build:telemetry_chrome_test${telemetry_target_suffix}",
+      ]
+    }
+  }
+
+  import("//tools/perf/chrome_telemetry_build/android_browser_types.gni")
+  foreach(_target_suffix, telemetry_android_browser_target_suffixes) {
+    telemetry_gpu_integration_test_android_template(
+        "telemetry_gpu_integration_test${_target_suffix}") {
+      telemetry_target_suffix = _target_suffix
+    }
+  }
+}
+
 if (is_win) {
   source_set("credential_provider_test_utils") {
     testonly = true
@@ -1337,6 +1358,7 @@
       "../browser/browsing_data/navigation_entry_remover_browsertest.cc",
       "../browser/browsing_data/third_party_data_remover_browsertest.cc",
       "../browser/capability_delegation_browsertest.cc",
+      "../browser/cart/cart_service_browsertest.cc",
       "../browser/chrome_back_forward_cache_browsertest.cc",
       "../browser/chrome_content_browser_client_browsertest.cc",
       "../browser/chrome_do_not_track_browsertest.cc",
@@ -1974,7 +1996,6 @@
     # files will probably not be applicable.
     sources += [
       "../browser/speech/extension_api/tts_extension_apitest.cc",
-      "../browser/speech/network_speech_recognizer_browsertest.cc",
       "../browser/speech/speech_recognition_browsertest.cc",
     ]
     deps += [ "//services/audio/public/cpp:test_support" ]
@@ -3331,6 +3352,7 @@
         "../browser/resources/chromeos/zip_archiver/test/zip_archiver_jstest.cc",
         "../browser/sessions/session_restore_browsertest_chromeos.cc",
         "../browser/signin/chromeos_mirror_account_consistency_browsertest.cc",
+        "../browser/speech/network_speech_recognizer_browsertest.cc",
         "../browser/speech/on_device_speech_recognizer_browsertest.cc",
         "../browser/ui/app_list/app_list_client_impl_browsertest.cc",
         "../browser/ui/app_list/app_service/app_service_app_item_browsertest.cc",
@@ -3768,8 +3790,10 @@
     }
 
     if (is_win || is_mac || (is_linux && !is_chromeos_lacros)) {
-      sources +=
-          [ "../browser/ui/views/profiles/profile_picker_view_browsertest.cc" ]
+      sources += [
+        "../browser/ui/views/profiles/profile_picker_view_browsertest.cc",
+        "../browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_browsertest.cc",
+      ]
     }
 
     sources += metric_integration_sources
@@ -3899,10 +3923,8 @@
   }
 }
 
-group("telemetry_perf_unittests") {
+group("telemetry_perf_unittests_base") {
   testonly = true
-  deps = [ "//tools/perf:perf" ]
-
   data = [
     # For isolate contract.
     "//testing/scripts/run_telemetry_as_googletest.py",
@@ -3921,16 +3943,72 @@
   data_deps = [ "//testing:test_scripts_shared" ]
 }
 
-group("telemetry_perf_tests") {
+group("telemetry_perf_unittests") {
   testonly = true
-  deps = [ "//tools/perf/:perf" ]
+  data_deps = [
+    ":telemetry_perf_unittests_base",
+    "//tools/perf:perf",
+  ]
+}
 
+if (is_android) {
+  template("telemetry_perf_unittests_android_template") {
+    forward_variables_from(invoker, [ "telemetry_target_suffix" ])
+    group(target_name) {
+      testonly = true
+      data_deps = [
+        ":telemetry_perf_unittests_base",
+        "//tools/perf:perf${telemetry_target_suffix}",
+      ]
+    }
+  }
+
+  import("//tools/perf/chrome_telemetry_build/android_browser_types.gni")
+  foreach(_target_suffix, telemetry_android_browser_target_suffixes) {
+    telemetry_perf_unittests_android_template(
+        "telemetry_perf_unittests${_target_suffix}") {
+      telemetry_target_suffix = _target_suffix
+    }
+  }
+}
+
+group("telemetry_perf_tests_base") {
+  testonly = true
   data_deps = [
     # Needed for isolate script to execute.
     "//testing:test_scripts_shared",
   ]
 }
 
+group("telemetry_perf_tests") {
+  testonly = true
+  data_deps = [
+    ":telemetry_perf_tests_base",
+    "//tools/perf/:perf",
+  ]
+}
+
+if (is_android) {
+  template("telemetry_perf_tests_android_template") {
+    forward_variables_from(invoker, [ "telemetry_target_suffix" ])
+    group(target_name) {
+      testonly = true
+      data_deps = [
+        ":telemetry_perf_tests_base",
+        "//tools/perf/:perf${telemetry_target_suffix}",
+      ]
+    }
+  }
+
+  import("//tools/perf/chrome_telemetry_build/android_browser_types.gni")
+  foreach(_target_suffix, telemetry_android_browser_target_suffixes) {
+    telemetry_perf_tests_android_template(
+        "telemetry_perf_tests${_target_suffix}") {
+      telemetry_target_suffix = _target_suffix
+    }
+  }
+}
+
 group("ct_telemetry_perf_tests_without_chrome") {
   testonly = true
   deps = [ "//tools/perf/:perf_without_chrome" ]
@@ -3950,8 +4028,12 @@
 
 # New target that will replace telemetry_perf_tests when testing
 # is done.
-template("performance_test_suite_template") {
-  forward_variables_from(invoker, [ "override_board" ])
+template("performance_test_suite_template_base") {
+  forward_variables_from(invoker,
+                         [
+                           "override_board",
+                           "data_deps",
+                         ])
   script_test(target_name) {
     run_under_python2 = true
     script = "//testing/scripts/run_performance_tests.py"
@@ -3965,9 +4047,8 @@
       args = [ "../../tools/perf/run_benchmark" ]
     }
 
-    data_deps = [
+    data_deps += [
       "//base:base_perftests",
-      "//chrome/test:telemetry_perf_tests",
       "//components:components_perftests",
       "//components/tracing:tracing_perftests",
       "//gpu:command_buffer_perftests",
@@ -4001,9 +4082,40 @@
   }
 }
 
+template("performance_test_suite_template") {
+  forward_variables_from(invoker, [ "override_board" ])
+  performance_test_suite_template_base(target_name) {
+    data_deps = [ "//chrome/test:telemetry_perf_tests" ]
+  }
+}
+
+if (is_android) {
+  template("performance_test_suite_template_android") {
+    forward_variables_from(invoker,
+                           [
+                             "override_board",
+                             "telemetry_target_suffix",
+                           ])
+    performance_test_suite_template_base(target_name) {
+      data_deps =
+          [ "//chrome/test:telemetry_perf_tests${telemetry_target_suffix}" ]
+    }
+  }
+}
+
 performance_test_suite_template("performance_test_suite") {
 }
 
+if (is_android) {
+  import("//tools/perf/chrome_telemetry_build/android_browser_types.gni")
+  foreach(_target_suffix, telemetry_android_browser_target_suffixes) {
+    performance_test_suite_template_android(
+        "performance_test_suite${_target_suffix}") {
+      telemetry_target_suffix = _target_suffix
+    }
+  }
+}
+
 # Generate performance_test_suite_${board} targets.
 # These set --board to ${board} instead of deriving it from the target.
 # This is useful for Lacros, where the generic build is deployed to
@@ -4018,25 +4130,27 @@
   }
 }
 
-# Difference between this and performance_test_suite is that this runs a devil
-# script before the build, to remove the system webview. See
-# //testing/buildbot/gn_isolate_map.pyl
-group("performance_webview_test_suite") {
-  testonly = true
-  deps = [ "//chrome/test:performance_test_suite" ]
-}
+if (is_android) {
+  # Difference between this and performance_test_suite is that this runs a devil
+  # script before the build, to remove the system webview. See
+  # //testing/buildbot/gn_isolate_map.pyl
+  group("performance_webview_test_suite") {
+    testonly = true
+    deps = [ "//chrome/test:performance_test_suite_android_webview" ]
+  }
 
-group("performance_weblayer_test_suite") {
-  testonly = true
-  deps = [ "//chrome/test:performance_test_suite" ]
-}
+  group("performance_weblayer_test_suite") {
+    testonly = true
+    deps = [ "//chrome/test:performance_test_suite_android_weblayer" ]
+  }
 
-# Difference between this and telemetry_perf_tests is that this runs a devil
-# script before the build, to remove the system webview. See
-# //testing/buildbot/gn_isolate_map.pyl
-group("telemetry_perf_webview_tests") {
-  testonly = true
-  deps = [ "//chrome/test:telemetry_perf_tests" ]
+  # Difference between this and telemetry_perf_tests is that this runs a devil
+  # script before the build, to remove the system webview. See
+  # //testing/buildbot/gn_isolate_map.pyl
+  group("telemetry_perf_webview_tests") {
+    testonly = true
+    deps = [ "//chrome/test:telemetry_perf_tests_android_webview" ]
+  }
 }
 
 config("disable_thinlto_cache_flags") {
@@ -6818,6 +6932,7 @@
       "../browser/ui/views/relaunch_notification/relaunch_notification_controller_unittest.cc",
       "../browser/ui/views/relaunch_notification/relaunch_required_timer_internal_unittest.cc",
       "../browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl_unittest.cc",
+      "../browser/ui/views/send_tab_to_self/send_tab_to_self_toolbar_bubble_view_unittest.cc",
       "../browser/ui/views/tab_contents/chrome_web_contents_view_delegate_views_unittest.cc",
       "../browser/ui/views/tabs/color_picker_view_unittest.cc",
       "../browser/ui/views/tabs/fake_base_tab_strip_controller.cc",
@@ -8469,9 +8584,8 @@
 }
 
 if (is_mac || is_win || is_android) {
-  group("rendering_representative_perf_tests") {
+  group("rendering_representative_perf_tests_base") {
     testonly = true
-    deps = [ "//tools/perf/chrome_telemetry_build:telemetry_chrome_test" ]
     data = [
       "//build/android/pylib",
       "//chrome/test/data/perf",
@@ -8482,6 +8596,35 @@
     ]
     data_deps = [ "//testing:test_scripts_shared" ]
   }
+
+  group("rendering_representative_perf_tests") {
+    testonly = true
+    data_deps = [
+      ":rendering_representative_perf_tests_base",
+      "//tools/perf/chrome_telemetry_build:telemetry_chrome_test",
+    ]
+  }
+
+  if (is_android) {
+    template("rendering_representative_perf_tests_android_template") {
+      forward_variables_from(invoker, [ "telemetry_target_suffix" ])
+      group(target_name) {
+        testonly = true
+        data_deps = [
+          ":rendering_representative_perf_tests_base",
+          "//tools/perf/chrome_telemetry_build:telemetry_chrome_test${telemetry_target_suffix}",
+        ]
+      }
+    }
+
+    import("//tools/perf/chrome_telemetry_build/android_browser_types.gni")
+    foreach(_target_suffix, telemetry_android_browser_target_suffixes) {
+      rendering_representative_perf_tests_android_template(
+          "rendering_representative_perf_tests${_target_suffix}") {
+        telemetry_target_suffix = _target_suffix
+      }
+    }
+  }
 }
 
 if (is_win) {
diff --git a/chrome/test/base/in_process_browser_test.cc b/chrome/test/base/in_process_browser_test.cc
index a3c931c..a59eddae 100644
--- a/chrome/test/base/in_process_browser_test.cc
+++ b/chrome/test/base/in_process_browser_test.cc
@@ -67,8 +67,6 @@
 #include "components/google/core/common/google_util.h"
 #include "components/os_crypt/os_crypt_mocker.h"
 #include "content/public/browser/devtools_agent_host.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/browser/notification_types.h"
 #include "content/public/common/content_paths.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test_utils.h"
@@ -600,11 +598,9 @@
 #endif  // !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
 
 void InProcessBrowserTest::AddBlankTabAndShow(Browser* browser) {
-  content::WindowedNotificationObserver observer(
-      content::NOTIFICATION_LOAD_STOP,
-      content::NotificationService::AllSources());
-  chrome::AddSelectedTabWithURL(browser, GURL(url::kAboutBlankURL),
-                                ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
+  content::WebContents* blank_tab = chrome::AddSelectedTabWithURL(
+      browser, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
+  content::TestNavigationObserver observer(blank_tab);
   observer.Wait();
 
   browser->window()->Show();
diff --git a/chrome/test/data/extensions/api_test/service_worker/worker_based_background/extension_csp_modification/extension_page.html b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/extension_csp_modification/extension_page.html
new file mode 100644
index 0000000..ac60f7ff
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/extension_csp_modification/extension_page.html
@@ -0,0 +1,10 @@
+<!--
+  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.
+-->
+
+<!DOCTYPE html>
+<script>
+  scriptExecuted = true;
+</script>
diff --git a/chrome/test/data/extensions/api_test/service_worker/worker_based_background/extension_csp_modification/manifest.json b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/extension_csp_modification/manifest.json
new file mode 100644
index 0000000..ccd592c3
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/extension_csp_modification/manifest.json
@@ -0,0 +1,6 @@
+{
+  "name": "Extension CSP modification",
+  "version": "1.0",
+  "manifest_version": 3,
+  "background": {"service_worker": "service_worker.js"}
+}
diff --git a/chrome/test/data/extensions/api_test/service_worker/worker_based_background/extension_csp_modification/service_worker.js b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/extension_csp_modification/service_worker.js
new file mode 100644
index 0000000..260f928
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/service_worker/worker_based_background/extension_csp_modification/service_worker.js
@@ -0,0 +1,29 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+self.addEventListener('fetch', event => {
+  if (!event.request.url.endsWith('extension_page.html'))
+    return;
+
+  // Strip the CSP header from the request.
+  return event.respondWith(
+    fetch(event.request).then(response => {
+      const kCSPHeader = 'content-security-policy';
+      chrome.test.sendMessage(response.headers.get(kCSPHeader));
+      let updatedHeaders = new Headers(response.headers);
+      updatedHeaders.delete(kCSPHeader);
+      let init = {
+        status: response.status,
+        statusText: response.statusText,
+        headers: updatedHeaders
+      };
+      return new Response(response.body, init);
+    })
+  );
+});
+
+self.addEventListener('activate', event => {
+  chrome.test.sendMessage('ready');
+  return self.clients.claim();
+});
diff --git a/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_browsertest.js b/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_browsertest.js
index 6d2b5eb..1893546b 100644
--- a/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_browsertest.js
+++ b/chrome/test/data/webui/chromeos/emoji_picker/emoji_picker_browsertest.js
@@ -35,7 +35,14 @@
   }
 };
 
-TEST_F('EmojiPickerMainTest', 'All', function() {
+// Flaky on debug builds, see https://crbug.com/1216145
+GEN('#if !defined(NDEBUG)');
+GEN('#define MAYBE_All DISABLED_All');
+GEN('#else');
+GEN('#define MAYBE_All All');
+GEN('#endif');
+
+TEST_F('EmojiPickerMainTest', 'MAYBE_All', function() {
   mocha.run();
 });
 
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.js b/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.js
index 56cd05b..ffd62dd 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.js
+++ b/chrome/test/data/webui/chromeos/shimless_rma/fake_shimless_rma_service_test.js
@@ -145,16 +145,69 @@
     });
   });
 
-  test('UpdateChromeDefaultUndefined', () => {
-    return service.updateChrome().then((error) => {
-      assertEquals(error, undefined);
+  test('UpdateChromeOk', () => {
+    let states = [
+      {state: RmaState.kUpdateChrome, error: RmadErrorCode.kOk},
+      {state: RmaState.kChooseDestination, error: RmadErrorCode.kOk},
+    ];
+    service.setStates(states);
+
+    return service.updateChrome().then((state) => {
+      assertEquals(state.state, RmaState.kChooseDestination);
+      assertEquals(state.error, RmadErrorCode.kOk);
     });
   });
 
-  test('SetUpdateChromeResultUpdatesResult', () => {
-    service.setUpdateChromeResult(RmadErrorCode.kRequestInvalid);
-    return service.updateChrome().then((error) => {
-      assertEquals(error.error, RmadErrorCode.kRequestInvalid);
+  test('UpdateChromeWhenRmaNotRequired', () => {
+    return service.updateChrome().then((state) => {
+      assertEquals(state.state, RmaState.kUnknown);
+      assertEquals(state.error, RmadErrorCode.kRmaNotRequired);
+    });
+  });
+
+  test('UpdateChromeWrongStateFails', () => {
+    let states = [
+      {state: RmaState.kWelcomeScreen, error: RmadErrorCode.kOk},
+      {state: RmaState.kChooseDestination, error: RmadErrorCode.kOk},
+    ];
+    service.setStates(states);
+
+    return service.updateChrome().then((state) => {
+      assertEquals(state.state, RmaState.kWelcomeScreen);
+      assertEquals(state.error, RmadErrorCode.kRequestInvalid);
+    });
+  });
+
+  test('UpdateChromeSkippedOk', () => {
+    let states = [
+      {state: RmaState.kUpdateChrome, error: RmadErrorCode.kOk},
+      {state: RmaState.kChooseDestination, error: RmadErrorCode.kOk},
+    ];
+    service.setStates(states);
+
+    return service.updateChromeSkipped().then((state) => {
+      assertEquals(state.state, RmaState.kChooseDestination);
+      assertEquals(state.error, RmadErrorCode.kOk);
+    });
+  });
+
+  test('UpdateChromeSkippedWhenRmaNotRequired', () => {
+    return service.updateChrome().then((state) => {
+      assertEquals(state.state, RmaState.kUnknown);
+      assertEquals(state.error, RmadErrorCode.kRmaNotRequired);
+    });
+  });
+
+  test('UpdateChromeSkippedWrongStateFails', () => {
+    let states = [
+      {state: RmaState.kWelcomeScreen, error: RmadErrorCode.kOk},
+      {state: RmaState.kChooseDestination, error: RmadErrorCode.kOk},
+    ];
+    service.setStates(states);
+
+    return service.updateChrome().then((state) => {
+      assertEquals(state.state, RmaState.kWelcomeScreen);
+      assertEquals(state.error, RmadErrorCode.kRequestInvalid);
     });
   });
 
diff --git a/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js b/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js
index 0098ba73..16901d6 100644
--- a/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js
+++ b/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js
@@ -772,15 +772,6 @@
     return destination_dialog_cros_test.suiteName;
   }
 
-  /** @override */
-  get featureList() {
-    const featureList = super.featureList || [];
-    const kPrintServerScaling = ['chromeos::features::kPrintServerScaling'];
-    featureList.enabled = featureList.enabled ?
-        featureList.enabled.concat(kPrintServerScaling) :
-        kPrintServerScaling;
-    return featureList;
-  }
 };
 
 TEST_F('PrintPreviewDestinationDialogCrosTest', 'PrinterList', function() {
@@ -1124,16 +1115,6 @@
   get suiteName() {
     return destination_item_test_cros.suiteName;
   }
-
-  /** @override */
-  get featureList() {
-    const kPrinterStatusDialog = ['chromeos::features::kPrinterStatusDialog'];
-    const featureList = super.featureList || [];
-    featureList.enabled = featureList.enabled ?
-        featureList.enabled.concat(kPrinterStatusDialog) :
-        kPrinterStatusDialog;
-    return featureList;
-  }
 };
 
 TEST_F(
diff --git a/chrome/test/data/webui/settings/chromeos/network_summary_item_test.js b/chrome/test/data/webui/settings/chromeos/network_summary_item_test.js
index 1ca11ab..45476fd 100644
--- a/chrome/test/data/webui/settings/chromeos/network_summary_item_test.js
+++ b/chrome/test/data/webui/settings/chromeos/network_summary_item_test.js
@@ -20,17 +20,19 @@
     return (el !== null) && (el.style.display !== 'none');
   }
 
-  function initWithPSimOnlyLocked(flag_enabled) {
+  function initWithPSimOnly(flagEnabled, isLocked) {
     const mojom = chromeos.networkConfig.mojom;
     const kTestIccid1 = '00000000000000000000';
 
+    const simLockStatus = isLocked ? {lockType: 'sim-pin'} : {lockType: ''};
+
     netSummaryItem.setProperties({
-      isUpdatedCellularUiEnabled_: flag_enabled,
+      isUpdatedCellularUiEnabled_: flagEnabled,
       deviceState: {
         deviceState: mojom.DeviceStateType.kEnabled,
         type: mojom.NetworkType.kCellular,
         simAbsent: false,
-        simLockStatus: {lockType: 'sim-pin'},
+        simLockStatus: simLockStatus,
         simInfos: [{slot_id: 1, eid: '', iccid: kTestIccid1, isPrimary: true}],
       },
       activeNetworkState: {
@@ -44,12 +46,12 @@
     Polymer.dom.flush();
   }
 
-  function initWithESimLocked(flag_enabled) {
+  function initWithESimLocked(flagEnabled) {
     const mojom = chromeos.networkConfig.mojom;
     const kTestIccid1 = '00000000000000000000';
 
     netSummaryItem.setProperties({
-      isUpdatedCellularUiEnabled_: flag_enabled,
+      isUpdatedCellularUiEnabled_: flagEnabled,
       deviceState: {
         deviceState: mojom.DeviceStateType.kEnabled,
         type: mojom.NetworkType.kCellular,
@@ -241,49 +243,65 @@
   });
 
   test('Mobile data toggle shown on locked device, flag on', function() {
-    initWithESimLocked(/*flag_enabled = */ true);
+    initWithESimLocked(/*flagEnabled = */ true);
     assertNotEquals(netSummaryItem.$$('#deviceEnabledButton'), null);
+    assertTrue(doesElementExist('#deviceEnabledButton'));
   });
 
   test('Mobile data toggle shown on locked device, flag off', function() {
-    initWithESimLocked(/*flag_enabled = */ false);
+    initWithESimLocked(/*flagEnabled = */ false);
     assertEquals(netSummaryItem.$$('#deviceEnabledButton'), null);
+    assertFalse(doesElementExist('#deviceEnabledButton'));
   });
 
   test('pSIM-only locked device, show SIM locked UI, flag off', function() {
-    initWithPSimOnlyLocked(/*flag_enabled = */ false);
+    initWithPSimOnly(/*flagEnabled = */ false, /*isLocked = */ true);
     assertTrue(doesElementExist('network-siminfo'));
     assertFalse(netSummaryItem.$$('#networkState')
                     .classList.contains('locked-warning-message'));
     assertTrue(
         netSummaryItem.$$('#networkState').classList.contains('network-state'));
+    assertFalse(doesElementExist('#deviceEnabledButton'));
   });
 
   test('eSIM enabled locked device, show SIM locked UI, flag off', function() {
-    initWithESimLocked(/*flag_enabled = */ false);
+    initWithESimLocked(/*flagEnabled = */ false);
     assertTrue(doesElementExist('network-siminfo'));
     assertFalse(netSummaryItem.$$('#networkState')
                     .classList.contains('locked-warning-message'));
     assertTrue(
         netSummaryItem.$$('#networkState').classList.contains('network-state'));
+    assertFalse(doesElementExist('#deviceEnabledButton'));
   });
 
   test('pSIM-only locked device, show SIM locked UI, flag on', function() {
-    initWithPSimOnlyLocked(/*flag_enabled = */ true);
+    initWithPSimOnly(/*flagEnabled = */ true, /*isLocked = */ true);
     assertTrue(doesElementExist('network-siminfo'));
     assertTrue(netSummaryItem.$$('#networkState')
                    .classList.contains('locked-warning-message'));
     assertFalse(
         netSummaryItem.$$('#networkState').classList.contains('network-state'));
+    assertFalse(doesElementExist('#deviceEnabledButton'));
   });
 
-  test('eSIM enabled locked device, show SIM locked UI, flag on', function() {
-    initWithESimLocked(/*flag_enabled = */ true);
+  test('pSIM-only locked device, no SIM locked UI, flag on', function() {
+    initWithPSimOnly(/*flagEnabled = */ true, /*isLocked = */ false);
     assertFalse(doesElementExist('network-siminfo'));
     assertFalse(netSummaryItem.$$('#networkState')
                     .classList.contains('locked-warning-message'));
     assertTrue(
         netSummaryItem.$$('#networkState').classList.contains('network-state'));
+    assertTrue(doesElementExist('#deviceEnabledButton'));
+  });
+
+  test('eSIM enabled locked device, show SIM locked UI, flag on', function() {
+    initWithESimLocked(/*flagEnabled = */ true);
+    assertFalse(doesElementExist('network-siminfo'));
+    assertFalse(netSummaryItem.$$('#networkState')
+                    .classList.contains('locked-warning-message'));
+    assertTrue(
+        netSummaryItem.$$('#networkState').classList.contains('network-state'));
+    assertTrue(doesElementExist('#deviceEnabledButton'));
   });
 
   test(
diff --git a/chrome/test/data/webui/tab_search/BUILD.gn b/chrome/test/data/webui/tab_search/BUILD.gn
index 7b5f807..947bb3e 100644
--- a/chrome/test/data/webui/tab_search/BUILD.gn
+++ b/chrome/test/data/webui/tab_search/BUILD.gn
@@ -19,6 +19,9 @@
                     "js_module_root=" + rebase_path(
                             "$root_gen_dir/mojom-webui/chrome/browser/ui/webui/tab_search",
                             root_build_dir),
+                    "js_module_root=" + rebase_path(
+                            "$root_gen_dir/mojom-webui/components/tab_groups/public/mojom",
+                            root_build_dir),
                   ]
   deps = [
     ":bimap_test",
diff --git a/chrome/test/data/webui/tab_search/tab_search_app_test.js b/chrome/test/data/webui/tab_search/tab_search_app_test.js
index cfda003..e1e5d2e 100644
--- a/chrome/test/data/webui/tab_search/tab_search_app_test.js
+++ b/chrome/test/data/webui/tab_search/tab_search_app_test.js
@@ -3,12 +3,12 @@
 // found in the LICENSE file.
 
 import {keyDownOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
-import {ProfileData, Tab, TabSearchApiProxyImpl, TabSearchAppElement, TabSearchSearchField} from 'chrome://tab-search.top-chrome/tab_search.js';
+import {ProfileData, Tab, TabGroup, TabGroupColor, TabSearchApiProxyImpl, TabSearchAppElement, TabSearchSearchField} from 'chrome://tab-search.top-chrome/tab_search.js';
 
 import {assertEquals, assertFalse, assertNotEquals, assertTrue} from '../../chai_assert.js';
 import {flushTasks, waitAfterNextRender} from '../../test_util.m.js';
 
-import {generateSampleDataFromSiteNames, generateSampleTabsFromSiteNames, SAMPLE_RECENTLY_CLOSED_DATA, SAMPLE_WINDOW_DATA, SAMPLE_WINDOW_HEIGHT, sampleData} from './tab_search_test_data.js';
+import {generateSampleDataFromSiteNames, generateSampleTabsFromSiteNames, SAMPLE_RECENTLY_CLOSED_DATA, SAMPLE_WINDOW_DATA, SAMPLE_WINDOW_HEIGHT, sampleData, sampleToken} from './tab_search_test_data.js';
 import {initLoadTimeDataWithDefaults} from './tab_search_test_helper.js';
 import {TestTabSearchApiProxy} from './test_tab_search_api_proxy.js';
 
@@ -64,7 +64,8 @@
   test('return all open and recently closed tabs', async () => {
     await setupTest({
       windows: SAMPLE_WINDOW_DATA,
-      recentlyClosedTabs: SAMPLE_RECENTLY_CLOSED_DATA
+      recentlyClosedTabs: SAMPLE_RECENTLY_CLOSED_DATA,
+      tabGroups: [],
     });
     tabSearchApp.shadowRoot.querySelector('#tabsList')
         .ensureAllDomItemsAvailable();
@@ -82,6 +83,7 @@
           }],
           recentlyClosedTabs: generateSampleTabsFromSiteNames(
               ['RecentlyClosedTab1', 'RecentlyClosedTab2'], false),
+          tabGroups: [],
         },
         {recentlyClosedDefaultItemDisplayCount: 1});
 
@@ -97,7 +99,8 @@
   test('Search text changes tab items', async () => {
     await setupTest({
       windows: SAMPLE_WINDOW_DATA,
-      recentlyClosedTabs: SAMPLE_RECENTLY_CLOSED_DATA
+      recentlyClosedTabs: SAMPLE_RECENTLY_CLOSED_DATA,
+      tabGroups: [],
     });
     const searchField = /** @type {!TabSearchSearchField} */
         (tabSearchApp.shadowRoot.querySelector('#searchField'));
@@ -129,8 +132,11 @@
       title: 'Google',
       url: 'https://www.google.com',
     };
-    await setupTest(
-        {windows: [{active: true, tabs: [tabData]}], recentlyClosedTabs: []});
+    await setupTest({
+      windows: [{active: true, tabs: [tabData]}],
+      recentlyClosedTabs: [],
+      tabGroups: []
+    });
 
     const tabSearchItem = /** @type {!HTMLElement} */
         (tabSearchApp.shadowRoot.querySelector('#tabsList')
@@ -169,7 +175,8 @@
           url: 'https://www.google.com',
         }]
       }],
-      recentlyClosedTabs: [tabData]
+      recentlyClosedTabs: [tabData],
+      tabGroups: [],
     });
 
     let tabSearchItem = /** @type {!HTMLElement} */
@@ -181,8 +188,11 @@
   });
 
   test('Keyboard navigation on an empty list', async () => {
-    await setupTest(
-        {windows: [{active: true, tabs: []}], recentlyClosedTabs: []});
+    await setupTest({
+      windows: [{active: true, tabs: []}],
+      recentlyClosedTabs: [],
+      tabGroups: []
+    });
 
     const searchField = /** @type {!TabSearchSearchField} */
         (tabSearchApp.shadowRoot.querySelector("#searchField"));
@@ -265,7 +275,7 @@
     await setupTest(sampleData());
     verifyTabIds(queryRows(), [1, 5, 6, 2, 3, 4]);
     testProxy.getCallbackRouterRemote().tabsChanged(
-        {windows: [], recentlyClosedTabs: []});
+        {windows: [], recentlyClosedTabs: [], tabGroups: []});
     await flushTasks();
     verifyTabIds(queryRows(), []);
     assertEquals(-1, tabSearchApp.getSelectedIndex());
@@ -280,14 +290,18 @@
     keyDownOn(searchField, 0, [], 'ArrowDown');
     assertEquals(1, tabSearchApp.getSelectedIndex());
 
-    testProxy.getCallbackRouterRemote().tabsChanged(
-        {windows: [testData.windows[0]], recentlyClosedTabs: []});
+    testProxy.getCallbackRouterRemote().tabsChanged({
+      windows: [testData.windows[0]],
+      recentlyClosedTabs: [],
+      tabGroups: []
+    });
     await flushTasks();
     assertEquals(1, tabSearchApp.getSelectedIndex());
 
     testProxy.getCallbackRouterRemote().tabsChanged({
       windows: [{active: true, tabs: [testData.windows[0].tabs[0]]}],
-      recentlyClosedTabs: []
+      recentlyClosedTabs: [],
+      tabGroups: [],
     });
     await flushTasks();
     assertEquals(0, tabSearchApp.getSelectedIndex());
@@ -495,14 +509,16 @@
     // Move active tab to the bottom of the list.
     await setupTest({
       windows: [{active: true, height: SAMPLE_WINDOW_HEIGHT, tabs}],
-      recentlyClosedTabs: []
+      recentlyClosedTabs: [],
+      tabGroups: [],
     });
     verifyTabIds(queryRows(), [3, 1, 2]);
 
     await setupTest(
         {
           windows: [{active: true, height: SAMPLE_WINDOW_HEIGHT, tabs}],
-          recentlyClosedTabs: []
+          recentlyClosedTabs: [],
+          tabGroups: [],
         },
         {'moveActiveTabToBottom': false});
     verifyTabIds(queryRows(), [2, 3, 1]);
@@ -524,4 +540,36 @@
 
     assertEquals(3, testProxy.getCallCount('closeUI'));
   });
+
+  test('Tab associated with TabGroup data', async () => {
+    const token = sampleToken(1, 1);
+    const tabs = [
+      {
+        index: 0,
+        tabId: 1,
+        groupId: token,
+        title: 'Google',
+        url: 'https://www.google.com',
+        lastActiveTimeTicks: {internalValue: BigInt(2)},
+        lastActiveElapsedText: '',
+      },
+    ];
+    const tabGroup = /** @type {!TabGroup} */ ({
+      id: token,
+      color: TabGroupColor.kBlue,
+      title: 'Search Engines',
+    });
+
+    await setupTest({
+      windows: [{active: true, height: SAMPLE_WINDOW_HEIGHT, tabs}],
+      recentlyClosedTabs: [],
+      tabGroups: [tabGroup],
+    });
+
+    let tabSearchItem = /** @type {!HTMLElement} */ (
+        tabSearchApp.shadowRoot.querySelector('#tabsList')
+            .querySelector('tab-search-item[id="1"]'));
+    assertEquals('Google', tabSearchItem.data.tab.title);
+    assertEquals('Search Engines', tabSearchItem.data.tabGroup.title);
+  });
 });
diff --git a/chrome/test/data/webui/tab_search/tab_search_item_test.js b/chrome/test/data/webui/tab_search/tab_search_item_test.js
index 3ada5c2..5daf57f 100644
--- a/chrome/test/data/webui/tab_search/tab_search_item_test.js
+++ b/chrome/test/data/webui/tab_search/tab_search_item_test.js
@@ -2,10 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {Tab, TabData, TabItemType, TabSearchItem} from 'chrome://tab-search.top-chrome/tab_search.js';
+import {Tab, TabData, TabGroup, TabGroupColor, TabItemType, TabSearchItem} from 'chrome://tab-search.top-chrome/tab_search.js';
+
 import {assertDeepEquals, assertEquals, assertNotEquals} from '../../chai_assert.js';
 import {flushTasks} from '../../test_util.m.js';
 
+import {sampleToken} from './tab_search_test_data.js';
+
 suite('TabSearchItemTest', () => {
   /** @type {!TabSearchItem} */
   let tabSearchItem;
@@ -104,4 +107,38 @@
         tabSearchItem.shadowRoot.querySelector('cr-icon-button'));
     assertEquals(null, tabSearchItemCloseButton);
   });
+
+  test('GroupDetailsPresence', async () => {
+    const token = sampleToken(1, 1);
+    const tab = /** @type {!Tab} */ ({
+      active: true,
+      index: 0,
+      isDefaultFavicon: true,
+      lastActiveTimeTicks: {internalValue: BigInt(0)},
+      pinned: false,
+      showIcon: true,
+      tabId: 0,
+      groupId: token,
+      url: 'https://example.com',
+      title: 'Example.com site',
+    });
+
+    const tabGroup = /** @type {!TabGroup} */ ({
+      id: token,
+      color: TabGroupColor.kBlue,
+      title: 'Examples',
+    });
+
+    await setupTest(/** @type {!TabData} */ (
+        {hostname: 'example', tab, type: TabItemType.OPEN, tabGroup}));
+    const groupDotElement = tabSearchItem.shadowRoot.querySelector('#groupDot');
+    assertNotEquals(null, groupDotElement);
+    const groupDotComputedStyle = getComputedStyle(groupDotElement);
+    assertEquals(
+        groupDotComputedStyle.getPropertyValue('--tab-group-color-blue'),
+        groupDotComputedStyle.getPropertyValue('--group-dot-color'));
+
+    assertNotEquals(
+        null, tabSearchItem.shadowRoot.querySelector('#groupTitle'));
+  });
 });
diff --git a/chrome/test/data/webui/tab_search/tab_search_test_data.js b/chrome/test/data/webui/tab_search/tab_search_test_data.js
index a4642728..a277789a 100644
--- a/chrome/test/data/webui/tab_search/tab_search_test_data.js
+++ b/chrome/test/data/webui/tab_search/tab_search_test_data.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {Token} from 'chrome://resources/mojo/mojo/public/mojom/base/token.mojom-webui.js';
+
 /** @type {number} */
 export const SAMPLE_WINDOW_HEIGHT = 448;
 
@@ -89,7 +91,7 @@
 
 /** @return {!Array} */
 export function sampleData() {
-  return {windows: SAMPLE_WINDOW_DATA, recentlyClosedTabs: []};
+  return {windows: SAMPLE_WINDOW_DATA, recentlyClosedTabs: [], tabGroups: []};
 }
 
 /**
@@ -136,6 +138,21 @@
       height: SAMPLE_WINDOW_HEIGHT,
       tabs: generateSampleTabsFromSiteNames(siteNames)
     }],
-    recentlyClosedTabs: []
+    recentlyClosedTabs: [],
+    tabGroups: [],
   };
 }
+
+/**
+ * @param {!bigint} high
+ * @param {!bigint} low
+ * @return {!Token}
+ */
+export function sampleToken(high, low) {
+  const token = new Token();
+  token.high = high;
+  token.low = low;
+  Object.freeze(token);
+
+  return token;
+}
diff --git a/chrome/test/media_router/BUILD.gn b/chrome/test/media_router/BUILD.gn
index 5d78dc1..d2c5191 100644
--- a/chrome/test/media_router/BUILD.gn
+++ b/chrome/test/media_router/BUILD.gn
@@ -144,14 +144,42 @@
 }
 
 if (enable_downstream_media_tests) {
-  group("media_router_perf_tests") {
+  group("media_router_perf_tests_base") {
     testonly = true
     data = [
       "$root_out_dir/mr_extension/",
       "internal/",
       "telemetry/",
     ]
-    deps = [ "//tools/perf:perf" ]
     data_deps = [ "//tools/perf/contrib/media_router_benchmarks:telemetry_extension_resources" ]
   }
+
+  group("media_router_perf_tests") {
+    testonly = true
+    data_deps = [
+      ":media_router_perf_tests_base",
+      "//tools/perf:perf",
+    ]
+  }
+
+  if (is_android) {
+    template("media_router_perf_tests_android_template") {
+      forward_variables_from(invoker, [ "telemetry_target_suffix" ])
+      group(target_name) {
+        testonly = true
+        data_deps = [
+          ":media_router_perf_tests_base",
+          "//tools/perf:perf${telemetry_target_suffix}",
+        ]
+      }
+    }
+
+    import("//tools/perf/chrome_telemetry_build/android_browser_types.gni")
+    foreach(_target_suffix, telemetry_android_browser_target_suffixes) {
+      media_router_perf_tests_android_template(
+          "media_router_perf_tests${_target_suffix}") {
+        telemetry_target_suffix = _target_suffix
+      }
+    }
+  }
 }
diff --git a/chromecast/browser/cast_content_gesture_handler_test.cc b/chromecast/browser/cast_content_gesture_handler_test.cc
index fe532b3..87ee316 100644
--- a/chromecast/browser/cast_content_gesture_handler_test.cc
+++ b/chromecast/browser/cast_content_gesture_handler_test.cc
@@ -40,12 +40,6 @@
 constexpr gfx::Point kOngoingRightGesturePoint1(400, 50);
 constexpr gfx::Point kRightGestureEndPoint(200, 60);
 
-ACTION_TEMPLATE(InvokeCallbackArgument,
-                HAS_1_TEMPLATE_PARAMS(int, k),
-                AND_1_VALUE_PARAMS(p0)) {
-  std::move(std::get<k>(args)).Run(p0);
-}
-
 }  // namespace
 
 class CastContentGestureHandlerTest : public testing::Test {
@@ -70,7 +64,8 @@
   EXPECT_CALL(*delegate_, GestureProgress(Eq(GestureType::GO_BACK),
                                           Eq(kOngoingBackGesturePoint1)));
   EXPECT_CALL(*delegate_, ConsumeGesture(Eq(GestureType::GO_BACK), _))
-      .WillRepeatedly(InvokeCallbackArgument<1>(true));
+      .WillRepeatedly(
+          [](auto, auto callback) { std::move(callback).Run(true); });
   dispatcher_.CanHandleSwipe(CastSideSwipeOrigin::LEFT);
   dispatcher_.HandleSideSwipe(CastSideSwipeEvent::BEGIN,
                               CastSideSwipeOrigin::LEFT, kLeftSidePoint);
@@ -117,7 +112,8 @@
   EXPECT_CALL(*delegate_,
               GestureProgress(Eq(GestureType::GO_BACK), Eq(kPastTheEndPoint1)));
   EXPECT_CALL(*delegate_, ConsumeGesture(Eq(GestureType::GO_BACK), _))
-      .WillRepeatedly(InvokeCallbackArgument<1>(true));
+      .WillRepeatedly(
+          [](auto, auto callback) { std::move(callback).Run(true); });
   dispatcher_.CanHandleSwipe(CastSideSwipeOrigin::LEFT);
   dispatcher_.HandleSideSwipe(CastSideSwipeEvent::BEGIN,
                               CastSideSwipeOrigin::LEFT, kLeftSidePoint);
@@ -215,7 +211,8 @@
   EXPECT_CALL(*delegate_, GestureProgress(Eq(GestureType::TOP_DRAG),
                                           Eq(kOngoingTopGesturePoint1)));
   EXPECT_CALL(*delegate_, ConsumeGesture(Eq(GestureType::TOP_DRAG), _))
-      .WillRepeatedly(InvokeCallbackArgument<1>(true));
+      .WillRepeatedly(
+          [](auto, auto callback) { std::move(callback).Run(true); });
   dispatcher_.CanHandleSwipe(CastSideSwipeOrigin::TOP);
   dispatcher_.HandleSideSwipe(CastSideSwipeEvent::BEGIN,
                               CastSideSwipeOrigin::TOP, kTopSidePoint);
@@ -238,7 +235,8 @@
   EXPECT_CALL(*delegate_, GestureProgress(Eq(GestureType::RIGHT_DRAG),
                                           Eq(kOngoingRightGesturePoint1)));
   EXPECT_CALL(*delegate_, ConsumeGesture(Eq(GestureType::RIGHT_DRAG), _))
-      .WillRepeatedly(InvokeCallbackArgument<1>(true));
+      .WillRepeatedly(
+          [](auto, auto callback) { std::move(callback).Run(true); });
   dispatcher_.CanHandleSwipe(CastSideSwipeOrigin::RIGHT);
   dispatcher_.HandleSideSwipe(CastSideSwipeEvent::BEGIN,
                               CastSideSwipeOrigin::RIGHT, kRightSidePoint);
diff --git a/chromecast/browser/test/mock_cast_content_window_delegate.cc b/chromecast/browser/test/mock_cast_content_window_delegate.cc
index 86e6725..f8430963 100644
--- a/chromecast/browser/test/mock_cast_content_window_delegate.cc
+++ b/chromecast/browser/test/mock_cast_content_window_delegate.cc
@@ -10,13 +10,6 @@
 
 MockCastContentWindowDelegate::~MockCastContentWindowDelegate() = default;
 
-void MockCastContentWindowDelegate::ConsumeGesture(
-    GestureType gesture_type,
-    GestureHandledCallback handled_callback) {
-  ConsumeGesture(gesture_type,
-                 base::AdaptCallbackForRepeating(std::move(handled_callback)));
-}
-
 std::string MockCastContentWindowDelegate::GetId() {
   return "mockContentWindowDelegate";
 }
diff --git a/chromecast/browser/test/mock_cast_content_window_delegate.h b/chromecast/browser/test/mock_cast_content_window_delegate.h
index 71f8f65..9a5fcdb 100644
--- a/chromecast/browser/test/mock_cast_content_window_delegate.h
+++ b/chromecast/browser/test/mock_cast_content_window_delegate.h
@@ -21,15 +21,12 @@
   MOCK_METHOD1(CanHandleGesture, bool(GestureType gesture_type));
   MOCK_METHOD2(ConsumeGesture,
                void(GestureType gesture_type,
-                    base::RepeatingCallback<void(bool)> handled_callback));
+                    GestureHandledCallback handled_callback));
   MOCK_METHOD1(CancelGesture, void(GestureType gesture_type));
   MOCK_METHOD2(GestureProgress,
                void(GestureType gesture_type,
                     const gfx::Point& touch_location));
 
-  void ConsumeGesture(GestureType gesture_type,
-                      GestureHandledCallback handled_callback) override;
-
   std::string GetId() override;
 };
 
diff --git a/chromecast/media/api/cma_backend_factory.h b/chromecast/media/api/cma_backend_factory.h
index 19a4e09..6ab51689 100644
--- a/chromecast/media/api/cma_backend_factory.h
+++ b/chromecast/media/api/cma_backend_factory.h
@@ -7,15 +7,24 @@
 
 #include <memory>
 
+namespace service_manager {
+class Connector;
+}  // namespace service_manager
+
 namespace chromecast {
 namespace media {
 
 class CmaBackend;
+class MediaPipelineBackendManager;
 struct MediaPipelineDeviceParams;
 
 // Abstract base class to create CmaBackend.
 class CmaBackendFactory {
  public:
+  static std::unique_ptr<CmaBackendFactory> Create(
+      MediaPipelineBackendManager* media_pipeline_backend_manager,
+      std::unique_ptr<service_manager::Connector> connector);
+
   virtual ~CmaBackendFactory() = default;
 
   // Creates a CMA backend. Must be called on the same thread as
diff --git a/chromecast/media/cdm/cast_cdm.cc b/chromecast/media/cdm/cast_cdm.cc
index 4511bbdd..37f181e 100644
--- a/chromecast/media/cdm/cast_cdm.cc
+++ b/chromecast/media/cdm/cast_cdm.cc
@@ -149,8 +149,9 @@
   session_message_cb_.Run(session_id, message_type, message);
 }
 
-void CastCdm::OnSessionClosed(const std::string& session_id) {
-  session_closed_cb_.Run(session_id);
+void CastCdm::OnSessionClosed(const std::string& session_id,
+                              ::media::CdmSessionClosedReason reason) {
+  session_closed_cb_.Run(session_id, reason);
 }
 
 void CastCdm::OnSessionKeysChange(const std::string& session_id,
diff --git a/chromecast/media/cdm/cast_cdm.h b/chromecast/media/cdm/cast_cdm.h
index 89711db..7debfde3 100644
--- a/chromecast/media/cdm/cast_cdm.h
+++ b/chromecast/media/cdm/cast_cdm.h
@@ -77,7 +77,8 @@
   void OnSessionMessage(const std::string& session_id,
                         const std::vector<uint8_t>& message,
                         ::media::CdmMessageType message_type);
-  void OnSessionClosed(const std::string& session_id);
+  void OnSessionClosed(const std::string& session_id,
+                       ::media::CdmSessionClosedReason reason);
   void OnSessionKeysChange(const std::string& session_id,
                            bool newly_usable_keys,
                            ::media::CdmKeysInfo keys_info);
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 1998f8ec..f7337879 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-14010.0.0
\ No newline at end of file
+14011.0.0
\ No newline at end of file
diff --git a/chromeos/components/camera_app_ui/resources/js/views/camera/preview.js b/chromeos/components/camera_app_ui/resources/js/views/camera/preview.js
index dcba8617..e6192f5 100644
--- a/chromeos/components/camera_app_ui/resources/js/views/camera/preview.js
+++ b/chromeos/components/camera_app_ui/resources/js/views/camera/preview.js
@@ -209,8 +209,7 @@
     });
     await video.play();
     this.video_.parentElement.replaceChild(tpl, this.video_);
-    this.video_.removeAttribute('srcObject');
-    this.video_.load();
+    this.video_.srcObject = null;
     this.video_ = video;
     video.addEventListener('resize', () => this.onIntrinsicSizeChanged_());
     video.addEventListener(
diff --git a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.cc b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.cc
index 121f2575..c3ea05e 100644
--- a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.cc
+++ b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.cc
@@ -334,7 +334,9 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DVLOG(2) << __func__;
   cdm_session_tracker_.RemoveSession(session_id);
-  session_closed_cb_.Run(session_id);
+  // TODO(crbug.com/1208618): Update cdm::mojom::ContentDecryptionModuleClient
+  // to support CdmSessionClosedReason.
+  session_closed_cb_.Run(session_id, media::CdmSessionClosedReason::kClose);
 }
 
 void ContentDecryptionModuleAdapter::OnSessionKeysChange(
@@ -536,7 +538,8 @@
 ContentDecryptionModuleAdapter::~ContentDecryptionModuleAdapter() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DVLOG(2) << __func__;
-  cdm_session_tracker_.CloseRemainingSessions(session_closed_cb_);
+  cdm_session_tracker_.CloseRemainingSessions(
+      session_closed_cb_, media::CdmSessionClosedReason::kCdmUnavailable);
 }
 
 void ContentDecryptionModuleAdapter::OnConnectionError() {
@@ -553,7 +556,8 @@
   // any open sessions.
   cdm_promise_adapter_.Clear(
       media::CdmPromiseAdapter::ClearReason::kConnectionError);
-  cdm_session_tracker_.CloseRemainingSessions(session_closed_cb_);
+  cdm_session_tracker_.CloseRemainingSessions(
+      session_closed_cb_, media::CdmSessionClosedReason::kCdmUnavailable);
 }
 
 void ContentDecryptionModuleAdapter::RejectTrackedPromise(
diff --git a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc
index 30447d8..a26bc0d 100644
--- a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc
+++ b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc
@@ -311,7 +311,7 @@
       media::CdmSessionType::kTemporary, media::EmeInitDataType::CENC,
       ToVector(kFakeEmeInitData), std::move(promise));
   // We should also be getting a session closed callback for any open sessions.
-  EXPECT_CALL(mock_session_closed_cb_, Run(kFakeSessionId1));
+  EXPECT_CALL(mock_session_closed_cb_, Run(kFakeSessionId1, _));
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(session_id, kFakeSessionId1);
 }
@@ -336,7 +336,7 @@
 TEST_F(ContentDecryptionModuleAdapterTest, LoadSession_Success) {
   LoadSession();
   // We should also be getting a session closed callback for any open sessions.
-  EXPECT_CALL(mock_session_closed_cb_, Run(kFakeSessionId2));
+  EXPECT_CALL(mock_session_closed_cb_, Run(kFakeSessionId2, _));
 }
 
 TEST_F(ContentDecryptionModuleAdapterTest, UpdateSession_Failure) {
@@ -428,7 +428,7 @@
 
 TEST_F(ContentDecryptionModuleAdapterTest, OnSessionClosed) {
   LoadSession();
-  EXPECT_CALL(mock_session_closed_cb_, Run(kFakeSessionId2));
+  EXPECT_CALL(mock_session_closed_cb_, Run(kFakeSessionId2, _));
   cdm_adapter_->OnSessionClosed(kFakeSessionId2);
 }
 
diff --git a/chromeos/crosapi/mojom/BUILD.gn b/chromeos/crosapi/mojom/BUILD.gn
index e29dbe1..98dd4a9 100644
--- a/chromeos/crosapi/mojom/BUILD.gn
+++ b/chromeos/crosapi/mojom/BUILD.gn
@@ -30,6 +30,7 @@
     "metrics_reporting.mojom",
     "notification.mojom",
     "prefs.mojom",
+    "remoting.mojom",
     "screen_manager.mojom",
     "select_file.mojom",
     "system_display.mojom",
@@ -49,6 +50,7 @@
     "//mojo/public/mojom/base",
     "//printing/backend/mojom",
     "//printing/mojom",
+    "//remoting/host/mojom:mojom",
     "//services/device/public/mojom:mojom",
     "//services/media_session/public/mojom:mojom",
     "//ui/accessibility:ax_enums_mojo",
diff --git a/chromeos/crosapi/mojom/crosapi.mojom b/chromeos/crosapi/mojom/crosapi.mojom
index a499f39f..1ffb62b 100644
--- a/chromeos/crosapi/mojom/crosapi.mojom
+++ b/chromeos/crosapi/mojom/crosapi.mojom
@@ -24,6 +24,7 @@
 import "chromeos/crosapi/mojom/message_center.mojom";
 import "chromeos/crosapi/mojom/metrics_reporting.mojom";
 import "chromeos/crosapi/mojom/prefs.mojom";
+import "chromeos/crosapi/mojom/remoting.mojom";
 import "chromeos/crosapi/mojom/screen_manager.mojom";
 import "chromeos/crosapi/mojom/select_file.mojom";
 import "chromeos/crosapi/mojom/system_display.mojom";
@@ -60,8 +61,8 @@
 // please note the milestone when you added it, to help us reason about
 // compatibility between the client applications and older ash-chrome binaries.
 //
-// Next version: 30
-// Next method id: 35
+// Next version: 33
+// Next method id: 38
 [Stable, Uuid="8b79c34f-2bf8-4499-979a-b17cac522c1e",
  RenamedFrom="crosapi.mojom.AshChromeService"]
 interface Crosapi {
@@ -169,6 +170,11 @@
   // Added in M89.
   [MinVersion=11] BindPrefs@16(pending_receiver<Prefs> receiver);
 
+  // Binds the Remoting service to allow websites running in Lacros to interact
+  // with Chrome Remote Desktop functionality available in ash-chrome.
+  // Added in M93.
+  [MinVersion=32] BindRemoting@37(pending_receiver<Remoting> receiver);
+
   // Binds the ScreenManager interface for interacting with windows, screens and
   // displays.
   // Added in M86.
diff --git a/chromeos/crosapi/mojom/remoting.mojom b/chromeos/crosapi/mojom/remoting.mojom
new file mode 100644
index 0000000..b64ae82
--- /dev/null
+++ b/chromeos/crosapi/mojom/remoting.mojom
@@ -0,0 +1,27 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module crosapi.mojom;
+
+import "remoting/host/mojom/remote_support.mojom";
+
+// Exposes Chrome Remote Desktop functionality in ash-chrome to callers running
+// in lacros-chrome.
+// Next version: 1
+// Next method id: 2
+[Stable, Uuid="9865af55-8513-4f06-a785-2cb064963c3b"]
+interface Remoting {
+  // Returns the version and supported features of the CRD host in ash-chrome.
+  GetSupportHostDetails@0() => (remoting.mojom.SupportHostDetails host_details);
+
+  // Provides a way for a caller in lacros-chrome to request a remote support
+  // session using the Chrome Remote Desktop host running in ash-chrome.
+  // The caller in this case is expected to be a built-in native message host
+  // which is acting on the behalf of the Chrome Remote Desktop website client
+  // (Website URLs and extension IDs are gated behind allow_lists).
+  // Note: Starting a new session will disconnect the active session if one
+  // exists.
+  StartSupportSession@1(remoting.mojom.SupportSessionParams params) => (
+      remoting.mojom.StartSupportSessionResponse response);
+};
\ No newline at end of file
diff --git a/chromeos/dbus/shill/fake_modem_messaging_client.cc b/chromeos/dbus/shill/fake_modem_messaging_client.cc
index 2327785..531d01b 100644
--- a/chromeos/dbus/shill/fake_modem_messaging_client.cc
+++ b/chromeos/dbus/shill/fake_modem_messaging_client.cc
@@ -20,41 +20,47 @@
     const std::string& service_name,
     const dbus::ObjectPath& object_path,
     const SmsReceivedHandler& handler) {
-  sms_received_handler_ = handler;
+  sms_received_handlers_.insert(
+      std::pair<dbus::ObjectPath, SmsReceivedHandler>(object_path, handler));
+  message_paths_map_.insert(
+      std::pair<dbus::ObjectPath, std::vector<dbus::ObjectPath>>(object_path,
+                                                                 {}));
 }
 
 void FakeModemMessagingClient::ResetSmsReceivedHandler(
     const std::string& service_name,
     const dbus::ObjectPath& object_path) {
-  sms_received_handler_.Reset();
+  sms_received_handlers_[object_path].Reset();
 }
 
 void FakeModemMessagingClient::Delete(const std::string& service_name,
                                       const dbus::ObjectPath& object_path,
                                       const dbus::ObjectPath& sms_path,
                                       VoidDBusMethodCallback callback) {
-  std::vector<dbus::ObjectPath>::iterator it(
-      find(message_paths_.begin(), message_paths_.end(), sms_path));
-  if (it != message_paths_.end())
-    message_paths_.erase(it);
+  std::vector<dbus::ObjectPath> message_paths = message_paths_map_[object_path];
+  auto iter = find(message_paths.begin(), message_paths.end(), sms_path);
+  if (iter != message_paths.end())
+    message_paths.erase(iter);
   std::move(callback).Run(true);
 }
 
 void FakeModemMessagingClient::List(const std::string& service_name,
                                     const dbus::ObjectPath& object_path,
                                     ListCallback callback) {
-  // This entire FakeModemMessagingClient is for testing.
-  // Calling List with |service_name| equal to "AddSMS" allows unit
-  // tests to confirm that the sms_received_handler is functioning.
-  if (service_name == "AddSMS") {
-    const dbus::ObjectPath kSmsPath("/SMS/0");
-    message_paths_.push_back(kSmsPath);
-    if (!sms_received_handler_.is_null())
-      sms_received_handler_.Run(kSmsPath, true);
-    std::move(callback).Run({});
-  } else {
-    std::move(callback).Run(message_paths_);
-  }
+  std::move(callback).Run(message_paths_map_[object_path]);
+}
+
+ModemMessagingClient::TestInterface*
+FakeModemMessagingClient::GetTestInterface() {
+  return this;
+}
+
+// ModemMessagingClient::TestInterface overrides.
+
+void FakeModemMessagingClient::ReceiveSms(const dbus::ObjectPath& object_path,
+                                          const dbus::ObjectPath& sms_path) {
+  message_paths_map_[object_path].push_back(sms_path);
+  sms_received_handlers_[object_path].Run(sms_path, true);
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/shill/fake_modem_messaging_client.h b/chromeos/dbus/shill/fake_modem_messaging_client.h
index 316763c..e3aeebd 100644
--- a/chromeos/dbus/shill/fake_modem_messaging_client.h
+++ b/chromeos/dbus/shill/fake_modem_messaging_client.h
@@ -5,6 +5,7 @@
 #ifndef CHROMEOS_DBUS_SHILL_FAKE_MODEM_MESSAGING_CLIENT_H_
 #define CHROMEOS_DBUS_SHILL_FAKE_MODEM_MESSAGING_CLIENT_H_
 
+#include <map>
 #include <string>
 #include <vector>
 
@@ -16,7 +17,8 @@
 namespace chromeos {
 
 class COMPONENT_EXPORT(SHILL_CLIENT) FakeModemMessagingClient
-    : public ModemMessagingClient {
+    : public ModemMessagingClient,
+      public ModemMessagingClient::TestInterface {
  public:
   FakeModemMessagingClient();
   ~FakeModemMessagingClient() override;
@@ -34,9 +36,15 @@
             const dbus::ObjectPath& object_path,
             ListCallback callback) override;
 
+  ModemMessagingClient::TestInterface* GetTestInterface() override;
+
+  // ModemMessagingClient::TestInterface overrides.
+  void ReceiveSms(const dbus::ObjectPath& object_path,
+                  const dbus::ObjectPath& sms_path) override;
+
  private:
-  SmsReceivedHandler sms_received_handler_;
-  std::vector<dbus::ObjectPath> message_paths_;
+  std::map<dbus::ObjectPath, SmsReceivedHandler> sms_received_handlers_;
+  std::map<dbus::ObjectPath, std::vector<dbus::ObjectPath>> message_paths_map_;
 
   DISALLOW_COPY_AND_ASSIGN(FakeModemMessagingClient);
 };
diff --git a/chromeos/dbus/shill/modem_messaging_client.cc b/chromeos/dbus/shill/modem_messaging_client.cc
index 972c05bc..7458d12 100644
--- a/chromeos/dbus/shill/modem_messaging_client.cc
+++ b/chromeos/dbus/shill/modem_messaging_client.cc
@@ -164,6 +164,8 @@
     GetProxy(service_name, object_path)->List(std::move(callback));
   }
 
+  TestInterface* GetTestInterface() override { return nullptr; }
+
  private:
   using ProxyMap = std::map<std::pair<std::string, std::string>,
                             std::unique_ptr<ModemMessagingProxy>>;
diff --git a/chromeos/dbus/shill/modem_messaging_client.h b/chromeos/dbus/shill/modem_messaging_client.h
index 83e4da78..d3df4a9 100644
--- a/chromeos/dbus/shill/modem_messaging_client.h
+++ b/chromeos/dbus/shill/modem_messaging_client.h
@@ -30,6 +30,17 @@
       base::RepeatingCallback<void(const dbus::ObjectPath& message_path,
                                    bool complete)>;
 
+  // Interface for performing modem messaging actions for testing.
+  // Accessed through GetTestInterface(), only implemented in the Stub Impl.
+  class TestInterface {
+   public:
+    virtual void ReceiveSms(const dbus::ObjectPath& object_path,
+                            const dbus::ObjectPath& sms_path) = 0;
+
+   protected:
+    virtual ~TestInterface() {}
+  };
+
   // Creates and initializes the global instance. |bus| must not be null.
   static void Initialize(dbus::Bus* bus);
 
@@ -63,6 +74,9 @@
                     const dbus::ObjectPath& object_path,
                     ListCallback callback) = 0;
 
+  // Returns an interface for testing (stub only), or returns null.
+  virtual TestInterface* GetTestInterface() = 0;
+
  protected:
   friend class ModemMessagingClientTest;
 
diff --git a/chromeos/lacros/lacros_chrome_service_impl.cc b/chromeos/lacros/lacros_chrome_service_impl.cc
index f40de446..0d7958de 100644
--- a/chromeos/lacros/lacros_chrome_service_impl.cc
+++ b/chromeos/lacros/lacros_chrome_service_impl.cc
@@ -32,6 +32,7 @@
 #include "chromeos/crosapi/mojom/message_center.mojom.h"
 #include "chromeos/crosapi/mojom/metrics_reporting.mojom.h"
 #include "chromeos/crosapi/mojom/prefs.mojom.h"
+#include "chromeos/crosapi/mojom/remoting.mojom.h"
 #include "chromeos/crosapi/mojom/screen_manager.mojom.h"
 #include "chromeos/crosapi/mojom/select_file.mojom.h"
 #include "chromeos/crosapi/mojom/system_display.mojom.h"
@@ -240,6 +241,9 @@
                   Crosapi::MethodMinVersions::kBindMessageCenterMinVersion>();
   ConstructRemote<crosapi::mojom::Prefs, &crosapi::mojom::Crosapi::BindPrefs,
                   Crosapi::MethodMinVersions::kBindPrefsMinVersion>();
+  ConstructRemote<crosapi::mojom::Remoting,
+                  &crosapi::mojom::Crosapi::BindRemoting,
+                  Crosapi::MethodMinVersions::kBindRemotingMinVersion>();
   ConstructRemote<crosapi::mojom::SelectFile,
                   &crosapi::mojom::Crosapi::BindSelectFile,
                   Crosapi::MethodMinVersions::kBindSelectFileMinVersion>();
diff --git a/chromeos/network/BUILD.gn b/chromeos/network/BUILD.gn
index e05bc18..340b6061b 100644
--- a/chromeos/network/BUILD.gn
+++ b/chromeos/network/BUILD.gn
@@ -70,6 +70,8 @@
     "firewall_hole.h",
     "geolocation_handler.cc",
     "geolocation_handler.h",
+    "hermes_metrics_util.cc",
+    "hermes_metrics_util.h",
     "managed_network_configuration_handler.cc",
     "managed_network_configuration_handler.h",
     "managed_network_configuration_handler_impl.cc",
@@ -286,6 +288,7 @@
     "cellular_metrics_logger_unittest.cc",
     "certificate_helper_unittest.cc",
     "client_cert_resolver_unittest.cc",
+    "device_state_unittest.cc",
     "fast_transition_observer_unittest.cc",
     "firewall_hole_unittest.cc",
     "geolocation_handler_unittest.cc",
diff --git a/chromeos/network/cellular_utils.cc b/chromeos/network/cellular_utils.cc
index 4aadeb4..41a07fe 100644
--- a/chromeos/network/cellular_utils.cc
+++ b/chromeos/network/cellular_utils.cc
@@ -116,7 +116,7 @@
   const base::flat_map<int32_t, std::string> esim_slot_to_eid =
       GetESimSlotToEidMap();
 
-  DeviceState::CellularSIMSlotInfos sim_slot_infos = device->sim_slot_infos();
+  DeviceState::CellularSIMSlotInfos sim_slot_infos = device->GetSimSlotInfos();
   for (auto& sim_slot_info : sim_slot_infos) {
     const std::string shill_provided_eid = sim_slot_info.eid;
 
@@ -140,9 +140,7 @@
 }
 
 bool IsSimPrimary(const std::string& iccid, const DeviceState* device) {
-  const DeviceState::CellularSIMSlotInfos& sim_slot_infos =
-      device->sim_slot_infos();
-  for (auto& sim_slot_info : sim_slot_infos) {
+  for (const auto& sim_slot_info : device->GetSimSlotInfos()) {
     if (sim_slot_info.iccid == iccid && sim_slot_info.primary) {
       return true;
     }
diff --git a/chromeos/network/client_cert_resolver_unittest.cc b/chromeos/network/client_cert_resolver_unittest.cc
index 6572659..2780036 100644
--- a/chromeos/network/client_cert_resolver_unittest.cc
+++ b/chromeos/network/client_cert_resolver_unittest.cc
@@ -139,6 +139,7 @@
     network_profile_handler_.reset();
     network_state_handler_.reset();
     NetworkCertLoader::Shutdown();
+    SystemTokenCertDbStorage::Shutdown();
     shill_clients::Shutdown();
   }
 
diff --git a/chromeos/network/device_state.cc b/chromeos/network/device_state.cc
index e2c6d797..754bf3b 100644
--- a/chromeos/network/device_state.cc
+++ b/chromeos/network/device_state.cc
@@ -153,6 +153,30 @@
   return name();
 }
 
+DeviceState::CellularSIMSlotInfos DeviceState::GetSimSlotInfos() const {
+  // If information was provided from Shill, return it directly.
+  if (!sim_slot_infos_.empty())
+    return sim_slot_infos_;
+
+  // Non-cellular types do not have any SIM slots.
+  if (type() != shill::kTypeCellular) {
+    NET_LOG(ERROR) << "Attempted to fetch SIM slots for device of type "
+                   << type() << ". Returning empty list.";
+    return {};
+  }
+
+  // Some devices do not return SIMSlotInfo properties (see b/189874098). If the
+  // list is currently empty, we assume that this is a single-pSIM device and
+  // return one CellularSIMSlotInfo object representing the single pSIM.
+  CellularSIMSlotInfo info;
+  info.slot_id = 1;          // Slot numbers start at 1, not 0.
+  info.eid = std::string();  // Empty EID implies a physical SIM slot.
+  info.iccid = iccid();      // Copy ICCID property.
+  info.primary = true;       // Only one slot, so it must be the primary one.
+
+  return CellularSIMSlotInfos{info};
+}
+
 std::string DeviceState::GetIpAddressByType(const std::string& type) const {
   for (base::DictionaryValue::Iterator iter(ip_configs_); !iter.IsAtEnd();
        iter.Advance()) {
diff --git a/chromeos/network/device_state.h b/chromeos/network/device_state.h
index 63ea91c..d025331 100644
--- a/chromeos/network/device_state.h
+++ b/chromeos/network/device_state.h
@@ -54,7 +54,6 @@
   const std::string& iccid() const { return iccid_; }
   const std::string& mdn() const { return mdn_; }
   const CellularScanResults& scan_results() const { return scan_results_; }
-  const CellularSIMSlotInfos& sim_slot_infos() const { return sim_slot_infos_; }
   bool inhibited() const { return inhibited_; }
 
   // |ip_configs_| is kept up to date by NetworkStateHandler.
@@ -81,6 +80,9 @@
     available_managed_network_path_ = available_managed_network_path;
   }
 
+  // Non-cellular devices return an empty list.
+  CellularSIMSlotInfos GetSimSlotInfos() const;
+
   // Returns a human readable string for the device.
   std::string GetName() const;
 
diff --git a/chromeos/network/device_state_unittest.cc b/chromeos/network/device_state_unittest.cc
new file mode 100644
index 0000000..7a01b08
--- /dev/null
+++ b/chromeos/network/device_state_unittest.cc
@@ -0,0 +1,119 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/device_state.h"
+
+#include "base/test/task_environment.h"
+#include "chromeos/network/network_state_handler.h"
+#include "chromeos/network/network_state_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
+
+namespace chromeos {
+namespace {
+
+const char kTestCellularDevicePath[] = "cellular_path";
+const char kTestCellularDeviceName[] = "cellular_name";
+const char kTestCellularPSimIccid[] = "psim_iccid";
+const char kTestCellularESimIccid[] = "esim_iccid";
+const char kTestCellularEid[] = "eid";
+
+const char kTestWifiDevicePath[] = "wifi_path";
+const char kTestWifiDeviceName[] = "wifi_name";
+
+// Creates a list of cellular SIM slots with an eSIM and pSIM slot.
+base::Value GenerateTestSimSlotInfos() {
+  base::Value::ListStorage sim_slot_infos;
+
+  base::Value psim_slot_info(base::Value::Type::DICTIONARY);
+  psim_slot_info.SetStringKey(shill::kSIMSlotInfoICCID, kTestCellularPSimIccid);
+  psim_slot_info.SetStringKey(shill::kSIMSlotInfoEID, std::string());
+  psim_slot_info.SetBoolKey(shill::kSIMSlotInfoPrimary, true);
+  sim_slot_infos.push_back(std::move(psim_slot_info));
+
+  base::Value esim_slot_info(base::Value::Type::DICTIONARY);
+  psim_slot_info.SetStringKey(shill::kSIMSlotInfoICCID, kTestCellularESimIccid);
+  psim_slot_info.SetStringKey(shill::kSIMSlotInfoEID, kTestCellularEid);
+  psim_slot_info.SetBoolKey(shill::kSIMSlotInfoPrimary, false);
+  sim_slot_infos.push_back(std::move(psim_slot_info));
+
+  return base::Value(sim_slot_infos);
+}
+
+}  // namespace
+
+class DeviceStateTest : public testing::Test {
+ protected:
+  DeviceStateTest() = default;
+  DeviceStateTest(const DeviceStateTest&) = delete;
+  DeviceStateTest& operator=(const DeviceStateTest&) = delete;
+  ~DeviceStateTest() override = default;
+
+  // testing::Test:
+  void SetUp() override {
+    helper_.device_test()->AddDevice(
+        kTestCellularDevicePath, shill::kTypeCellular, kTestCellularDeviceName);
+    helper_.device_test()->AddDevice(kTestWifiDevicePath, shill::kTypeWifi,
+                                     kTestWifiDeviceName);
+    base::RunLoop().RunUntilIdle();
+  }
+
+  const DeviceState* GetCellularDevice() {
+    return helper_.network_state_handler()->GetDeviceState(
+        kTestCellularDevicePath);
+  }
+
+  const DeviceState* GetWifiDevice() {
+    return helper_.network_state_handler()->GetDeviceState(kTestWifiDevicePath);
+  }
+
+  void SetIccid() {
+    helper_.device_test()->SetDeviceProperty(
+        kTestCellularDevicePath, shill::kIccidProperty,
+        base::Value(kTestCellularPSimIccid), /*notify_changed=*/true);
+    base::RunLoop().RunUntilIdle();
+  }
+
+  void SetSimSlotInfos() {
+    helper_.device_test()->SetDeviceProperty(
+        kTestCellularDevicePath, shill::kSIMSlotInfoProperty,
+        GenerateTestSimSlotInfos(), /*notify_changed=*/true);
+    base::RunLoop().RunUntilIdle();
+  }
+
+ private:
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  NetworkStateTestHelper helper_{/*use_default_devices_and_services=*/false};
+};
+
+TEST_F(DeviceStateTest, SimSlotInfo_Cellular) {
+  // Set an ICCID, but do not set the SIMSlotInfo property.
+  SetIccid();
+
+  // A single SIM slot should have been created, copying the ICCID.
+  DeviceState::CellularSIMSlotInfos infos =
+      GetCellularDevice()->GetSimSlotInfos();
+  EXPECT_EQ(1u, infos.size());
+  EXPECT_EQ(kTestCellularPSimIccid, infos[0].iccid);
+  EXPECT_TRUE(infos[0].eid.empty());
+  EXPECT_TRUE(infos[0].primary);
+
+  // Set the SIM Slot infos.
+  SetSimSlotInfos();
+  infos = GetCellularDevice()->GetSimSlotInfos();
+  EXPECT_EQ(2u, infos.size());
+  EXPECT_EQ(kTestCellularPSimIccid, infos[0].iccid);
+  EXPECT_TRUE(infos[0].eid.empty());
+  EXPECT_TRUE(infos[0].primary);
+  EXPECT_EQ(kTestCellularESimIccid, infos[1].iccid);
+  EXPECT_EQ(kTestCellularEid, infos[1].eid);
+  EXPECT_FALSE(infos[1].primary);
+}
+
+TEST_F(DeviceStateTest, SimSlotInfo_Wifi) {
+  // Default SIM slots should not be created for non-cellular.
+  EXPECT_TRUE(GetWifiDevice()->GetSimSlotInfos().empty());
+}
+
+}  // namespace chromeos
diff --git a/chromeos/services/cellular_setup/metrics_util.cc b/chromeos/network/hermes_metrics_util.cc
similarity index 78%
rename from chromeos/services/cellular_setup/metrics_util.cc
rename to chromeos/network/hermes_metrics_util.cc
index 38834703..c45bcf6 100644
--- a/chromeos/services/cellular_setup/metrics_util.cc
+++ b/chromeos/network/hermes_metrics_util.cc
@@ -2,13 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chromeos/services/cellular_setup/metrics_util.h"
+#include "chromeos/network/hermes_metrics_util.h"
 
 #include "base/metrics/histogram_functions.h"
 
 namespace chromeos {
-namespace cellular_setup {
-namespace metrics {
+namespace hermes_metrics {
 
 void LogInstallViaQrCodeResult(HermesResponseStatus status) {
   base::UmaHistogramEnumeration("Network.Cellular.ESim.InstallViaQrCode.Result",
@@ -20,6 +19,5 @@
       "Network.Cellular.ESim.InstallPendingProfile.Result", status);
 }
 
-}  // namespace metrics
-}  // namespace cellular_setup
+}  // namespace hermes_metrics
 }  // namespace chromeos
diff --git a/chromeos/network/hermes_metrics_util.h b/chromeos/network/hermes_metrics_util.h
new file mode 100644
index 0000000..d4e54bc6
--- /dev/null
+++ b/chromeos/network/hermes_metrics_util.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 CHROMEOS_NETWORK_HERMES_METRICS_UTIL_H_
+#define CHROMEOS_NETWORK_HERMES_METRICS_UTIL_H_
+
+#include "base/component_export.h"
+#include "chromeos/dbus/hermes/hermes_response_status.h"
+
+namespace chromeos {
+namespace hermes_metrics {
+
+void COMPONENT_EXPORT(CHROMEOS_NETWORK)
+    LogInstallViaQrCodeResult(HermesResponseStatus status);
+
+void COMPONENT_EXPORT(CHROMEOS_NETWORK)
+    LogInstallPendingProfileResult(HermesResponseStatus status);
+
+}  // namespace hermes_metrics
+}  // namespace chromeos
+
+#endif  // CHROMEOS_NETWORK_HERMES_METRICS_UTIL_H_
diff --git a/chromeos/network/network_connection_handler_impl.cc b/chromeos/network/network_connection_handler_impl.cc
index 6d06848..f947732 100644
--- a/chromeos/network/network_connection_handler_impl.cc
+++ b/chromeos/network/network_connection_handler_impl.cc
@@ -550,8 +550,7 @@
     const std::string& service_path,
     base::TimeDelta timeout) {
   ConnectRequest* request = GetPendingRequest(service_path);
-  if (!request)
-    return;
+  DCHECK(request);
 
   request->timer = std::make_unique<base::OneShotTimer>();
   request->timer->Start(
diff --git a/chromeos/network/network_sms_handler.cc b/chromeos/network/network_sms_handler.cc
index ed2721fe..1d9ac0bd 100644
--- a/chromeos/network/network_sms_handler.cc
+++ b/chromeos/network/network_sms_handler.cc
@@ -41,8 +41,6 @@
  public:
   NetworkSmsDeviceHandler() = default;
   virtual ~NetworkSmsDeviceHandler() = default;
-
-  virtual void RequestUpdate() = 0;
 };
 
 class NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler
@@ -52,8 +50,6 @@
                                        const std::string& service_name,
                                        const dbus::ObjectPath& object_path);
 
-  void RequestUpdate() override;
-
  private:
   void ListCallback(absl::optional<std::vector<dbus::ObjectPath>> paths);
   void SmsReceivedCallback(const dbus::ObjectPath& path, bool complete);
@@ -101,16 +97,6 @@
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
-void NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::RequestUpdate() {
-  // Calling List using the service "AddSMS" causes the stub
-  // implementation to deliver new sms messages.
-  ModemMessagingClient::Get()->List(
-      std::string("AddSMS"), dbus::ObjectPath("/"),
-      base::BindOnce(&NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::
-                         ListCallback,
-                     weak_ptr_factory_.GetWeakPtr()));
-}
-
 void NetworkSmsHandler::ModemManager1NetworkSmsDeviceHandler::ListCallback(
     absl::optional<std::vector<dbus::ObjectPath>> paths) {
   // This receives all messages, so clear any pending gets and deletes.
@@ -228,16 +214,10 @@
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
-void NetworkSmsHandler::RequestUpdate(bool request_existing) {
-  // If we already received messages and |request_existing| is true, send
-  // updates for existing messages.
+void NetworkSmsHandler::RequestUpdate() {
   for (const auto& message : received_messages_) {
     NotifyMessageReceived(message);
   }
-  // Request updates from each device.
-  for (auto& handler : device_handlers_) {
-    handler->RequestUpdate();
-  }
 }
 
 void NetworkSmsHandler::AddObserver(Observer* observer) {
diff --git a/chromeos/network/network_sms_handler.h b/chromeos/network/network_sms_handler.h
index a158f3a..4505302 100644
--- a/chromeos/network/network_sms_handler.h
+++ b/chromeos/network/network_sms_handler.h
@@ -43,9 +43,8 @@
 
   ~NetworkSmsHandler() override;
 
-  // Requests an immediate check for new messages. If |request_existing| is
-  // true then also requests to be notified for any already received messages.
-  void RequestUpdate(bool request_existing);
+  // Requests an immediate check for new messages.
+  void RequestUpdate();
 
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
diff --git a/chromeos/network/network_sms_handler_unittest.cc b/chromeos/network/network_sms_handler_unittest.cc
index dd8789f3..ecbe032 100644
--- a/chromeos/network/network_sms_handler_unittest.cc
+++ b/chromeos/network/network_sms_handler_unittest.cc
@@ -12,8 +12,10 @@
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
 #include "chromeos/dbus/constants/dbus_switches.h"
+#include "chromeos/dbus/shill/modem_messaging_client.h"
 #include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/dbus/shill/shill_device_client.h"
+#include "dbus/object_path.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
 
@@ -21,6 +23,10 @@
 
 namespace {
 
+const char kCellularDeviceObjectPath[] =
+    "/org/freedesktop/ModemManager1/stub/0";
+const char kSmsPath[] = "/SMS/0";
+
 class TestObserver : public NetworkSmsHandler::Observer {
  public:
   TestObserver() = default;
@@ -65,19 +71,16 @@
     ShillDeviceClient::TestInterface* device_test =
         ShillDeviceClient::Get()->GetTestInterface();
     ASSERT_TRUE(device_test);
-    device_test->AddDevice("/org/freedesktop/ModemManager1/stub/0",
-                           shill::kTypeCellular,
+    device_test->AddDevice(kCellularDeviceObjectPath, shill::kTypeCellular,
                            "stub_cellular_device2");
 
     // This relies on the stub dbus implementations for ShillManagerClient,
     // ShillDeviceClient, ModemMessagingClient and SMSClient.
-    // Initialize a sms handler. The stub dbus clients will not send the
-    // first test message until RequestUpdate has been called.
     network_sms_handler_.reset(new NetworkSmsHandler());
     network_sms_handler_->Init();
     test_observer_ = std::make_unique<TestObserver>();
     network_sms_handler_->AddObserver(test_observer_.get());
-    network_sms_handler_->RequestUpdate(true);
+    network_sms_handler_->RequestUpdate();
     base::RunLoop().RunUntilIdle();
   }
 
@@ -106,7 +109,13 @@
 
   // Test for messages delivered by signals.
   test_observer_->ClearMessages();
-  network_sms_handler_->RequestUpdate(false);
+  ModemMessagingClient::TestInterface* modem_messaging_test =
+      ModemMessagingClient::Get()->GetTestInterface();
+  modem_messaging_test->ReceiveSms(dbus::ObjectPath(kCellularDeviceObjectPath),
+                                   dbus::ObjectPath(kSmsPath));
+  base::RunLoop().RunUntilIdle();
+
+  network_sms_handler_->RequestUpdate();
   base::RunLoop().RunUntilIdle();
   EXPECT_GE(test_observer_->message_count(), 1);
   EXPECT_NE(messages.find(kMessage1), messages.end());
diff --git a/chromeos/network/network_state_handler.cc b/chromeos/network/network_state_handler.cc
index 962dcb10..04236d58 100644
--- a/chromeos/network/network_state_handler.cc
+++ b/chromeos/network/network_state_handler.cc
@@ -218,6 +218,18 @@
   NotifyNetworkListChanged();
 }
 
+void NetworkStateHandler::RequestTrafficCounters(
+    const std::string& service_path,
+    ShillServiceClient::ListValueCallback callback) {
+  shill_property_handler_->RequestTrafficCounters(service_path,
+                                                  std::move(callback));
+}
+
+void NetworkStateHandler::ResetTrafficCounters(
+    const std::string& service_path) {
+  shill_property_handler_->ResetTrafficCounters(service_path);
+}
+
 // static
 std::unique_ptr<NetworkStateHandler> NetworkStateHandler::InitializeForTest() {
   auto handler = base::WrapUnique(new NetworkStateHandler());
diff --git a/chromeos/network/network_state_handler.h b/chromeos/network/network_state_handler.h
index b67f9162..5f42baf7 100644
--- a/chromeos/network/network_state_handler.h
+++ b/chromeos/network/network_state_handler.h
@@ -446,6 +446,13 @@
   // set, sorts network list and notifies network list change if required.
   void SyncStubCellularNetworks();
 
+  // Requests traffic counters for a service denoted by |service_path|.
+  void RequestTrafficCounters(const std::string& service_path,
+                              ShillServiceClient::ListValueCallback callback);
+
+  // Resets traffic counters for a service denoted by |service_path|.
+  void ResetTrafficCounters(const std::string& service_path);
+
   bool default_network_is_metered() const {
     return default_network_is_metered_;
   }
diff --git a/chromeos/network/network_state_handler_unittest.cc b/chromeos/network/network_state_handler_unittest.cc
index 6dc432b..34c1b780f 100644
--- a/chromeos/network/network_state_handler_unittest.cc
+++ b/chromeos/network/network_state_handler_unittest.cc
@@ -2549,4 +2549,38 @@
   EXPECT_EQ(kShillManagerClientStubCellular, network_list[1]->path());
 }
 
+TEST_F(NetworkStateHandlerTest, RequestTrafficCounters) {
+  // Set up the traffic counters.
+  base::Value traffic_counters(base::Value::Type::LIST);
+
+  base::Value chrome_dict(base::Value::Type::DICTIONARY);
+  chrome_dict.SetKey("source", base::Value(shill::kTrafficCounterSourceChrome));
+  chrome_dict.SetKey("rx_bytes", base::Value(12));
+  chrome_dict.SetKey("tx_bytes", base::Value(32));
+  traffic_counters.Append(std::move(chrome_dict));
+
+  base::Value user_dict(base::Value::Type::DICTIONARY);
+  user_dict.SetKey("source", base::Value(shill::kTrafficCounterSourceUser));
+  user_dict.SetKey("rx_bytes", base::Value(90));
+  user_dict.SetKey("tx_bytes", base::Value(87));
+  traffic_counters.Append(std::move(user_dict));
+
+  service_test_->SetFakeTrafficCounters(traffic_counters.Clone());
+  ASSERT_TRUE(traffic_counters.is_list());
+
+  base::RunLoop run_loop;
+  network_state_handler_->RequestTrafficCounters(
+      kWifiName1,
+      base::BindOnce(
+          [](base::Value* expected_traffic_counters,
+             base::OnceClosure quit_closure,
+             const base::ListValue& actual_traffic_counters) {
+            EXPECT_EQ(base::Value::AsListValue(*expected_traffic_counters),
+                      actual_traffic_counters);
+            std::move(quit_closure).Run();
+          },
+          &traffic_counters, run_loop.QuitClosure()));
+  run_loop.Run();
+}
+
 }  // namespace chromeos
diff --git a/chromeos/network/stub_cellular_networks_provider.cc b/chromeos/network/stub_cellular_networks_provider.cc
index e74288c..cc45864 100644
--- a/chromeos/network/stub_cellular_networks_provider.cc
+++ b/chromeos/network/stub_cellular_networks_provider.cc
@@ -134,7 +134,7 @@
 
   // Now, iterate through SIM slots and add metadata for pSIM networks.
   for (const CellularSIMSlotInfo& sim_slot_info :
-       cellular_device->sim_slot_infos()) {
+       cellular_device->GetSimSlotInfos()) {
     // Skip empty SIM slots.
     if (sim_slot_info.iccid.empty())
       continue;
diff --git a/chromeos/network/system_token_cert_db_storage.cc b/chromeos/network/system_token_cert_db_storage.cc
index 3e83a09..1e8e50b 100644
--- a/chromeos/network/system_token_cert_db_storage.cc
+++ b/chromeos/network/system_token_cert_db_storage.cc
@@ -4,9 +4,6 @@
 
 #include "chromeos/network/system_token_cert_db_storage.h"
 
-#include <memory>
-#include <utility>
-
 #include "base/callback.h"
 #include "base/callback_list.h"
 #include "base/check_op.h"
@@ -37,16 +34,6 @@
 
 // static
 void SystemTokenCertDbStorage::Shutdown() {
-  DCHECK(g_system_token_cert_db_storage);
-
-  // Notify observers that the SystemTokenCertDbStorage and the
-  // NSSCertDatabase it provides can not be used anymore.
-  for (auto& observer : g_system_token_cert_db_storage->observers_)
-    observer.OnSystemTokenCertDbDestroyed();
-
-  // Now it's safe to destroy the NSSCertDatabase.
-  g_system_token_cert_db_storage->system_token_cert_database_.reset();
-
   DCHECK_NE(g_system_token_cert_db_storage, nullptr);
   delete g_system_token_cert_db_storage;
   g_system_token_cert_db_storage = nullptr;
@@ -58,14 +45,27 @@
 }
 
 void SystemTokenCertDbStorage::SetDatabase(
-    std::unique_ptr<net::NSSCertDatabase> system_token_cert_database) {
+    net::NSSCertDatabase* system_token_cert_database) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   DCHECK_EQ(system_token_cert_database_, nullptr);
   system_token_cert_database_ = std::move(system_token_cert_database);
   system_token_cert_db_retrieval_timer_.Stop();
-  get_system_token_cert_db_callback_list_.Notify(
-      system_token_cert_database_.get());
+  get_system_token_cert_db_callback_list_.Notify(system_token_cert_database_);
+}
+
+void SystemTokenCertDbStorage::ResetDatabase() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  system_token_cert_database_ = nullptr;
+  // If any consumer asks for the database between now and when
+  // SystemTokenCertDbStorage is destroyed, respond with a failure.
+  system_token_cert_db_retrieval_failed_ = true;
+
+  // Notify observers that the SystemTokenCertDbStorage and the
+  // NSSCertDatabase it provides can not be used anymore.
+  for (auto& observer : g_system_token_cert_db_storage->observers_)
+    observer.OnSystemTokenCertDbDestroyed();
 }
 
 void SystemTokenCertDbStorage::GetDatabase(GetDatabaseCallback callback) {
@@ -74,7 +74,7 @@
   DCHECK(callback);
 
   if (system_token_cert_database_) {
-    std::move(callback).Run(system_token_cert_database_.get());
+    std::move(callback).Run(system_token_cert_database_);
   } else if (system_token_cert_db_retrieval_failed_) {
     std::move(callback).Run(/*nss_cert_database=*/nullptr);
   } else {
diff --git a/chromeos/network/system_token_cert_db_storage.h b/chromeos/network/system_token_cert_db_storage.h
index 0bab9371..150243ba 100644
--- a/chromeos/network/system_token_cert_db_storage.h
+++ b/chromeos/network/system_token_cert_db_storage.h
@@ -5,8 +5,6 @@
 #ifndef CHROMEOS_NETWORK_SYSTEM_TOKEN_CERT_DB_STORAGE_H_
 #define CHROMEOS_NETWORK_SYSTEM_TOKEN_CERT_DB_STORAGE_H_
 
-#include <memory>
-
 #include "base/callback.h"
 #include "base/callback_forward.h"
 #include "base/callback_list.h"
@@ -66,8 +64,11 @@
   // database when it is ready.
   // Note: This method is expected to be called only once by the
   // SystemTokenCertDbInitializer.
-  void SetDatabase(
-      std::unique_ptr<net::NSSCertDatabase> system_token_cert_database);
+  void SetDatabase(net::NSSCertDatabase* system_token_cert_database);
+
+  // Used by SystemTokenCertDbInitializer to reset the system token certificate
+  // database and notify observers that it is not usable anymore.
+  void ResetDatabase();
 
   // Retrieves the global NSSCertDatabase for the system token and passes it to
   // |callback|. If the database is already initialized, calls |callback|
@@ -89,9 +90,6 @@
   // informing callers that the database initialization failed.
   void OnSystemTokenDbRetrievalTimeout();
 
-  // Global NSSCertDatabase which sees the system token.
-  std::unique_ptr<net::NSSCertDatabase> system_token_cert_database_;
-
   // List of callbacks that should be executed when the system token certificate
   // database is created.
   base::OnceCallbackList<GetDatabaseCallback::RunType>
@@ -101,6 +99,10 @@
   // NSSCertDatabase is destroyed.
   base::ObserverList<Observer> observers_;
 
+  // Global NSSCertDatabase which sees the system token. Owned by
+  // SystemTokenCertDbInitializer.
+  net::NSSCertDatabase* system_token_cert_database_ = nullptr;
+
   bool system_token_cert_db_retrieval_failed_ = false;
 
   base::OneShotTimer system_token_cert_db_retrieval_timer_;
diff --git a/chromeos/network/system_token_cert_db_storage_test_util.cc b/chromeos/network/system_token_cert_db_storage_test_util.cc
index 7ecde58..50f3d7ab 100644
--- a/chromeos/network/system_token_cert_db_storage_test_util.cc
+++ b/chromeos/network/system_token_cert_db_storage_test_util.cc
@@ -47,4 +47,18 @@
   run_loop_.Quit();
 }
 
+FakeSystemTokenCertDbStorageObserver::FakeSystemTokenCertDbStorageObserver() =
+    default;
+
+FakeSystemTokenCertDbStorageObserver::~FakeSystemTokenCertDbStorageObserver() =
+    default;
+
+bool FakeSystemTokenCertDbStorageObserver::HasBeenNotified() {
+  return has_been_notified_;
+}
+
+void FakeSystemTokenCertDbStorageObserver::OnSystemTokenCertDbDestroyed() {
+  has_been_notified_ = true;
+}
+
 }  // namespace chromeos
diff --git a/chromeos/network/system_token_cert_db_storage_test_util.h b/chromeos/network/system_token_cert_db_storage_test_util.h
index 75c7f9c..fcccae3 100644
--- a/chromeos/network/system_token_cert_db_storage_test_util.h
+++ b/chromeos/network/system_token_cert_db_storage_test_util.h
@@ -49,6 +49,26 @@
       this};
 };
 
+class FakeSystemTokenCertDbStorageObserver
+    : public SystemTokenCertDbStorage::Observer {
+ public:
+  FakeSystemTokenCertDbStorageObserver();
+  FakeSystemTokenCertDbStorageObserver(
+      const FakeSystemTokenCertDbStorageObserver& other) = delete;
+  FakeSystemTokenCertDbStorageObserver& operator=(
+      const FakeSystemTokenCertDbStorageObserver& other) = delete;
+  ~FakeSystemTokenCertDbStorageObserver() override;
+
+  // Waits until the observer has been notified with .
+  bool HasBeenNotified();
+
+ private:
+  // SystemTokenCertDbStorage::Obsever:
+  void OnSystemTokenCertDbDestroyed() override;
+
+  bool has_been_notified_ = false;
+};
+
 }  // namespace chromeos
 
 #endif  // CHROMEOS_NETWORK_SYSTEM_TOKEN_CERT_DB_STORAGE_TEST_UTIL_H_
diff --git a/chromeos/network/system_token_cert_db_storage_unittest.cc b/chromeos/network/system_token_cert_db_storage_unittest.cc
index c86230b..1c414086 100644
--- a/chromeos/network/system_token_cert_db_storage_unittest.cc
+++ b/chromeos/network/system_token_cert_db_storage_unittest.cc
@@ -39,12 +39,12 @@
 
  protected:
   void SetSystemSlotInStorage() {
-    auto fake_nss_cert_db = std::make_unique<net::NSSCertDatabase>(
+    test_cert_database_ = std::make_unique<net::NSSCertDatabase>(
         /*public_slot=*/crypto::ScopedPK11Slot(
             PK11_ReferenceSlot(test_nssdb_->slot())),
         /*private_slot=*/crypto::ScopedPK11Slot(
             PK11_ReferenceSlot(test_nssdb_->slot())));
-    SystemTokenCertDbStorage::Get()->SetDatabase(std::move(fake_nss_cert_db));
+    SystemTokenCertDbStorage::Get()->SetDatabase(test_cert_database_.get());
   }
 
   base::test::TaskEnvironment* task_environment() { return &task_environment_; }
@@ -54,6 +54,7 @@
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
 
   std::unique_ptr<crypto::ScopedTestNSSDB> test_nssdb_;
+  std::unique_ptr<net::NSSCertDatabase> test_cert_database_;
 };
 
 // Tests that the system token certificate database will be returned
@@ -156,4 +157,47 @@
       get_system_token_cert_db_callback_wrapper_2.IsDbRetrievalSucceeded());
 }
 
+// Tests that calling ResetDatabase notifies the observers.
+TEST_F(SystemTokenCertDbStorageTest, ResetDatabaseNotifiesObservers) {
+  // Successfully request the database.
+  GetSystemTokenCertDbCallbackWrapper get_system_token_cert_db_callback_wrapper;
+  SystemTokenCertDbStorage::Get()->GetDatabase(
+      get_system_token_cert_db_callback_wrapper.GetCallback());
+  SetSystemSlotInStorage();
+  get_system_token_cert_db_callback_wrapper.Wait();
+  EXPECT_TRUE(get_system_token_cert_db_callback_wrapper.IsCallbackCalled());
+
+  // When the database is reset, observers are notified about this.
+  FakeSystemTokenCertDbStorageObserver observer;
+  SystemTokenCertDbStorage::Get()->AddObserver(&observer);
+
+  SystemTokenCertDbStorage::Get()->ResetDatabase();
+
+  EXPECT_TRUE(observer.HasBeenNotified());
+  SystemTokenCertDbStorage::Get()->RemoveObserver(&observer);
+}
+
+// Tests that requesting a database after it has been reset fails.
+TEST_F(SystemTokenCertDbStorageTest, RequestingDatabaseFailsAfterReset) {
+  // Successfully request the database.
+  GetSystemTokenCertDbCallbackWrapper get_system_token_cert_db_callback_wrapper;
+  SystemTokenCertDbStorage::Get()->GetDatabase(
+      get_system_token_cert_db_callback_wrapper.GetCallback());
+  SetSystemSlotInStorage();
+  get_system_token_cert_db_callback_wrapper.Wait();
+  EXPECT_TRUE(get_system_token_cert_db_callback_wrapper.IsCallbackCalled());
+
+  // Now reset the database.
+  SystemTokenCertDbStorage::Get()->ResetDatabase();
+
+  // Requesting the database after that results in a failure.
+  GetSystemTokenCertDbCallbackWrapper
+      get_system_token_cert_db_callback_wrapper_2;
+  SystemTokenCertDbStorage::Get()->GetDatabase(
+      get_system_token_cert_db_callback_wrapper_2.GetCallback());
+  EXPECT_TRUE(get_system_token_cert_db_callback_wrapper_2.IsCallbackCalled());
+  EXPECT_FALSE(
+      get_system_token_cert_db_callback_wrapper_2.IsDbRetrievalSucceeded());
+}
+
 }  // namespace chromeos
diff --git a/chromeos/profiles/orderfile.newest.txt b/chromeos/profiles/orderfile.newest.txt
index 0e80bec..3af6988 100644
--- a/chromeos/profiles/orderfile.newest.txt
+++ b/chromeos/profiles/orderfile.newest.txt
@@ -1 +1 @@
-chromeos-chrome-orderfile-field-92-4484.0-1621849150-benchmark-92.0.4515.32-r1.orderfile.xz
+chromeos-chrome-orderfile-field-92-4515.9-1622461123-benchmark-92.0.4515.41-r1.orderfile.xz
diff --git a/chromeos/services/assistant/media_host.cc b/chromeos/services/assistant/media_host.cc
index c8c91e0..94dea32 100644
--- a/chromeos/services/assistant/media_host.cc
+++ b/chromeos/services/assistant/media_host.cc
@@ -283,9 +283,6 @@
 }
 
 void MediaHost::StartObservingMediaController() {
-  if (!features::IsMediaSessionIntegrationEnabled())
-    return;
-
   if (chromeos_media_state_observer_)
     return;
 
diff --git a/chromeos/services/assistant/public/cpp/features.cc b/chromeos/services/assistant/public/cpp/features.cc
index 13b2406..0b19075a 100644
--- a/chromeos/services/assistant/public/cpp/features.cc
+++ b/chromeos/services/assistant/public/cpp/features.cc
@@ -50,9 +50,6 @@
 const base::Feature kEnableLibAssistantBetaBackend{
     "LibAssistantBetaBackend", base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kEnableMediaSessionIntegration{
-    "AssistantEnableMediaSessionIntegration", base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Disable voice match for test purpose.
 const base::Feature kDisableVoiceMatch{"DisableVoiceMatch",
                                        base::FEATURE_DISABLED_BY_DEFAULT};
@@ -90,10 +87,6 @@
   return base::FeatureList::IsEnabled(kAssistantLauncherChipIntegration);
 }
 
-bool IsMediaSessionIntegrationEnabled() {
-  return base::FeatureList::IsEnabled(kEnableMediaSessionIntegration);
-}
-
 bool IsPowerManagerEnabled() {
   return base::FeatureList::IsEnabled(kEnablePowerManager);
 }
diff --git a/chromeos/services/assistant/public/cpp/features.h b/chromeos/services/assistant/public/cpp/features.h
index 5955d79..2e9576b 100644
--- a/chromeos/services/assistant/public/cpp/features.h
+++ b/chromeos/services/assistant/public/cpp/features.h
@@ -48,10 +48,6 @@
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC)
 extern const base::Feature kEnableDspHotword;
 
-// Enables MediaSession Integration.
-COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC)
-extern const base::Feature kEnableMediaSessionIntegration;
-
 // Enables stereo audio input.
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC)
 extern const base::Feature kEnableStereoAudioInput;
@@ -64,8 +60,6 @@
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC)
 extern const base::Feature kEnableLibAssistantBetaBackend;
 
-COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC) bool IsAmbientAssistantEnabled();
-
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC) bool IsAppSupportEnabled();
 
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC) bool IsAudioEraserEnabled();
@@ -85,17 +79,11 @@
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC)
 bool IsLauncherChipIntegrationEnabled();
 
-COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC)
-bool IsMediaSessionIntegrationEnabled();
-
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC) bool IsPowerManagerEnabled();
 
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC)
 bool IsLibAssistantBetaBackendEnabled();
 
-COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC)
-bool IsResponseProcessingV2Enabled();
-
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC) bool IsRoutinesEnabled();
 
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC) bool IsStereoAudioInputEnabled();
diff --git a/chromeos/services/cellular_setup/BUILD.gn b/chromeos/services/cellular_setup/BUILD.gn
index d9a3fb9f..aa818d9 100644
--- a/chromeos/services/cellular_setup/BUILD.gn
+++ b/chromeos/services/cellular_setup/BUILD.gn
@@ -36,8 +36,6 @@
     "esim_profile.h",
     "euicc.cc",
     "euicc.h",
-    "metrics_util.cc",
-    "metrics_util.h",
   ]
 
   deps = [
diff --git a/chromeos/services/cellular_setup/esim_profile.cc b/chromeos/services/cellular_setup/esim_profile.cc
index 72aedec..497b54d 100644
--- a/chromeos/services/cellular_setup/esim_profile.cc
+++ b/chromeos/services/cellular_setup/esim_profile.cc
@@ -14,6 +14,7 @@
 #include "chromeos/network/cellular_esim_profile.h"
 #include "chromeos/network/cellular_esim_uninstall_handler.h"
 #include "chromeos/network/cellular_inhibitor.h"
+#include "chromeos/network/hermes_metrics_util.h"
 #include "chromeos/network/network_connection_handler.h"
 #include "chromeos/network/network_event_log.h"
 #include "chromeos/network/network_handler.h"
@@ -21,7 +22,6 @@
 #include "chromeos/services/cellular_setup/esim_manager.h"
 #include "chromeos/services/cellular_setup/esim_mojo_utils.h"
 #include "chromeos/services/cellular_setup/euicc.h"
-#include "chromeos/services/cellular_setup/metrics_util.h"
 #include "chromeos/services/cellular_setup/public/mojom/esim_manager.mojom-shared.h"
 #include "chromeos/services/cellular_setup/public/mojom/esim_manager.mojom.h"
 #include "components/device_event_log/device_event_log.h"
@@ -388,7 +388,7 @@
 void ESimProfile::OnPendingProfileInstallResult(
     std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock,
     HermesResponseStatus status) {
-  metrics::LogInstallPendingProfileResult(status);
+  hermes_metrics::LogInstallPendingProfileResult(status);
 
   if (status != HermesResponseStatus::kSuccess) {
     NET_LOG(ERROR) << "Error Installing pending profile status="
diff --git a/chromeos/services/cellular_setup/euicc.cc b/chromeos/services/cellular_setup/euicc.cc
index d0eefef1..b65fd7d 100644
--- a/chromeos/services/cellular_setup/euicc.cc
+++ b/chromeos/services/cellular_setup/euicc.cc
@@ -13,13 +13,13 @@
 #include "chromeos/network/cellular_connection_handler.h"
 #include "chromeos/network/cellular_esim_profile.h"
 #include "chromeos/network/cellular_inhibitor.h"
+#include "chromeos/network/hermes_metrics_util.h"
 #include "chromeos/network/network_connection_handler.h"
 #include "chromeos/network/network_event_log.h"
 #include "chromeos/network/network_state_handler.h"
 #include "chromeos/services/cellular_setup/esim_manager.h"
 #include "chromeos/services/cellular_setup/esim_mojo_utils.h"
 #include "chromeos/services/cellular_setup/esim_profile.h"
-#include "chromeos/services/cellular_setup/metrics_util.h"
 #include "chromeos/services/cellular_setup/public/mojom/esim_manager.mojom-shared.h"
 #include "components/device_event_log/device_event_log.h"
 #include "components/qr_code_generator/qr_code_generator.h"
@@ -260,7 +260,7 @@
     std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock,
     HermesResponseStatus status,
     const dbus::ObjectPath* profile_path) {
-  metrics::LogInstallViaQrCodeResult(status);
+  hermes_metrics::LogInstallViaQrCodeResult(status);
 
   if (status != HermesResponseStatus::kSuccess) {
     NET_LOG(ERROR) << "Error Installing profile status="
diff --git a/chromeos/services/cellular_setup/metrics_util.h b/chromeos/services/cellular_setup/metrics_util.h
deleted file mode 100644
index 1a9a1e4..0000000
--- a/chromeos/services/cellular_setup/metrics_util.h
+++ /dev/null
@@ -1,21 +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 CHROMEOS_SERVICES_CELLULAR_SETUP_METRICS_UTIL_H_
-#define CHROMEOS_SERVICES_CELLULAR_SETUP_METRICS_UTIL_H_
-
-#include "chromeos/dbus/hermes/hermes_response_status.h"
-
-namespace chromeos {
-namespace cellular_setup {
-namespace metrics {
-
-void LogInstallViaQrCodeResult(HermesResponseStatus status);
-void LogInstallPendingProfileResult(HermesResponseStatus status);
-
-}  // namespace metrics
-}  // namespace cellular_setup
-}  // namespace chromeos
-
-#endif  // CHROMEOS_SERVICES_CELLULAR_SETUP_METRICS_UTIL_H_
diff --git a/chromeos/services/cellular_setup/ota_activator_impl_unittest.cc b/chromeos/services/cellular_setup/ota_activator_impl_unittest.cc
index cabc4f4..4f5bf0a 100644
--- a/chromeos/services/cellular_setup/ota_activator_impl_unittest.cc
+++ b/chromeos/services/cellular_setup/ota_activator_impl_unittest.cc
@@ -103,10 +103,11 @@
       device_test->SetDeviceProperty(
           kTestCellularDevicePath, shill::kMdnProperty,
           base::Value(kTestCellularDeviceMdn), false /* notify_changed */);
-    } else if (has_physical_slots) {
+    } else {
+      std::string eid = has_physical_slots ? std::string() : "test_eid";
       device_test->SetDeviceProperty(
           kTestCellularDevicePath, shill::kSIMSlotInfoProperty,
-          CreateCellularSIMSlotInfo(kTestCellularServiceIccid),
+          CreateCellularSIMSlotInfo(kTestCellularServiceIccid, eid),
           false /* notify_changed */);
     }
 
@@ -254,10 +255,12 @@
     is_finished_ = true;
   }
 
-  base::Value CreateCellularSIMSlotInfo(const std::string& iccid) {
+  base::Value CreateCellularSIMSlotInfo(
+      const std::string& iccid,
+      const std::string& eid = std::string()) {
     base::Value::ListStorage sim_slot_infos;
     base::Value slot_info_item(base::Value::Type::DICTIONARY);
-    slot_info_item.SetStringKey(shill::kSIMSlotInfoEID, std::string());
+    slot_info_item.SetStringKey(shill::kSIMSlotInfoEID, eid);
     slot_info_item.SetStringKey(shill::kSIMSlotInfoICCID, iccid);
     slot_info_item.SetBoolKey(shill::kSIMSlotInfoPrimary, false);
     sim_slot_infos.push_back(std::move(slot_info_item));
diff --git a/chromeos/services/libassistant/display_connection_impl.cc b/chromeos/services/libassistant/display_connection_impl.cc
index 2cbeffe1..7474d42 100644
--- a/chromeos/services/libassistant/display_connection_impl.cc
+++ b/chromeos/services/libassistant/display_connection_impl.cc
@@ -15,11 +15,9 @@
 
 DisplayConnectionImpl::DisplayConnectionImpl(
     DisplayConnectionObserver* observer,
-    bool feedback_ui_enabled,
-    bool media_session_enabled)
+    bool feedback_ui_enabled)
     : observer_(observer),
       feedback_ui_enabled_(feedback_ui_enabled),
-      media_session_enabled_(media_session_enabled),
       task_runner_(base::SequencedTaskRunnerHandle::Get()) {
   DCHECK(observer_);
 }
@@ -136,14 +134,12 @@
     }
   }
 
-  if (media_session_enabled_) {
-    set_capabilities_request->mutable_supported_features()
-        ->set_media_session_detection(
-            related_info_enabled_
-                ? ::assistant::api::RELIABLE_MEDIA_SESSION_DETECTION
-                : ::assistant::api::
-                      MEDIA_SESSION_DETECTION_DISABLED_SCREEN_CONTEXT);
-  }
+  set_capabilities_request->mutable_supported_features()
+      ->set_media_session_detection(
+          related_info_enabled_
+              ? ::assistant::api::RELIABLE_MEDIA_SESSION_DETECTION
+              : ::assistant::api::
+                    MEDIA_SESSION_DETECTION_DISABLED_SCREEN_CONTEXT);
 }
 
 }  // namespace libassistant
diff --git a/chromeos/services/libassistant/display_connection_impl.h b/chromeos/services/libassistant/display_connection_impl.h
index 53095f9..387ba99 100644
--- a/chromeos/services/libassistant/display_connection_impl.h
+++ b/chromeos/services/libassistant/display_connection_impl.h
@@ -27,8 +27,7 @@
 class DisplayConnectionImpl : public assistant_client::DisplayConnection {
  public:
   DisplayConnectionImpl(DisplayConnectionObserver* observer,
-                        bool feedback_ui_enabled,
-                        bool media_session_enabled);
+                        bool feedback_ui_enabled);
   DisplayConnectionImpl(const DisplayConnectionImpl&) = delete;
   DisplayConnectionImpl& operator=(const DisplayConnectionImpl&) = delete;
   ~DisplayConnectionImpl() override;
@@ -60,9 +59,6 @@
   // Whether Assistant feedback UI is enabled.
   const bool feedback_ui_enabled_;
 
-  // Whether Media Session support is enabled.
-  const bool media_session_enabled_;
-
   // Whether ARC++ is enabled.
   bool arc_play_store_enabled_ GUARDED_BY(update_display_request_mutex_) =
       false;
diff --git a/chromeos/services/libassistant/display_controller.cc b/chromeos/services/libassistant/display_controller.cc
index 6f8e959..5509e60b 100644
--- a/chromeos/services/libassistant/display_controller.cc
+++ b/chromeos/services/libassistant/display_controller.cc
@@ -50,8 +50,7 @@
     : event_observer_(std::make_unique<EventObserver>(this)),
       display_connection_(std::make_unique<DisplayConnectionImpl>(
           event_observer_.get(),
-          /*feedback_ui_enabled=*/true,
-          assistant::features::IsMediaSessionIntegrationEnabled())),
+          /*feedback_ui_enabled=*/true)),
       speech_recognition_observers_(*speech_recognition_observers),
       mojom_task_runner_(base::SequencedTaskRunnerHandle::Get()) {
   DCHECK(speech_recognition_observers);
diff --git a/chromeos/services/network_config/cros_network_config.cc b/chromeos/services/network_config/cros_network_config.cc
index 9ae3bb3..9c725d3ec 100644
--- a/chromeos/services/network_config/cros_network_config.cc
+++ b/chromeos/services/network_config/cros_network_config.cc
@@ -4,6 +4,7 @@
 
 #include "chromeos/services/network_config/cros_network_config.h"
 
+#include <cmath>
 #include <vector>
 
 #include "ash/constants/ash_features.h"
@@ -433,14 +434,36 @@
   return sim_info_mojos;
 }
 
-mojom::InhibitReason GetInhibitReason(CellularInhibitor* cellular_inhibitor) {
+bool IsCellularConnecting(NetworkStateHandler* network_state_handler) {
+  NetworkStateHandler::NetworkStateList cellular_networks;
+  network_state_handler->GetVisibleNetworkListByType(
+      NetworkTypePattern::Cellular(), &cellular_networks);
+  auto iter = std::find_if(cellular_networks.begin(), cellular_networks.end(),
+                           [](const NetworkState* network_state) {
+                             return network_state->IsConnectingState();
+                           });
+  return iter != cellular_networks.end();
+}
+
+mojom::InhibitReason GetInhibitReason(
+    NetworkStateHandler* network_state_handler,
+    CellularInhibitor* cellular_inhibitor) {
   if (!cellular_inhibitor)
     return mojom::InhibitReason::kNotInhibited;
 
   absl::optional<CellularInhibitor::InhibitReason> inhibit_reason =
       cellular_inhibitor->GetInhibitReason();
-  if (!inhibit_reason)
+  if (!inhibit_reason) {
+    // For devices with EUICC, the UI should be inhibited when a cellular
+    // network connection is in progress to prevent additional requests. This is
+    // due to complexity in switching slots.
+    if (!chromeos::HermesManagerClient::Get()->GetAvailableEuiccs().empty() &&
+        IsCellularConnecting(network_state_handler)) {
+      return mojom::InhibitReason::kConnectingToProfile;
+    }
+
     return mojom::InhibitReason::kNotInhibited;
+  }
 
   switch (*inhibit_reason) {
     case CellularInhibitor::InhibitReason::kInstallingProfile:
@@ -458,6 +481,7 @@
 
 mojom::DeviceStatePropertiesPtr DeviceStateToMojo(
     const DeviceState* device,
+    NetworkStateHandler* network_state_handler,
     CellularInhibitor* cellular_inhibitor,
     mojom::DeviceStateType technology_state) {
   mojom::NetworkType type = ShillTypeToMojo(device->type());
@@ -503,7 +527,8 @@
   }
   if (type == mojom::NetworkType::kCellular) {
     result->sim_infos = CellularSIMInfosToMojo(device);
-    result->inhibit_reason = GetInhibitReason(cellular_inhibitor);
+    result->inhibit_reason =
+        GetInhibitReason(network_state_handler, cellular_inhibitor);
   }
   return result;
 }
@@ -1819,6 +1844,30 @@
   return result;
 }
 
+mojom::TrafficCounterSource ConvertToTrafficCounterSourceEnum(
+    const std::string& source) {
+  if (source == shill::kTrafficCounterSourceUnknown)
+    return mojom::TrafficCounterSource::kUnknown;
+  if (source == shill::kTrafficCounterSourceChrome)
+    return mojom::TrafficCounterSource::kChrome;
+  if (source == shill::kTrafficCounterSourceUser)
+    return mojom::TrafficCounterSource::kUser;
+  if (source == shill::kTrafficCounterSourceArc)
+    return mojom::TrafficCounterSource::kArc;
+  if (source == shill::kTrafficCounterSourceCrosvm)
+    return mojom::TrafficCounterSource::kCrosvm;
+  if (source == shill::kTrafficCounterSourcePluginvm)
+    return mojom::TrafficCounterSource::kPluginvm;
+  if (source == shill::kTrafficCounterSourceUpdateEngine)
+    return mojom::TrafficCounterSource::kUpdateEngine;
+  if (source == shill::kTrafficCounterSourceVpn)
+    return mojom::TrafficCounterSource::kVpn;
+  if (source == shill::kTrafficCounterSourceSystem)
+    return mojom::TrafficCounterSource::kSystem;
+  NOTREACHED() << "Unknown traffic counter source: " << source;
+  return mojom::TrafficCounterSource::kUnknown;
+}
+
 }  // namespace
 
 CrosNetworkConfig::CrosNetworkConfig()
@@ -1958,8 +2007,8 @@
       NET_LOG(ERROR) << "Device state unavailable: " << device->name();
       continue;
     }
-    mojom::DeviceStatePropertiesPtr mojo_device =
-        DeviceStateToMojo(device, cellular_inhibitor_, technology_state);
+    mojom::DeviceStatePropertiesPtr mojo_device = DeviceStateToMojo(
+        device, network_state_handler_, cellular_inhibitor_, technology_state);
     if (mojo_device)
       result.emplace_back(std::move(mojo_device));
   }
@@ -2799,6 +2848,77 @@
   network_profile_handler_->SetAlwaysOnVpnService(profile->path, service_path);
 }
 
+void CrosNetworkConfig::RequestTrafficCounters(
+    const std::string& guid,
+    RequestTrafficCountersCallback callback) {
+  std::string service_path = GetServicePathFromGuid(guid);
+  if (service_path.empty()) {
+    NET_LOG(ERROR) << "RequestTrafficCounters: service path for guid " << guid
+                   << " not found";
+    std::move(callback).Run({});
+    return;
+  }
+  network_state_handler_->RequestTrafficCounters(
+      service_path,
+      base::BindOnce(&CrosNetworkConfig::PopulateTrafficCounters,
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+// static
+mojom::TrafficCounterSource CrosNetworkConfig::GetTrafficCounterEnumForTesting(
+    const std::string& source) {
+  return ConvertToTrafficCounterSourceEnum(source);
+}
+
+void CrosNetworkConfig::PopulateTrafficCounters(
+    RequestTrafficCountersCallback callback,
+    const base::ListValue& traffic_counters) {
+  if (!traffic_counters.GetList().size()) {
+    std::move(callback).Run({});
+    return;
+  }
+  std::vector<mojom::TrafficCounterPtr> counters;
+  for (const base::Value& tc : traffic_counters.GetList()) {
+    DCHECK(tc.is_dict());
+    const base::Value* source =
+        tc.FindKeyOfType("source", base::Value::Type::STRING);
+    DCHECK(source);
+
+    // Since rx_bytes may be larger than the maximum value representable by
+    // uint32_t, we must check whether it was implicitly converted to a double
+    // during D-Bus deserialization.
+    uint64_t rx_bytes;
+    const base::Value* rb = tc.FindKey("rx_bytes");
+    DCHECK(rb);
+    if (rb->type() == base::Value::Type::INTEGER) {
+      rx_bytes = rb->GetInt();
+    } else if (rb->type() == base::Value::Type::DOUBLE) {
+      rx_bytes = std::floor(rb->GetDouble());
+    } else {
+      NOTREACHED();
+    }
+
+    // Since tx_bytes may be larger than the maximum value representable by
+    // uint32_t, we must check whether it was implicitly converted to a double
+    // during D-Bus deserialization.
+    uint64_t tx_bytes;
+    const base::Value* tb = tc.FindKey("tx_bytes");
+    DCHECK(tb);
+    if (tb->type() == base::Value::Type::INTEGER) {
+      tx_bytes = tb->GetInt();
+    } else if (tb->type() == base::Value::Type::DOUBLE) {
+      tx_bytes = std::floor(tb->GetDouble());
+    } else {
+      NOTREACHED();
+    }
+
+    counters.push_back(mojom::TrafficCounter::New(
+        ConvertToTrafficCounterSourceEnum(source->GetString()), rx_bytes,
+        tx_bytes));
+  }
+  std::move(callback).Run(std::move(counters));
+}
+
 // NetworkStateHandlerObserver
 void CrosNetworkConfig::NetworkListChanged() {
   for (auto& observer : observers_)
@@ -2848,6 +2968,17 @@
   DeviceListChanged();
 }
 
+void CrosNetworkConfig::NetworkConnectionStateChanged(
+    const NetworkState* network) {
+  if (!network->Matches(NetworkTypePattern::Cellular())) {
+    return;
+  }
+  // inhibit_reason device property is dependent on network connection state of
+  // cellular networks. Notify device list change so that clients will update
+  // with new inhibit reason.
+  DeviceListChanged();
+}
+
 void CrosNetworkConfig::OnShuttingDown() {
   if (network_state_handler_->HasObserver(this))
     network_state_handler_->RemoveObserver(this, FROM_HERE);
diff --git a/chromeos/services/network_config/cros_network_config.h b/chromeos/services/network_config/cros_network_config.h
index cdc3e548..aa26a0c 100644
--- a/chromeos/services/network_config/cros_network_config.h
+++ b/chromeos/services/network_config/cros_network_config.h
@@ -94,6 +94,12 @@
   void GetNetworkCertificates(GetNetworkCertificatesCallback callback) override;
   void GetAlwaysOnVpn(GetAlwaysOnVpnCallback callback) override;
   void SetAlwaysOnVpn(mojom::AlwaysOnVpnPropertiesPtr properties) override;
+  void RequestTrafficCounters(const std::string& guid,
+                              RequestTrafficCountersCallback callback) override;
+
+  // static
+  static mojom::TrafficCounterSource GetTrafficCounterEnumForTesting(
+      const std::string& source);
 
  private:
   void OnGetManagedProperties(GetManagedPropertiesCallback callback,
@@ -153,6 +159,8 @@
   void OnGetAlwaysOnVpn(GetAlwaysOnVpnCallback callback,
                         std::string mode,
                         std::string service_path);
+  void PopulateTrafficCounters(RequestTrafficCountersCallback callback,
+                               const base::ListValue& traffic_counters);
 
   // NetworkStateHandlerObserver:
   void NetworkListChanged() override;
@@ -164,6 +172,7 @@
   void OnShuttingDown() override;
   void ScanStarted(const DeviceState* device) override;
   void ScanCompleted(const DeviceState* device) override;
+  void NetworkConnectionStateChanged(const NetworkState* network) override;
 
   // NetworkCertificateHandler::Observer
   void OnCertificatesChanged() override;
diff --git a/chromeos/services/network_config/cros_network_config_unittest.cc b/chromeos/services/network_config/cros_network_config_unittest.cc
index acc1de0..6c5afb4 100644
--- a/chromeos/services/network_config/cros_network_config_unittest.cc
+++ b/chromeos/services/network_config/cros_network_config_unittest.cc
@@ -76,6 +76,37 @@
 const char kCellularTestApnPassword3[] = "Test Pass";
 const char kCellularTestApnAttach3[] = "attach";
 
+enum ComparisonType {
+  INTEGER = 0,
+  DOUBLE,
+};
+
+void CompareTrafficCounters(
+    const std::vector<mojom::TrafficCounterPtr>& actual_traffic_counters,
+    const base::Value* expected_traffic_counters,
+    enum ComparisonType comparison_type) {
+  EXPECT_EQ(actual_traffic_counters.size(),
+            expected_traffic_counters->GetList().size());
+  for (size_t i = 0; i < actual_traffic_counters.size(); i++) {
+    auto& actual_tc = actual_traffic_counters[i];
+    auto& expected_tc = expected_traffic_counters->GetList()[i];
+    EXPECT_EQ(actual_tc->source,
+              CrosNetworkConfig::GetTrafficCounterEnumForTesting(
+                  expected_tc.FindKey("source")->GetString()));
+    if (comparison_type == ComparisonType::INTEGER) {
+      EXPECT_EQ(actual_tc->rx_bytes,
+                (size_t)expected_tc.FindKey("rx_bytes")->GetInt());
+      EXPECT_EQ(actual_tc->tx_bytes,
+                (size_t)expected_tc.FindKey("tx_bytes")->GetInt());
+    } else if (comparison_type == ComparisonType::DOUBLE) {
+      EXPECT_EQ(actual_tc->rx_bytes,
+                (size_t)expected_tc.FindKey("rx_bytes")->GetDouble());
+      EXPECT_EQ(actual_tc->tx_bytes,
+                (size_t)expected_tc.FindKey("tx_bytes")->GetDouble());
+    }
+  }
+}
+
 }  // namespace
 
 class CrosNetworkConfigTest : public testing::Test {
@@ -582,6 +613,25 @@
     return inhibit_lock;
   }
 
+  void RequestTrafficCountersAndCompareTrafficCounters(
+      const std::string& guid,
+      base::Value traffic_counters,
+      ComparisonType comparison_type) {
+    base::RunLoop run_loop;
+    cros_network_config()->RequestTrafficCounters(
+        guid,
+        base::BindOnce(
+            [](base::Value* expected_traffic_counters, ComparisonType* type,
+               base::OnceClosure quit_closure,
+               std::vector<mojom::TrafficCounterPtr> actual_traffic_counters) {
+              CompareTrafficCounters(actual_traffic_counters,
+                                     expected_traffic_counters, *type);
+              std::move(quit_closure).Run();
+            },
+            &traffic_counters, &comparison_type, run_loop.QuitClosure()));
+    run_loop.Run();
+  }
+
   NetworkHandlerTestHelper* helper() { return helper_.get(); }
   CrosNetworkConfigTestObserver* observer() { return observer_.get(); }
   CrosNetworkConfig* cros_network_config() {
@@ -1344,6 +1394,39 @@
   EXPECT_EQ(mojom::InhibitReason::kInstallingProfile, cellular->inhibit_reason);
 }
 
+TEST_F(CrosNetworkConfigTest, CellularInhibitState_Connecting) {
+  const char kTestEuiccPath[] = "euicc_path";
+  mojom::DeviceStatePropertiesPtr cellular =
+      GetDeviceStateFromList(mojom::NetworkType::kCellular);
+  ASSERT_TRUE(cellular);
+  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
+  EXPECT_EQ(mojom::InhibitReason::kNotInhibited, cellular->inhibit_reason);
+
+  // Set connect requested on cellular network.
+  NetworkStateHandler* network_state_handler =
+      NetworkHandler::Get()->network_state_handler();
+  const NetworkState* network_state =
+      network_state_handler->GetNetworkStateFromGuid("cellular_guid");
+  network_state_handler->SetNetworkConnectRequested(network_state->path(),
+                                                    true);
+
+  // Verify the inhibit state is not set when connecting if there are no EUICC.
+  cellular = GetDeviceStateFromList(mojom::NetworkType::kCellular);
+  ASSERT_TRUE(cellular);
+  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
+  EXPECT_EQ(mojom::InhibitReason::kNotInhibited, cellular->inhibit_reason);
+
+  // Verify the adding EUICC sets the inhibit reason correctly.
+  helper()->hermes_manager_test()->AddEuicc(dbus::ObjectPath(kTestEuiccPath),
+                                            "eid", /*is_active=*/true,
+                                            /*physical_slot=*/0);
+  cellular = GetDeviceStateFromList(mojom::NetworkType::kCellular);
+  ASSERT_TRUE(cellular);
+  EXPECT_EQ(mojom::DeviceStateType::kEnabled, cellular->device_state);
+  EXPECT_EQ(mojom::InhibitReason::kConnectingToProfile,
+            cellular->inhibit_reason);
+}
+
 TEST_F(CrosNetworkConfigTest, SetCellularSimState) {
   // Assert initial state.
   mojom::DeviceStatePropertiesPtr cellular =
@@ -1633,10 +1716,12 @@
   SetupObserver();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(0, observer()->device_state_list_changed());
+  NetworkStateHandler* network_state_handler =
+      NetworkHandler::Get()->network_state_handler();
 
   // Disable wifi
-  NetworkHandler::Get()->network_state_handler()->SetTechnologyEnabled(
-      NetworkTypePattern::WiFi(), false, network_handler::ErrorCallback());
+  network_state_handler->SetTechnologyEnabled(NetworkTypePattern::WiFi(), false,
+                                              network_handler::ErrorCallback());
   base::RunLoop().RunUntilIdle();
   // This will trigger three device list updates. First when wifi is in the
   // disabling state, next when it's actually disabled, and lastly when
@@ -1644,22 +1729,31 @@
   EXPECT_EQ(3, observer()->device_state_list_changed());
 
   // Enable Tethering
-  NetworkHandler::Get()->network_state_handler()->SetTetherTechnologyState(
+  network_state_handler->SetTetherTechnologyState(
       NetworkStateHandler::TechnologyState::TECHNOLOGY_ENABLED);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(4, observer()->device_state_list_changed());
 
   // Tests that observers are notified of device state list change
   // when a tether scan begins for a device.
-  NetworkHandler::Get()->network_state_handler()->SetTetherScanState(true);
+  network_state_handler->SetTetherScanState(true);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(5, observer()->device_state_list_changed());
 
   // Tests that observers are notified of device state list change
   // when a tether scan completes.
-  NetworkHandler::Get()->network_state_handler()->SetTetherScanState(false);
+  network_state_handler->SetTetherScanState(false);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(6, observer()->device_state_list_changed());
+
+  // Test that observers are notified of device state list change
+  // when a cellular network connection state changes.
+  const NetworkState* network_state =
+      network_state_handler->GetNetworkStateFromGuid("cellular_guid");
+  network_state_handler->SetNetworkConnectRequested(network_state->path(),
+                                                    /*connect_requested=*/true);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(7, observer()->device_state_list_changed());
 }
 
 TEST_F(CrosNetworkConfigTest, ActiveNetworksChanged) {
@@ -1806,5 +1900,51 @@
                             shill::kAlwaysOnVpnServiceProperty));
 }
 
+TEST_F(CrosNetworkConfigTest, RequestTrafficCountersWithIntegerType) {
+  base::Value traffic_counters(base::Value::Type::LIST);
+
+  base::Value chrome_dict(base::Value::Type::DICTIONARY);
+  chrome_dict.SetKey("source", base::Value(shill::kTrafficCounterSourceChrome));
+  chrome_dict.SetKey("rx_bytes", base::Value(12));
+  chrome_dict.SetKey("tx_bytes", base::Value(32));
+  traffic_counters.Append(std::move(chrome_dict));
+
+  base::Value user_dict(base::Value::Type::DICTIONARY);
+  user_dict.SetKey("source", base::Value(shill::kTrafficCounterSourceUser));
+  user_dict.SetKey("rx_bytes", base::Value(90));
+  user_dict.SetKey("tx_bytes", base::Value(87));
+  traffic_counters.Append(std::move(user_dict));
+
+  ASSERT_TRUE(traffic_counters.is_list());
+  ASSERT_EQ(traffic_counters.GetList().size(), (size_t)2);
+  helper()->service_test()->SetFakeTrafficCounters(traffic_counters.Clone());
+
+  RequestTrafficCountersAndCompareTrafficCounters(
+      "wifi1_guid", traffic_counters.Clone(), ComparisonType::INTEGER);
+}
+
+TEST_F(CrosNetworkConfigTest, RequestTrafficCountersWithDoubleType) {
+  base::Value traffic_counters(base::Value::Type::LIST);
+
+  base::Value chrome_dict(base::Value::Type::DICTIONARY);
+  chrome_dict.SetKey("source", base::Value(shill::kTrafficCounterSourceChrome));
+  chrome_dict.SetKey("rx_bytes", base::Value(123456789987.0));
+  chrome_dict.SetKey("tx_bytes", base::Value(3211234567898.0));
+  traffic_counters.Append(std::move(chrome_dict));
+
+  base::Value user_dict(base::Value::Type::DICTIONARY);
+  user_dict.SetKey("source", base::Value(shill::kTrafficCounterSourceUser));
+  user_dict.SetKey("rx_bytes", base::Value(9000000000000000.0));
+  user_dict.SetKey("tx_bytes", base::Value(8765432112345.0));
+  traffic_counters.Append(std::move(user_dict));
+
+  ASSERT_TRUE(traffic_counters.is_list());
+  ASSERT_EQ(traffic_counters.GetList().size(), (size_t)2);
+  helper()->service_test()->SetFakeTrafficCounters(traffic_counters.Clone());
+
+  RequestTrafficCountersAndCompareTrafficCounters(
+      "wifi1_guid", traffic_counters.Clone(), ComparisonType::DOUBLE);
+}
+
 }  // namespace network_config
 }  // namespace chromeos
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 676e0d5..acbf310 100644
--- a/chromeos/services/network_config/public/mojom/cros_network_config.mojom
+++ b/chromeos/services/network_config/public/mojom/cros_network_config.mojom
@@ -907,6 +907,32 @@
   string service_guid;
 };
 
+// Source of the traffic. These sources are kept in sync with
+// third_party/cros_system_api/dbus/shill/dbus-constants.h.
+enum TrafficCounterSource {
+  kUnknown,
+  kChrome,
+  kUser,
+  kArc,
+  kCrosvm,
+  kPluginvm,
+  kUpdateEngine,
+  kVpn,
+  kSystem,
+};
+
+// Information about a traffic counter for a single source.
+struct TrafficCounter {
+  // Source of traffic.
+  TrafficCounterSource source;
+  // Corresponds to the number of bytes received by |source| since the last
+  // reset.
+  uint64 rx_bytes;
+  // Corresponds to the number of bytes transmitted by |source| since the last
+  // reset.
+  uint64 tx_bytes;
+};
+
 // Interface for fetching and setting network configuration properties, e.g.
 // from Settings WebUI or the SystemTray.
 interface CrosNetworkConfig {
@@ -1023,6 +1049,10 @@
   // Sets the always-on VPN mode and service with |properties.mode| and
   // |properties.service_guid|. The service GUID must match a VPN service.
   SetAlwaysOnVpn(AlwaysOnVpnProperties properties);
+
+  // Requests traffic counters for a service with ID |guid|.
+  RequestTrafficCounters(string guid) =>
+    (array<TrafficCounter> traffic_counters);
 };
 
 interface CrosNetworkConfigObserver {
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index 1b537c07..5ba2235 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -958,7 +958,8 @@
 void AutofillAgent::FormControlElementClicked(
     const WebFormControlElement& element,
     bool was_focused) {
-  last_clicked_form_control_element_for_testing_ = element;
+  last_clicked_form_control_element_for_testing_ =
+      FieldRendererId(element.UniqueRendererFormControlId());
   last_clicked_form_control_element_was_focused_for_testing_ = was_focused;
   was_last_action_fill_ = false;
 
@@ -1158,7 +1159,7 @@
 
 void AutofillAgent::ResetLastInteractedElements() {
   last_interacted_form_.Reset();
-  last_clicked_form_control_element_for_testing_.Reset();
+  last_clicked_form_control_element_for_testing_ = {};
   formless_elements_user_edited_.clear();
   provisionally_saved_form_.reset();
 }
diff --git a/components/autofill/content/renderer/autofill_agent.h b/components/autofill/content/renderer/autofill_agent.h
index 7276726..a3a96731 100644
--- a/components/autofill/content/renderer/autofill_agent.h
+++ b/components/autofill/content/renderer/autofill_agent.h
@@ -354,7 +354,7 @@
 
   bool focused_node_was_last_clicked_ = false;
   bool was_focused_before_now_ = false;
-  blink::WebFormControlElement last_clicked_form_control_element_for_testing_;
+  FieldRendererId last_clicked_form_control_element_for_testing_;
   bool last_clicked_form_control_element_was_focused_for_testing_ = false;
 
   FormTracker form_tracker_;
diff --git a/components/autofill/core/browser/autofill_suggestion_generator.cc b/components/autofill/core/browser/autofill_suggestion_generator.cc
index f8df0f64..ebfc20e 100644
--- a/components/autofill/core/browser/autofill_suggestion_generator.cc
+++ b/components/autofill/core/browser/autofill_suggestion_generator.cc
@@ -241,7 +241,6 @@
 
     // TODO(crbug.com/1196021): Populate custom_icon with card art if available.
     suggestion.frontend_id = POPUP_ITEM_ID_VIRTUAL_CREDIT_CARD_ENTRY;
-    suggestion.is_value_secondary = true;
   }
 
   return suggestion;
diff --git a/components/autofill/core/browser/data_model/autofill_structured_address_component.h b/components/autofill/core/browser/data_model/autofill_structured_address_component.h
index b97444f..ec0b659e 100644
--- a/components/autofill/core/browser/data_model/autofill_structured_address_component.h
+++ b/components/autofill/core/browser/data_model/autofill_structured_address_component.h
@@ -26,6 +26,7 @@
 // Represents the validation status of value stored in the AutofillProfile.
 // The associated integer values used to store the verification code in SQL and
 // should not be modified.
+// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.autofill
 enum class VerificationStatus {
   // No verification status assigned.
   kNoStatus = 0,
diff --git a/components/breadcrumbs/core/breadcrumb_manager.cc b/components/breadcrumbs/core/breadcrumb_manager.cc
index 6a6f7ea..0d97455 100644
--- a/components/breadcrumbs/core/breadcrumb_manager.cc
+++ b/components/breadcrumbs/core/breadcrumb_manager.cc
@@ -24,7 +24,7 @@
 const int kMinEventsBuckets = 2;
 
 // Returns a Time used to bucket events for easier discarding of expired events.
-base::Time EventBucket(const base::Time& time) {
+base::Time GetBucketTime(const base::Time& time) {
   base::Time::Exploded exploded;
   time.LocalExplode(&exploded);
   exploded.millisecond = 0;
@@ -47,8 +47,7 @@
 
   size_t count = 0;
   for (auto it = event_buckets_.rbegin(); it != event_buckets_.rend(); ++it) {
-    std::list<std::string> bucket_events = it->second;
-    count += bucket_events.size();
+    count += it->events.size();
   }
   return count;
 }
@@ -59,10 +58,10 @@
 
   std::list<std::string> events;
   for (auto it = event_buckets_.rbegin(); it != event_buckets_.rend(); ++it) {
-    std::list<std::string> bucket_events = it->second;
+    const std::list<std::string>& bucket_events = it->events;
     for (auto event_it = bucket_events.rbegin();
          event_it != bucket_events.rend(); ++event_it) {
-      std::string event = *event_it;
+      const std::string& event = *event_it;
       events.push_front(event);
       if (event_count_limit > 0 && events.size() >= event_count_limit) {
         return events;
@@ -74,13 +73,11 @@
 
 void BreadcrumbManager::AddEvent(const std::string& event) {
   base::Time time = base::Time::Now();
-  base::Time bucket_time = EventBucket(time);
+  base::Time bucket_time = GetBucketTime(time);
 
-  // If bucket exists, it will be at the end of the list.
-  if (event_buckets_.empty() || event_buckets_.back().first != bucket_time) {
-    std::pair<base::Time, std::list<std::string>> bucket(
-        bucket_time, std::list<std::string>());
-    event_buckets_.push_back(bucket);
+  // If a bucket exists, it will be at the end of the list.
+  if (event_buckets_.empty() || event_buckets_.back().time != bucket_time) {
+    event_buckets_.emplace_back(bucket_time);
   }
 
   base::Time::Exploded exploded;
@@ -89,7 +86,7 @@
       base::StringPrintf("%02d:%02d", exploded.minute, exploded.second);
   std::string event_log =
       base::StringPrintf("%s %s", timestamp.c_str(), event.c_str());
-  event_buckets_.back().second.push_back(event_log);
+  event_buckets_.back().events.push_back(event_log);
 
   for (auto& observer : observers_) {
     observer.EventAdded(this, event_log);
@@ -106,7 +103,7 @@
   base::Time now = base::Time::Now();
   // Drop buckets which are more than kMessageExpirationTime old.
   while (event_buckets_.size() > kMinEventsBuckets) {
-    base::Time oldest_bucket_time = event_buckets_.front().first;
+    base::Time oldest_bucket_time = event_buckets_.front().time;
     if (now - oldest_bucket_time < kMessageExpirationTime) {
       break;
     }
@@ -118,7 +115,7 @@
   unsigned long newer_event_count = 0;
   auto event_bucket_it = event_buckets_.rbegin();
   while (event_bucket_it != event_buckets_.rend()) {
-    std::list<std::string> bucket_events = event_bucket_it->second;
+    std::list<std::string> bucket_events = event_bucket_it->events;
     if (newer_event_count > kMaxUsefulBreadcrumbEvents) {
       event_buckets_.erase(event_buckets_.begin(), event_bucket_it.base());
       old_buckets_dropped = true;
@@ -143,4 +140,9 @@
   observers_.RemoveObserver(observer);
 }
 
+BreadcrumbManager::EventBucket::EventBucket(base::Time bucket_time)
+    : time(bucket_time) {}
+BreadcrumbManager::EventBucket::EventBucket(const EventBucket&) = default;
+BreadcrumbManager::EventBucket::~EventBucket() = default;
+
 }  // namespace breadcrumbs
diff --git a/components/breadcrumbs/core/breadcrumb_manager.h b/components/breadcrumbs/core/breadcrumb_manager.h
index 22bec33..1c162e8b 100644
--- a/components/breadcrumbs/core/breadcrumb_manager.h
+++ b/components/breadcrumbs/core/breadcrumb_manager.h
@@ -59,9 +59,17 @@
   // Creation time of the BreadcrumbManager.
   const base::Time start_time_;
 
-  // List of events, paired with the time which they were logged to minute
-  // resolution. Newer events are at the end of the list.
-  std::list<std::pair<base::Time, std::list<std::string>>> event_buckets_;
+  // List of events, paired with the time they were logged to minute resolution.
+  // Newer events are at the end of the list.
+  struct EventBucket {
+    base::Time time;
+    std::list<std::string> events;
+
+    explicit EventBucket(base::Time bucket_time);
+    EventBucket(const EventBucket&);
+    ~EventBucket();
+  };
+  std::list<EventBucket> event_buckets_;
 
   base::ObserverList<BreadcrumbManagerObserver, /*check_empty=*/true>
       observers_;
diff --git a/components/cronet/android/sample/AndroidManifest.xml b/components/cronet/android/sample/AndroidManifest.xml
index 26d99b0..64b7e06 100644
--- a/components/cronet/android/sample/AndroidManifest.xml
+++ b/components/cronet/android/sample/AndroidManifest.xml
@@ -7,6 +7,7 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     package="org.chromium.cronet_sample_apk">
 
     <uses-permission android:name="android.permission.INTERNET"/>
@@ -23,5 +24,12 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <!-- Disables at startup init of Emoji2. See http://crbug.com/1205141 -->
+        <provider
+            android:authorities="org.chromium.cronet_sample_apk.androidx-startup"
+            android:name="androidx.startup.InitializationProvider"
+            android:exported="false"
+            tools:node="remove">
+        </provider>
     </application>
 </manifest>
diff --git a/components/feedback/redaction_tool.cc b/components/feedback/redaction_tool.cc
index deaad29..06d82ad 100644
--- a/components/feedback/redaction_tool.cc
+++ b/components/feedback/redaction_tool.cc
@@ -726,6 +726,7 @@
   re2::StringPiece text(input);
   re2::StringPiece skipped;
   re2::StringPiece matched_id;
+  const re2::StringPiece dash("-");
   while (FindAndConsumeAndGetSkipped(&text, *re, &skipped, &matched_id)) {
     if (IsUrlExempt(matched_id, first_party_extension_ids_)) {
       skipped.AppendToString(&result);
@@ -737,6 +738,14 @@
     if (identifier_space->count(matched_id_as_string) == 0) {
       replacement_id = MaybeScrubIPAddress(matched_id_as_string);
       if (replacement_id != matched_id_as_string) {
+        // USB paths can be confused with IPv4 Addresses because they can look
+        // similar: n-n.n.n.n . Ignore replacement if previous char is `-`
+        if (skipped.ends_with(dash) && strcmp("IPv4", pattern.alias) == 0) {
+          skipped.AppendToString(&result);
+          matched_id.AppendToString(&result);
+          continue;
+        }
+
         // The weird NumberToString trick is because Windows does not like
         // to deal with %zu and a size_t in printf, nor does it support %llu.
         replacement_id = base::StringPrintf(
diff --git a/components/feedback/redaction_tool_unittest.cc b/components/feedback/redaction_tool_unittest.cc
index 594de525..10f06bb 100644
--- a/components/feedback/redaction_tool_unittest.cc
+++ b/components/feedback/redaction_tool_unittest.cc
@@ -216,8 +216,18 @@
   EXPECT_EQ("[<IPv6: 4>]", RedactCustomPatterns("[aa::bb]"));
   EXPECT_EQ("State::Abort", RedactCustomPatterns("State::Abort"));
 
+  // Real IPv4 address
   EXPECT_EQ("<IPv4: 1>", RedactCustomPatterns("192.160.0.1"));
 
+  // Non-PII IPv4 address (see MaybeScrubIPAddress)
+  EXPECT_EQ("255.255.255.255", RedactCustomPatterns("255.255.255.255"));
+
+  // Not an actual IPv4 address
+  EXPECT_EQ("75.748.86.91", RedactCustomPatterns("75.748.86.91"));
+
+  // USB Path - not an actual IPv4 Address
+  EXPECT_EQ("4-3.3.3.3", RedactCustomPatterns("4-3.3.3.3"));
+
   EXPECT_EQ("<URL: 1>", RedactCustomPatterns("http://example.com/foo?test=1"));
   EXPECT_EQ("Foo <URL: 2> Bar",
             RedactCustomPatterns("Foo http://192.168.0.1/foo?test=1#123 Bar"));
@@ -368,6 +378,8 @@
      "255.255.259.255"},
     {"255.300.255.255",  // Not an IP address.
      "255.300.255.255"},
+    {"3-1.2.3.4",  // USB path, not an IP address.
+     "3-1.2.3.4"},
     {"aaaa123.123.45.4aaa",  // IP address.
      "aaaa<IPv4: 23>aaa"},
     {"11:11;11::11",  // IP address.
diff --git a/components/gcm_driver/crypto/p256_key_util.cc b/components/gcm_driver/crypto/p256_key_util.cc
index 9026682..5954d20 100644
--- a/components/gcm_driver/crypto/p256_key_util.cc
+++ b/components/gcm_driver/crypto/p256_key_util.cc
@@ -21,9 +21,6 @@
 
 namespace {
 
-// The first byte in an uncompressed P-256 point per SEC1 2.3.3.
-const char kUncompressedPointForm = 0x04;
-
 // A P-256 field element consists of 32 bytes.
 const size_t kFieldBytes = 32;
 
@@ -38,17 +35,14 @@
   std::string candidate_public_key;
 
   // ECPrivateKey::ExportRawPublicKey() returns the EC point in the uncompressed
-  // point format, but does not include the leading byte of value 0x04 that
-  // indicates usage of uncompressed points, per SEC1 2.3.3.
+  // point format.
   if (!key.ExportRawPublicKey(&candidate_public_key) ||
-      candidate_public_key.size() != 2 * kFieldBytes) {
+      candidate_public_key.size() != kUncompressedPointBytes) {
     DLOG(ERROR) << "Unable to export the public key.";
     return false;
   }
-  // Concatenate the leading 0x04 byte and the two uncompressed points.
   public_key->erase();
   public_key->reserve(kUncompressedPointBytes);
-  public_key->push_back(kUncompressedPointForm);
   public_key->append(candidate_public_key);
   return true;
 }
@@ -79,11 +73,10 @@
   bssl::UniquePtr<EC_POINT> point(
       EC_POINT_new(EC_KEY_get0_group(ec_private_key)));
 
-  if (!point ||
-      !EC_POINT_oct2point(
-          EC_KEY_get0_group(ec_private_key), point.get(),
-          reinterpret_cast<const uint8_t*>(peer_public_key.data()),
-          peer_public_key.size(), nullptr)) {
+  if (!point || !EC_POINT_oct2point(
+                    EC_KEY_get0_group(ec_private_key), point.get(),
+                    reinterpret_cast<const uint8_t*>(peer_public_key.data()),
+                    peer_public_key.size(), nullptr)) {
     DLOG(ERROR) << "Can't convert peer public value to curve point.";
     return false;
   }
diff --git a/components/live_caption/BUILD.gn b/components/live_caption/BUILD.gn
index 0cfc6b44..0766c46e 100644
--- a/components/live_caption/BUILD.gn
+++ b/components/live_caption/BUILD.gn
@@ -2,7 +2,9 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-if (!is_android) {
+import("//build/config/ui.gni")
+
+if (toolkit_views) {
   static_library("live_caption") {
     sources = [
       "views/caption_bubble.cc",
@@ -24,7 +26,7 @@
       "//ui/views",
     ]
   }
-}  # !is_android
+}  # toolkit_views
 
 source_set("constants") {
   sources = [
diff --git a/components/lookalikes/core/lookalike_url_ui_util.cc b/components/lookalikes/core/lookalike_url_ui_util.cc
index 5907d14..c507738 100644
--- a/components/lookalikes/core/lookalike_url_ui_util.cc
+++ b/components/lookalikes/core/lookalike_url_ui_util.cc
@@ -41,19 +41,18 @@
   }
 }
 
-void PopulateLookalikeUrlBlockingPageStrings(
-    base::DictionaryValue* load_time_data,
-    const GURL& safe_url,
-    const GURL& request_url) {
+void PopulateLookalikeUrlBlockingPageStrings(base::Value* load_time_data,
+                                             const GURL& safe_url,
+                                             const GURL& request_url) {
   CHECK(load_time_data);
 
   PopulateStringsForSharedHTML(load_time_data);
-  load_time_data->SetString("tabTitle",
-                            l10n_util::GetStringUTF16(IDS_LOOKALIKE_URL_TITLE));
-  load_time_data->SetString(
+  load_time_data->SetStringKey(
+      "tabTitle", l10n_util::GetStringUTF16(IDS_LOOKALIKE_URL_TITLE));
+  load_time_data->SetStringKey(
       "optInLink",
       l10n_util::GetStringUTF16(IDS_SAFE_BROWSING_SCOUT_REPORTING_AGREE));
-  load_time_data->SetString(
+  load_time_data->SetStringKey(
       "enhancedProtectionMessage",
       l10n_util::GetStringUTF16(IDS_SAFE_BROWSING_ENHANCED_PROTECTION_MESSAGE));
 
@@ -61,60 +60,60 @@
     const std::u16string hostname =
         security_interstitials::common_string_util::GetFormattedHostName(
             safe_url);
-    load_time_data->SetString(
+    load_time_data->SetStringKey(
         "heading",
         l10n_util::GetStringFUTF16(IDS_LOOKALIKE_URL_HEADING, hostname));
-    load_time_data->SetString(
+    load_time_data->SetStringKey(
         "primaryParagraph",
         l10n_util::GetStringUTF16(IDS_LOOKALIKE_URL_PRIMARY_PARAGRAPH));
-    load_time_data->SetString(
+    load_time_data->SetStringKey(
         "proceedButtonText",
         l10n_util::GetStringUTF16(IDS_LOOKALIKE_URL_IGNORE));
-    load_time_data->SetString(
+    load_time_data->SetStringKey(
         "primaryButtonText",
         l10n_util::GetStringFUTF16(IDS_LOOKALIKE_URL_CONTINUE, hostname));
   } else {
     // No safe URL available to suggest. This can happen when the navigated
     // domain fails IDN spoof checks but isn't a lookalike of a known domain.
     // TODO: Change to actual strings.
-    load_time_data->SetString(
+    load_time_data->SetStringKey(
         "heading",
         l10n_util::GetStringUTF16(IDS_LOOKALIKE_URL_HEADING_NO_SUGGESTED_URL));
-    load_time_data->SetString(
+    load_time_data->SetStringKey(
         "primaryParagraph",
         l10n_util::GetStringUTF16(
             IDS_LOOKALIKE_URL_PRIMARY_PARAGRAPH_NO_SUGGESTED_URL));
-    load_time_data->SetString(
+    load_time_data->SetStringKey(
         "proceedButtonText",
         l10n_util::GetStringUTF16(IDS_LOOKALIKE_URL_IGNORE));
-    load_time_data->SetString(
+    load_time_data->SetStringKey(
         "primaryButtonText",
         l10n_util::GetStringUTF16(IDS_LOOKALIKE_URL_BACK_TO_SAFETY));
 #if defined(OS_IOS)
     // On iOS, offer to close the page instead of navigating to NTP when the
     // safe URL is empty or invalid, and unable to go back.
-    bool show_close_page = false;
-    load_time_data->GetBoolean("cant_go_back", &show_close_page);
-    if (show_close_page) {
-      load_time_data->SetString(
+    absl::optional<bool> maybe_cant_go_back =
+        load_time_data->FindBoolKey("cant_go_back");
+    if (maybe_cant_go_back && *maybe_cant_go_back) {
+      load_time_data->SetStringKey(
           "primaryButtonText",
           l10n_util::GetStringUTF16(IDS_LOOKALIKE_URL_CLOSE_PAGE));
     }
 #endif
   }
-  load_time_data->SetString("lookalikeRequestHostname", request_url.host());
+  load_time_data->SetStringKey("lookalikeRequestHostname", request_url.host());
 }
 
-void PopulateStringsForSharedHTML(base::DictionaryValue* load_time_data) {
-  load_time_data->SetBoolean("lookalike_url", true);
-  load_time_data->SetBoolean("overridable", false);
-  load_time_data->SetBoolean("hide_primary_button", false);
-  load_time_data->SetBoolean("show_recurrent_error_paragraph", false);
+void PopulateStringsForSharedHTML(base::Value* load_time_data) {
+  load_time_data->SetBoolKey("lookalike_url", true);
+  load_time_data->SetBoolKey("overridable", false);
+  load_time_data->SetBoolKey("hide_primary_button", false);
+  load_time_data->SetBoolKey("show_recurrent_error_paragraph", false);
 
-  load_time_data->SetString("recurrentErrorParagraph", "");
-  load_time_data->SetString("openDetails", "");
-  load_time_data->SetString("explanationParagraph", "");
-  load_time_data->SetString("finalParagraph", "");
+  load_time_data->SetStringKey("recurrentErrorParagraph", "");
+  load_time_data->SetStringKey("openDetails", "");
+  load_time_data->SetStringKey("explanationParagraph", "");
+  load_time_data->SetStringKey("finalParagraph", "");
 
-  load_time_data->SetString("type", "LOOKALIKE");
+  load_time_data->SetStringKey("type", "LOOKALIKE");
 }
diff --git a/components/lookalikes/core/lookalike_url_ui_util.h b/components/lookalikes/core/lookalike_url_ui_util.h
index 81e2e08..61cb9bc 100644
--- a/components/lookalikes/core/lookalike_url_ui_util.h
+++ b/components/lookalikes/core/lookalike_url_ui_util.h
@@ -9,7 +9,7 @@
 #include "services/metrics/public/cpp/ukm_source_id.h"
 
 namespace base {
-class DictionaryValue;
+class Value;
 }  // namespace base
 
 // Allow easier reporting of UKM when no interstitial is shown.
@@ -27,12 +27,11 @@
     bool triggered_by_initial_url);
 
 // Populates |load_time_data| for interstitial HTML.
-void PopulateLookalikeUrlBlockingPageStrings(
-    base::DictionaryValue* load_time_data,
-    const GURL& safe_url,
-    const GURL& request_url);
+void PopulateLookalikeUrlBlockingPageStrings(base::Value* load_time_data,
+                                             const GURL& safe_url,
+                                             const GURL& request_url);
 
 // Values added to get shared interstitial HTML to play nice.
-void PopulateStringsForSharedHTML(base::DictionaryValue* load_time_data);
+void PopulateStringsForSharedHTML(base::Value* load_time_data);
 
 #endif  // COMPONENTS_LOOKALIKES_CORE_LOOKALIKE_URL_UI_UTIL_H_
diff --git a/components/metrics/log_store.h b/components/metrics/log_store.h
index d624f4a..cb0f633 100644
--- a/components/metrics/log_store.h
+++ b/components/metrics/log_store.h
@@ -7,6 +7,8 @@
 
 #include <string>
 
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
 namespace metrics {
 
 // Interface for local storage of serialized logs to be reported.
@@ -33,6 +35,12 @@
   // Will trigger a DCHECK if there is no staged log.
   virtual const std::string& staged_log_signature() const = 0;
 
+  // User id associated with the staged log. Empty if the log was
+  // recorded during no particular user session or during guest session.
+  //
+  // Will trigger a DCHECK if there is no staged log.
+  virtual absl::optional<uint64_t> staged_log_user_id() const = 0;
+
   // Populates staged_log() with the next stored log to send.
   // The order in which logs are staged is up to the implementor.
   // The staged_log must remain the same even if additional logs are added.
diff --git a/components/metrics/metrics_log.cc b/components/metrics/metrics_log.cc
index 384f849..5259f59 100644
--- a/components/metrics/metrics_log.cc
+++ b/components/metrics/metrics_log.cc
@@ -51,6 +51,23 @@
 
 namespace metrics {
 
+LogMetadata::LogMetadata()
+    : samples_count(absl::nullopt), user_id(absl::nullopt) {}
+LogMetadata::LogMetadata(
+    const absl::optional<base::HistogramBase::Count> samples_count,
+    const absl::optional<uint64_t> user_id)
+    : samples_count(samples_count), user_id(user_id) {}
+LogMetadata::LogMetadata(const LogMetadata& other) = default;
+LogMetadata::~LogMetadata() = default;
+
+void LogMetadata::AddSampleCount(base::HistogramBase::Count sample_count) {
+  if (samples_count.has_value()) {
+    samples_count = samples_count.value() + sample_count;
+  } else {
+    samples_count = sample_count;
+  }
+}
+
 namespace {
 
 // A simple class to write histogram data to a log.
@@ -134,8 +151,7 @@
   RecordCoreSystemProfile(client_, system_profile);
 }
 
-MetricsLog::~MetricsLog() {
-}
+MetricsLog::~MetricsLog() = default;
 
 // static
 void MetricsLog::RegisterPrefs(PrefRegistrySimple* registry) {
@@ -270,7 +286,7 @@
 void MetricsLog::RecordHistogramDelta(const std::string& histogram_name,
                                       const base::HistogramSamples& snapshot) {
   DCHECK(!closed_);
-  samples_count_ += snapshot.TotalCount();
+  log_metadata_.AddSampleCount(snapshot.TotalCount());
   EncodeHistogramDelta(histogram_name, snapshot, &uma_proto_);
 }
 
diff --git a/components/metrics/metrics_log.h b/components/metrics/metrics_log.h
index 4043e92..ffbe252 100644
--- a/components/metrics/metrics_log.h
+++ b/components/metrics/metrics_log.h
@@ -20,6 +20,7 @@
 #include "base/strings/string_piece_forward.h"
 #include "base/time/time.h"
 #include "components/metrics/metrics_reporting_default_state.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
 #include "third_party/metrics_proto/system_profile.pb.h"
 
@@ -33,6 +34,25 @@
 
 namespace metrics {
 
+// Holds optional metadata associated with a log to be stored.
+struct LogMetadata {
+  LogMetadata();
+  LogMetadata(absl::optional<base::HistogramBase::Count> samples_count,
+              absl::optional<uint64_t> user_id);
+  LogMetadata(const LogMetadata& other);
+  ~LogMetadata();
+
+  // Adds |sample_count| to |samples_count|. If |samples_count| is empty, then
+  // |sample_count| will populate |samples_count|.
+  void AddSampleCount(base::HistogramBase::Count sample_count);
+
+  // The total number of samples in this log if applicable.
+  absl::optional<base::HistogramBase::Count> samples_count;
+
+  // User id associated with the log.
+  absl::optional<uint64_t> user_id;
+};
+
 class MetricsProvider;
 class MetricsServiceClient;
 class DelegatingProvider;
@@ -170,9 +190,7 @@
 
   LogType log_type() const { return log_type_; }
 
-  // Returns the number of samples in this log, it is only valid after the
-  // histogram delta is calculated.
-  base::HistogramBase::Count samples_count() const { return samples_count_; }
+  const LogMetadata& log_metadata() const { return log_metadata_; }
 
   // Exposed for the sake of mocking/accessing in test code.
   ChromeUserMetricsExtension* UmaProtoForTest() { return &uma_proto_; }
@@ -219,8 +237,8 @@
   // RecordEnvironment() or LoadSavedEnvironmentFromPrefs().
   bool has_environment_;
 
-  // The number of samples in this log.
-  base::HistogramBase::Count samples_count_ = 0;
+  // Optional metadata associated with the log.
+  LogMetadata log_metadata_;
 
   DISALLOW_COPY_AND_ASSIGN(MetricsLog);
 };
diff --git a/components/metrics/metrics_log_manager.cc b/components/metrics/metrics_log_manager.cc
index 072ea3e..998fd81 100644
--- a/components/metrics/metrics_log_manager.cc
+++ b/components/metrics/metrics_log_manager.cc
@@ -30,7 +30,7 @@
   current_log_->GetEncodedLog(&log_data);
   if (!log_data.empty()) {
     log_store->StoreLog(log_data, current_log_->log_type(),
-                        absl::make_optional(current_log_->samples_count()));
+                        current_log_->log_metadata());
   }
   current_log_.reset();
 }
diff --git a/components/metrics/metrics_log_store.cc b/components/metrics/metrics_log_store.cc
index 8dfbddf..99b9f6b 100644
--- a/components/metrics/metrics_log_store.cc
+++ b/components/metrics/metrics_log_store.cc
@@ -48,17 +48,16 @@
   unsent_logs_loaded_ = true;
 }
 
-void MetricsLogStore::StoreLog(
-    const std::string& log_data,
-    MetricsLog::LogType log_type,
-    absl::optional<base::HistogramBase::Count> samples_count) {
+void MetricsLogStore::StoreLog(const std::string& log_data,
+                               MetricsLog::LogType log_type,
+                               const LogMetadata& log_metadata) {
   switch (log_type) {
     case MetricsLog::INITIAL_STABILITY_LOG:
-      initial_log_queue_.StoreLog(log_data, samples_count);
+      initial_log_queue_.StoreLog(log_data, log_metadata);
       break;
     case MetricsLog::ONGOING_LOG:
     case MetricsLog::INDEPENDENT_LOG:
-      ongoing_log_queue_.StoreLog(log_data, samples_count);
+      ongoing_log_queue_.StoreLog(log_data, log_metadata);
       break;
   }
 }
@@ -90,6 +89,12 @@
              : ongoing_log_queue_.staged_log_signature();
 }
 
+absl::optional<uint64_t> MetricsLogStore::staged_log_user_id() const {
+  // MetricsLogStore base class should never have any logs associated with a
+  // user ID.
+  return absl::nullopt;
+}
+
 void MetricsLogStore::StageNextLog() {
   DCHECK(!has_staged_log());
   if (initial_log_queue_.has_unsent_logs())
diff --git a/components/metrics/metrics_log_store.h b/components/metrics/metrics_log_store.h
index 14f1552..f17590f 100644
--- a/components/metrics/metrics_log_store.h
+++ b/components/metrics/metrics_log_store.h
@@ -64,7 +64,7 @@
   // Saves |log_data| as the given type.
   void StoreLog(const std::string& log_data,
                 MetricsLog::LogType log_type,
-                absl::optional<base::HistogramBase::Count> samples_count);
+                const LogMetadata& log_metadata);
 
   // LogStore:
   bool has_unsent_logs() const override;
@@ -72,6 +72,7 @@
   const std::string& staged_log() const override;
   const std::string& staged_log_hash() const override;
   const std::string& staged_log_signature() const override;
+  absl::optional<uint64_t> staged_log_user_id() const override;
   void StageNextLog() override;
   void DiscardStagedLog() override;
   void MarkStagedLogAsSent() override;
diff --git a/components/metrics/metrics_log_store_unittest.cc b/components/metrics/metrics_log_store_unittest.cc
index da9ee76..71647cf 100644
--- a/components/metrics/metrics_log_store_unittest.cc
+++ b/components/metrics/metrics_log_store_unittest.cc
@@ -50,7 +50,7 @@
   EXPECT_FALSE(log_store.has_staged_log());
   EXPECT_FALSE(log_store.has_unsent_logs());
 
-  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, absl::nullopt);
+  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata());
   EXPECT_TRUE(log_store.has_unsent_logs());
   EXPECT_FALSE(log_store.has_staged_log());
 
@@ -71,7 +71,7 @@
                               std::string());
     log_store.LoadPersistedUnsentLogs();
     EXPECT_FALSE(log_store.has_unsent_logs());
-    log_store.StoreLog("a", MetricsLog::ONGOING_LOG, absl::nullopt);
+    log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata());
     log_store.TrimAndPersistUnsentLogs();
     EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
     EXPECT_EQ(1U, TypeCount(MetricsLog::ONGOING_LOG));
@@ -85,9 +85,9 @@
     EXPECT_TRUE(log_store.has_unsent_logs());
     EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
     EXPECT_EQ(1U, TypeCount(MetricsLog::ONGOING_LOG));
-    log_store.StoreLog("x", MetricsLog::INITIAL_STABILITY_LOG, absl::nullopt);
+    log_store.StoreLog("x", MetricsLog::INITIAL_STABILITY_LOG, LogMetadata());
     log_store.StageNextLog();
-    log_store.StoreLog("b", MetricsLog::ONGOING_LOG, absl::nullopt);
+    log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata());
 
     EXPECT_TRUE(log_store.has_unsent_logs());
     EXPECT_TRUE(log_store.has_staged_log());
@@ -139,7 +139,7 @@
   MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                             std::string());
   log_store.LoadPersistedUnsentLogs();
-  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, absl::nullopt);
+  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata());
   log_store.StageNextLog();
   log_store.TrimAndPersistUnsentLogs();
 
@@ -152,7 +152,7 @@
   MetricsLogStore log_store(&pref_service_, client_.GetStorageLimits(),
                             std::string());
   log_store.LoadPersistedUnsentLogs();
-  log_store.StoreLog("b", MetricsLog::INITIAL_STABILITY_LOG, absl::nullopt);
+  log_store.StoreLog("b", MetricsLog::INITIAL_STABILITY_LOG, LogMetadata());
   log_store.StageNextLog();
   log_store.TrimAndPersistUnsentLogs();
 
@@ -168,8 +168,8 @@
   log_store.LoadPersistedUnsentLogs();
 
   log_store.StoreLog("persisted", MetricsLog::INITIAL_STABILITY_LOG,
-                     absl::nullopt);
-  log_store.StoreLog("not_persisted", MetricsLog::ONGOING_LOG, absl::nullopt);
+                     LogMetadata());
+  log_store.StoreLog("not_persisted", MetricsLog::ONGOING_LOG, LogMetadata());
 
   // Only the stability log should be written out, due to the threshold.
   log_store.TrimAndPersistUnsentLogs();
@@ -184,10 +184,10 @@
                             std::string());
   log_store.LoadPersistedUnsentLogs();
 
-  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, absl::nullopt);
-  log_store.StoreLog("b", MetricsLog::ONGOING_LOG, absl::nullopt);
+  log_store.StoreLog("a", MetricsLog::ONGOING_LOG, LogMetadata());
+  log_store.StoreLog("b", MetricsLog::ONGOING_LOG, LogMetadata());
   log_store.StageNextLog();
-  log_store.StoreLog("c", MetricsLog::INITIAL_STABILITY_LOG, absl::nullopt);
+  log_store.StoreLog("c", MetricsLog::INITIAL_STABILITY_LOG, LogMetadata());
   EXPECT_EQ(2U, log_store.ongoing_log_count());
   EXPECT_EQ(1U, log_store.initial_log_count());
   // Should discard the ongoing log staged earlier.
diff --git a/components/metrics/metrics_log_unittest.cc b/components/metrics/metrics_log_unittest.cc
index 0393b81..5ade3a8d 100644
--- a/components/metrics/metrics_log_unittest.cc
+++ b/components/metrics/metrics_log_unittest.cc
@@ -292,6 +292,29 @@
   EXPECT_EQ(12, histogram_proto.bucket(4).max());
 }
 
+TEST_F(MetricsLogTest, HistogramSamplesCount) {
+  const std::string histogram_name = "test";
+  TestMetricsServiceClient client;
+  TestingPrefServiceSimple prefs;
+  TestMetricsLog log(kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client);
+
+  // Create buckets: 1-5.
+  base::BucketRanges ranges(2);
+  ranges.set_range(0, 1);
+  ranges.set_range(1, 5);
+
+  // Add two samples.
+  base::SampleVector samples(1, &ranges);
+  samples.Accumulate(3, 2);
+  log.RecordHistogramDelta(histogram_name, samples);
+
+  EXPECT_EQ(2, log.log_metadata().samples_count.value());
+
+  // Add two more samples.
+  log.RecordHistogramDelta(histogram_name, samples);
+  EXPECT_EQ(4, log.log_metadata().samples_count.value());
+}
+
 TEST_F(MetricsLogTest, RecordEnvironment) {
   TestMetricsServiceClient client;
   TestMetricsLog log(kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client);
diff --git a/components/metrics/metrics_service_client.cc b/components/metrics/metrics_service_client.cc
index 5054743..ba12046 100644
--- a/components/metrics/metrics_service_client.cc
+++ b/components/metrics/metrics_service_client.cc
@@ -64,8 +64,7 @@
   return nullptr;
 }
 
-bool MetricsServiceClient::ShouldUploadMetricsForUserId(
-    const uint64_t user_id) {
+bool MetricsServiceClient::ShouldUploadMetricsForUserId(uint64_t user_id) {
   return true;
 }
 
diff --git a/components/metrics/metrics_service_client.h b/components/metrics/metrics_service_client.h
index 277cbd69..5655fa2 100644
--- a/components/metrics/metrics_service_client.h
+++ b/components/metrics/metrics_service_client.h
@@ -49,7 +49,7 @@
 
   // Returns true if metrics should be uploaded for the given |user_id|, which
   // corresponds to the |user_id| field in ChromeUserMetricsExtension.
-  virtual bool ShouldUploadMetricsForUserId(const uint64_t user_id);
+  virtual bool ShouldUploadMetricsForUserId(uint64_t user_id);
 
   // Registers the client id with other services (e.g. crash reporting), called
   // when metrics recording gets enabled.
diff --git a/components/metrics/metrics_service_unittest.cc b/components/metrics/metrics_service_unittest.cc
index 5d84be0..a61a1fce 100644
--- a/components/metrics/metrics_service_unittest.cc
+++ b/components/metrics/metrics_service_unittest.cc
@@ -528,7 +528,7 @@
   // is never deserialized to proto, so we're just passing some dummy content.
   ASSERT_EQ(0u, test_log_store->initial_log_count());
   ASSERT_EQ(0u, test_log_store->ongoing_log_count());
-  test_log_store->StoreLog("blah_blah", MetricsLog::ONGOING_LOG, absl::nullopt);
+  test_log_store->StoreLog("blah_blah", MetricsLog::ONGOING_LOG, LogMetadata());
   // Note: |initial_log_count()| refers to initial stability logs, so the above
   // log is counted an ongoing log (per its type).
   ASSERT_EQ(0u, test_log_store->initial_log_count());
diff --git a/components/metrics/reporting_service.cc b/components/metrics/reporting_service.cc
index bfbdbe8..e46d164 100644
--- a/components/metrics/reporting_service.cc
+++ b/components/metrics/reporting_service.cc
@@ -6,6 +6,7 @@
 
 #include "components/metrics/reporting_service.h"
 
+#include <cstdio>
 #include <memory>
 
 #include "base/base64.h"
@@ -116,6 +117,25 @@
     log_store()->StageNextLog();
   }
 
+  // Check whether the log should be uploaded based on user id. If it should not
+  // be sent, then discard the log from the store and notify the scheduler.
+  auto staged_user_id = log_store()->staged_log_user_id();
+  if (staged_user_id.has_value() &&
+      !client_->ShouldUploadMetricsForUserId(staged_user_id.value())) {
+    // Remove the log and update list to disk.
+    log_store()->DiscardStagedLog();
+    log_store()->TrimAndPersistUnsentLogs();
+
+    // Notify the scheduler that the next log should be uploaded. If there are
+    // no more logs, then stop the scheduler.
+    if (!log_store()->has_unsent_logs()) {
+      DVLOG(1) << "Stopping upload_scheduler_.";
+      upload_scheduler_->Stop();
+    }
+    upload_scheduler_->UploadFinished(true);
+    return;
+  }
+
   // Proceed to stage the log for upload if log size satisfies cellular log
   // upload constrains.
   bool upload_canceled = false;
diff --git a/components/metrics/reporting_service_unittest.cc b/components/metrics/reporting_service_unittest.cc
index b48b7b9..47f2ec6 100644
--- a/components/metrics/reporting_service_unittest.cc
+++ b/components/metrics/reporting_service_unittest.cc
@@ -26,6 +26,18 @@
 
 namespace {
 
+// Represent a flushed log and its metadata to be used for testing.
+struct TestLog {
+  explicit TestLog(const std::string& log) : log(log), user_id(absl::nullopt) {}
+  TestLog(const std::string& log, uint64_t user_id)
+      : log(log), user_id(user_id) {}
+  TestLog(const TestLog& other) = default;
+  ~TestLog() = default;
+
+  const std::string log;
+  const absl::optional<uint64_t> user_id;
+};
+
 const char kTestUploadUrl[] = "test_url";
 const char kTestMimeType[] = "test_mime_type";
 
@@ -34,21 +46,25 @@
   TestLogStore() {}
   ~TestLogStore() {}
 
-  void AddLog(const std::string& log) { logs_.push_back(log); }
+  void AddLog(const TestLog& log) { logs_.push_back(log); }
 
   // LogStore:
   bool has_unsent_logs() const override { return !logs_.empty(); }
   bool has_staged_log() const override { return !staged_log_hash_.empty(); }
-  const std::string& staged_log() const override { return logs_.front(); }
+  const std::string& staged_log() const override { return logs_.front().log; }
   const std::string& staged_log_hash() const override {
     return staged_log_hash_;
   }
+  absl::optional<uint64_t> staged_log_user_id() const override {
+    return logs_.front().user_id;
+  }
   const std::string& staged_log_signature() const override {
     return base::EmptyString();
   }
   void StageNextLog() override {
-    if (has_unsent_logs())
-      staged_log_hash_ = base::SHA1HashString(logs_.front());
+    if (has_unsent_logs()) {
+      staged_log_hash_ = base::SHA1HashString(logs_.front().log);
+    }
   }
   void DiscardStagedLog() override {
     if (!has_staged_log())
@@ -62,7 +78,7 @@
 
  private:
   std::string staged_log_hash_;
-  std::deque<std::string> logs_;
+  std::deque<TestLog> logs_;
 };
 
 class TestReportingService : public ReportingService {
@@ -73,7 +89,7 @@
   }
   ~TestReportingService() override {}
 
-  void AddLog(const std::string& log) { log_store_.AddLog(log); }
+  void AddLog(const TestLog& log) { log_store_.AddLog(log); }
 
  private:
   // ReportingService:
@@ -117,8 +133,8 @@
 
 TEST_F(ReportingServiceTest, BasicTest) {
   TestReportingService service(&client_, GetLocalState());
-  service.AddLog("log1");
-  service.AddLog("log2");
+  service.AddLog(TestLog("log1"));
+  service.AddLog(TestLog("log2"));
 
   service.EnableReporting();
   task_runner_->RunPendingTasks();
@@ -144,4 +160,41 @@
   EXPECT_FALSE(client_.uploader()->is_uploading());
 }
 
+TEST_F(ReportingServiceTest, UserIdLogsUploadedIfUserConsented) {
+  uint64_t user_id = 12345;
+
+  TestReportingService service(&client_, GetLocalState());
+  service.AddLog(TestLog("log1", user_id));
+  service.AddLog(TestLog("log2", user_id));
+  service.EnableReporting();
+  client_.AllowMetricUploadForUserId(user_id);
+
+  task_runner_->RunPendingTasks();
+  EXPECT_TRUE(client_.uploader()->is_uploading());
+  EXPECT_EQ(1, client_.uploader()->reporting_info().attempt_count());
+  EXPECT_FALSE(client_.uploader()->reporting_info().has_last_response_code());
+  client_.uploader()->CompleteUpload(200);
+
+  // Upload 2nd log and last response code logged.
+  task_runner_->RunPendingTasks();
+  EXPECT_EQ(200, client_.uploader()->reporting_info().last_response_code());
+  EXPECT_TRUE(client_.uploader()->is_uploading());
+
+  client_.uploader()->CompleteUpload(200);
+  EXPECT_EQ(0U, task_runner_->NumPendingTasks());
+  EXPECT_FALSE(client_.uploader()->is_uploading());
+}
+
+TEST_F(ReportingServiceTest, UserIdLogsNotUploadedIfUserNotConsented) {
+  TestReportingService service(&client_, GetLocalState());
+  service.AddLog(TestLog("log1", 12345));
+  service.AddLog(TestLog("log2", 12345));
+  service.EnableReporting();
+
+  // Log with user id should never be in uploading state if user upload
+  // disabled. |client_.uploader()| should be nullptr since it is lazily
+  // created when a log is to be uploaded for the first time.
+  EXPECT_EQ(client_.uploader(), nullptr);
+}
+
 }  // namespace metrics
diff --git a/components/metrics/test/test_metrics_service_client.cc b/components/metrics/test/test_metrics_service_client.cc
index 2be627da..5e515255 100644
--- a/components/metrics/test/test_metrics_service_client.cc
+++ b/components/metrics/test/test_metrics_service_client.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/callback.h"
+#include "base/containers/contains.h"
 #include "components/metrics/metrics_log_uploader.h"
 #include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
 
@@ -33,6 +34,10 @@
   client_id_ = client_id;
 }
 
+bool TestMetricsServiceClient::ShouldUploadMetricsForUserId(uint64_t user_id) {
+  return base::Contains(allowed_user_ids_, user_id);
+}
+
 int32_t TestMetricsServiceClient::GetProduct() {
   return product_;
 }
@@ -99,4 +104,12 @@
   return storage_limits_;
 }
 
+void TestMetricsServiceClient::AllowMetricUploadForUserId(uint64_t user_id) {
+  allowed_user_ids_.insert(user_id);
+}
+
+void TestMetricsServiceClient::RemoveMetricUploadForUserId(uint64_t user_id) {
+  allowed_user_ids_.erase(user_id);
+}
+
 }  // namespace metrics
diff --git a/components/metrics/test/test_metrics_service_client.h b/components/metrics/test/test_metrics_service_client.h
index 8baaaca..569fc86 100644
--- a/components/metrics/test/test_metrics_service_client.h
+++ b/components/metrics/test/test_metrics_service_client.h
@@ -29,6 +29,7 @@
   // MetricsServiceClient:
   metrics::MetricsService* GetMetricsService() override;
   void SetMetricsClientId(const std::string& client_id) override;
+  bool ShouldUploadMetricsForUserId(uint64_t user_id) override;
   int32_t GetProduct() override;
   std::string GetApplicationLocale() override;
   bool GetBrand(std::string* brand_code) override;
@@ -49,6 +50,11 @@
   bool ShouldResetClientIdsOnClonedInstall() override;
   MetricsLogStore::StorageLimits GetStorageLimits() const override;
 
+  // Adds/removes |user_id| from the set of user ids that have metrics consent
+  // as true.
+  void AllowMetricUploadForUserId(uint64_t user_id);
+  void RemoveMetricUploadForUserId(uint64_t user_id);
+
   const std::string& get_client_id() const { return client_id_; }
   // Returns a weak ref to the last created uploader.
   TestMetricsLogUploader* uploader() { return uploader_; }
@@ -75,9 +81,10 @@
   EnableMetricsDefault enable_default_;
   bool should_reset_client_ids_on_cloned_install_ = false;
   MetricsLogStore::StorageLimits storage_limits_;
+  std::set<uint64_t> allowed_user_ids_;
 
   // A weak ref to the last created TestMetricsLogUploader.
-  TestMetricsLogUploader* uploader_;
+  TestMetricsLogUploader* uploader_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(TestMetricsServiceClient);
 };
diff --git a/components/metrics/unsent_log_store.cc b/components/metrics/unsent_log_store.cc
index e1515c8..92e493e 100644
--- a/components/metrics/unsent_log_store.cc
+++ b/components/metrics/unsent_log_store.cc
@@ -32,6 +32,7 @@
 const char kLogUnsentCountKey[] = "unsent_samples_count";
 const char kLogSentCountKey[] = "sent_samples_count";
 const char kLogPersistedSizeInKbKey[] = "unsent_persisted_size_in_kb";
+const char kLogUserIdKey[] = "user_id";
 
 std::string EncodeToBase64(const std::string& to_convert) {
   DCHECK(to_convert.data());
@@ -53,12 +54,11 @@
     default;
 UnsentLogStore::LogInfo::~LogInfo() = default;
 
-void UnsentLogStore::LogInfo::Init(
-    UnsentLogStoreMetrics* metrics,
-    const std::string& log_data,
-    const std::string& log_timestamp,
-    const std::string& signing_key,
-    absl::optional<base::HistogramBase::Count> samples_count) {
+void UnsentLogStore::LogInfo::Init(UnsentLogStoreMetrics* metrics,
+                                   const std::string& log_data,
+                                   const std::string& log_timestamp,
+                                   const std::string& signing_key,
+                                   const LogMetadata& optional_log_metadata) {
   DCHECK(!log_data.empty());
 
   if (!compression::GzipCompress(log_data, &compressed_log_data)) {
@@ -75,7 +75,7 @@
   }
 
   timestamp = log_timestamp;
-  this->samples_count = samples_count;
+  this->log_metadata = optional_log_metadata;
 }
 
 UnsentLogStore::UnsentLogStore(std::unique_ptr<UnsentLogStoreMetrics> metrics,
@@ -135,6 +135,12 @@
   return list_[staged_log_index_]->timestamp;
 }
 
+// Returns the user id of the current staged log.
+absl::optional<uint64_t> UnsentLogStore::staged_log_user_id() const {
+  DCHECK(has_staged_log());
+  return list_[staged_log_index_]->log_metadata.user_id;
+}
+
 // static
 bool UnsentLogStore::ComputeHMACForLog(const std::string& log_data,
                                        const std::string& signing_key,
@@ -166,8 +172,9 @@
 void UnsentLogStore::MarkStagedLogAsSent() {
   DCHECK(has_staged_log());
   DCHECK_LT(static_cast<size_t>(staged_log_index_), list_.size());
-  if (list_[staged_log_index_]->samples_count.has_value())
-    total_samples_sent_ += list_[staged_log_index_]->samples_count.value();
+  auto samples_count = list_[staged_log_index_]->log_metadata.samples_count;
+  if (samples_count.has_value())
+    total_samples_sent_ += samples_count.value();
 }
 
 void UnsentLogStore::TrimAndPersistUnsentLogs() {
@@ -178,16 +185,15 @@
 
 void UnsentLogStore::LoadPersistedUnsentLogs() {
   ReadLogsFromPrefList(*local_state_->GetList(log_data_pref_name_));
-  RecordMetaDataMertics();
+  RecordMetaDataMetrics();
 }
 
-void UnsentLogStore::StoreLog(
-    const std::string& log_data,
-    absl::optional<base::HistogramBase::Count> samples_count) {
+void UnsentLogStore::StoreLog(const std::string& log_data,
+                              const LogMetadata& log_metadata) {
   LogInfo info;
   info.Init(metrics_.get(), log_data,
             base::NumberToString(base::Time::Now().ToTimeT()), signing_key_,
-            samples_count);
+            log_metadata);
   list_.emplace_back(std::make_unique<LogInfo>(info));
 }
 
@@ -197,10 +203,9 @@
   return list_[index]->compressed_log_data;
 }
 
-std::string UnsentLogStore::ReplaceLogAtIndex(
-    size_t index,
-    const std::string& new_log_data,
-    absl::optional<base::HistogramBase::Count> samples_count) {
+std::string UnsentLogStore::ReplaceLogAtIndex(size_t index,
+                                              const std::string& new_log_data,
+                                              const LogMetadata& log_metadata) {
   DCHECK_GE(index, 0U);
   DCHECK_LT(index, list_.size());
 
@@ -214,7 +219,7 @@
   // just return a pointer to the logInfo so we could combine the next 3 lines.
   LogInfo info;
   info.Init(metrics_.get(), new_log_data, old_timestamp, signing_key_,
-            samples_count);
+            log_metadata);
 
   list_[index] = std::make_unique<LogInfo>(info);
   return old_log_data;
@@ -263,6 +268,16 @@
     info.signature = DecodeFromBase64(info.signature);
     // timestamp doesn't need to be decoded.
 
+    // Extract user id of the log if it exists.
+    std::string user_id_str;
+    if (dict->GetString(kLogUserIdKey, &user_id_str)) {
+      uint64_t user_id;
+
+      // Only initialize the metadata if conversion was successful.
+      if (base::StringToUint64(DecodeFromBase64(user_id_str), &user_id))
+        info.log_metadata.user_id = user_id;
+    }
+
     list_[i] = std::make_unique<LogInfo>(info);
   }
 
@@ -332,10 +347,17 @@
     dict_value->SetString(kLogDataKey,
                           EncodeToBase64(log->compressed_log_data));
     dict_value->SetString(kLogTimestampKey, log->timestamp);
+
+    auto user_id = log->log_metadata.user_id;
+    if (user_id.has_value()) {
+      dict_value->SetString(
+          kLogUserIdKey, EncodeToBase64(base::NumberToString(user_id.value())));
+    }
     list_value->Append(std::move(dict_value));
 
-    if (log->samples_count.has_value()) {
-      unsent_samples_count += log->samples_count.value();
+    auto samples_count = log->log_metadata.samples_count;
+    if (samples_count.has_value()) {
+      unsent_samples_count += samples_count.value();
     }
     unsent_persisted_size += log->compressed_log_data.length();
   }
@@ -360,7 +382,7 @@
       base::Value(static_cast<int>(std::ceil(unsent_persisted_size / 1024.0))));
 }
 
-void UnsentLogStore::RecordMetaDataMertics() {
+void UnsentLogStore::RecordMetaDataMetrics() {
   if (metadata_pref_name_ == nullptr)
     return;
 
diff --git a/components/metrics/unsent_log_store.h b/components/metrics/unsent_log_store.h
index af786182..3107650 100644
--- a/components/metrics/unsent_log_store.h
+++ b/components/metrics/unsent_log_store.h
@@ -17,6 +17,7 @@
 #include "base/metrics/histogram_base.h"
 #include "base/values.h"
 #include "components/metrics/log_store.h"
+#include "components/metrics/metrics_log.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 class PrefService;
@@ -62,26 +63,25 @@
   const std::string& staged_log() const override;
   const std::string& staged_log_hash() const override;
   const std::string& staged_log_signature() const override;
+  absl::optional<uint64_t> staged_log_user_id() const override;
   void StageNextLog() override;
   void DiscardStagedLog() override;
   void MarkStagedLogAsSent() override;
   void TrimAndPersistUnsentLogs() override;
   void LoadPersistedUnsentLogs() override;
 
-  // Adds a UMA log to the list, |samples_count| is the total number of samples
-  // in the log (if available).
-  void StoreLog(const std::string& log_data,
-                absl::optional<base::HistogramBase::Count> samples_count);
+  // Adds a UMA log to the list. |log_metadata| refers to metadata associated
+  // with the log.
+  void StoreLog(const std::string& log_data, const LogMetadata& log_metadata);
 
   // Gets log data at the given index in the list.
   const std::string& GetLogAtIndex(size_t index);
 
-  // Replaces the compressed log at |index| in the store with given log data
-  // reusing the same timestamp from the original log, and returns old log data.
-  std::string ReplaceLogAtIndex(
-      size_t index,
-      const std::string& new_log_data,
-      absl::optional<base::HistogramBase::Count> samples_count);
+  // Replaces the compressed log at |index| in the store with given log data and
+  // |log_metadata| reusing the same timestamp.
+  std::string ReplaceLogAtIndex(size_t index,
+                                const std::string& new_log_data,
+                                const LogMetadata& log_metadata);
 
   // Deletes all logs, in memory and on disk.
   void Purge();
@@ -119,7 +119,7 @@
                           size_t persisted_size) const;
 
   // Records the info in |metadata_pref_name_| as UMA metrics.
-  void RecordMetaDataMertics();
+  void RecordMetaDataMetrics();
 
   // An object for recording UMA metrics.
   std::unique_ptr<UnsentLogStoreMetrics> metrics_;
@@ -159,13 +159,14 @@
     // serialized log protobuf. A hash and a signature are computed from
     // |log_data|. The signature is produced using |signing_key|. |log_data|
     // will be compressed and stored in |compressed_log_data|. |log_timestamp|
-    // is stored as is.
+    // is stored as is. |log_metadata| is any optional metadata that will be
+    // attached to the log.
     // |metrics| is the parent's metrics_ object, and should not be held.
     void Init(UnsentLogStoreMetrics* metrics,
               const std::string& log_data,
               const std::string& log_timestamp,
               const std::string& signing_key,
-              absl::optional<base::HistogramBase::Count> samples_count);
+              const LogMetadata& log_metadata);
 
     // Compressed log data - a serialized protobuf that's been gzipped.
     std::string compressed_log_data;
@@ -182,8 +183,8 @@
     // The timestamp of when the log was created as a time_t value.
     std::string timestamp;
 
-    // The total number of samples in this log if applicable.
-    absl::optional<base::HistogramBase::Count> samples_count;
+    // Properties of the log.
+    LogMetadata log_metadata;
   };
   // A list of all of the stored logs, stored with SHA1 hashes to check for
   // corruption while they are stored in memory.
diff --git a/components/metrics/unsent_log_store_unittest.cc b/components/metrics/unsent_log_store_unittest.cc
index c769fc32..30e80ad6 100644
--- a/components/metrics/unsent_log_store_unittest.cc
+++ b/components/metrics/unsent_log_store_unittest.cc
@@ -5,6 +5,7 @@
 #include "components/metrics/unsent_log_store.h"
 
 #include <stddef.h>
+#include <limits>
 
 #include "base/base64.h"
 #include "base/hash/sha1.h"
@@ -45,8 +46,8 @@
     rand_bytes.append(base::RandBytesAsString(min_compressed_size));
   std::string base64_data_for_logging;
   base::Base64Encode(rand_bytes, &base64_data_for_logging);
-  SCOPED_TRACE(testing::Message() << "Using random data "
-                                  << base64_data_for_logging);
+  SCOPED_TRACE(testing::Message()
+               << "Using random data " << base64_data_for_logging);
   return rand_bytes;
 }
 
@@ -150,7 +151,8 @@
 TEST_F(UnsentLogStoreTest, SingleElementLogList) {
   TestUnsentLogStore unsent_log_store(&prefs_, kLogByteLimit);
 
-  unsent_log_store.StoreLog("Hello world!", absl::nullopt);
+  LogMetadata log_metadata;
+  unsent_log_store.StoreLog("Hello world!", log_metadata);
   unsent_log_store.TrimAndPersistUnsentLogs();
 
   TestUnsentLogStore result_unsent_log_store(&prefs_, kLogByteLimit);
@@ -174,10 +176,11 @@
 // bytes. This should leave the logs unchanged.
 TEST_F(UnsentLogStoreTest, LongButTinyLogList) {
   TestUnsentLogStore unsent_log_store(&prefs_, kLogByteLimit);
+  LogMetadata log_metadata;
 
   size_t log_count = kLogCountLimit * 5;
   for (size_t i = 0; i < log_count; ++i)
-    unsent_log_store.StoreLog("x", absl::nullopt);
+    unsent_log_store.StoreLog("x", log_metadata);
 
   EXPECT_EQ(log_count, unsent_log_store.size());
   unsent_log_store.TrimAndPersistUnsentLogs();
@@ -195,6 +198,7 @@
 TEST_F(UnsentLogStoreTest, LongButSmallLogList) {
   size_t log_count = kLogCountLimit * 5;
   size_t log_size = 50;
+  LogMetadata log_metadata;
 
   std::string first_kept = "First to keep";
   first_kept.resize(log_size, ' ');
@@ -205,18 +209,18 @@
   last_kept.resize(log_size, ' ');
 
   // Set the byte limit enough to keep everything but the first two logs.
-  const size_t min_log_bytes =
-      Compress(first_kept).length() + Compress(last_kept).length() +
-      (log_count - 4) * Compress(blank_log).length();
+  const size_t min_log_bytes = Compress(first_kept).length() +
+                               Compress(last_kept).length() +
+                               (log_count - 4) * Compress(blank_log).length();
   TestUnsentLogStore unsent_log_store(&prefs_, min_log_bytes);
 
-  unsent_log_store.StoreLog("one", absl::nullopt);
-  unsent_log_store.StoreLog("two", absl::nullopt);
-  unsent_log_store.StoreLog(first_kept, absl::nullopt);
+  unsent_log_store.StoreLog("one", log_metadata);
+  unsent_log_store.StoreLog("two", log_metadata);
+  unsent_log_store.StoreLog(first_kept, log_metadata);
   for (size_t i = unsent_log_store.size(); i < log_count - 1; ++i) {
-    unsent_log_store.StoreLog(blank_log, absl::nullopt);
+    unsent_log_store.StoreLog(blank_log, log_metadata);
   }
-  unsent_log_store.StoreLog(last_kept, absl::nullopt);
+  unsent_log_store.StoreLog(last_kept, log_metadata);
 
   size_t original_size = unsent_log_store.size();
   unsent_log_store.TrimAndPersistUnsentLogs();
@@ -244,8 +248,9 @@
   std::string log_data = GenerateLogWithMinCompressedSize(log_size);
 
   TestUnsentLogStore unsent_log_store(&prefs_, kLogByteLimit);
+  LogMetadata log_metadata;
   for (size_t i = 0; i < log_count; ++i) {
-    unsent_log_store.StoreLog(log_data, absl::nullopt);
+    unsent_log_store.StoreLog(log_data, log_metadata);
   }
   unsent_log_store.TrimAndPersistUnsentLogs();
 
@@ -270,11 +275,12 @@
   target_log += GenerateLogWithMinCompressedSize(log_size);
 
   std::string log_data = GenerateLogWithMinCompressedSize(log_size);
+  LogMetadata log_metadata;
   for (size_t i = 0; i < log_count; ++i) {
     if (i == log_count - kLogCountLimit)
-      unsent_log_store.StoreLog(target_log, absl::nullopt);
+      unsent_log_store.StoreLog(target_log, log_metadata);
     else
-      unsent_log_store.StoreLog(log_data, absl::nullopt);
+      unsent_log_store.StoreLog(log_data, log_metadata);
   }
 
   unsent_log_store.TrimAndPersistUnsentLogs();
@@ -294,16 +300,17 @@
 // Check that the store/stage/discard functions work as expected.
 TEST_F(UnsentLogStoreTest, Staging) {
   TestUnsentLogStore unsent_log_store(&prefs_, kLogByteLimit);
+  LogMetadata log_metadata;
   std::string tmp;
 
   EXPECT_FALSE(unsent_log_store.has_staged_log());
-  unsent_log_store.StoreLog("one", absl::nullopt);
+  unsent_log_store.StoreLog("one", log_metadata);
   EXPECT_FALSE(unsent_log_store.has_staged_log());
-  unsent_log_store.StoreLog("two", absl::nullopt);
+  unsent_log_store.StoreLog("two", log_metadata);
   unsent_log_store.StageNextLog();
   EXPECT_TRUE(unsent_log_store.has_staged_log());
   EXPECT_EQ(unsent_log_store.staged_log(), Compress("two"));
-  unsent_log_store.StoreLog("three", absl::nullopt);
+  unsent_log_store.StoreLog("three", log_metadata);
   EXPECT_EQ(unsent_log_store.staged_log(), Compress("two"));
   EXPECT_EQ(unsent_log_store.size(), 3U);
   unsent_log_store.DiscardStagedLog();
@@ -323,10 +330,11 @@
   // Ensure that the correct log is discarded if new logs are pushed while
   // a log is staged.
   TestUnsentLogStore unsent_log_store(&prefs_, kLogByteLimit);
+  LogMetadata log_metadata;
 
-  unsent_log_store.StoreLog("one", absl::nullopt);
+  unsent_log_store.StoreLog("one", log_metadata);
   unsent_log_store.StageNextLog();
-  unsent_log_store.StoreLog("two", absl::nullopt);
+  unsent_log_store.StoreLog("two", log_metadata);
   unsent_log_store.DiscardStagedLog();
   unsent_log_store.TrimAndPersistUnsentLogs();
 
@@ -336,13 +344,13 @@
   result_unsent_log_store.ExpectNextLog("two");
 }
 
-
 TEST_F(UnsentLogStoreTest, Hashes) {
   const char kFooText[] = "foo";
   const std::string foo_hash = base::SHA1HashString(kFooText);
+  LogMetadata log_metadata;
 
   TestUnsentLogStore unsent_log_store(&prefs_, kLogByteLimit);
-  unsent_log_store.StoreLog(kFooText, absl::nullopt);
+  unsent_log_store.StoreLog(kFooText, log_metadata);
   unsent_log_store.StageNextLog();
 
   EXPECT_EQ(Compress(kFooText), unsent_log_store.staged_log());
@@ -351,9 +359,10 @@
 
 TEST_F(UnsentLogStoreTest, Signatures) {
   const char kFooText[] = "foo";
+  LogMetadata log_metadata;
 
   TestUnsentLogStore unsent_log_store(&prefs_, kLogByteLimit);
-  unsent_log_store.StoreLog(kFooText, absl::nullopt);
+  unsent_log_store.StoreLog(kFooText, log_metadata);
   unsent_log_store.StageNextLog();
 
   EXPECT_EQ(Compress(kFooText), unsent_log_store.staged_log());
@@ -377,9 +386,9 @@
   // Test a different key results in a different signature.
   std::string key = "secret key, don't tell anyone";
   TestUnsentLogStore unsent_log_store_different_key(&prefs_, kLogByteLimit,
-    key);
+                                                    key);
 
-  unsent_log_store_different_key.StoreLog(kFooText, absl::nullopt);
+  unsent_log_store_different_key.StoreLog(kFooText, log_metadata);
   unsent_log_store_different_key.StageNextLog();
 
   EXPECT_EQ(Compress(kFooText), unsent_log_store_different_key.staged_log());
@@ -394,6 +403,54 @@
   EXPECT_EQ(expected_signature_base64, actual_signature_base64);
 }
 
+TEST_F(UnsentLogStoreTest, StoreLogWithUserId) {
+  const char foo_text[] = "foo";
+  const uint64_t user_id = 12345L;
+
+  TestUnsentLogStore unsent_log_store(&prefs_, kLogByteLimit);
+  LogMetadata log_metadata(absl::nullopt, user_id);
+  unsent_log_store.StoreLog(foo_text, log_metadata);
+  unsent_log_store.StageNextLog();
+
+  EXPECT_EQ(Compress(foo_text), unsent_log_store.staged_log());
+  EXPECT_EQ(unsent_log_store.staged_log_user_id().value(), user_id);
+
+  unsent_log_store.TrimAndPersistUnsentLogs();
+
+  // Reads persisted logs from new log store.
+  TestUnsentLogStore read_unsent_log_store(&prefs_, kLogByteLimit);
+  read_unsent_log_store.LoadPersistedUnsentLogs();
+  EXPECT_EQ(1U, read_unsent_log_store.size());
+
+  // Ensure that the user_id was parsed correctly.
+  read_unsent_log_store.StageNextLog();
+  EXPECT_EQ(user_id, read_unsent_log_store.staged_log_user_id().value());
+}
+
+TEST_F(UnsentLogStoreTest, StoreLogWithLargeUserId) {
+  const char foo_text[] = "foo";
+  const uint64_t large_user_id = std::numeric_limits<uint64_t>::max();
+
+  TestUnsentLogStore unsent_log_store(&prefs_, kLogByteLimit);
+  LogMetadata log_metadata(absl::nullopt, large_user_id);
+  unsent_log_store.StoreLog(foo_text, log_metadata);
+  unsent_log_store.StageNextLog();
+
+  EXPECT_EQ(Compress(foo_text), unsent_log_store.staged_log());
+  EXPECT_EQ(unsent_log_store.staged_log_user_id().value(), large_user_id);
+
+  unsent_log_store.TrimAndPersistUnsentLogs();
+
+  // Reads persisted logs from new log store.
+  TestUnsentLogStore read_unsent_log_store(&prefs_, kLogByteLimit);
+  read_unsent_log_store.LoadPersistedUnsentLogs();
+  EXPECT_EQ(1U, read_unsent_log_store.size());
+
+  // Ensure that the user_id was parsed correctly.
+  read_unsent_log_store.StageNextLog();
+  EXPECT_EQ(large_user_id, read_unsent_log_store.staged_log_user_id().value());
+}
+
 TEST_F(UnsentLogStoreTest, UnsentLogMetadataMetrics) {
   std::unique_ptr<TestUnsentLogStoreMetrics> metrics =
       std::make_unique<TestUnsentLogStoreMetrics>();
@@ -418,20 +475,23 @@
   // The log without the SampleCount will not be counted to metrics.
   const char kNoSampleLog[] = "no sample log";
 
-  unsent_log_store.StoreLog(
-      oversize_log,
-      absl::make_optional<base::HistogramBase::Count>(kOversizeLogSampleCount));
-  unsent_log_store.StoreLog(kNoSampleLog, absl::nullopt);
-  unsent_log_store.StoreLog(
-      kFooText, absl::optional<base::HistogramBase::Count>(kFooSampleCount));
+  LogMetadata log_metadata_with_oversize_sample(kOversizeLogSampleCount,
+                                                absl::nullopt);
+  unsent_log_store.StoreLog(oversize_log, log_metadata_with_oversize_sample);
+
+  LogMetadata log_metadata_with_no_sample;
+  unsent_log_store.StoreLog(kNoSampleLog, log_metadata_with_no_sample);
+
+  LogMetadata log_metadata_foo_sample(kFooSampleCount, absl::nullopt);
+  unsent_log_store.StoreLog(kFooText, log_metadata_foo_sample);
+
   // The foobar_log will be staged first.
-  unsent_log_store.StoreLog(
-      foobar_log,
-      absl::optional<base::HistogramBase::Count>(kFooBarSampleCount));
+  LogMetadata log_metadata_foo_bar_sample(kFooBarSampleCount, absl::nullopt);
+  unsent_log_store.StoreLog(foobar_log, log_metadata_foo_bar_sample);
 
   unsent_log_store.TrimAndPersistUnsentLogs();
 
-  unsent_log_store.RecordMetaDataMertics();
+  unsent_log_store.RecordMetaDataMetrics();
   // The |oversize_log| was ignored, the kNoSampleLog won't be counted to
   // metrics,
   EXPECT_EQ(kFooSampleCount + kFooBarSampleCount, m->unsent_samples_count());
@@ -443,7 +503,7 @@
   unsent_log_store.MarkStagedLogAsSent();
   unsent_log_store.DiscardStagedLog();
   unsent_log_store.TrimAndPersistUnsentLogs();
-  unsent_log_store.RecordMetaDataMertics();
+  unsent_log_store.RecordMetaDataMetrics();
 
   // The |foobar_log| shall be sent.
   EXPECT_EQ(kFooSampleCount, m->unsent_samples_count());
@@ -454,7 +514,7 @@
   unsent_log_store.StageNextLog();
   unsent_log_store.DiscardStagedLog();
   unsent_log_store.TrimAndPersistUnsentLogs();
-  unsent_log_store.RecordMetaDataMertics();
+  unsent_log_store.RecordMetaDataMetrics();
 
   // Verify the failed upload wasn't added to the sent samples count.
   EXPECT_EQ(0, m->unsent_samples_count());
diff --git a/components/nacl/loader/BUILD.gn b/components/nacl/loader/BUILD.gn
index 34b43f9..77559ac 100644
--- a/components/nacl/loader/BUILD.gn
+++ b/components/nacl/loader/BUILD.gn
@@ -140,7 +140,7 @@
       "//url/ipc:url_ipc",
     ]
 
-    if (is_chromeos_ash) {
+    if (is_chromeos) {
       # NaCl is not working with compiler-rt in ChromeOS.
       # Force libgcc as a workaround. See https://crbug.com/761103
       ldflags = [
diff --git a/components/services/storage/public/cpp/BUILD.gn b/components/services/storage/public/cpp/BUILD.gn
index e735ecb..fca4518 100644
--- a/components/services/storage/public/cpp/BUILD.gn
+++ b/components/services/storage/public/cpp/BUILD.gn
@@ -7,6 +7,7 @@
 
   public = [
     "constants.h",
+    "origin_quota_client.h",
     "quota_client_callback_wrapper.h",
     "quota_error_or.h",
   ]
diff --git a/components/services/storage/public/cpp/origin_quota_client.h b/components/services/storage/public/cpp/origin_quota_client.h
new file mode 100644
index 0000000..4d59a73
--- /dev/null
+++ b/components/services/storage/public/cpp/origin_quota_client.h
@@ -0,0 +1,29 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SERVICES_STORAGE_PUBLIC_CPP_ORIGIN_QUOTA_CLIENT_H_
+#define COMPONENTS_SERVICES_STORAGE_PUBLIC_CPP_ORIGIN_QUOTA_CLIENT_H_
+
+#include "components/services/storage/public/mojom/quota_client.mojom.h"
+
+namespace storage {
+
+// Interface for origin based QuotaClient to be inherited by quota managed
+// storage APIs that have not implemented Storage Buckets support yet. As
+// Storage APIs migrate their implementation to support Storage Buckets, they
+// should use the bucket base QuotaClient to be added as part of
+// crbug.com/1199417.
+//
+// TODO(crbug.com/1214066): Once all storage API's have migrated off origin
+// based QuotaClient. This class should be removed and all API's should inherit
+// from bucket based QuotaClient.
+class COMPONENT_EXPORT(STORAGE_SERVICE_PUBLIC) OriginQuotaClient
+    : public mojom::QuotaClient {
+ protected:
+  ~OriginQuotaClient() override = default;
+};
+
+}  // namespace storage
+
+#endif  // COMPONENTS_SERVICES_STORAGE_PUBLIC_CPP_ORIGIN_QUOTA_CLIENT_H_
diff --git a/components/services/storage/public/cpp/quota_error_or.h b/components/services/storage/public/cpp/quota_error_or.h
index ceb35f0c..98ec31e 100644
--- a/components/services/storage/public/cpp/quota_error_or.h
+++ b/components/services/storage/public/cpp/quota_error_or.h
@@ -13,6 +13,7 @@
   kNone = 0,
   kUnknownError,
   kDatabaseError,
+  kDatabaseNotFound,
   kEntryExistsError,
 };
 
diff --git a/components/signin/public/identity_manager/access_token_constants.cc b/components/signin/public/identity_manager/access_token_constants.cc
index 381209fc..cfbe377 100644
--- a/components/signin/public/identity_manager/access_token_constants.cc
+++ b/components/signin/public/identity_manager/access_token_constants.cc
@@ -59,9 +59,6 @@
       // Required by Permission Request Creator.
       GaiaConstants::kClassifyUrlKidPermissionOAuth2Scope,
 
-      // Required by Enterprise policy extensions.
-      GaiaConstants::kChromeWebstoreOAuth2Scope,
-
       // Required by ChromeOS only.
 #if BUILDFLAG(IS_CHROMEOS_ASH)
       GaiaConstants::kAccountsReauthOAuth2Scope,
diff --git a/components/tab_groups/BUILD.gn b/components/tab_groups/BUILD.gn
index 316f14f..a11a4d5 100644
--- a/components/tab_groups/BUILD.gn
+++ b/components/tab_groups/BUILD.gn
@@ -17,6 +17,7 @@
   deps = [
     "//base",
     "//components/strings",
+    "//components/tab_groups/public/mojom:mojo_bindings_webui_js",
     "//skia",
     "//ui/base",
     "//ui/gfx",
diff --git a/components/tab_groups/public/mojom/BUILD.gn b/components/tab_groups/public/mojom/BUILD.gn
new file mode 100644
index 0000000..bd5a15a
--- /dev/null
+++ b/components/tab_groups/public/mojom/BUILD.gn
@@ -0,0 +1,27 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("mojo_bindings") {
+  sources = [ "tab_group_types.mojom" ]
+  public_deps = [
+    "//mojo/public/mojom/base",
+    "//skia/public/mojom",
+  ]
+
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "tab_groups.mojom.Color"
+          cpp = "::tab_groups::TabGroupColorId"
+        },
+      ]
+      traits_headers = [ "tab_groups_mojom_traits.h" ]
+    },
+  ]
+
+  webui_module_path = ""
+}
diff --git a/components/tab_groups/public/mojom/OWNERS b/components/tab_groups/public/mojom/OWNERS
new file mode 100644
index 0000000..82b3215
--- /dev/null
+++ b/components/tab_groups/public/mojom/OWNERS
@@ -0,0 +1,6 @@
+file://chrome/browser/ui/tabs/OWNERS
+
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+per-file *_mojom_traits*.*=set noparent
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/components/tab_groups/public/mojom/tab_group_types.mojom b/components/tab_groups/public/mojom/tab_group_types.mojom
new file mode 100644
index 0000000..56b8922
--- /dev/null
+++ b/components/tab_groups/public/mojom/tab_group_types.mojom
@@ -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.
+
+module tab_groups.mojom;
+
+// The color Id associated with a given Tab Group. Maps to
+// tab_groups::TabGroupColorId in components/tab_groups/tab_group_color.h.
+enum Color {
+  kGrey,
+  kBlue,
+  kRed,
+  kYellow,
+  kGreen,
+  kPink,
+  kPurple,
+  kCyan,
+};
diff --git a/components/tab_groups/public/mojom/tab_groups_mojom_traits.h b/components/tab_groups/public/mojom/tab_groups_mojom_traits.h
new file mode 100644
index 0000000..4a6c411
--- /dev/null
+++ b/components/tab_groups/public/mojom/tab_groups_mojom_traits.h
@@ -0,0 +1,75 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_TAB_GROUPS_PUBLIC_MOJOM_TAB_GROUPS_MOJOM_TRAITS_H_
+#define COMPONENTS_TAB_GROUPS_PUBLIC_MOJOM_TAB_GROUPS_MOJOM_TRAITS_H_
+
+#include "components/tab_groups/public/mojom/tab_group_types.mojom.h"
+#include "components/tab_groups/tab_group_color.h"
+
+namespace mojo {
+
+template <>
+struct EnumTraits<tab_groups::mojom::Color, tab_groups::TabGroupColorId> {
+  using TabGroupColorId = tab_groups::TabGroupColorId;
+  using MojoTabGroupColorId = tab_groups::mojom::Color;
+
+  static MojoTabGroupColorId ToMojom(TabGroupColorId input) {
+    switch (input) {
+      case TabGroupColorId::kGrey:
+        return MojoTabGroupColorId::kGrey;
+      case TabGroupColorId::kBlue:
+        return MojoTabGroupColorId::kBlue;
+      case TabGroupColorId::kRed:
+        return MojoTabGroupColorId::kRed;
+      case TabGroupColorId::kYellow:
+        return MojoTabGroupColorId::kYellow;
+      case TabGroupColorId::kGreen:
+        return MojoTabGroupColorId::kGreen;
+      case TabGroupColorId::kPink:
+        return MojoTabGroupColorId::kPink;
+      case TabGroupColorId::kPurple:
+        return MojoTabGroupColorId::kPurple;
+      case TabGroupColorId::kCyan:
+        return MojoTabGroupColorId::kCyan;
+    }
+    NOTREACHED();
+    return MojoTabGroupColorId::kGrey;
+  }
+
+  static bool FromMojom(MojoTabGroupColorId input, TabGroupColorId* out) {
+    switch (input) {
+      case MojoTabGroupColorId::kGrey:
+        *out = TabGroupColorId::kGrey;
+        return true;
+      case MojoTabGroupColorId::kBlue:
+        *out = TabGroupColorId::kBlue;
+        return true;
+      case MojoTabGroupColorId::kRed:
+        *out = TabGroupColorId::kRed;
+        return true;
+      case MojoTabGroupColorId::kYellow:
+        *out = TabGroupColorId::kYellow;
+        return true;
+      case MojoTabGroupColorId::kGreen:
+        *out = TabGroupColorId::kGreen;
+        return true;
+      case MojoTabGroupColorId::kPink:
+        *out = TabGroupColorId::kPink;
+        return true;
+      case MojoTabGroupColorId::kPurple:
+        *out = TabGroupColorId::kPurple;
+        return true;
+      case MojoTabGroupColorId::kCyan:
+        *out = TabGroupColorId::kCyan;
+        return true;
+    }
+    NOTREACHED();
+    return false;
+  }
+};
+
+}  // namespace mojo
+
+#endif  // COMPONENTS_TAB_GROUPS_PUBLIC_MOJOM_TAB_GROUPS_MOJOM_TRAITS_H_
diff --git a/components/translate/ios/browser/ios_translate_driver.mm b/components/translate/ios/browser/ios_translate_driver.mm
index 2b97c6d3..602574a 100644
--- a/components/translate/ios/browser/ios_translate_driver.mm
+++ b/components/translate/ios/browser/ios_translate_driver.mm
@@ -87,7 +87,8 @@
   translate_manager_->GetLanguageState()->LanguageDetermined(
       details.adopted_language, true);
 
-  if (web_state_)
+  // Don't offer translation on pages with notranslate meta tag.
+  if (web_state_ && !details.has_notranslate)
     translate_manager_->InitiateTranslation(details.adopted_language);
 
   for (auto& observer : language_detection_observers())
diff --git a/components/translate/ios/browser/language_detection_controller.h b/components/translate/ios/browser/language_detection_controller.h
index bf2eca6..f8633b8 100644
--- a/components/translate/ios/browser/language_detection_controller.h
+++ b/components/translate/ios/browser/language_detection_controller.h
@@ -50,7 +50,8 @@
                       web::WebFrame* sender_frame);
 
   // Completion handler used to retrieve the buffered text.
-  void OnTextRetrieved(const std::string& http_content_language,
+  void OnTextRetrieved(const bool has_notranslate,
+                       const std::string& http_content_language,
                        const std::string& html_lang,
                        const GURL& url,
                        const base::Value* text_content);
diff --git a/components/translate/ios/browser/language_detection_controller.mm b/components/translate/ios/browser/language_detection_controller.mm
index e68966c..d69cd76c 100644
--- a/components/translate/ios/browser/language_detection_controller.mm
+++ b/components/translate/ios/browser/language_detection_controller.mm
@@ -94,18 +94,14 @@
       *text_captured_command != "languageDetection.textCaptured") {
     return;
   }
-  absl::optional<bool> translation_allowed =
-      command.FindBoolKey("translationAllowed");
-  if (!translation_allowed.value_or(false)) {
-    // Translation not allowed by the page. Done processing.
-    return;
-  }
+  absl::optional<bool> has_notranslate = command.FindBoolKey("hasNoTranslate");
   absl::optional<double> capture_text_time =
       command.FindDoubleKey("captureTextTime");
   const std::string* html_lang = command.FindStringKey("htmlLang");
   const std::string* http_content_language =
       command.FindStringKey("httpContentLanguage");
-  if (!capture_text_time.has_value() || !html_lang || !http_content_language) {
+  if (!has_notranslate.has_value() || !capture_text_time.has_value() ||
+      !html_lang || !http_content_language) {
     return;
   }
 
@@ -119,13 +115,14 @@
   sender_frame->CallJavaScriptFunction(
       "languageDetection.retrieveBufferedTextContent", {},
       base::BindRepeating(&LanguageDetectionController::OnTextRetrieved,
-                          weak_method_factory_.GetWeakPtr(),
+                          weak_method_factory_.GetWeakPtr(), *has_notranslate,
                           *http_content_language, *html_lang, url),
       base::TimeDelta::FromMilliseconds(
           web::kJavaScriptFunctionCallDefaultTimeout));
 }
 
 void LanguageDetectionController::OnTextRetrieved(
+    const bool has_notranslate,
     const std::string& http_content_language,
     const std::string& html_lang,
     const GURL& url,
@@ -149,6 +146,7 @@
   LanguageDetectionDetails details;
   details.time = base::Time::Now();
   details.url = url;
+  details.has_notranslate = has_notranslate;
   details.content_language = http_content_language;
   details.model_detected_language = model_detected_language;
   details.is_model_reliable = is_model_reliable;
diff --git a/components/translate/ios/browser/resources/language_detection.js b/components/translate/ios/browser/resources/language_detection.js
index fc8ed1d..f5b774d 100644
--- a/components/translate/ios/browser/resources/language_detection.js
+++ b/components/translate/ios/browser/resources/language_detection.js
@@ -34,21 +34,20 @@
 __gCrWeb.languageDetection.activeRequests = 0;
 
 /**
- * Returns true if translation of the page is allowed.
- * Translation is not allowed when a "notranslate" meta tag is defined.
- * @return {boolean} true if translation of the page is allowed.
+ * Searches page elements for "notranslate" meta tag.
+ * @return {boolean} true if "notranslate" meta tag is defined.
  */
-__gCrWeb.languageDetection['translationAllowed'] = function() {
+__gCrWeb.languageDetection['hasNoTranslate'] = function() {
   const metaTags = document.getElementsByTagName('meta');
   for (let i = 0; i < metaTags.length; ++i) {
     if (metaTags[i].name === 'google') {
       if (metaTags[i].content === 'notranslate' ||
           metaTags[i].getAttribute('value') === 'notranslate') {
-        return false;
+        return true;
       }
     }
   }
-  return true;
+  return false;
 };
 
 /**
@@ -127,43 +126,40 @@
 /**
  * Detects if a page has content that needs translation and informs the native
  * side. The text content of a page is cached in
- * |__gCrWeb.languageDetection.bufferedTextContent| and retrived at a later time
- * retrived at a later time directly from the Obj-C side. This is to avoid
- * using |invokeOnHost|.
+ * |__gCrWeb.languageDetection.bufferedTextContent| and retrieved at a later
+ * time directly from the Obj-C side. This is to avoid using |invokeOnHost|.
  */
 __gCrWeb.languageDetection['detectLanguage'] = function() {
-  if (!__gCrWeb.languageDetection.translationAllowed()) {
-    __gCrWeb.message.invokeOnHost({
-        'command': 'languageDetection.textCaptured',
-        'translationAllowed': false});
-  } else {
-    // Constant for the maximum length of the extracted text returned by
-    // |-detectLanguage| to the native side.
-    // Matches desktop implementation.
-    // Note: This should stay in sync with the constant in
-    // language_detection_controller.mm .
-    const kMaxIndexChars = 65535;
-    const captureBeginTime = new Date();
-    __gCrWeb.languageDetection.activeRequests += 1;
-    __gCrWeb.languageDetection.bufferedTextContent =
-        __gCrWeb.languageDetection.getTextContent(document.body,
-            kMaxIndexChars);
-    const captureTextTime =
-        (new Date()).getMilliseconds() - captureBeginTime.getMilliseconds();
-    const httpContentLanguage =
-        __gCrWeb.languageDetection.getMetaContentByHttpEquiv(
-            'content-language');
-    __gCrWeb.message.invokeOnHost({
-        'command': 'languageDetection.textCaptured',
-        'translationAllowed': true,
-        'captureTextTime': captureTextTime,
-        'htmlLang': document.documentElement.lang,
-        'httpContentLanguage': httpContentLanguage});
+  // Constant for the maximum length of the extracted text returned by
+  // |-detectLanguage| to the native side.
+  // Matches desktop implementation.
+  // Note: This should stay in sync with the constant in
+  // language_detection_controller.mm .
+  const kMaxIndexChars = 65535;
+  const captureBeginTime = new Date();
+  __gCrWeb.languageDetection.activeRequests += 1;
+  __gCrWeb.languageDetection.bufferedTextContent =
+      __gCrWeb.languageDetection.getTextContent(document.body, kMaxIndexChars);
+  const captureTextTime =
+      (new Date()).getMilliseconds() - captureBeginTime.getMilliseconds();
+  const httpContentLanguage =
+      __gCrWeb.languageDetection.getMetaContentByHttpEquiv('content-language');
+  const textCapturedCommand = {
+    'command': 'languageDetection.textCaptured',
+    'hasNoTranslate': false,
+    'captureTextTime': captureTextTime,
+    'htmlLang': document.documentElement.lang,
+    'httpContentLanguage': httpContentLanguage
+  };
+
+  if (__gCrWeb.languageDetection.hasNoTranslate()) {
+    textCapturedCommand['hasNoTranslate'] = true;
   }
+  __gCrWeb.message.invokeOnHost(textCapturedCommand);
 };
 
 /**
- * Retrives the cached text content of a page. Returns it and then purges the
+ * Retrieves the cached text content of a page. Returns it and then purges the
  * cache.
  */
 __gCrWeb.languageDetection['retrieveBufferedTextContent'] = function() {
diff --git a/components/ukm/ukm_service.cc b/components/ukm/ukm_service.cc
index 515d0db..53fee732 100644
--- a/components/ukm/ukm_service.cc
+++ b/components/ukm/ukm_service.cc
@@ -160,7 +160,7 @@
     // Replace the compressed log in the store by its filtered version.
     const std::string old_compressed_log_data =
         ukm_log_store->ReplaceLogAtIndex(index, reserialized_log_data,
-                                         absl::nullopt);
+                                         metrics::LogMetadata());
 
     // Reached here only if extensions were found in the log, so data should now
     // be different after filtering.
@@ -430,7 +430,8 @@
 
   std::string serialized_log =
       UkmService::SerializeReportProtoToString(&report);
-  reporting_service_.ukm_log_store()->StoreLog(serialized_log, absl::nullopt);
+  metrics::LogMetadata log_metadata;
+  reporting_service_.ukm_log_store()->StoreLog(serialized_log, log_metadata);
 }
 
 bool UkmService::ShouldRestrictToWhitelistedEntries() const {
diff --git a/components/ukm/ukm_service_unittest.cc b/components/ukm/ukm_service_unittest.cc
index d60e2bc7..9b26dd6 100644
--- a/components/ukm/ukm_service_unittest.cc
+++ b/components/ukm/ukm_service_unittest.cc
@@ -306,7 +306,8 @@
   report.SerializeToString(&serialized_log);
   // Makes sure that the serialized ukm report can be parsed.
   ASSERT_TRUE(UkmService::LogCanBeParsed(serialized_log));
-  unsent_log_store->StoreLog(serialized_log, absl::nullopt);
+  metrics::LogMetadata log_metadata;
+  unsent_log_store->StoreLog(serialized_log, log_metadata);
 
   // Do extension purging.
   service.PurgeExtensions();
@@ -464,8 +465,7 @@
         ++number_of_invocations;
       });
 
-  UkmService service(&prefs_, &client_,
-                     std::move(provider));
+  UkmService service(&prefs_, &client_, std::move(provider));
   TestRecordingHelper recorder(&service);
 
   service.Initialize();
@@ -505,8 +505,7 @@
       .Times(2)
       .WillRepeatedly([](Report* report) {});
 
-  UkmService service(&prefs_, &client_,
-                     std::move(provider));
+  UkmService service(&prefs_, &client_, std::move(provider));
   TestRecordingHelper recorder(&service);
   service.Initialize();
 
@@ -538,8 +537,7 @@
               ProvideSyncedUserNoisedBirthYearAndGenderToReport(testing::_))
       .Times(0);
 
-  UkmService service(&prefs_, &client_,
-                     std::move(provider));
+  UkmService service(&prefs_, &client_, std::move(provider));
   TestRecordingHelper recorder(&service);
 
   service.Initialize();
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc
index 725c325..f2395ae 100644
--- a/content/browser/accessibility/browser_accessibility_android.cc
+++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -1051,9 +1051,6 @@
     case ax::mojom::Role::kAlert:
       message_id = IDS_AX_ROLE_ALERT;
       break;
-    case ax::mojom::Role::kAnchor:
-      // No role description.
-      break;
     case ax::mojom::Role::kApplication:
       message_id = IDS_AX_ROLE_APPLICATION;
       break;
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index 53477cb0..7029f05 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -623,6 +623,11 @@
   RunHtmlTest(FILE_PATH_LITERAL("continuations.html"));
 }
 
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+                       AccessibilityContinuationsParserSplitsMarkup) {
+  RunHtmlTest(FILE_PATH_LITERAL("continuations-parser-splits-markup.html"));
+}
+
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityAriaControls) {
   RunAriaTest(FILE_PATH_LITERAL("aria-controls.html"));
 }
diff --git a/content/browser/appcache/appcache_quota_client.h b/content/browser/appcache/appcache_quota_client.h
index 899a26b..7ec673a 100644
--- a/content/browser/appcache/appcache_quota_client.h
+++ b/content/browser/appcache/appcache_quota_client.h
@@ -13,7 +13,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/sequence_checker.h"
-#include "components/services/storage/public/mojom/quota_client.mojom.h"
+#include "components/services/storage/public/cpp/origin_quota_client.h"
 #include "content/browser/appcache/appcache_storage.h"
 #include "content/common/content_export.h"
 #include "net/base/completion_repeating_callback.h"
@@ -27,12 +27,12 @@
 class AppCacheServiceImpl;
 class AppCacheStorageImpl;
 
-// A QuotaClient implementation to integrate the appcache service
-// with the quota management system. The QuotaClient interface is
+// An OriginQuotaClient implementation to integrate the appcache service
+// with the quota management system. The OriginQuotaClient interface is
 // used on the IO thread by the quota manager. This class deletes
 // itself when both the quota manager and the appcache service have
 // been destroyed.
-class AppCacheQuotaClient : public storage::mojom::QuotaClient {
+class AppCacheQuotaClient : public storage::OriginQuotaClient {
  public:
   using RequestQueue = base::circular_deque<base::OnceClosure>;
 
@@ -41,7 +41,7 @@
 
   ~AppCacheQuotaClient() override;
 
-  // mojom::QuotaClient method overrides
+  // storage::OriginQuotaClient method overrides.
   void GetOriginUsage(const url::Origin& origin,
                       blink::mojom::StorageType type,
                       GetOriginUsageCallback callback) override;
diff --git a/content/browser/background_sync/background_sync_manager.cc b/content/browser/background_sync/background_sync_manager.cc
index 668c0c6..0636f49 100644
--- a/content/browser/background_sync/background_sync_manager.cc
+++ b/content/browser/background_sync/background_sync_manager.cc
@@ -1362,7 +1362,7 @@
   if (devtools_context_->IsRecording(
           DevToolsBackgroundService::kBackgroundSync)) {
     devtools_context_->LogBackgroundServiceEventOnCoreThread(
-        active_version->registration_id(), active_version->origin(),
+        active_version->registration_id(), active_version->key().origin(),
         DevToolsBackgroundService::kBackgroundSync,
         /* event_name= */ "Dispatched sync event",
         /* instance_id= */ tag,
@@ -1404,7 +1404,7 @@
   if (devtools_context_->IsRecording(
           DevToolsBackgroundService::kPeriodicBackgroundSync)) {
     devtools_context_->LogBackgroundServiceEventOnCoreThread(
-        active_version->registration_id(), active_version->origin(),
+        active_version->registration_id(), active_version->key().origin(),
         DevToolsBackgroundService::kPeriodicBackgroundSync,
         /* event_name= */ "Dispatched periodicsync event",
         /* instance_id= */ tag,
diff --git a/content/browser/cache_storage/cache_storage_quota_client.h b/content/browser/cache_storage/cache_storage_quota_client.h
index 223f1c7..9d71c65 100644
--- a/content/browser/cache_storage/cache_storage_quota_client.h
+++ b/content/browser/cache_storage/cache_storage_quota_client.h
@@ -8,8 +8,8 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
+#include "components/services/storage/public/cpp/origin_quota_client.h"
 #include "components/services/storage/public/mojom/cache_storage_control.mojom.h"
-#include "components/services/storage/public/mojom/quota_client.mojom.h"
 #include "content/common/content_export.h"
 #include "storage/browser/quota/quota_client_type.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
@@ -25,13 +25,13 @@
 // CacheStorageOwner tuple.  Created and accessed on the cache storage task
 // runner.
 class CONTENT_EXPORT CacheStorageQuotaClient
-    : public storage::mojom::QuotaClient {
+    : public storage::OriginQuotaClient {
  public:
   CacheStorageQuotaClient(scoped_refptr<CacheStorageManager> cache_manager,
                           storage::mojom::CacheStorageOwner owner);
   ~CacheStorageQuotaClient() override;
 
-  // QuotaClient.
+  // storage::OriginQuotaClient method overrides.
   void GetOriginUsage(const url::Origin& origin,
                       blink::mojom::StorageType type,
                       GetOriginUsageCallback callback) override;
diff --git a/content/browser/content_index/content_index_database.cc b/content/browser/content_index/content_index_database.cc
index 30427583..32bcdbc 100644
--- a/content/browser/content_index/content_index_database.cc
+++ b/content/browser/content_index/content_index_database.cc
@@ -698,13 +698,13 @@
 
   // Don't allow DB operations while the `contentdelete` event is firing.
   // This is to prevent re-registering the deleted content within the event.
-  BlockOrigin(service_worker->origin());
+  BlockOrigin(service_worker->key().origin());
 
   int request_id = service_worker->StartRequest(
       ServiceWorkerMetrics::EventType::CONTENT_DELETE,
       base::BindOnce(&ContentIndexDatabase::DidDispatchEvent,
                      weak_ptr_factory_core_.GetWeakPtr(),
-                     service_worker->origin()));
+                     service_worker->key().origin()));
 
   service_worker->endpoint()->DispatchContentDeleteEvent(
       description_id, service_worker->CreateSimpleEventCallback(request_id));
diff --git a/content/browser/conversions/conversion_storage_sql.cc b/content/browser/conversions/conversion_storage_sql.cc
index 8f0d24a..e7efc7f 100644
--- a/content/browser/conversions/conversion_storage_sql.cc
+++ b/content/browser/conversions/conversion_storage_sql.cc
@@ -61,12 +61,16 @@
 //
 // Version 6 - 2021/05/06 - https://crrev.com/c/2878235
 //
-// Version 6 adds the impression.priority column.
-const int kCurrentVersionNumber = 6;
+// Version 6 adds the impressions.priority column.
+//
+// Version 7 - 2021/06/03 - https://crrev.com/c/2904386
+//
+// Version 7 adds the impressions.impression_site column.
+const int kCurrentVersionNumber = 7;
 
 // Earliest version which can use a |kCurrentVersionNumber| database
 // without failing.
-const int kCompatibleVersionNumber = 6;
+const int kCompatibleVersionNumber = 7;
 
 // Latest version of the database that cannot be upgraded to
 // |kCurrentVersionNumber| without razing the database. No versions are
@@ -168,8 +172,8 @@
       "(impression_data, impression_origin, conversion_origin, "
       "conversion_destination, "
       "reporting_origin, impression_time, expiry_time, source_type, "
-      "attributed_truthfully, priority) "
-      "VALUES (?,?,?,?,?,?,?,?,?,?)";
+      "attributed_truthfully, priority, impression_site) "
+      "VALUES (?,?,?,?,?,?,?,?,?,?,?)";
   sql::Statement statement(
       db_->GetCachedStatement(SQL_FROM_HERE, kInsertImpressionSql));
   statement.BindString(
@@ -184,6 +188,7 @@
   statement.BindInt(
       8, static_cast<int>(delegate_->SelectAttributionLogic(impression)));
   statement.BindInt64(9, impression.priority());
+  statement.BindString(10, impression.ImpressionSite().Serialize());
   statement.Run();
 
   transaction.Commit();
@@ -961,6 +966,8 @@
   // |kNavigation|.
   // |attributed_truthfully| corresponds to the
   // |StorableImpression::AttributionLogic| enum.
+  // |impression_site| is used to optimize the lookup of impressions;
+  // |StorableImpression::ImpressionSite| is always derived from the origin.
   const char kImpressionTableSql[] =
       "CREATE TABLE IF NOT EXISTS impressions"
       "(impression_id INTEGER PRIMARY KEY,"
@@ -975,7 +982,8 @@
       "conversion_destination TEXT NOT NULL,"
       "source_type INTEGER NOT NULL,"
       "attributed_truthfully INTEGER NOT NULL,"
-      "priority INTEGER NOT NULL)";
+      "priority INTEGER NOT NULL,"
+      "impression_site TEXT NOT NULL)";
   if (!db_->Execute(kImpressionTableSql))
     return false;
 
@@ -1007,6 +1015,13 @@
   if (!db_->Execute(kImpressionOriginIndexSql))
     return false;
 
+  // Optimizes `EnsureCapacityForPendingDestinationLimit()`.
+  const char kImpressionSiteIndexSql[] =
+      "CREATE INDEX IF NOT EXISTS impression_site_idx "
+      "ON impressions(active, impression_site, source_type)";
+  if (!db_->Execute(kImpressionSiteIndexSql))
+    return false;
+
   // All columns in this table are const. |impression_id| is the primary key of
   // a row in the [impressions] table, [impressions.impression_id].
   // |conversion_time| is the time at which the conversion was registered, and
diff --git a/content/browser/conversions/conversion_storage_sql_migrations.cc b/content/browser/conversions/conversion_storage_sql_migrations.cc
index dd6043e..2c35e373 100644
--- a/content/browser/conversions/conversion_storage_sql_migrations.cc
+++ b/content/browser/conversions/conversion_storage_sql_migrations.cc
@@ -57,6 +57,41 @@
   return impressions;
 }
 
+struct ImpressionIdAndImpressionOrigin {
+  int64_t impression_id;
+  url::Origin impression_origin;
+};
+
+std::vector<ImpressionIdAndImpressionOrigin>
+GetImpressionIdAndImpressionOrigins(sql::Database* db,
+                                    int64_t start_impression_id,
+                                    int num_impressions) {
+  DCHECK_GE(num_impressions, 0);
+  const char kGetImpressionsSql[] =
+      "SELECT impression_id, impression_origin "
+      "FROM impressions "
+      "WHERE impression_id >= ? "
+      "ORDER BY impression_id "
+      "LIMIT ?";
+
+  sql::Statement statement(
+      db->GetCachedStatement(SQL_FROM_HERE, kGetImpressionsSql));
+  statement.BindInt64(0, start_impression_id);
+  statement.BindInt(1, num_impressions);
+
+  std::vector<ImpressionIdAndImpressionOrigin> impressions;
+  while (statement.Step()) {
+    int64_t impression_id = statement.ColumnInt64(0);
+    url::Origin impression_origin =
+        DeserializeOrigin(statement.ColumnString(1));
+
+    impressions.push_back({impression_id, std::move(impression_origin)});
+  }
+  if (!statement.Succeeded())
+    return {};
+  return impressions;
+}
+
 }  // namespace
 
 bool ConversionStorageSqlMigrations::UpgradeSchema(
@@ -85,6 +120,10 @@
     if (!MigrateToVersion6(conversion_storage, db, meta_table))
       return false;
   }
+  if (meta_table->GetVersionNumber() == 6) {
+    if (!MigrateToVersion7(conversion_storage, db, meta_table))
+      return false;
+  }
   // Add similar if () blocks for new versions here.
 
   base::UmaHistogramMediumTimes("Conversions.Storage.MigrationTime",
@@ -417,4 +456,130 @@
   return transaction.Commit();
 }
 
+bool ConversionStorageSqlMigrations::MigrateToVersion7(
+    ConversionStorageSql* conversion_storage,
+    sql::Database* db,
+    sql::MetaTable* meta_table) {
+  // Wrap each migration in its own transaction. See comment in
+  // |MigrateToVersion2|.
+  sql::Transaction transaction(db);
+  if (!transaction.Begin())
+    return false;
+
+  // Add new impression_site column to the impressions table. This follows the
+  // steps documented at https://sqlite.org/lang_altertable.html#otheralter.
+  // Other approaches, like using "ALTER ... ADD COLUMN" require setting a
+  // DEFAULT value for the column which is undesirable.
+  const char kNewImpressionTableSql[] =
+      "CREATE TABLE IF NOT EXISTS new_impressions"
+      "(impression_id INTEGER PRIMARY KEY,"
+      "impression_data TEXT NOT NULL,"
+      "impression_origin TEXT NOT NULL,"
+      "conversion_origin TEXT NOT NULL,"
+      "reporting_origin TEXT NOT NULL,"
+      "impression_time INTEGER NOT NULL,"
+      "expiry_time INTEGER NOT NULL,"
+      "num_conversions INTEGER DEFAULT 0,"
+      "active INTEGER DEFAULT 1,"
+      "conversion_destination TEXT NOT NULL,"
+      "source_type INTEGER NOT NULL,"
+      "attributed_truthfully INTEGER NOT NULL,"
+      "priority INTEGER NOT NULL,"
+      "impression_site TEXT NOT NULL)";
+  if (!db->Execute(kNewImpressionTableSql))
+    return false;
+
+  // Transfer the existing rows to the new table, inserting placeholder values
+  // for the impression_site column.
+  const char kPopulateNewImpressionTableSql[] =
+      "INSERT INTO new_impressions SELECT "
+      "impression_id,impression_data,impression_origin,"
+      "conversion_origin,reporting_origin,impression_time,"
+      "expiry_time,num_conversions,active,conversion_destination,source_type,"
+      "attributed_truthfully,priority,'' "
+      "FROM impressions";
+  sql::Statement populate_statement(
+      db->GetCachedStatement(SQL_FROM_HERE, kPopulateNewImpressionTableSql));
+  if (!populate_statement.Run())
+    return false;
+
+  const char kDropOldImpressionTableSql[] = "DROP TABLE impressions";
+  if (!db->Execute(kDropOldImpressionTableSql))
+    return false;
+
+  const char kRenameImpressionTableSql[] =
+      "ALTER TABLE new_impressions RENAME TO impressions";
+  if (!db->Execute(kRenameImpressionTableSql))
+    return false;
+
+  // Update each of the impression rows to have the correct associated
+  // impression_site.
+  //
+  // We update `kNumImpressionsPerUpdate` rows at a time, to avoid pulling the
+  // entire impressions table into memory.
+  int64_t start_impression_id = 0;
+  const size_t kNumImpressionsPerUpdate = 100u;
+  std::vector<ImpressionIdAndImpressionOrigin> impressions =
+      GetImpressionIdAndImpressionOrigins(db, start_impression_id,
+                                          kNumImpressionsPerUpdate);
+
+  const char kUpdateImpressionSiteSql[] =
+      "UPDATE impressions SET impression_site = ? WHERE impression_id = ?";
+  sql::Statement update_impression_site_statement(
+      db->GetCachedStatement(SQL_FROM_HERE, kUpdateImpressionSiteSql));
+
+  while (!impressions.empty()) {
+    // Perform the column updates for each row we pulled into memory.
+    for (const auto& impression : impressions) {
+      update_impression_site_statement.Reset(/*clear_bound_vars=*/true);
+
+      // The impression site is derived from the impression origin dynamically.
+      update_impression_site_statement.BindString(
+          0, net::SchemefulSite(impression.impression_origin).Serialize());
+      update_impression_site_statement.BindInt64(1, impression.impression_id);
+      if (!update_impression_site_statement.Run())
+        return false;
+
+      // Track the largest row id. This is more efficient than sorting all the
+      // rows.
+      if (impression.impression_id > start_impression_id)
+        start_impression_id = impression.impression_id;
+    }
+
+    // Fetch the next batch of rows from the database.
+    start_impression_id += 1;
+    impressions = GetImpressionIdAndImpressionOrigins(db, start_impression_id,
+                                                      kNumImpressionsPerUpdate);
+  }
+
+  // Create the pre-existing impression table indices on the new table.
+  const char kImpressionExpiryIndexSql[] =
+      "CREATE INDEX IF NOT EXISTS impression_expiry_idx "
+      "ON impressions(expiry_time)";
+  if (!db->Execute(kImpressionExpiryIndexSql))
+    return false;
+
+  const char kImpressionOriginIndexSql[] =
+      "CREATE INDEX IF NOT EXISTS impression_origin_idx "
+      "ON impressions(impression_origin)";
+  if (!db->Execute(kImpressionOriginIndexSql))
+    return false;
+
+  const char kConversionDestinationIndexSql[] =
+      "CREATE INDEX IF NOT EXISTS conversion_destination_idx "
+      "ON impressions(active, conversion_destination, reporting_origin)";
+  if (!db->Execute(kConversionDestinationIndexSql))
+    return false;
+
+  // Create the new impression table index.
+  const char kImpressionSiteIndexSql[] =
+      "CREATE INDEX IF NOT EXISTS impression_site_idx "
+      "ON impressions(active, impression_site, source_type)";
+  if (!db->Execute(kImpressionSiteIndexSql))
+    return false;
+
+  meta_table->SetVersionNumber(7);
+  return transaction.Commit();
+}
+
 }  // namespace content
diff --git a/content/browser/conversions/conversion_storage_sql_migrations.h b/content/browser/conversions/conversion_storage_sql_migrations.h
index 6492786..6dc8429 100644
--- a/content/browser/conversions/conversion_storage_sql_migrations.h
+++ b/content/browser/conversions/conversion_storage_sql_migrations.h
@@ -70,6 +70,9 @@
   static bool MigrateToVersion6(ConversionStorageSql* conversion_storage,
                                 sql::Database* db,
                                 sql::MetaTable* meta_table);
+  static bool MigrateToVersion7(ConversionStorageSql* conversion_storage,
+                                sql::Database* db,
+                                sql::MetaTable* meta_table);
 };
 
 }  // namespace content
diff --git a/content/browser/conversions/conversion_storage_sql_migrations_unittest.cc b/content/browser/conversions/conversion_storage_sql_migrations_unittest.cc
index a7f2fa8..40f7d62e 100644
--- a/content/browser/conversions/conversion_storage_sql_migrations_unittest.cc
+++ b/content/browser/conversions/conversion_storage_sql_migrations_unittest.cc
@@ -29,7 +29,7 @@
   return output;
 }
 
-const int kCurrentVersionNumber = 6;
+const int kCurrentVersionNumber = 7;
 
 }  // namespace
 
@@ -57,7 +57,7 @@
   std::string GetCurrentSchema() {
     base::FilePath current_version_path = temp_directory_.GetPath().Append(
         FILE_PATH_LITERAL("TestCurrentVersion.db"));
-    LoadDatabase(FILE_PATH_LITERAL("version_6.sql"), current_version_path);
+    LoadDatabase(FILE_PATH_LITERAL("version_7.sql"), current_version_path);
     sql::Database db;
     EXPECT_TRUE(db.Open(current_version_path));
     return db.GetSchema();
@@ -402,4 +402,55 @@
   histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
 }
 
+TEST_F(ConversionStorageSqlMigrationsTest, MigrateVersion6ToCurrent) {
+  base::HistogramTester histograms;
+  LoadDatabase(FILE_PATH_LITERAL("version_6.sql"), DbPath());
+
+  // Verify pre-conditions.
+  {
+    sql::Database db;
+    ASSERT_TRUE(db.Open(DbPath()));
+
+    ASSERT_FALSE(db.DoesColumnExist("impressions", "impression_site"));
+  }
+
+  MigrateDatabase();
+
+  // Verify schema is current.
+  {
+    sql::Database db;
+    ASSERT_TRUE(db.Open(DbPath()));
+
+    // Check version.
+    EXPECT_EQ(kCurrentVersionNumber, VersionFromDatabase(&db));
+
+    // Compare without quotes as sometimes migrations cause table names to be
+    // string literals.
+    EXPECT_EQ(RemoveQuotes(GetCurrentSchema()), RemoveQuotes(db.GetSchema()));
+
+    // Check that the relevant schema changes are made.
+    EXPECT_TRUE(db.DoesColumnExist("impressions", "impression_site"));
+
+    // Verify that data is preserved across the migration.
+    size_t rows = 0;
+    sql::test::CountTableRows(&db, "impressions", &rows);
+    EXPECT_EQ(2u, rows);
+
+    sql::Statement s(db.GetUniqueStatement(
+        "SELECT impression_origin, impression_site FROM impressions"));
+
+    ASSERT_TRUE(s.Step());
+    ASSERT_EQ("https://a.impression.test", s.ColumnString(0));
+    ASSERT_EQ("https://impression.test", s.ColumnString(1));
+    ASSERT_TRUE(s.Step());
+    ASSERT_EQ("https://b.impression.test", s.ColumnString(0));
+    ASSERT_EQ("https://impression.test", s.ColumnString(1));
+    ASSERT_FALSE(s.Step());
+  }
+
+  // DB migration histograms should be recorded.
+  histograms.ExpectTotalCount("Conversions.Storage.CreationTime", 0);
+  histograms.ExpectTotalCount("Conversions.Storage.MigrationTime", 1);
+}
+
 }  // namespace content
diff --git a/content/browser/conversions/conversion_storage_sql_unittest.cc b/content/browser/conversions/conversion_storage_sql_unittest.cc
index b2d0dea..12435d4 100644
--- a/content/browser/conversions/conversion_storage_sql_unittest.cc
+++ b/content/browser/conversions/conversion_storage_sql_unittest.cc
@@ -104,11 +104,11 @@
     EXPECT_EQ(4u, sql::test::CountSQLTables(&raw_db));
 
     // [conversion_domain_idx], [impression_expiry_idx],
-    // [impression_origin_idx], [conversion_report_time_idx],
-    // [conversion_impression_id_idx], [rate_limit_origin_type_idx],
-    // [rate_limit_conversion_time_idx], [rate_limit_impression_id_idx] and the
-    // meta table index.
-    EXPECT_EQ(9u, sql::test::CountSQLIndices(&raw_db));
+    // [impression_origin_idx], [impression_site_idx],
+    // [conversion_report_time_idx], [conversion_impression_id_idx],
+    // [rate_limit_origin_type_idx], [rate_limit_conversion_time_idx],
+    // [rate_limit_impression_id_idx] and the meta table index.
+    EXPECT_EQ(10u, sql::test::CountSQLIndices(&raw_db));
   }
 }
 
diff --git a/content/browser/conversions/conversion_storage_unittest.cc b/content/browser/conversions/conversion_storage_unittest.cc
index 0655f3a..40ac328 100644
--- a/content/browser/conversions/conversion_storage_unittest.cc
+++ b/content/browser/conversions/conversion_storage_unittest.cc
@@ -775,6 +775,44 @@
   EXPECT_TRUE(ReportsEqual({expected_report, expected_report}, actual_reports));
 }
 
+TEST_F(ConversionStorageTest,
+       MaxAttributionReportsBetweenSites_RespectsSourceType) {
+  delegate()->set_rate_limits({
+      .time_window = base::TimeDelta::Max(),
+      .max_attributions_per_window = 1,
+  });
+
+  storage()->StoreImpression(
+      ImpressionBuilder(clock()->Now())
+          .SetSourceType(StorableImpression::SourceType::kNavigation)
+          .Build());
+  EXPECT_TRUE(
+      storage()->MaybeCreateAndStoreConversionReport(DefaultConversion()));
+
+  storage()->StoreImpression(
+      ImpressionBuilder(clock()->Now())
+          .SetSourceType(StorableImpression::SourceType::kEvent)
+          .Build());
+  // This would fail if the source types had a combined limit or the incorrect
+  // source type were stored.
+  EXPECT_TRUE(
+      storage()->MaybeCreateAndStoreConversionReport(DefaultConversion()));
+
+  storage()->StoreImpression(
+      ImpressionBuilder(clock()->Now())
+          .SetSourceType(StorableImpression::SourceType::kEvent)
+          .Build());
+  EXPECT_FALSE(
+      storage()->MaybeCreateAndStoreConversionReport(DefaultConversion()));
+
+  storage()->StoreImpression(
+      ImpressionBuilder(clock()->Now())
+          .SetSourceType(StorableImpression::SourceType::kNavigation)
+          .Build());
+  EXPECT_FALSE(
+      storage()->MaybeCreateAndStoreConversionReport(DefaultConversion()));
+}
+
 TEST_F(ConversionStorageTest, NeverAttributeImpression_ReportNotStored) {
   delegate()->set_attribution_logic(
       StorableImpression::AttributionLogic::kNever);
diff --git a/content/browser/conversions/rate_limit_table.cc b/content/browser/conversions/rate_limit_table.cc
index ce96b32..299f0f91 100644
--- a/content/browser/conversions/rate_limit_table.cc
+++ b/content/browser/conversions/rate_limit_table.cc
@@ -29,7 +29,8 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   // All columns in this table are const.
-  // |attribution_type| is the type of the report, currently always kNavigation.
+  // |attribution_type| corresponds to the `StorableImpression::SourceType`
+  // of the impression.
   // |impression_id| is the primary key of a row in the |impressions| table,
   // though the row may not exist.
   // |impression_site| is the eTLD+1 of the impression.
@@ -87,7 +88,7 @@
       "VALUES(?,?,?,?,?,?,?)";
   sql::Statement statement(
       db->GetCachedStatement(SQL_FROM_HERE, kStoreRateLimitSql));
-  statement.BindInt(0, static_cast<int>(AttributionType::kNavigation));
+  statement.BindInt(0, static_cast<int>(report.impression.source_type()));
   statement.BindInt64(1, *report.impression.impression_id());
   statement.BindString(
       2, net::SchemefulSite(report.impression.impression_origin()).Serialize());
@@ -116,7 +117,7 @@
       "AND conversion_time > ?";
   sql::Statement statement(
       db->GetCachedStatement(SQL_FROM_HERE, kAttributionAllowedSql));
-  statement.BindInt(0, static_cast<int>(AttributionType::kNavigation));
+  statement.BindInt(0, static_cast<int>(report.impression.source_type()));
   statement.BindString(
       1, net::SchemefulSite(report.impression.impression_origin()).Serialize());
   statement.BindString(2,
diff --git a/content/browser/conversions/rate_limit_table.h b/content/browser/conversions/rate_limit_table.h
index 1ec8356..6308196d 100644
--- a/content/browser/conversions/rate_limit_table.h
+++ b/content/browser/conversions/rate_limit_table.h
@@ -29,11 +29,6 @@
 // destroyed on the same sequence. The sequence must outlive |this|.
 class CONTENT_EXPORT RateLimitTable {
  public:
-  enum class AttributionType {
-    kNavigation = 0,
-    kMaxValue = kNavigation,
-  };
-
   RateLimitTable(const ConversionStorage::Delegate* delegate,
                  const base::Clock* clock);
   RateLimitTable(const RateLimitTable& other) = delete;
diff --git a/content/browser/conversions/rate_limit_table_unittest.cc b/content/browser/conversions/rate_limit_table_unittest.cc
index b5bf9c2..7c6d9d9 100644
--- a/content/browser/conversions/rate_limit_table_unittest.cc
+++ b/content/browser/conversions/rate_limit_table_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/time/time.h"
 #include "content/browser/conversions/conversion_report.h"
 #include "content/browser/conversions/conversion_test_utils.h"
+#include "content/browser/conversions/storable_impression.h"
 #include "sql/database.h"
 #include "sql/test/test_helpers.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -30,13 +31,17 @@
     table_ = std::make_unique<RateLimitTable>(delegate_.get(), &clock_);
   }
 
-  ConversionReport NewConversionReport(const url::Origin& impression_origin,
-                                       const url::Origin& conversion_origin,
-                                       int64_t impression_id = 0) {
+  ConversionReport NewConversionReport(
+      const url::Origin& impression_origin,
+      const url::Origin& conversion_origin,
+      int64_t impression_id = 0,
+      StorableImpression::SourceType source_type =
+          StorableImpression::SourceType::kNavigation) {
     return ConversionReport(ImpressionBuilder(clock()->Now())
                                 .SetImpressionOrigin(impression_origin)
                                 .SetConversionOrigin(conversion_origin)
                                 .SetImpressionId(impression_id)
+                                .SetSourceType(source_type)
                                 .Build(),
                             /*conversion_data=*/0,
                             /*conversion_time=*/clock()->Now(),
@@ -148,15 +153,15 @@
   const auto report_b_c = NewConversionReport(example_b, example_c);
   // conversion doesn't match
   const auto report_a_b = NewConversionReport(example_a, example_b);
-  // neither impression and conversion match
-  const auto report_b_d = NewConversionReport(example_a, example_b);
+  // neither impression nor conversion match
+  const auto report_b_a = NewConversionReport(example_b, example_a);
 
   base::Time now = clock()->Now();
   EXPECT_FALSE(table()->IsAttributionAllowed(&db, report_a_c, now));
   EXPECT_TRUE(table()->IsAttributionAllowed(&db, report_a_d, now));
   EXPECT_TRUE(table()->IsAttributionAllowed(&db, report_b_c, now));
   EXPECT_TRUE(table()->IsAttributionAllowed(&db, report_a_b, now));
-  EXPECT_TRUE(table()->IsAttributionAllowed(&db, report_b_d, now));
+  EXPECT_TRUE(table()->IsAttributionAllowed(&db, report_b_a, now));
 
   // Expire the first row above by advancing to +10d.
   clock()->Advance(base::TimeDelta::FromDays(4));
@@ -165,11 +170,56 @@
   EXPECT_TRUE(table()->IsAttributionAllowed(&db, report_a_d, now));
   EXPECT_TRUE(table()->IsAttributionAllowed(&db, report_b_c, now));
   EXPECT_TRUE(table()->IsAttributionAllowed(&db, report_a_b, now));
-  EXPECT_TRUE(table()->IsAttributionAllowed(&db, report_b_d, now));
+  EXPECT_TRUE(table()->IsAttributionAllowed(&db, report_b_a, now));
 
   EXPECT_EQ(3u, GetRateLimitRows(&db));
 }
 
+TEST_F(RateLimitTableTest, IsAttributionAllowed_SourceTypesIndependent) {
+  // Tests that limits are calculated independently for each
+  // `StorableImpression::SourceType`. In the future, we may change this so that
+  // there is a combined calculation but each source type is weighted
+  // differently.
+
+  sql::Database db;
+  EXPECT_TRUE(db.Open(db_path()));
+  EXPECT_TRUE(table()->CreateTable(&db));
+
+  delegate()->set_rate_limits({
+      .time_window = base::TimeDelta::FromDays(2),
+      .max_attributions_per_window = 2,
+  });
+
+  const url::Origin example_a = url::Origin::Create(GURL("https://a.example/"));
+  const url::Origin example_c = url::Origin::Create(GURL("https://c.example/"));
+
+  const auto report_navigation =
+      NewConversionReport(example_a, example_c, /*impression_id=*/0,
+                          StorableImpression::SourceType::kNavigation);
+  const auto report_event =
+      NewConversionReport(example_a, example_c, /*impression_id=*/0,
+                          StorableImpression::SourceType::kEvent);
+
+  // Add distinct source types on the same origin to ensure independence.
+  EXPECT_TRUE(table()->AddRateLimit(&db, report_navigation));
+  EXPECT_TRUE(table()->AddRateLimit(&db, report_event));
+  EXPECT_EQ(2u, GetRateLimitRows(&db));
+
+  base::Time now = clock()->Now();
+  EXPECT_TRUE(table()->IsAttributionAllowed(&db, report_navigation, now));
+  EXPECT_TRUE(table()->IsAttributionAllowed(&db, report_event, now));
+
+  EXPECT_TRUE(table()->AddRateLimit(&db, report_navigation));
+  EXPECT_EQ(3u, GetRateLimitRows(&db));
+  EXPECT_FALSE(table()->IsAttributionAllowed(&db, report_navigation, now));
+  EXPECT_TRUE(table()->IsAttributionAllowed(&db, report_event, now));
+
+  EXPECT_TRUE(table()->AddRateLimit(&db, report_event));
+  EXPECT_EQ(4u, GetRateLimitRows(&db));
+  EXPECT_FALSE(table()->IsAttributionAllowed(&db, report_navigation, now));
+  EXPECT_FALSE(table()->IsAttributionAllowed(&db, report_event, now));
+}
+
 TEST_F(RateLimitTableTest,
        IsAttributionAllowed_ConversionDestinationSubdomains) {
   sql::Database db;
diff --git a/content/browser/conversions/storable_impression.cc b/content/browser/conversions/storable_impression.cc
index bf9fe85..e607bf8 100644
--- a/content/browser/conversions/storable_impression.cc
+++ b/content/browser/conversions/storable_impression.cc
@@ -43,4 +43,8 @@
   return net::SchemefulSite(conversion_origin_);
 }
 
+net::SchemefulSite StorableImpression::ImpressionSite() const {
+  return net::SchemefulSite(impression_origin_);
+}
+
 }  // namespace content
diff --git a/content/browser/conversions/storable_impression.h b/content/browser/conversions/storable_impression.h
index 19a4063..2bdcc03c 100644
--- a/content/browser/conversions/storable_impression.h
+++ b/content/browser/conversions/storable_impression.h
@@ -77,9 +77,15 @@
   // Returns the schemeful site of |conversion_origin|.
   //
   // TODO(johnidel): Consider storing the SchemefulSite as a separate member so
-  // that we avoid unnecessary copies of |conversion_origin|.
+  // that we avoid unnecessary copies of |conversion_origin_|.
   net::SchemefulSite ConversionDestination() const;
 
+  // Returns the schemeful site of |impression_origin|.
+  //
+  // TODO(johnidel): Consider storing the SchemefulSite as a separate member so
+  // that we avoid unnecessary copies of |impression_origin_|.
+  net::SchemefulSite ImpressionSite() const;
+
  private:
   uint64_t impression_data_;
   url::Origin impression_origin_;
diff --git a/content/browser/indexed_db/indexed_db_quota_client.h b/content/browser/indexed_db/indexed_db_quota_client.h
index a15862e..39c46f0 100644
--- a/content/browser/indexed_db/indexed_db_quota_client.h
+++ b/content/browser/indexed_db/indexed_db_quota_client.h
@@ -12,7 +12,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/thread_annotations.h"
-#include "components/services/storage/public/mojom/quota_client.mojom.h"
+#include "components/services/storage/public/cpp/origin_quota_client.h"
 #include "content/common/content_export.h"
 #include "storage/browser/quota/quota_client_type.h"
 #include "storage/browser/quota/quota_task.h"
@@ -25,7 +25,7 @@
 // Integrates IndexedDB with the quota management system.
 //
 // Each instance is owned by an IndexedDBContextImpl.
-class IndexedDBQuotaClient : public storage::mojom::QuotaClient {
+class IndexedDBQuotaClient : public storage::OriginQuotaClient {
  public:
   CONTENT_EXPORT explicit IndexedDBQuotaClient(
       IndexedDBContextImpl& indexed_db_context);
@@ -35,7 +35,7 @@
 
   CONTENT_EXPORT ~IndexedDBQuotaClient() override;
 
-  // QuotaClient implementation:
+  // storage::OriginQuotaClient implementation:
   void GetOriginUsage(const url::Origin& origin,
                       blink::mojom::StorageType type,
                       GetOriginUsageCallback callback) override;
diff --git a/content/browser/interest_group/auction_runner.cc b/content/browser/interest_group/auction_runner.cc
index adc43f6..2254ddef 100644
--- a/content/browser/interest_group/auction_runner.cc
+++ b/content/browser/interest_group/auction_runner.cc
@@ -146,7 +146,7 @@
 }
 
 void AuctionRunner::StartBidding() {
-  // Auctions are only run when there are bidders participating. As-is, and
+  // Auctions are only run when there are bidders participating. As-is, an
   // empty bidder vector here would result in synchronously calling back into
   // the creator, which isn't allowed.
   DCHECK(!bid_states_.empty());
@@ -157,6 +157,8 @@
     auction_worklet::mojom::BiddingInterestGroup* bidder =
         bid_state.bidder.get();
 
+    DCHECK_EQ(bid_state.state, BidState::State::kWaitingToLoadWorklet);
+
     // Assemble list of URLs the bidder can request.
 
     // TODO(mmenke): This largely duplicates logic in the auction worklet
@@ -196,6 +198,8 @@
             bid_state.bidder->group->bidding_url.value_or(GURL()),
             trusted_bidding_signals_full_url);
 
+    bid_state.state = BidState::State::kGeneratingBid;
+
     delegate_->GetWorkletService()->LoadBidderWorkletAndGenerateBid(
         bid_state.bidder_worklet.BindNewPipeAndPassReceiver(),
         std::move(url_loader_factory), bidder->Clone(),
@@ -240,8 +244,7 @@
     const std::vector<std::string>& errors) {
   DCHECK(!state->bid_result);
   DCHECK_GT(outstanding_bids_, 0);
-
-  --outstanding_bids_;
+  DCHECK_EQ(state->state, BidState::State::kGeneratingBid);
 
   errors_.insert(errors_.end(), errors.begin(), errors.end());
 
@@ -252,19 +255,25 @@
       bid.reset();
   }
 
-  // On failure, close the worklet pipe. On success, clear the disconnect
-  // handler - crashed bidders only matters if it's the winning bidder that
-  // crashed. That's checked for at the end of the auction.
   if (!bid) {
+    // On failure, close the worklet pipe.
     state->bidder_worklet.reset();
-  } else {
-    state->bidder_worklet.set_disconnect_handler(base::OnceClosure());
+
+    state->state = BidState::State::kScoringComplete;
+    --outstanding_bids_;
+    MaybeCompleteAuction();
+    return;
   }
 
-  state->bid_result = std::move(bid);
+  // On success, clear the disconnect handler. After generating a bid, a crashed
+  // bidder only matters if it's the winning bidder that crashed. That's checked
+  // for at the end of the auction.
+  state->bidder_worklet.set_disconnect_handler(base::OnceClosure());
 
-  if (ReadyToScore())
-    ScoreOne();
+  state->bid_result = std::move(bid);
+  state->state = BidState::State::kWaitingOnSellerWorkletLoad;
+  if (seller_loaded_)
+    ScoreBid(state);
 }
 
 void AuctionRunner::OnSellerWorkletLoaded(
@@ -274,8 +283,15 @@
 
   if (load_result) {
     seller_loaded_ = true;
-    if (ReadyToScore())
-      ScoreOne();
+    // Start scoring any bids that were waiting on the seller worklet to load.
+    for (BidState& state : bid_states_) {
+      // Bids can be complete at this point (if no bid was offered, or on
+      // error), but they can't be scoring a bid.
+      DCHECK_NE(state.state, BidState::State::kSellerScoringBid);
+
+      if (state.state == BidState::State::kWaitingOnSellerWorkletLoad)
+        ScoreBid(&state);
+    }
   } else {
     // Failed to load the seller/auction script --- nothing useful can be
     // done, so abort, possibly cancelling other fetches, so we don't waste
@@ -284,43 +300,42 @@
   }
 }
 
-void AuctionRunner::ScoreOne() {
-  size_t num_bidders = bid_states_.size();
-
-  // Find next valid bid to score, if any.
-  while (seller_considering_ < num_bidders) {
-    BidState* bid_state = &bid_states_[seller_considering_];
-
-    // Skip over bidders that produced no valid bid.
-    if (!bid_state->bid_result) {
-      ++seller_considering_;
-      continue;
-    }
-
-    ScoreBid(bid_state);
-    return;
-  }
-
-  DCHECK_EQ(seller_considering_, num_bidders);
-  CompleteAuction();
-}
-
-void AuctionRunner::ScoreBid(const BidState* state) {
+void AuctionRunner::ScoreBid(BidState* state) {
+  DCHECK_EQ(state->state, BidState::State::kWaitingOnSellerWorkletLoad);
+  state->state = BidState::State::kSellerScoringBid;
   seller_worklet_->ScoreAd(
       state->bid_result->ad, state->bid_result->bid, auction_config_.Clone(),
       browser_signals_->top_frame_origin, state->bidder->group->owner,
       AdRenderFingerprint(state),
       state->bid_result->bid_duration.InMilliseconds(),
       base::BindOnce(&AuctionRunner::OnBidScored,
-                     weak_ptr_factory_.GetWeakPtr()));
+                     weak_ptr_factory_.GetWeakPtr(), state));
 }
 
-void AuctionRunner::OnBidScored(double score,
+void AuctionRunner::OnBidScored(BidState* state,
+                                double score,
                                 const std::vector<std::string>& errors) {
-  bid_states_[seller_considering_].seller_score = score;
+  DCHECK_EQ(state->state, BidState::State::kSellerScoringBid);
+  state->seller_score = score;
+
+  // Check if this is the top bidder. If not, unload its worklet.
+  //
+  // TODO(morlovich): What if there is a tie?
+  if (score > 0 && (!top_bidder_ || score > top_bidder_->seller_score)) {
+    // If this bidder out scored a previous winning bidder, unload that bidder's
+    // worklet.
+    if (top_bidder_)
+      top_bidder_->bidder_worklet.reset();
+    top_bidder_ = state;
+  } else {
+    state->bidder_worklet.reset();
+  }
+
+  --outstanding_bids_;
+  state->state = BidState::State::kScoringComplete;
+
   errors_.insert(errors_.end(), errors.begin(), errors.end());
-  ++seller_considering_;
-  ScoreOne();
+  MaybeCompleteAuction();
 }
 
 std::string AuctionRunner::AdRenderFingerprint(const BidState* state) {
@@ -342,49 +357,46 @@
   return absl::nullopt;
 }
 
-void AuctionRunner::CompleteAuction() {
-  double best_bid_score = 0.0;
-  BidState* best_bid = nullptr;
+void AuctionRunner::MaybeCompleteAuction() {
+  if (!AllBidsScored())
+    return;
 
-  // Find the best bid, if any, and record the bid of all bidders that made a
-  // bid. Record bidders at this point because the auction has successfully
-  // completed, and need to record bids even in the case the seller rejected all
-  // bids.
+  // Record which interest groups bid.
   //
-  // TODO(morlovich): What if there is a tie?
+  // TODO(mmenke): Maybe this should be recorded at bid time, and the interest
+  // group thrown away if it's not the top bid?
   for (BidState& bid_state : bid_states_) {
     if (bid_state.bid_result) {
       interest_group_manager_->RecordInterestGroupBid(
           bid_state.bidder->group->owner, bid_state.bidder->group->name);
-      if (bid_state.seller_score > best_bid_score) {
-        best_bid_score = bid_state.seller_score;
-        best_bid = &bid_state;
-      }
     }
   }
 
-  if (best_bid) {
+  if (top_bidder_) {
     // Will eventually send a report to the seller and clean up `this`.
-    ReportSellerResult(best_bid);
+    ReportSellerResult();
   } else {
     FailAuction();
   }
 }
 
-void AuctionRunner::ReportSellerResult(BidState* best_bid) {
-  DCHECK(best_bid->bid_result);
-  DCHECK_GT(best_bid->seller_score, 0);
+void AuctionRunner::ReportSellerResult() {
+  DCHECK(top_bidder_);
+
+  DCHECK(top_bidder_->bid_result);
+  DCHECK_GT(top_bidder_->seller_score, 0);
+  DCHECK(top_bidder_->bidder_worklet);
+
   seller_worklet_->ReportResult(
       auction_config_.Clone(), browser_signals_->top_frame_origin,
-      best_bid->bidder->group->owner, best_bid->bid_result->render_url,
-      AdRenderFingerprint(best_bid), best_bid->bid_result->bid,
-      best_bid->seller_score,
+      top_bidder_->bidder->group->owner, top_bidder_->bid_result->render_url,
+      AdRenderFingerprint(top_bidder_), top_bidder_->bid_result->bid,
+      top_bidder_->seller_score,
       base::BindOnce(&AuctionRunner::OnReportSellerResultComplete,
-                     weak_ptr_factory_.GetWeakPtr(), best_bid));
+                     weak_ptr_factory_.GetWeakPtr()));
 }
 
 void AuctionRunner::OnReportSellerResultComplete(
-    BidState* best_bid,
     const absl::optional<std::string>& signals_for_winner,
     const absl::optional<GURL>& seller_report_url,
     const std::vector<std::string>& errors) {
@@ -398,13 +410,12 @@
     return;
   }
 
-  ReportBidWin(best_bid, signals_for_winner);
+  ReportBidWin(signals_for_winner);
 }
 
 void AuctionRunner::ReportBidWin(
-    BidState* best_bid,
     const absl::optional<std::string>& signals_for_winner) {
-  CHECK(best_bid->bid_result);
+  DCHECK(top_bidder_->bid_result);
   std::string signals_for_winner_arg;
   if (signals_for_winner) {
     signals_for_winner_arg = *signals_for_winner;
@@ -422,26 +433,25 @@
   // TODO(mmenke): Be smarter about process crashes in general. Even without
   // the report URL, can display the ad and report to the seller (though will
   // need to think more about that case).
-  if (!best_bid->bidder_worklet.is_connected()) {
+  if (!top_bidder_->bidder_worklet.is_connected()) {
     FailAuctionWithError(
-        base::StrCat({best_bid->bidder->group->bidding_url->spec(),
+        base::StrCat({top_bidder_->bidder->group->bidding_url->spec(),
                       " crashed while idle."}));
     return;
   }
 
-  best_bid->bidder_worklet->ReportWin(
-      signals_for_winner_arg, best_bid->bid_result->render_url,
-      AdRenderFingerprint(best_bid), best_bid->bid_result->bid,
+  top_bidder_->bidder_worklet->ReportWin(
+      signals_for_winner_arg, top_bidder_->bid_result->render_url,
+      AdRenderFingerprint(top_bidder_), top_bidder_->bid_result->bid,
       base::BindOnce(&AuctionRunner::OnReportBidWinComplete,
-                     weak_ptr_factory_.GetWeakPtr(), best_bid));
-  best_bid->bidder_worklet.set_disconnect_handler(base::BindOnce(
+                     weak_ptr_factory_.GetWeakPtr()));
+  top_bidder_->bidder_worklet.set_disconnect_handler(base::BindOnce(
       &AuctionRunner::FailAuctionWithError, weak_ptr_factory_.GetWeakPtr(),
-      base::StrCat({best_bid->bidder->group->bidding_url->spec(),
+      base::StrCat({top_bidder_->bidder->group->bidding_url->spec(),
                     " crashed while trying to run reportWin()."})));
 }
 
 void AuctionRunner::OnReportBidWinComplete(
-    const BidState* best_bid,
     const absl::optional<GURL>& bidder_report_url,
     const std::vector<std::string>& errors) {
   if (bidder_report_url && !IsUrlValid(*bidder_report_url)) {
@@ -452,7 +462,7 @@
 
   bidder_report_url_ = bidder_report_url;
   errors_.insert(errors_.end(), errors.begin(), errors.end());
-  ReportSuccess(best_bid);
+  ReportSuccess();
 }
 
 void AuctionRunner::FailAuction() {
@@ -468,27 +478,29 @@
   FailAuction();
 }
 
-void AuctionRunner::ReportSuccess(const BidState* state) {
+void AuctionRunner::ReportSuccess() {
   DCHECK(callback_);
-  DCHECK(state->bid_result);
+  DCHECK(top_bidder_->bid_result);
   ClosePipes();
 
   std::string ad_metadata;
-  if (state->bid_ad->metadata) {
+  if (top_bidder_->bid_ad->metadata) {
     //`metadata` is already in JSON so no quotes are needed.
     ad_metadata =
         base::StringPrintf(R"({"render_url":"%s","metadata":%s})",
-                           state->bid_result->render_url.spec().c_str(),
-                           state->bid_ad->metadata.value().c_str());
+                           top_bidder_->bid_result->render_url.spec().c_str(),
+                           top_bidder_->bid_ad->metadata.value().c_str());
   } else {
-    ad_metadata = base::StringPrintf(
-        R"({"render_url":"%s"})", state->bid_result->render_url.spec().c_str());
+    ad_metadata =
+        base::StringPrintf(R"({"render_url":"%s"})",
+                           top_bidder_->bid_result->render_url.spec().c_str());
   }
 
   interest_group_manager_->RecordInterestGroupWin(
-      state->bidder->group->owner, state->bidder->group->name, ad_metadata);
+      top_bidder_->bidder->group->owner, top_bidder_->bidder->group->name,
+      ad_metadata);
 
-  std::move(callback_).Run(this, state->bid_result->render_url,
+  std::move(callback_).Run(this, top_bidder_->bid_result->render_url,
                            std::move(bidder_report_url_),
                            std::move(seller_report_url_), std::move(errors_));
 }
diff --git a/content/browser/interest_group/auction_runner.h b/content/browser/interest_group/auction_runner.h
index 4a71399..4366ce48 100644
--- a/content/browser/interest_group/auction_runner.h
+++ b/content/browser/interest_group/auction_runner.h
@@ -31,19 +31,6 @@
 
 // An AuctionRunner loads and runs the bidder and seller worklets, along with
 // their reporting phases and produces the result via a callback.
-//
-// At present it initiates all fetches in parallel, running all bidder scripts
-// once they and any trusted signals they need are ready, then when all bids are
-// in runs all the scoring, and finally the reporting worklets.
-//
-// TODO(morlovich): There is no need to wait for all bidders to finish to start
-// scoring.
-//
-// TODO(mmenke): Merge this with `ad_auction`, and provide worklet-specific
-// URLLoaderFactories.
-//
-// TODO(mmenke): Add checking of values returned by auctions (e.g., for bids <=
-// 0).
 class CONTENT_EXPORT AuctionRunner {
  public:
   // Invoked when a FLEDGE auction is complete.
@@ -121,6 +108,30 @@
 
  private:
   struct BidState {
+    enum class State {
+      // Waiting for all the interest groups to load before starting to load
+      // worklets.
+      //
+      // TODO(mmenke): Consider removing this phase.
+      kWaitingToLoadWorklet,
+
+      // Loading the bidder worklet script / trusted data and generating the
+      // bid.
+      kGeneratingBid,
+
+      // Waiting on the seller worklet to load.
+      kWaitingOnSellerWorkletLoad,
+
+      // Waiting on the seller worklet to score the bid.
+      kSellerScoringBid,
+
+      // Seller worklet has completed scoring the bid, or doesn't need to. If
+      // this is not potentially the winning bidder, the worklet has been
+      // unloaded. Otherwise, the worklet is still in memory, as it may still be
+      // necessary to call reporting methods, if this is the winning bidder.
+      kScoringComplete,
+    };
+
     BidState();
     BidState(BidState&&);
     ~BidState();
@@ -131,6 +142,8 @@
     BidState(BidState&) = delete;
     BidState& operator=(BidState&) = delete;
 
+    State state = State::kWaitingToLoadWorklet;
+
     auction_worklet::mojom::BiddingInterestGroupPtr bidder;
 
     // URLLoaderFactory proxy class configured only to load the URLs the bidder
@@ -174,38 +187,35 @@
                              const std::vector<std::string>& errors);
 
   // True if all bid results and the seller script load are complete.
-  bool ReadyToScore() const { return outstanding_bids_ == 0 && seller_loaded_; }
+  bool AllBidsScored() const { return outstanding_bids_ == 0; }
   void OnSellerWorkletLoaded(bool load_result,
                              const std::vector<std::string>& errors);
 
-  // Calls into the seller asynchronously to score each outstanding bid, in
-  // series. Once there are no outstanding bids, proceeds to selecting the
-  // winner and running the Worklets reporting methods.
-  void ScoreOne();
-  void ScoreBid(const BidState* state);
+  // Calls into the seller asynchronously to score the passed in bid.
+  void ScoreBid(BidState* state);
   // Callback from ScoreBid().
-  void OnBidScored(double score, const std::vector<std::string>& errors);
+  void OnBidScored(BidState* state,
+                   double score,
+                   const std::vector<std::string>& errors);
 
   std::string AdRenderFingerprint(const BidState* state);
   absl::optional<std::string> PerBuyerSignals(const BidState* state);
 
-  // Completes the auction, invoking `callback_`. Consumer must be able to
-  // safely delete `this` when the callback is invoked.
-  void CompleteAuction();
+  // If there are no `outstanding_bids_`, starts starts completing the auction,
+  // either invoking `callback_` or calling reporting methods on worklets.
+  // Consumer must be able to safely delete `this` when the callback is invoked.
+  void MaybeCompleteAuction();
 
   // Sequence of asynchronous methods to call into the bidder/seller results to
   // report a a win, Will ultimately invoke ReportSuccess(), which will delete
   // the auction.
-  void ReportSellerResult(BidState* state);
+  void ReportSellerResult();
   void OnReportSellerResultComplete(
-      BidState* best_bid,
       const absl::optional<std::string>& signals_for_winner,
       const absl::optional<GURL>& seller_report_url,
       const std::vector<std::string>& error_msgs);
-  void ReportBidWin(BidState* state,
-                    const absl::optional<std::string>& signals_for_winner);
-  void OnReportBidWinComplete(const BidState* best_bid,
-                              const absl::optional<GURL>& bidder_report_url,
+  void ReportBidWin(const absl::optional<std::string>& signals_for_winner);
+  void OnReportBidWinComplete(const absl::optional<GURL>& bidder_report_url,
                               const std::vector<std::string>& error_msgs);
 
   // These complete the auction, invoking `callback_` and preventing any future
@@ -214,7 +224,7 @@
   void FailAuction();
   // Appends `error` to `errors_` before calling FailAuciton().
   void FailAuctionWithError(std::string error);
-  void ReportSuccess(const BidState* state);
+  void ReportSuccess();
 
   // Closes all open pipes, to avoid receiving any Mojo callbacks after
   // completion.
@@ -234,13 +244,21 @@
   const url::Origin frame_origin_;
   RunAuctionCallback callback_;
 
-  // State for the bidding phase.
-  int outstanding_bids_;  // number of bids for which we're waiting on a fetch.
-  std::vector<BidState> bid_states_;  // State of all loaded bidders.
+  // Number of bids which the seller has not yet scored. These bids may be
+  // fetching URLs, generating bids, waiting for the seller worklet to load, or
+  // the seller worklet may be scoring their bids.
+  int outstanding_bids_;
+  // State of all loaded interest groups.
+  std::vector<BidState> bid_states_;
   // The time the auction started. Use a single base time for all Worklets, to
   // present a more consistent view of the universe.
   const base::Time auction_start_time_ = base::Time::Now();
 
+  // The bidder with the highest scoring bid so far. No other scored bidder
+  // worklet can win the auction, so the other worklets are all unloaded right
+  // after scoring.
+  BidState* top_bidder_ = nullptr;
+
   // URLLoaderFactory proxy class configured only to load the URL the seller
   // needs.
   std::unique_ptr<AuctionURLLoaderFactoryProxy> seller_url_loader_factory_;
@@ -252,7 +270,6 @@
   // load failed, the entire process is aborted since there is nothing useful
   // that can be done.
   bool seller_loaded_ = false;
-  size_t seller_considering_ = 0;
 
   // Seller script reportResult() results.
   absl::optional<GURL> seller_report_url_;
diff --git a/content/browser/interest_group/auction_runner_unittest.cc b/content/browser/interest_group/auction_runner_unittest.cc
index 88fee1d..e864d86 100644
--- a/content/browser/interest_group/auction_runner_unittest.cc
+++ b/content/browser/interest_group/auction_runner_unittest.cc
@@ -246,7 +246,10 @@
       : load_bidder_worklet_and_generate_bid_callback_(
             std::move(load_bidder_worklet_and_generate_bid_callback)),
         url_loader_factory_(std::move(pending_url_loader_factory)),
-        receiver_(this, std::move(pending_receiver)) {}
+        receiver_(this, std::move(pending_receiver)) {
+    receiver_.set_disconnect_handler(base::BindOnce(
+        &MockBidderWorklet::OnPipeClosed, base::Unretained(this)));
+  }
 
   MockBidderWorklet(const MockBidderWorklet&) = delete;
   const MockBidderWorklet& operator=(const MockBidderWorklet&) = delete;
@@ -315,11 +318,21 @@
     return url_loader_factory_;
   }
 
+  // Flush the receiver pipe and return whether or not its closed.
+  bool PipeIsClosed() {
+    receiver_.FlushForTesting();
+    return pipe_closed_;
+  }
+
  private:
+  void OnPipeClosed() { pipe_closed_ = true; }
+
   auction_worklet::mojom::AuctionWorkletService::
       LoadBidderWorkletAndGenerateBidCallback
           load_bidder_worklet_and_generate_bid_callback_;
 
+  bool pipe_closed_ = false;
+
   std::unique_ptr<base::RunLoop> report_win_run_loop_;
   ReportWinCallback report_win_callback_;
 
@@ -442,6 +455,8 @@
     return url_loader_factory_;
   }
 
+  void Flush() { receiver_.FlushForTesting(); }
+
  private:
   auction_worklet::mojom::AuctionWorkletService::LoadSellerWorkletCallback
       load_worklet_callback_;
@@ -2117,5 +2132,170 @@
   mock_worklet_service_.reset();
 }
 
+// Check that BidderWorklets that don't make a bid are destroyed immediately.
+TEST_F(AuctionRunnerTest, DestroyBidderWorkletWithoutBid) {
+  StartStandardAuctionWithMockService();
+
+  auto seller_worklet = mock_worklet_service_->TakeSellerWorklet();
+  ASSERT_TRUE(seller_worklet);
+  auto bidder1_worklet =
+      mock_worklet_service_->TakeBidderWorklet(kBidder1, kBidder1Name);
+  ASSERT_TRUE(bidder1_worklet);
+  auto bidder2_worklet =
+      mock_worklet_service_->TakeBidderWorklet(kBidder2, kBidder2Name);
+  ASSERT_TRUE(bidder2_worklet);
+
+  seller_worklet->CompleteLoading();
+
+  bidder1_worklet->CompleteLoadingWithoutBid();
+  // Need to flush the service pipe to make sure the AuctionRunner has received
+  // the bid.
+  mock_worklet_service_->Flush();
+  // The AuctionRunner should have closed the pipe.
+  EXPECT_TRUE(bidder1_worklet->PipeIsClosed());
+
+  // Bidder2 returns a bid, which is then scored.
+  bidder2_worklet->CompleteLoadingAndBid(7 /* bid */, GURL("https://ad2.com/"));
+  auto score_ad_params = seller_worklet->WaitForScoreAd();
+  EXPECT_EQ(kBidder2, score_ad_params->interest_group_owner);
+  EXPECT_EQ(7, score_ad_params->bid);
+  seller_worklet->InvokeScoreAdCallback(11 /* score */);
+
+  // Finish the auction.
+  seller_worklet->WaitForReportResult();
+  seller_worklet->InvokeReportResultCallback();
+  bidder2_worklet->WaitForReportWin();
+  bidder2_worklet->InvokeReportWinCallback();
+  auction_run_loop_->Run();
+
+  // Bidder2 won.
+  EXPECT_EQ(GURL("https://ad2.com/"), result_.ad_url);
+  EXPECT_FALSE(result_.seller_report_url);
+  EXPECT_FALSE(result_.bidder_report_url);
+  EXPECT_EQ(5, result_.bidder1_bid_count);
+  EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
+  EXPECT_EQ(6, result_.bidder2_bid_count);
+  ASSERT_EQ(4u, result_.bidder2_prev_wins.size());
+  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})",
+            result_.bidder2_prev_wins[3]->ad_json);
+  EXPECT_THAT(result_.errors, testing::ElementsAre());
+}
+
+// Check that BidderWorklets are destroyed as soon as there's a higher bid. The
+// first BidderWorklet to have its bid scored is the one that loses the auction.
+TEST_F(AuctionRunnerTest, DestroyLosingBidderWorkletFirstBidderLoses) {
+  StartStandardAuctionWithMockService();
+
+  auto seller_worklet = mock_worklet_service_->TakeSellerWorklet();
+  ASSERT_TRUE(seller_worklet);
+  auto bidder1_worklet =
+      mock_worklet_service_->TakeBidderWorklet(kBidder1, kBidder1Name);
+  ASSERT_TRUE(bidder1_worklet);
+  auto bidder2_worklet =
+      mock_worklet_service_->TakeBidderWorklet(kBidder2, kBidder2Name);
+  ASSERT_TRUE(bidder2_worklet);
+
+  seller_worklet->CompleteLoading();
+
+  // Bidder1 returns a bid, which is then scored.
+  bidder1_worklet->CompleteLoadingAndBid(5 /* bid */, GURL("https://ad1.com/"));
+  auto score_ad_params = seller_worklet->WaitForScoreAd();
+  EXPECT_EQ(kBidder1, score_ad_params->interest_group_owner);
+  EXPECT_EQ(5, score_ad_params->bid);
+  seller_worklet->InvokeScoreAdCallback(10 /* score */);
+  // Need to flush the service pipe to make sure the AuctionRunner has received
+  // the score.
+  mock_worklet_service_->Flush();
+  // The AuctionRunner should not have closed the bidder pipe, since it could
+  // still be the winning bid.
+  EXPECT_FALSE(bidder1_worklet->PipeIsClosed());
+
+  // Bidder2 returns a bid, which is then scored.
+  bidder2_worklet->CompleteLoadingAndBid(7 /* bid */, GURL("https://ad2.com/"));
+  score_ad_params = seller_worklet->WaitForScoreAd();
+  EXPECT_EQ(kBidder2, score_ad_params->interest_group_owner);
+  EXPECT_EQ(7, score_ad_params->bid);
+  seller_worklet->InvokeScoreAdCallback(11 /* score */);
+  // Need to flush the service pipe to make sure the AuctionRunner has received
+  // the score.
+  seller_worklet->Flush();
+  // The losing bidder should now be destroyed.
+  EXPECT_TRUE(bidder1_worklet->PipeIsClosed());
+
+  // Finish the auction.
+  seller_worklet->WaitForReportResult();
+  seller_worklet->InvokeReportResultCallback();
+  bidder2_worklet->WaitForReportWin();
+  bidder2_worklet->InvokeReportWinCallback();
+  auction_run_loop_->Run();
+
+  // Bidder2 won.
+  EXPECT_EQ(GURL("https://ad2.com/"), result_.ad_url);
+  EXPECT_FALSE(result_.seller_report_url);
+  EXPECT_FALSE(result_.bidder_report_url);
+  EXPECT_EQ(6, result_.bidder1_bid_count);
+  EXPECT_EQ(3u, result_.bidder1_prev_wins.size());
+  EXPECT_EQ(6, result_.bidder2_bid_count);
+  ASSERT_EQ(4u, result_.bidder2_prev_wins.size());
+  EXPECT_EQ(R"({"render_url":"https://ad2.com/"})",
+            result_.bidder2_prev_wins[3]->ad_json);
+  EXPECT_THAT(result_.errors, testing::ElementsAre());
+}
+
+// Check that BidderWorklets are destroyed as soon as there's a higher bid. The
+// last BidderWorklet to have its bid scored is the one that loses the auction.
+TEST_F(AuctionRunnerTest, DestroyLosingBidderWorkletLastBidderLoses) {
+  StartStandardAuctionWithMockService();
+
+  auto seller_worklet = mock_worklet_service_->TakeSellerWorklet();
+  ASSERT_TRUE(seller_worklet);
+  auto bidder1_worklet =
+      mock_worklet_service_->TakeBidderWorklet(kBidder1, kBidder1Name);
+  ASSERT_TRUE(bidder1_worklet);
+  auto bidder2_worklet =
+      mock_worklet_service_->TakeBidderWorklet(kBidder2, kBidder2Name);
+  ASSERT_TRUE(bidder2_worklet);
+
+  seller_worklet->CompleteLoading();
+
+  // Bidder1 returns a bid, which is then scored.
+  bidder1_worklet->CompleteLoadingAndBid(5 /* bid */, GURL("https://ad1.com/"));
+  auto score_ad_params = seller_worklet->WaitForScoreAd();
+  EXPECT_EQ(kBidder1, score_ad_params->interest_group_owner);
+  EXPECT_EQ(5, score_ad_params->bid);
+  seller_worklet->InvokeScoreAdCallback(11 /* score */);
+
+  // Bidder2 returns a bid, which is then scored.
+  bidder2_worklet->CompleteLoadingAndBid(7 /* bid */, GURL("https://ad2.com/"));
+  score_ad_params = seller_worklet->WaitForScoreAd();
+  EXPECT_EQ(kBidder2, score_ad_params->interest_group_owner);
+  EXPECT_EQ(7, score_ad_params->bid);
+  seller_worklet->InvokeScoreAdCallback(10 /* score */);
+  // Need to flush the service pipe to make sure the AuctionRunner has received
+  // the score.
+  seller_worklet->Flush();
+  // The losing bidder should now be destroyed.
+  EXPECT_TRUE(bidder2_worklet->PipeIsClosed());
+
+  // Finish the auction.
+  seller_worklet->WaitForReportResult();
+  seller_worklet->InvokeReportResultCallback();
+  bidder1_worklet->WaitForReportWin();
+  bidder1_worklet->InvokeReportWinCallback();
+  auction_run_loop_->Run();
+
+  // Bidder1 won.
+  EXPECT_EQ(GURL("https://ad1.com/"), result_.ad_url);
+  EXPECT_FALSE(result_.seller_report_url);
+  EXPECT_FALSE(result_.bidder_report_url);
+  EXPECT_EQ(6, result_.bidder1_bid_count);
+  ASSERT_EQ(4u, result_.bidder1_prev_wins.size());
+  EXPECT_EQ(6, result_.bidder2_bid_count);
+  EXPECT_EQ(3u, result_.bidder2_prev_wins.size());
+  EXPECT_EQ(R"({"render_url":"https://ad1.com/","metadata":{"ads": true}})",
+            result_.bidder1_prev_wins[3]->ad_json);
+  EXPECT_THAT(result_.errors, testing::ElementsAre());
+}
+
 }  // namespace
 }  // namespace content
diff --git a/content/browser/interest_group/interest_group_browsertest.cc b/content/browser/interest_group/interest_group_browsertest.cc
index 38425bd..bc2c7d9 100644
--- a/content/browser/interest_group/interest_group_browsertest.cc
+++ b/content/browser/interest_group/interest_group_browsertest.cc
@@ -1716,242 +1716,10 @@
                      .spec())));
 }
 
-// These tests exercise the interest group and ad auction services directly,
+// This test exercises the interest group and ad auction services directly,
 // rather than via Blink, to ensure that those services running in the browser
 // implement important security checks (Blink may also perform its own
 // checking, but the render process is untrusted).
-
-IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
-                       JoinInterestGroupBasicBypassBlink) {
-  GURL test_url_a = https_server_->GetURL("a.test", "/echo");
-  url::Origin test_origin_a = url::Origin::Create(test_url_a);
-  ASSERT_TRUE(test_url_a.SchemeIs(url::kHttpsScheme));
-  ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
-
-  mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
-  InterestGroupServiceImpl::CreateMojoService(
-      shell()->web_contents()->GetMainFrame(),
-      interest_service.BindNewPipeAndPassReceiver());
-
-  auto interest_group = blink::mojom::InterestGroup::New();
-  constexpr char kGroupName[] = "cars";
-  interest_group->expiry =
-      base::Time::Now() + base::TimeDelta::FromSeconds(300);
-  interest_group->name = kGroupName;
-  interest_group->owner = test_origin_a;
-  interest_service->JoinInterestGroup(std::move(interest_group));
-  interest_service.FlushForTesting();
-  EXPECT_EQ(1, GetJoinCount(test_origin_a, kGroupName));
-}
-
-IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
-                       JoinInterestGroupOriginNotHttps) {
-  // JS API is https-only, but a compromised renderer could send a http origin.
-  GURL test_url_http = embedded_test_server()->GetURL("a.test", "/echo");
-  url::Origin test_origin_http = url::Origin::Create(test_url_http);
-  ASSERT_TRUE(test_url_http.SchemeIs(url::kHttpScheme));
-  ASSERT_TRUE(NavigateToURL(shell(), test_url_http));
-
-  mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
-  InterestGroupServiceImpl::CreateMojoService(
-      shell()->web_contents()->GetMainFrame(),
-      interest_service.BindNewPipeAndPassReceiver());
-
-  // Silently fails to join -- the frame origin isn't https.
-  auto interest_group_http = blink::mojom::InterestGroup::New();
-  constexpr char kGroupName[] = "cars";
-  interest_group_http->expiry =
-      base::Time::Now() + base::TimeDelta::FromSeconds(300);
-  interest_group_http->name = kGroupName;
-  interest_group_http->owner = test_origin_http;
-  interest_service->JoinInterestGroup(std::move(interest_group_http));
-  interest_service.FlushForTesting();
-  EXPECT_EQ(0, GetJoinCount(test_origin_http, kGroupName));
-}
-
-IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
-                       JoinInterestGroupWrongOwnerOrigin) {
-  GURL test_url_a = https_server_->GetURL("a.test", "/echo");
-  GURL test_url_b = https_server_->GetURL("b.test", "/echo");
-  url::Origin test_origin_a = url::Origin::Create(test_url_a);
-  url::Origin test_origin_b = url::Origin::Create(test_url_b);
-  ASSERT_TRUE(test_url_a.SchemeIs(url::kHttpsScheme));
-  ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
-  ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
-
-  mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
-  InterestGroupServiceImpl::CreateMojoService(
-      shell()->web_contents()->GetMainFrame(),
-      interest_service.BindNewPipeAndPassReceiver());
-
-  // Silently fails to join -- the owner origin doesn't match the frame origin.
-  auto interest_group = blink::mojom::InterestGroup::New();
-  constexpr char kGroupName[] = "cars";
-  interest_group->expiry =
-      base::Time::Now() + base::TimeDelta::FromSeconds(300);
-  interest_group->name = kGroupName;
-  interest_group->owner = test_origin_b;
-  interest_service->JoinInterestGroup(std::move(interest_group));
-  interest_service.FlushForTesting();
-  EXPECT_EQ(0, GetJoinCount(test_origin_b, kGroupName));
-}
-
-IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
-                       JoinInterestGroupWrongBiddingUrlOrigin) {
-  GURL test_url_a = https_server_->GetURL("a.test", "/echo");
-  GURL test_url_b = https_server_->GetURL("b.test", "/echo");
-  url::Origin test_origin_a = url::Origin::Create(test_url_a);
-  url::Origin test_origin_b = url::Origin::Create(test_url_b);
-  ASSERT_TRUE(test_url_a.SchemeIs(url::kHttpsScheme));
-  ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
-  ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
-
-  mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
-  InterestGroupServiceImpl::CreateMojoService(
-      shell()->web_contents()->GetMainFrame(),
-      interest_service.BindNewPipeAndPassReceiver());
-
-  // Silently fails to join -- the bidding URL origin doesn't match the frame
-  // origin.
-  auto interest_group = blink::mojom::InterestGroup::New();
-  constexpr char kGroupName[] = "cars";
-  interest_group->expiry =
-      base::Time::Now() + base::TimeDelta::FromSeconds(300);
-  interest_group->name = kGroupName;
-  interest_group->owner = test_origin_a;
-  interest_group->bidding_url = test_url_b;
-  interest_service->JoinInterestGroup(std::move(interest_group));
-  interest_service.FlushForTesting();
-  EXPECT_EQ(0, GetJoinCount(test_origin_a, kGroupName));
-}
-
-IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
-                       JoinInterestGroupWrongUpdateUrlOrigin) {
-  GURL test_url_a = https_server_->GetURL("a.test", "/echo");
-  GURL test_url_b = https_server_->GetURL("b.test", "/echo");
-  url::Origin test_origin_a = url::Origin::Create(test_url_a);
-  url::Origin test_origin_b = url::Origin::Create(test_url_b);
-  ASSERT_TRUE(test_url_a.SchemeIs(url::kHttpsScheme));
-  ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
-  ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
-
-  mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
-  InterestGroupServiceImpl::CreateMojoService(
-      shell()->web_contents()->GetMainFrame(),
-      interest_service.BindNewPipeAndPassReceiver());
-
-  // Silently fails to join -- the update URL origin doesn't match the frame
-  // origin.
-  auto interest_group = blink::mojom::InterestGroup::New();
-  constexpr char kGroupName[] = "cars";
-  interest_group->expiry =
-      base::Time::Now() + base::TimeDelta::FromSeconds(300);
-  interest_group->name = kGroupName;
-  interest_group->owner = test_origin_a;
-  interest_group->update_url = test_url_b;
-  interest_service->JoinInterestGroup(std::move(interest_group));
-  interest_service.FlushForTesting();
-  EXPECT_EQ(0, GetJoinCount(test_origin_a, kGroupName));
-}
-
-IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
-                       JoinInterestGroupWrongTrustedBiddingSignalsOrigin) {
-  GURL test_url_a = https_server_->GetURL("a.test", "/echo");
-  GURL test_url_b = https_server_->GetURL("b.test", "/echo");
-  url::Origin test_origin_a = url::Origin::Create(test_url_a);
-  url::Origin test_origin_b = url::Origin::Create(test_url_b);
-  ASSERT_TRUE(test_url_a.SchemeIs(url::kHttpsScheme));
-  ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
-  ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
-
-  mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
-  InterestGroupServiceImpl::CreateMojoService(
-      shell()->web_contents()->GetMainFrame(),
-      interest_service.BindNewPipeAndPassReceiver());
-
-  // Silently fails to join -- the trusted bidding signals URL origin doesn't
-  // match the frame origin.
-  auto interest_group = blink::mojom::InterestGroup::New();
-  constexpr char kGroupName[] = "cars";
-  interest_group->expiry =
-      base::Time::Now() + base::TimeDelta::FromSeconds(300);
-  interest_group->name = kGroupName;
-  interest_group->owner = test_origin_a;
-  interest_group->trusted_bidding_signals_url = test_url_b;
-  interest_service->JoinInterestGroup(std::move(interest_group));
-  interest_service.FlushForTesting();
-  EXPECT_EQ(0, GetJoinCount(test_origin_a, kGroupName));
-}
-
-IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
-                       JoinInterestGroupAdRenderingUrlNotHttps) {
-  GURL test_url_https = https_server_->GetURL("a.test", "/echo");
-  GURL test_url_http = embedded_test_server()->GetURL("b.test", "/echo");
-  url::Origin test_origin_https = url::Origin::Create(test_url_https);
-  ASSERT_TRUE(test_url_https.SchemeIs(url::kHttpsScheme));
-  ASSERT_TRUE(test_url_http.SchemeIs(url::kHttpScheme));
-  ASSERT_TRUE(NavigateToURL(shell(), test_url_https));
-
-  mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
-  InterestGroupServiceImpl::CreateMojoService(
-      shell()->web_contents()->GetMainFrame(),
-      interest_service.BindNewPipeAndPassReceiver());
-
-  // Silently fails to join -- the ad renderingUrl doesn't use https.
-  auto interest_group = blink::mojom::InterestGroup::New();
-  constexpr char kGroupName[] = "cars";
-  interest_group->expiry =
-      base::Time::Now() + base::TimeDelta::FromSeconds(300);
-  interest_group->name = kGroupName;
-  interest_group->owner = test_origin_https;
-  auto ad = blink::mojom::InterestGroupAd::New();
-  ad->render_url = test_url_http;
-  interest_group->ads.emplace();
-  interest_group->ads->push_back(std::move(ad));
-  interest_service->JoinInterestGroup(std::move(interest_group));
-  interest_service.FlushForTesting();
-  EXPECT_EQ(0, GetJoinCount(test_origin_https, kGroupName));
-}
-
-IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
-                       LeaveInterestGroupWrongOwnerOrigin) {
-  GURL test_url_a = https_server_->GetURL("a.test", "/echo");
-  GURL test_url_b = https_server_->GetURL("b.test", "/echo");
-  url::Origin test_origin_a = url::Origin::Create(test_url_a);
-  url::Origin test_origin_b = url::Origin::Create(test_url_b);
-  ASSERT_TRUE(test_url_a.SchemeIs(url::kHttpsScheme));
-  ASSERT_TRUE(test_url_b.SchemeIs(url::kHttpsScheme));
-  ASSERT_TRUE(NavigateToURL(shell(), test_url_a));
-
-  mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service1;
-  InterestGroupServiceImpl::CreateMojoService(
-      shell()->web_contents()->GetMainFrame(),
-      interest_service1.BindNewPipeAndPassReceiver());
-
-  // Join succeeds.
-  auto interest_group = blink::mojom::InterestGroup::New();
-  constexpr char kGroupName[] = "cars";
-  interest_group->expiry =
-      base::Time::Now() + base::TimeDelta::FromSeconds(300);
-  interest_group->name = kGroupName;
-  interest_group->owner = test_origin_a;
-  interest_service1->JoinInterestGroup(std::move(interest_group));
-  interest_service1.FlushForTesting();
-  EXPECT_EQ(1, GetJoinCount(test_origin_a, kGroupName));
-
-  ASSERT_TRUE(NavigateToURL(shell(), test_url_b));
-  // The frame changes upon navigation -- connect to the new service.
-  mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service2;
-  InterestGroupServiceImpl::CreateMojoService(
-      shell()->web_contents()->GetMainFrame(),
-      interest_service2.BindNewPipeAndPassReceiver());
-
-  // Silently fails to leave -- the owner origin doesn't match the frame origin.
-  interest_service2->LeaveInterestGroup(test_origin_a, kGroupName);
-  interest_service2.FlushForTesting();
-  EXPECT_EQ(1, GetJoinCount(test_origin_a, kGroupName));
-}
-
 IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, RunAdAuctionBasicBypassBlink) {
   ASSERT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL("/echo")));
 
diff --git a/content/browser/interest_group/interest_group_service_impl.cc b/content/browser/interest_group/interest_group_service_impl.cc
index 13a37fc8..a22dc6a 100644
--- a/content/browser/interest_group/interest_group_service_impl.cc
+++ b/content/browser/interest_group/interest_group_service_impl.cc
@@ -16,6 +16,27 @@
 
 constexpr base::TimeDelta kMaxExpiry = base::TimeDelta::FromDays(30);
 
+// Check if `url` can be used as an interest group's ad render URL. Ad URLs can
+// be cross origin, unlike other interest group URLs, but are still restricted
+// to HTTPS with no reference or embedded credentials.
+bool IsUrlAllowedForRenderUrls(const GURL& url) {
+  if (url.scheme() != url::kHttpsScheme)
+    return false;
+
+  return !url.has_ref() && !url.has_username() && !url.has_password();
+}
+
+// Check if `url` can be used with the specified interest group for any of
+// script URL, update URL, or realtime data URL. Ad render URLs should be
+// checked with IsUrlAllowedForRenderUrls(), which doesn't have the same-origin
+// check.
+bool IsUrlAllowed(const GURL& url, const blink::mojom::InterestGroup& group) {
+  if (url::Origin::Create(url) != group.owner)
+    return false;
+
+  return IsUrlAllowedForRenderUrls(url);
+}
+
 }  // namespace
 
 InterestGroupServiceImpl::InterestGroupServiceImpl(
@@ -51,27 +72,33 @@
   // to devtools to get a better error debugging experience.
   if (origin().scheme() != url::kHttpsScheme)
     return;
+
   if (group->owner != origin())
     return;
-  if (group->bidding_url &&
-      url::Origin::Create(*group->bidding_url) != origin()) {
+
+  if (group->bidding_url && !IsUrlAllowed(*group->bidding_url, *group))
     return;
-  }
-  if (group->update_url &&
-      url::Origin::Create(*group->update_url) != origin()) {
+
+  if (group->update_url && !IsUrlAllowed(*group->update_url, *group))
     return;
+
+  if (group->trusted_bidding_signals_url) {
+    if (!IsUrlAllowed(*group->trusted_bidding_signals_url, *group))
+      return;
+
+    // `trusted_bidding_signals_url` must not have a query string, since the
+    // query parameter needs to be set as part of running an auction.
+    if (group->trusted_bidding_signals_url->has_query())
+      return;
   }
-  if (group->trusted_bidding_signals_url &&
-      url::Origin::Create(*group->trusted_bidding_signals_url) != origin()) {
-    return;
-  }
+
   if (group->ads) {
     for (const auto& ad : group->ads.value()) {
-      if (!ad->render_url.SchemeIs(url::kHttpsScheme)) {
+      if (!IsUrlAllowedForRenderUrls(ad->render_url))
         return;
-      }
     }
   }
+
   base::Time max_expiry = base::Time::Now() + kMaxExpiry;
   if (group->expiry > max_expiry)
     group->expiry = max_expiry;
@@ -86,12 +113,12 @@
     return;
   }
 
-  if (origin().scheme() != url::kHttpsScheme) {
+  if (origin().scheme() != url::kHttpsScheme)
     return;
-  }
-  if (owner != origin()) {
+
+  if (owner != origin())
     return;
-  }
+
   interest_group_manager_.LeaveInterestGroup(owner, name);
 }
 
diff --git a/content/browser/interest_group/interest_group_service_impl_unittest.cc b/content/browser/interest_group/interest_group_service_impl_unittest.cc
new file mode 100644
index 0000000..0264a11
--- /dev/null
+++ b/content/browser/interest_group/interest_group_service_impl_unittest.cc
@@ -0,0 +1,356 @@
+// 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/interest_group/interest_group_service_impl.h"
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
+#include "content/browser/storage_partition_impl.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/common/content_client.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/test/test_content_browser_client.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom.h"
+#include "third_party/blink/public/mojom/interest_group/restricted_interest_group_store.mojom.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace content {
+
+namespace {
+
+const char kInterestGroupName[] = "interest-group-name";
+
+class AllowInterestGroupContentBrowserClient : public TestContentBrowserClient {
+ public:
+  explicit AllowInterestGroupContentBrowserClient() = default;
+  ~AllowInterestGroupContentBrowserClient() override = default;
+
+  AllowInterestGroupContentBrowserClient(
+      const AllowInterestGroupContentBrowserClient&) = delete;
+  AllowInterestGroupContentBrowserClient& operator=(
+      const AllowInterestGroupContentBrowserClient&) = delete;
+
+  // ContentBrowserClient overrides:
+  bool IsInterestGroupAPIAllowed(content::BrowserContext* browser_context,
+                                 const url::Origin& top_frame_origin,
+                                 const GURL& api_url) override {
+    return top_frame_origin.host() == "a.test" ||
+           top_frame_origin.host() == "b.test";
+  }
+};
+
+}  // namespace
+
+class InterestGroupServiceTest : public RenderViewHostTestHarness {
+ public:
+  InterestGroupServiceTest() {
+    feature_list_.InitAndEnableFeature(blink::features::kFledgeInterestGroups);
+    old_content_browser_client_ =
+        SetBrowserClientForTesting(&content_browser_client_);
+  }
+
+  ~InterestGroupServiceTest() override {
+    SetBrowserClientForTesting(old_content_browser_client_);
+  }
+
+  void SetUp() override {
+    RenderViewHostTestHarness::SetUp();
+    NavigateAndCommit(kUrlA);
+
+    storage_ = (static_cast<StoragePartitionImpl*>(
+                    browser_context()->GetDefaultStoragePartition()))
+                   ->GetInterestGroupStorage();
+  }
+
+  std::vector<auction_worklet::mojom::BiddingInterestGroupPtr>
+  GetInterestGroupsForOwner(const url::Origin& owner) {
+    std::vector<auction_worklet::mojom::BiddingInterestGroupPtr>
+        interest_groups;
+    base::RunLoop run_loop;
+    storage_->GetInterestGroupsForOwner(
+        owner,
+        base::BindLambdaForTesting(
+            [&run_loop, &interest_groups](
+                std::vector<auction_worklet::mojom::BiddingInterestGroupPtr>
+                    groups) {
+              interest_groups = std::move(groups);
+              run_loop.Quit();
+            }));
+    run_loop.Run();
+    return interest_groups;
+  }
+
+  int GetJoinCount(const url::Origin& owner, const std::string& name) {
+    for (const auto& interest_group : GetInterestGroupsForOwner(owner)) {
+      if (interest_group->group->name == name) {
+        return interest_group->signals->join_count;
+      }
+    }
+    return 0;
+  }
+
+  // Create a new InterestGroupServiceImpl and use it to try and join
+  // `interest_group`. Flushes the Mojo pipe to force the Mojo message to be
+  // handled before returning.
+  //
+  // Creates a new InterestGroupServiceImpl with each call so the RFH can be
+  // navigated between different sites. And InterestGroupServiceImpl only
+  // handles one site (cross site navs use different InterestGroupServiceImpls,
+  // and generally use different RFHs as well).
+  void JoinInterestGroupAndFlush(
+      blink::mojom::InterestGroupPtr interest_group) {
+    mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
+    InterestGroupServiceImpl::CreateMojoService(
+        web_contents()->GetMainFrame(),
+        interest_service.BindNewPipeAndPassReceiver());
+
+    interest_service->JoinInterestGroup(std::move(interest_group));
+    interest_service.FlushForTesting();
+  }
+
+  // Analogous to JoinInterestGroupAndFlush(), but leaves an interest group
+  // instead of joining one.
+  void LeaveInterestGroupAndFlush(const url::Origin& owner,
+                                  const std::string& name) {
+    mojo::Remote<blink::mojom::RestrictedInterestGroupStore> interest_service;
+    InterestGroupServiceImpl::CreateMojoService(
+        web_contents()->GetMainFrame(),
+        interest_service.BindNewPipeAndPassReceiver());
+
+    interest_service->LeaveInterestGroup(owner, name);
+    interest_service.FlushForTesting();
+  }
+
+  // Helper to create a valid interest group with only an origin and name. All
+  // URLs are nullopt.
+  blink::mojom::InterestGroupPtr CreateInterestGroup() {
+    auto interest_group = blink::mojom::InterestGroup::New();
+    interest_group->expiry =
+        base::Time::Now() + base::TimeDelta::FromSeconds(300);
+    interest_group->name = kInterestGroupName;
+    interest_group->owner = kOriginA;
+    return interest_group;
+  }
+
+ protected:
+  const GURL kUrlA = GURL("https://a.test/");
+  const url::Origin kOriginA = url::Origin::Create(kUrlA);
+  const GURL kUrlB = GURL("https://b.test/");
+  const url::Origin kOriginB = url::Origin::Create(kUrlB);
+
+  base::test::ScopedFeatureList feature_list_;
+
+  AllowInterestGroupContentBrowserClient content_browser_client_;
+  ContentBrowserClient* old_content_browser_client_ = nullptr;
+  InterestGroupManager* storage_;
+};
+
+// Check basic success case.
+TEST_F(InterestGroupServiceTest, JoinInterestGroupBasic) {
+  blink::mojom::InterestGroupPtr interest_group = CreateInterestGroup();
+  JoinInterestGroupAndFlush(std::move(interest_group));
+  EXPECT_EQ(1, GetJoinCount(kOriginA, kInterestGroupName));
+
+  // Several tests assume interest group API are also allowed on kOriginB, so
+  // make sure that's enabled correctly.
+  NavigateAndCommit(kUrlB);
+  interest_group = CreateInterestGroup();
+  interest_group->owner = kOriginB;
+  JoinInterestGroupAndFlush(std::move(interest_group));
+  EXPECT_EQ(1, GetJoinCount(kOriginB, kInterestGroupName));
+}
+
+// Non-HTTPS interest groups should be rejected.
+TEST_F(InterestGroupServiceTest, JoinInterestGroupOriginNotHttps) {
+  // Note that the ContentBrowserClient allows URLs based on hosts, not origins,
+  // so it should not block this URL. Instead, it should run into the HTTPS
+  // check.
+  const GURL kHttpUrlA = GURL("http://a.test/");
+  const url::Origin kHttpOriginA = url::Origin::Create(kHttpUrlA);
+  NavigateAndCommit(kHttpUrlA);
+  blink::mojom::InterestGroupPtr interest_group = CreateInterestGroup();
+  interest_group->owner = kHttpOriginA;
+  JoinInterestGroupAndFlush(std::move(interest_group));
+  EXPECT_EQ(0, GetJoinCount(kHttpOriginA, kInterestGroupName));
+}
+
+// Test one origin trying to add an interest group for another.
+TEST_F(InterestGroupServiceTest, JoinInterestGroupWrongOwnerOrigin) {
+  blink::mojom::InterestGroupPtr interest_group = CreateInterestGroup();
+  interest_group->owner = kOriginB;
+  JoinInterestGroupAndFlush(std::move(interest_group));
+  // Interest group should not be added for either origin.
+  EXPECT_EQ(0, GetJoinCount(kOriginA, kInterestGroupName));
+  EXPECT_EQ(0, GetJoinCount(kOriginB, kInterestGroupName));
+}
+
+// Check that `bidding_url`, `update_url`, and `trusted_bidding_signals_url`
+// must be same-origin and HTTPS.
+//
+// Ad URLs do not have to be same origin, so they're checked in a different
+// test.
+TEST_F(InterestGroupServiceTest, JoinInterestGroupUrlValidation) {
+  // Nested URL schemes, like filesystem URLs, are the only cases where a URL
+  // being same origin with an HTTPS origin does not imply the URL itself is
+  // also HTTPS.
+  const GURL kFileSystemUrl = GURL("filesystem:https://a.test/foo");
+  EXPECT_EQ(kOriginA, url::Origin::Create(kFileSystemUrl));
+
+  const GURL kRejectedUrls[] = {
+      // HTTP URLs are rejected: They're both the wrong scheme, and
+      // cross-origin.
+      GURL("http://a.test/"),
+      // Cross origin URLs are rejected.
+      GURL("https://b.test/"),
+      // URL with different ports are cross-origin.
+      GURL("https://a.test:1234/"),
+      // URLs with opaque origins are cross-origin.
+      GURL("data://text/html,payload"),
+
+      // filesystem URLs are rejected, even if they're same-origin with the page
+      // origin.
+      kFileSystemUrl,
+
+      // URLs with user/ports are rejected.
+      GURL("https://user:pass@a.test/"),
+      // References also aren't allowed, as they aren't sent over HTTP.
+      GURL("https://a.test/#foopy"),
+  };
+
+  for (const GURL& rejected_url : kRejectedUrls) {
+    SCOPED_TRACE(rejected_url.spec());
+
+    // Test `bidding_url`.
+    blink::mojom::InterestGroupPtr interest_group = CreateInterestGroup();
+    interest_group->bidding_url = rejected_url;
+    JoinInterestGroupAndFlush(std::move(interest_group));
+    EXPECT_EQ(0, GetJoinCount(kOriginA, kInterestGroupName));
+
+    // Test `update_url`.
+    interest_group = CreateInterestGroup();
+    interest_group->update_url = rejected_url;
+    JoinInterestGroupAndFlush(std::move(interest_group));
+    EXPECT_EQ(0, GetJoinCount(kOriginA, kInterestGroupName));
+
+    // Test `trusted_bidding_signals_url`.
+    interest_group = CreateInterestGroup();
+    interest_group->trusted_bidding_signals_url = rejected_url;
+    JoinInterestGroupAndFlush(std::move(interest_group));
+    EXPECT_EQ(0, GetJoinCount(kOriginA, kInterestGroupName));
+  }
+
+  // `trusted_bidding_signals_url` also can't include query strings.
+  blink::mojom::InterestGroupPtr interest_group = CreateInterestGroup();
+  interest_group->trusted_bidding_signals_url = GURL("https://a.test/?query");
+  JoinInterestGroupAndFlush(std::move(interest_group));
+  EXPECT_EQ(0, GetJoinCount(kOriginA, kInterestGroupName));
+
+  // Test success case for each field.
+
+  // Test `bidding_url`.
+  interest_group = CreateInterestGroup();
+  interest_group->bidding_url = GURL("https://a.test/foo/bar.js?query");
+  JoinInterestGroupAndFlush(std::move(interest_group));
+  EXPECT_EQ(1, GetJoinCount(kOriginA, kInterestGroupName));
+
+  // Test `update_url`.
+  interest_group = CreateInterestGroup();
+  interest_group->update_url = GURL("https://a.test/foo/bar.js?query");
+  JoinInterestGroupAndFlush(std::move(interest_group));
+  EXPECT_EQ(2, GetJoinCount(kOriginA, kInterestGroupName));
+
+  // Test `trusted_bidding_signals_url`.
+  interest_group = CreateInterestGroup();
+  interest_group->trusted_bidding_signals_url =
+      GURL("https://a.test/foo/bar.js");
+  JoinInterestGroupAndFlush(std::move(interest_group));
+  EXPECT_EQ(3, GetJoinCount(kOriginA, kInterestGroupName));
+}
+
+TEST_F(InterestGroupServiceTest, JoinInterestGroupAdUrlValidation) {
+  const struct {
+    bool expect_allowed;
+    GURL url;
+  } kTestCases[] = {
+      // Same origin URLs are allowed.
+      {true, GURL("https://a.test:1234/foo")},
+
+      // Cross origin URLs are allowed, as long as they're HTTPS.
+      {true, GURL("https://b.test/")},
+      {true, GURL("https://a.test:1234/")},
+
+      // URLs with the wrong scheme are rejected.
+      {false, GURL("http://a.test/")},
+      {false, GURL("data://text/html,payload")},
+      {false, GURL("filesystem:https://a.test/foo")},
+
+      // URLs with user/ports are rejected.
+      {false, GURL("https://user:pass@a.test/")},
+
+      // References also aren't allowed, as they aren't sent over HTTP.
+      {false, GURL("https://a.test/#foopy")},
+  };
+
+  for (const auto& test_case : kTestCases) {
+    SCOPED_TRACE(test_case.url.spec());
+
+    // Add an InterestGroup with the test cases's URL as the only ad's URL.
+    int initial_join_count = GetJoinCount(kOriginA, kInterestGroupName);
+    blink::mojom::InterestGroupPtr interest_group = CreateInterestGroup();
+    interest_group->ads.emplace();
+    interest_group->ads->emplace_back(blink::mojom::InterestGroupAd::New(
+        test_case.url, absl::nullopt /* metadata */));
+    JoinInterestGroupAndFlush(std::move(interest_group));
+    if (test_case.expect_allowed) {
+      EXPECT_EQ(initial_join_count + 1,
+                GetJoinCount(kOriginA, kInterestGroupName));
+    } else {
+      EXPECT_EQ(initial_join_count, GetJoinCount(kOriginA, kInterestGroupName));
+    }
+
+    // Add an InterestGroup with the test cases's URL as the second ad's URL.
+    initial_join_count = GetJoinCount(kOriginA, kInterestGroupName);
+    interest_group = CreateInterestGroup();
+    interest_group->ads.emplace();
+    interest_group->ads->emplace_back(blink::mojom::InterestGroupAd::New(
+        GURL("https://a.test/"), absl::nullopt /* metadata */));
+    interest_group->ads->emplace_back(blink::mojom::InterestGroupAd::New(
+        test_case.url, absl::nullopt /* metadata */));
+    JoinInterestGroupAndFlush(std::move(interest_group));
+    if (test_case.expect_allowed) {
+      EXPECT_EQ(initial_join_count + 1,
+                GetJoinCount(kOriginA, kInterestGroupName));
+    } else {
+      EXPECT_EQ(initial_join_count, GetJoinCount(kOriginA, kInterestGroupName));
+    }
+  }
+}
+
+// Check that cross-origin leave interest group operations don't work.
+TEST_F(InterestGroupServiceTest, LeaveInterestGroupWrongOwnerOrigin) {
+  // https://a.test/ joins an interest group.
+  JoinInterestGroupAndFlush(CreateInterestGroup());
+  EXPECT_EQ(1, GetJoinCount(kOriginA, kInterestGroupName));
+
+  // https://b.test/ cannot leave https://a.test/'s interest group.
+  NavigateAndCommit(kUrlB);
+  LeaveInterestGroupAndFlush(kOriginA, kInterestGroupName);
+  EXPECT_EQ(1, GetJoinCount(kOriginA, kInterestGroupName));
+
+  // https://a.test/ can leave its own the interest group.
+  NavigateAndCommit(GURL("https://a.test/"));
+  LeaveInterestGroupAndFlush(kOriginA, kInterestGroupName);
+  EXPECT_EQ(0, GetJoinCount(kOriginA, kInterestGroupName));
+}
+
+}  // namespace content
diff --git a/content/browser/native_io/native_io_quota_client.h b/content/browser/native_io/native_io_quota_client.h
index b3fccb7f..2b3b57d 100644
--- a/content/browser/native_io/native_io_quota_client.h
+++ b/content/browser/native_io/native_io_quota_client.h
@@ -6,7 +6,7 @@
 #define CONTENT_BROWSER_NATIVE_IO_NATIVE_IO_QUOTA_CLIENT_H_
 
 #include "base/sequence_checker.h"
-#include "components/services/storage/public/mojom/quota_client.mojom.h"
+#include "components/services/storage/public/cpp/origin_quota_client.h"
 #include "content/common/content_export.h"
 #include "storage/browser/quota/quota_client_type.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
@@ -19,7 +19,7 @@
 // Integrates NativeIO with the quota system.
 //
 // Each NativeIOManager owns exactly one NativeIOQuotaClient.
-class CONTENT_EXPORT NativeIOQuotaClient : public storage::mojom::QuotaClient {
+class CONTENT_EXPORT NativeIOQuotaClient : public storage::OriginQuotaClient {
  public:
   explicit NativeIOQuotaClient(NativeIOManager* manager);
   ~NativeIOQuotaClient() override;
@@ -27,7 +27,7 @@
   NativeIOQuotaClient(const NativeIOQuotaClient&) = delete;
   NativeIOQuotaClient& operator=(const NativeIOQuotaClient&) = delete;
 
-  // QuotaClient.
+  // storage::OriginQuotaClient method overrides.
   void GetOriginUsage(const url::Origin& origin,
                       blink::mojom::StorageType type,
                       GetOriginUsageCallback callback) override;
diff --git a/content/browser/push_messaging/push_messaging_router.cc b/content/browser/push_messaging/push_messaging_router.cc
index e10134a..f9c85a8 100644
--- a/content/browser/push_messaging/push_messaging_router.cc
+++ b/content/browser/push_messaging/push_messaging_router.cc
@@ -179,7 +179,7 @@
     if (payload)
       event_metadata["Payload"] = *payload;
     devtools_context->LogBackgroundServiceEventOnCoreThread(
-        service_worker->registration_id(), service_worker->origin(),
+        service_worker->registration_id(), service_worker->key().origin(),
         DevToolsBackgroundService::kPushMessaging, "Push event dispatched",
         message_id, event_metadata);
   }
@@ -245,7 +245,7 @@
       push_event_status !=
           blink::mojom::PushEventStatus::SERVICE_WORKER_ERROR) {
     devtools_context->LogBackgroundServiceEventOnCoreThread(
-        service_worker->registration_id(), service_worker->origin(),
+        service_worker->registration_id(), service_worker->key().origin(),
         DevToolsBackgroundService::kPushMessaging, "Push event completed",
         message_id, {{"Status", status_description}});
   }
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index ef41602..54821d8 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -913,8 +913,11 @@
       std::move(commit_params), browser_initiated,
       false /* from_begin_navigation */, false /* is_for_commit */, frame_entry,
       entry, std::move(navigation_ui_data), std::move(blob_url_loader_factory),
-      mojo::NullAssociatedRemote(), rfh_restored_from_back_forward_cache,
-      initiator_process_id, was_opener_suppressed));
+      mojo::NullAssociatedRemote(),
+      nullptr /* prefetched_signed_exchange_cache */,
+      nullptr /* web_bundle_handle_tracker */,
+      rfh_restored_from_back_forward_cache, initiator_process_id,
+      was_opener_suppressed));
 
   return navigation_request;
 }
@@ -1014,13 +1017,11 @@
       entry,
       nullptr,  // navigation_ui_data
       std::move(blob_url_loader_factory), std::move(navigation_client),
+      std::move(prefetched_signed_exchange_cache),
+      std::move(web_bundle_handle_tracker),
       nullptr,  // rfh_restored_from_back_forward_cache
       initiator_process_id,
       /*was_opener_suppressed=*/false));
-  navigation_request->prefetched_signed_exchange_cache_ =
-      std::move(prefetched_signed_exchange_cache);
-  navigation_request->web_bundle_handle_tracker_ =
-      std::move(web_bundle_handle_tracker);
 
   return navigation_request;
 }
@@ -1123,6 +1124,8 @@
       nullptr /* frame_navigation_entry */, nullptr /* navigation_entry */,
       nullptr /* navigation_ui_data */, nullptr /* blob_url_loader_factory */,
       mojo::NullAssociatedRemote(),
+      nullptr /* prefetched_signed_exchange_cache */,
+      nullptr /* web_bundle_handle_tracker */,
       nullptr /* rfh_restored_from_back_forward_cache */,
       ChildProcessHost::kInvalidUniqueID /* initiator_process_id */,
       false /* was_opener_suppressed */));
@@ -1163,6 +1166,9 @@
     std::unique_ptr<NavigationUIData> navigation_ui_data,
     scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
     mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client,
+    scoped_refptr<PrefetchedSignedExchangeCache>
+        prefetched_signed_exchange_cache,
+    std::unique_ptr<WebBundleHandleTracker> web_bundle_handle_tracker,
     RenderFrameHostImpl* rfh_restored_from_back_forward_cache,
     int initiator_process_id,
     bool was_opener_suppressed)
@@ -1186,6 +1192,9 @@
       navigation_entry_offset_(
           EstimateHistoryOffset(frame_tree_node_->navigator().controller(),
                                 common_params_->should_replace_current_entry)),
+      prefetched_signed_exchange_cache_(
+          std::move(prefetched_signed_exchange_cache)),
+      web_bundle_handle_tracker_(std::move(web_bundle_handle_tracker)),
       rfh_restored_from_back_forward_cache_(
           rfh_restored_from_back_forward_cache),
       // Store the old RenderFrameHost id at request creation to be used later.
diff --git a/content/browser/renderer_host/navigation_request.h b/content/browser/renderer_host/navigation_request.h
index d63388a..7a78c2d 100644
--- a/content/browser/renderer_host/navigation_request.h
+++ b/content/browser/renderer_host/navigation_request.h
@@ -911,6 +911,9 @@
       std::unique_ptr<NavigationUIData> navigation_ui_data,
       scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
       mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client,
+      scoped_refptr<PrefetchedSignedExchangeCache>
+          prefetched_signed_exchange_cache,
+      std::unique_ptr<WebBundleHandleTracker> web_bundle_handle_tracker,
       RenderFrameHostImpl* rfh_restored_from_back_forward_cache,
       int initiator_process_id,
       bool was_opener_suppressed);
@@ -1530,7 +1533,7 @@
 
   // Tracks navigations within a Web Bundle file. Used when WebBundles feature
   // is enabled or TrustableWebBundleFileUrl switch is set.
-  std::unique_ptr<WebBundleHandleTracker> web_bundle_handle_tracker_;
+  const std::unique_ptr<WebBundleHandleTracker> web_bundle_handle_tracker_;
 
   // Timing information of loading for the navigation. Used for recording UMAs.
   NavigationHandleTiming navigation_handle_timing_;
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 7549a07..7eb606f 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -830,15 +830,18 @@
   if (renderer_side_origin.opaque() && browser_side_origin.opaque())
     return;
 
-  // Navigating to file://localhost/... on windows causes `browser_side_origin`
-  // and `renderer_side_origin` to be different (file://localhost/ vs file:///).
-  // In particular, without the following block the test
+#if defined(OS_WIN)
+  // TODO(https://crbug.com/1214098): Navigating to a test-crafted
+  // (GURL::ReplaceComponents-crafted) file://localhost/C:/dir/file.txt URL will
+  // fail to round-trip the URL causing `browser_side_origin` and
+  // `renderer_side_origin` to be different (file://localhost/ vs file:///). In
+  // particular, without the following "if" statement the test
   // ContentSecurityPolicyBrowserTest.FileURLs fails.
-  if ((browser_side_origin.opaque() == renderer_side_origin.opaque()) &&
-      browser_side_origin.scheme() == url::kFileScheme &&
+  if (browser_side_origin.scheme() == url::kFileScheme &&
       renderer_side_origin.scheme() == url::kFileScheme) {
     return;
   }
+#endif
 
   DCHECK_EQ(browser_side_origin, renderer_side_origin)
       << "; navigation_request->GetURL() = " << navigation_request->GetURL();
diff --git a/content/browser/service_worker/embedded_worker_instance.cc b/content/browser/service_worker/embedded_worker_instance.cc
index cf913d6e..117e82da 100644
--- a/content/browser/service_worker/embedded_worker_instance.cc
+++ b/content/browser/service_worker/embedded_worker_instance.cc
@@ -48,6 +48,7 @@
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/mojom/loader/url_loader_factory_bundle.mojom.h"
 #include "third_party/blink/public/mojom/renderer_preference_watcher.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom.h"
@@ -1137,7 +1138,7 @@
       return;
 
     rph->BindCacheStorage(coep, std::move(coep_reporter_remote),
-                          owner_version_->origin(), std::move(receiver));
+                          owner_version_->key().origin(), std::move(receiver));
   }
   pending_cache_storage_receivers_.clear();
 }
diff --git a/content/browser/service_worker/service_worker_client_utils.cc b/content/browser/service_worker/service_worker_client_utils.cc
index e66da05..dd68eb4 100644
--- a/content/browser/service_worker/service_worker_client_utils.cc
+++ b/content/browser/service_worker/service_worker_client_utils.cc
@@ -433,10 +433,7 @@
   if (options->include_uncontrolled) {
     if (controller->context()) {
       for (auto it = controller->context()->GetClientContainerHostIterator(
-               // TODO(crbug.com/1199077): Update this when ServiceWorkerVersion
-               // implements StorageKey.
-               blink::StorageKey(controller->origin()),
-               false /* include_reserved_clients */,
+               controller->key(), false /* include_reserved_clients */,
                false /* include_back_forward_cached_clients */);
            !it->IsAtEnd(); it->Advance()) {
         AddNonWindowClient(it->GetContainerHost(), options->client_type,
@@ -478,10 +475,7 @@
   if (options->include_uncontrolled) {
     if (controller->context()) {
       for (auto it = controller->context()->GetClientContainerHostIterator(
-               // TODO(crbug.com/1199077): Update this when ServiceWorkerVersion
-               // implements StorageKey.
-               blink::StorageKey(controller->origin()),
-               false /* include_reserved_clients */,
+               controller->key(), false /* include_reserved_clients */,
                false /* include_back_forward_cached_clients */);
            !it->IsAtEnd(); it->Advance()) {
         AddWindowClient(it->GetContainerHost(), &clients_info);
@@ -507,6 +501,8 @@
                      std::move(clients)));
 }
 
+// TODO(crbug.com/1199077): Update `sane_origin` to StorageKey once
+// ServiceWorkerContainerHost implements StorageKey.
 void DidGetExecutionReadyClient(
     const base::WeakPtr<ServiceWorkerContextCore>& context,
     const std::string& client_uuid,
@@ -553,6 +549,7 @@
 
 void OpenWindow(const GURL& url,
                 const GURL& script_url,
+                const blink::StorageKey& key,
                 int worker_id,
                 int worker_process_id,
                 const base::WeakPtr<ServiceWorkerContextCore>& context,
@@ -564,12 +561,13 @@
       base::BindOnce(
           &OpenWindowOnUI, url, script_url, worker_id, worker_process_id,
           base::WrapRefCounted(context->wrapper()), type,
-          base::BindOnce(&DidNavigate, context, script_url.GetOrigin(),
+          base::BindOnce(&DidNavigate, context, script_url.GetOrigin(), key,
                          std::move(callback))));
 }
 
 void NavigateClient(const GURL& url,
                     const GURL& script_url,
+                    const blink::StorageKey& key,
                     int process_id,
                     int frame_id,
                     const base::WeakPtr<ServiceWorkerContextCore>& context,
@@ -580,7 +578,7 @@
       FROM_HERE, BrowserThread::UI,
       base::BindOnce(
           &NavigateClientOnUI, url, script_url, process_id, frame_id,
-          base::BindOnce(&DidNavigate, context, script_url.GetOrigin(),
+          base::BindOnce(&DidNavigate, context, script_url.GetOrigin(), key,
                          std::move(callback))));
 }
 
@@ -638,6 +636,7 @@
 
 void DidNavigate(const base::WeakPtr<ServiceWorkerContextCore>& context,
                  const GURL& origin,
+                 const blink::StorageKey& key,
                  NavigationCallback callback,
                  int render_process_id,
                  int render_frame_id) {
@@ -658,10 +657,7 @@
 
   for (std::unique_ptr<ServiceWorkerContextCore::ContainerHostIterator> it =
            context->GetClientContainerHostIterator(
-               // TODO(crbug.com/1199077): Update this when ServiceWorkerVersion
-               // implements StorageKey.
-               blink::StorageKey(url::Origin::Create(origin)),
-               true /* include_reserved_clients */,
+               key, true /* include_reserved_clients */,
                false /* include_back_forward_cached_clients */);
        !it->IsAtEnd(); it->Advance()) {
     ServiceWorkerContainerHost* container_host = it->GetContainerHost();
diff --git a/content/browser/service_worker/service_worker_client_utils.h b/content/browser/service_worker/service_worker_client_utils.h
index 2674fde..e156d72 100644
--- a/content/browser/service_worker/service_worker_client_utils.h
+++ b/content/browser/service_worker/service_worker_client_utils.h
@@ -13,6 +13,10 @@
 
 class GURL;
 
+namespace blink {
+class StorageKey;
+}  // namespace blink
+
 namespace content {
 
 class ServiceWorkerContainerHost;
@@ -42,24 +46,26 @@
 void FocusWindowClient(ServiceWorkerContainerHost* container_host,
                        ClientCallback callback);
 
-// Opens a new window and navigates it to |url|. |callback| is called with the
-// window's client information on completion. If |type| is NEW_TAB_WINDOW, we
-// will open a new app window, if there is an app installed that has |url| in
+// Opens a new window and navigates it to `url`. `callback` is called with the
+// window's client information on completion. If `type` is NEW_TAB_WINDOW, we
+// will open a new app window, if there is an app installed that has `url` in
 // its scope. What an "installed app" is depends on the embedder of content. In
 // Chrome's case, it is an installed Progressive Web App. If there is no such
 // app, we will open a new foreground tab instead.
 void OpenWindow(const GURL& url,
                 const GURL& script_url,
+                const blink::StorageKey& key,
                 int worker_id,
                 int worker_process_id,
                 const base::WeakPtr<ServiceWorkerContextCore>& context,
                 WindowType type,
                 NavigationCallback callback);
 
-// Navigates the client specified by |process_id| and |frame_id| to |url|.
-// |callback| is called with the client information on completion.
+// Navigates the client specified by `process_id` and `frame_id` to `url`.
+// `callback` is called with the client information on completion.
 void NavigateClient(const GURL& url,
                     const GURL& script_url,
+                    const blink::StorageKey& key,
                     int process_id,
                     int frame_id,
                     const base::WeakPtr<ServiceWorkerContextCore>& context,
@@ -76,12 +82,15 @@
                 blink::mojom::ServiceWorkerClientQueryOptionsPtr options,
                 blink::mojom::ServiceWorkerHost::GetClientsCallback callback);
 
-// Finds the provider host for |origin| in |context| then uses
-// |render_process_id| and |render_process_host| to create a relevant
-// blink::mojom::ServiceWorkerClientInfo struct and calls |callback| with it.
+// Finds the provider host for `key` in `context` then uses
+// `render_process_id` and `render_process_host` to create a relevant
+// blink::mojom::ServiceWorkerClientInfo struct and calls `callback` with it.
 // Must be called on the core thread.
+// TODO(crbug.com/1199077): Remove `origin` once DidGetExecutionReadyClient
+// implements StorageKey.
 void DidNavigate(const base::WeakPtr<ServiceWorkerContextCore>& context,
                  const GURL& origin,
+                 const blink::StorageKey& key,
                  NavigationCallback callback,
                  int render_process_id,
                  int render_frame_id);
diff --git a/content/browser/service_worker/service_worker_context_wrapper.cc b/content/browser/service_worker/service_worker_context_wrapper.cc
index e7b77f29..428d514f 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.cc
+++ b/content/browser/service_worker/service_worker_context_wrapper.cc
@@ -544,7 +544,7 @@
       GetAllLiveVersionInfo();
   for (const ServiceWorkerVersionInfo& info : live_version_info) {
     ServiceWorkerVersion* version = GetLiveVersion(info.version_id);
-    if (version && version->origin() == origin) {
+    if (version && version->key().origin() == origin) {
       return version->GetExternalRequestCountForTest();  // IN-TEST
     }
   }
@@ -808,7 +808,7 @@
   std::vector<ServiceWorkerVersionInfo> live_versions = GetAllLiveVersionInfo();
   for (const ServiceWorkerVersionInfo& info : live_versions) {
     ServiceWorkerVersion* version = GetLiveVersion(info.version_id);
-    if (version && version->origin() == origin)
+    if (version && version->key().origin() == origin)
       version->StopWorker(base::DoNothing());
   }
 }
diff --git a/content/browser/service_worker/service_worker_host.cc b/content/browser/service_worker/service_worker_host.cc
index dead583..4a53662 100644
--- a/content/browser/service_worker/service_worker_host.cc
+++ b/content/browser/service_worker/service_worker_host.cc
@@ -23,6 +23,7 @@
 #include "mojo/public/cpp/bindings/message.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/messaging/message_port_channel.h"
+#include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_client.mojom.h"
 
 namespace content {
@@ -64,7 +65,8 @@
   container_host_->set_service_worker_host(this);
   container_host_->UpdateUrls(
       version_->script_url(),
-      net::SiteForCookies::FromUrl(version_->script_url()), version_->origin());
+      net::SiteForCookies::FromUrl(version_->script_url()),
+      version_->key().origin());
 }
 
 ServiceWorkerHost::~ServiceWorkerHost() {
@@ -97,7 +99,7 @@
   RunOrPostTaskOnThread(
       FROM_HERE, BrowserThread::UI,
       base::BindOnce(&CreateWebTransportConnectorImpl, worker_process_id_,
-                     version_->origin(), GetNetworkIsolationKey(),
+                     version_->key().origin(), GetNetworkIsolationKey(),
                      std::move(receiver)));
 }
 
@@ -114,7 +116,7 @@
   // top-level browsing context, which shouldn't be use for ServiceWorkers used
   // in iframes.
   return net::NetworkIsolationKey::ToDoUseTopFrameOriginAsWell(
-      version_->origin());
+      version_->key().origin());
 }
 
 const base::UnguessableToken& ServiceWorkerHost::GetReportingSource() const {
diff --git a/content/browser/service_worker/service_worker_new_script_fetcher.cc b/content/browser/service_worker/service_worker_new_script_fetcher.cc
index e0426de..157e8b9 100644
--- a/content/browser/service_worker/service_worker_new_script_fetcher.cc
+++ b/content/browser/service_worker/service_worker_new_script_fetcher.cc
@@ -10,6 +10,7 @@
 #include "content/public/browser/global_request_id.h"
 #include "mojo/public/cpp/system/data_pipe_utils.h"
 #include "services/network/public/cpp/url_loader_completion_status.h"
+#include "third_party/blink/public/common/storage_key/storage_key.h"
 
 namespace content {
 
@@ -90,9 +91,9 @@
   }
   network::ResourceRequest request =
       service_worker_loader_helpers::CreateRequestForServiceWorkerScript(
-          version_->script_url(), version_->origin(), /*is_main_script=*/true,
-          version_->script_type(), *fetch_client_settings_object_,
-          *browser_context);
+          version_->script_url(), version_->key().origin(),
+          /*is_main_script=*/true, version_->script_type(),
+          *fetch_client_settings_object_, *browser_context);
   // Request SSLInfo. It will be persisted in service worker storage and may be
   // used by ServiceWorkerMainResourceLoader for navigations handled by this
   // service worker.
diff --git a/content/browser/service_worker/service_worker_quota_client.h b/content/browser/service_worker/service_worker_quota_client.h
index 60b50a64..315a540c 100644
--- a/content/browser/service_worker/service_worker_quota_client.h
+++ b/content/browser/service_worker/service_worker_quota_client.h
@@ -10,7 +10,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/sequence_checker.h"
 #include "base/thread_annotations.h"
-#include "components/services/storage/public/mojom/quota_client.mojom.h"
+#include "components/services/storage/public/cpp/origin_quota_client.h"
 #include "content/common/content_export.h"
 #include "storage/browser/quota/quota_client_type.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
@@ -22,7 +22,7 @@
 namespace content {
 class ServiceWorkerContextCore;
 
-class ServiceWorkerQuotaClient : public storage::mojom::QuotaClient {
+class ServiceWorkerQuotaClient : public storage::OriginQuotaClient {
  public:
   // `context` must outlive this instance. This is true because `context` owns
   // this instance.
@@ -40,7 +40,7 @@
     context_ = &new_context;
   }
 
-  // storage::mojom::QuotaClient:
+  // storage::OriginQuotaClient override methods.
   void GetOriginUsage(const url::Origin& origin,
                       blink::mojom::StorageType type,
                       GetOriginUsageCallback callback) override;
diff --git a/content/browser/service_worker/service_worker_registration_unittest.cc b/content/browser/service_worker/service_worker_registration_unittest.cc
index 62cd2b18..0197da4a 100644
--- a/content/browser/service_worker/service_worker_registration_unittest.cc
+++ b/content/browser/service_worker/service_worker_registration_unittest.cc
@@ -784,9 +784,9 @@
  protected:
   void SetUp() override {
     ServiceWorkerRegistrationTest::SetUp();
-    mojo::SetDefaultProcessErrorHandler(base::AdaptCallbackForRepeating(
-        base::BindOnce(&ServiceWorkerRegistrationObjectHostTest::OnMojoError,
-                       base::Unretained(this))));
+    mojo::SetDefaultProcessErrorHandler(base::BindRepeating(
+        &ServiceWorkerRegistrationObjectHostTest::OnMojoError,
+        base::Unretained(this)));
   }
 
   void TearDown() override {
diff --git a/content/browser/service_worker/service_worker_registry.cc b/content/browser/service_worker/service_worker_registry.cc
index 6237fa5e..cc870bf 100644
--- a/content/browser/service_worker/service_worker_registry.cc
+++ b/content/browser/service_worker/service_worker_registry.cc
@@ -173,6 +173,8 @@
                      std::move(callback)));
 }
 
+// TODO(http://crbug.com/1199077): This function doesn't need to take in a
+// StorageKey, it can get it from ServiceWorkerRegistration. Clean up.
 void ServiceWorkerRegistry::CreateNewVersion(
     scoped_refptr<ServiceWorkerRegistration> registration,
     const GURL& script_url,
diff --git a/content/browser/service_worker/service_worker_test_utils.cc b/content/browser/service_worker/service_worker_test_utils.cc
index 13ac2aa..5022aa5 100644
--- a/content/browser/service_worker/service_worker_test_utils.cc
+++ b/content/browser/service_worker/service_worker_test_utils.cc
@@ -385,8 +385,6 @@
   return registration;
 }
 
-// TODO(http://crbug.com/1199077): Update after ServiceWorkerVersion supports
-// StorageKey.
 scoped_refptr<ServiceWorkerVersion> CreateNewServiceWorkerVersion(
     ServiceWorkerRegistry* registry,
     scoped_refptr<ServiceWorkerRegistration> registration,
diff --git a/content/browser/service_worker/service_worker_version.cc b/content/browser/service_worker/service_worker_version.cc
index 561ca59..907cc25 100644
--- a/content/browser/service_worker/service_worker_version.cc
+++ b/content/browser/service_worker/service_worker_version.cc
@@ -161,6 +161,7 @@
 
 void DidShowPaymentHandlerWindow(
     const GURL& url,
+    const blink::StorageKey& key,
     const base::WeakPtr<ServiceWorkerContextCore>& context,
     blink::mojom::ServiceWorkerHost::OpenPaymentHandlerWindowCallback callback,
     bool success,
@@ -168,7 +169,7 @@
     int render_frame_id) {
   if (success) {
     service_worker_client_utils::DidNavigate(
-        context, url.GetOrigin(),
+        context, url.GetOrigin(), key,
         base::BindOnce(&OnOpenWindowFinished, std::move(callback)),
         render_process_id, render_frame_id);
   } else {
@@ -244,7 +245,7 @@
     : version_id_(version_id),
       registration_id_(registration->id()),
       script_url_(script_url),
-      origin_(registration->key().origin()),
+      key_(registration->key()),
       scope_(registration->scope()),
       script_type_(script_type),
       fetch_handler_existence_(FetchHandlerExistence::UNKNOWN),
@@ -384,7 +385,7 @@
   DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
   ServiceWorkerVersionInfo info(
       running_status(), status(), fetch_handler_existence(), script_url(),
-      scope(), origin(), registration_id(), version_id(),
+      scope(), key().origin(), registration_id(), version_id(),
       embedded_worker()->process_id(), embedded_worker()->thread_id(),
       embedded_worker()->worker_devtools_agent_route_id(), ukm_source_id());
   for (const auto& controllee : controllee_map_) {
@@ -467,7 +468,7 @@
   // get associated with it in
   // ServiceWorkerHost::CompleteStartWorkerPreparation.
   context_->registry()->FindRegistrationForId(
-      registration_id_, blink::StorageKey(origin_),
+      registration_id_, key_,
       base::BindOnce(
           &ServiceWorkerVersion::DidEnsureLiveRegistrationForStartWorker,
           weak_factory_.GetWeakPtr(), purpose, status_,
@@ -585,7 +586,7 @@
   if (!context_)
     return;
   context_->registry()->FindRegistrationForId(
-      registration_id_, blink::StorageKey(origin_),
+      registration_id_, key_,
       base::BindOnce(&ServiceWorkerVersion::FoundRegistrationForUpdate,
                      weak_factory_.GetWeakPtr()));
 }
@@ -1398,6 +1399,8 @@
 
 void ServiceWorkerVersion::OpenNewTab(const GURL& url,
                                       OpenNewTabCallback callback) {
+  // TODO(crbug.com/1199077): After StorageKey implements partitioning update
+  // this to reject with InvalidAccessError if key_ is partitioned.
   OpenWindow(url, service_worker_client_utils::WindowType::NEW_TAB_WINDOW,
              std::move(callback));
 }
@@ -1413,7 +1416,8 @@
     return;
   }
 
-  if (!url.is_valid() || !url::Origin::Create(url).IsSameOriginWith(origin_)) {
+  if (!url.is_valid() ||
+      !url::Origin::Create(url).IsSameOriginWith(key_.origin())) {
     mojo::ReportBadMessage(
         "Received PaymentRequestEvent#openWindow() request for a cross-origin "
         "URL.");
@@ -1423,7 +1427,7 @@
 
   PaymentHandlerSupport::ShowPaymentHandlerWindow(
       url, context_.get(),
-      base::BindOnce(&DidShowPaymentHandlerWindow, url, context_),
+      base::BindOnce(&DidShowPaymentHandlerWindow, url, key_, context_),
       base::BindOnce(
           &ServiceWorkerVersion::OpenWindow, weak_factory_.GetWeakPtr(), url,
           service_worker_client_utils::WindowType::PAYMENT_HANDLER_WINDOW),
@@ -1579,7 +1583,7 @@
   }
 
   service_worker_client_utils::NavigateClient(
-      url, script_url_, container_host->process_id(),
+      url, script_url_, key_, container_host->process_id(),
       container_host->frame_id(), context_,
       base::BindOnce(&DidNavigateClient, std::move(callback), url));
 }
@@ -1675,7 +1679,7 @@
   }
 
   service_worker_client_utils::OpenWindow(
-      url, script_url_, embedded_worker_->embedded_worker_id(),
+      url, script_url_, key_, embedded_worker_->embedded_worker_id(),
       embedded_worker_->process_id(), context_, type,
       base::BindOnce(&OnOpenWindowFinished, std::move(callback)));
 }
diff --git a/content/browser/service_worker/service_worker_version.h b/content/browser/service_worker/service_worker_version.h
index 3804f76e..baf2b913 100644
--- a/content/browser/service_worker/service_worker_version.h
+++ b/content/browser/service_worker/service_worker_version.h
@@ -52,6 +52,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/origin_trials/trial_token_validator.h"
 #include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
+#include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/mojom/loader/fetch_client_settings_object.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/controller_service_worker.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker.mojom.h"
@@ -204,7 +205,7 @@
   int64_t version_id() const { return version_id_; }
   int64_t registration_id() const { return registration_id_; }
   const GURL& script_url() const { return script_url_; }
-  const url::Origin& origin() const { return origin_; }
+  const blink::StorageKey& key() const { return key_; }
   const GURL& scope() const { return scope_; }
   blink::mojom::ScriptType script_type() const { return script_type_; }
   EmbeddedWorkerStatus running_status() const {
@@ -915,10 +916,10 @@
   const int64_t version_id_;
   const int64_t registration_id_;
   const GURL script_url_;
-  // |origin_| is computed from |scope_|. Warning: The |script_url_|'s origin
-  // and |origin_| may be different in some scenarios e.g.
+  // `key_` is computed from `scope_`. Warning: The `script_url_`'s origin
+  // and `key_` may be different in some scenarios e.g.
   // --disable-web-security.
-  const url::Origin origin_;
+  const blink::StorageKey key_;
   const GURL scope_;
   // A service worker has an associated type which is either
   // "classic" or "module". Unless stated otherwise, it is "classic".
diff --git a/content/browser/service_worker/service_worker_version_unittest.cc b/content/browser/service_worker/service_worker_version_unittest.cc
index bf16205..15f1c48 100644
--- a/content/browser/service_worker/service_worker_version_unittest.cc
+++ b/content/browser/service_worker/service_worker_version_unittest.cc
@@ -102,7 +102,7 @@
         helper_->context()->registry(), registration_.get(),
         GURL("https://www.example.com/test/service_worker.js"),
         blink::mojom::ScriptType::kClassic);
-    EXPECT_EQ(url::Origin::Create(scope_), version_->origin());
+    EXPECT_EQ(url::Origin::Create(scope_), version_->key().origin());
     std::vector<storage::mojom::ServiceWorkerResourceRecordPtr> records;
     records.push_back(WriteToDiskCacheWithIdSync(
         helper_->context()->GetStorageControl(), version_->script_url(), 10,
diff --git a/content/browser/tracing/startup_tracing_browsertest.cc b/content/browser/tracing/startup_tracing_browsertest.cc
index 44c9db4..e02f2840 100644
--- a/content/browser/tracing/startup_tracing_browsertest.cc
+++ b/content/browser/tracing/startup_tracing_browsertest.cc
@@ -337,7 +337,9 @@
         testing::Values(OutputLocation::kDirectoryWithDefaultBasename)));
 
 // TODO(crbug.com/1197278): Failing on Windows 7 debug builds.
-#if defined(OS_WIN) && DCHECK_IS_ON()
+// TODO(crbug.com/1211717): Failing on Linux TSAN builds.
+#if (defined(OS_WIN) && DCHECK_IS_ON()) || \
+    (defined(OS_LINUX) && defined(THREAD_SANITIZER))
 #define MAYBE_StopOnUIThread DISABLED_StopOnUIThread
 #else
 #define MAYBE_StopOnUIThread StopOnUIThread
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index 841b1e2..850bd0e 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -1860,6 +1860,85 @@
   EXPECT_EQ("1\n2\n3\n4", test_delegate.last_message());
 }
 
+class WebContentsImplBrowserTestWithDifferentOriginSubframeDialogSuppression
+    : public WebContentsImplBrowserTest {
+ public:
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kSuppressDifferentOriginSubframeJSDialogs);
+    WebContentsImplBrowserTest::SetUp();
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(
+    WebContentsImplBrowserTestWithDifferentOriginSubframeDialogSuppression,
+    OriginTrialDisablesSuppression) {
+  // Generated with tools/origin_trials/generate_token.py --expire-days 5000
+  // http://allowdialogs.test:9999
+  // DisableDifferentOriginSubframeDialogSuppression
+  std::string origin_trial_token =
+      "AwcVbxsLRzn8IXBNaeCrK7amKs211vWkv5oCYo+gssujKeltEtcIaQD+O9hWO+"
+      "GT3WtKUFhEA30+QuqyU3TUvQkAAAB/"
+      "eyJvcmlnaW4iOiAiaHR0cDovL2FsbG93ZGlhbG9ncy50ZXN0Ojk5OTkiLCAiZmVhdHVyZSI6"
+      "ICJEaXNhYmxlRGlmZmVyZW50T3JpZ2luU3ViZnJhbWVEaWFsb2dTdXBwcmVzc2lvbiIsICJl"
+      "eHBpcnkiOiAyMDU0NzU5MTcyfQ==";
+  GURL origin_trial_url = GURL("http://allowdialogs.test:9999");
+
+  WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
+  TestWCDelegateForDialogsAndFullscreen test_delegate(wc);
+
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  EXPECT_TRUE(NavigateToURL(
+      shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
+  EXPECT_TRUE(WaitForLoadStop(wc));
+
+  FrameTreeNode* root = wc->GetFrameTree()->root();
+  ASSERT_EQ(0U, root->child_count());
+
+  std::string script =
+      "var iframe = document.createElement('iframe');"
+      "document.body.appendChild(iframe);";
+  EXPECT_TRUE(ExecJs(root->current_frame_host(), script));
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+
+  ASSERT_EQ(1U, root->child_count());
+  FrameTreeNode* frame = root->child_at(0);
+  ASSERT_NE(nullptr, frame);
+
+  // We need to use a URLLoaderInterceptor for the subframe since origin trial
+  // is origin bound, and embedded test server randomizes ports.
+  URLLoaderInterceptor interceptor(base::BindLambdaForTesting(
+      [&](URLLoaderInterceptor::RequestParams* params) {
+        if (params->url_request.url != origin_trial_url)
+          return false;
+        URLLoaderInterceptor::WriteResponse(
+            "HTTP/1.1 200 OK\n"
+            "Content-type: text/html\n"
+            "Origin-Trial: " +
+                origin_trial_token + "\n\n",
+            "", params->client.get());
+        return true;
+      }));
+
+  // A dialog from the subframe.
+  // Navigate the subframe to the site with the origin trial meta tag.
+  EXPECT_TRUE(NavigateToURLFromRenderer(frame, origin_trial_url));
+  EXPECT_TRUE(WaitForLoadStop(wc));
+
+  // A dialog from the subframe, which should show even though different origin
+  // subframe dialog suppression is enabled, since the origin trial overrides
+  // it.
+  std::string alert_location = "alert(document.location)";
+  test_delegate.WillWaitForDialog();
+  EXPECT_TRUE(ExecJs(frame->current_frame_host(), alert_location));
+  test_delegate.Wait();
+  EXPECT_EQ(origin_trial_url, GURL(test_delegate.last_message()));
+}
+
 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
                        CreateWebContentsWithRendererProcess) {
   ASSERT_TRUE(embedded_test_server()->Start());
diff --git a/content/public/renderer/content_renderer_client.cc b/content/public/renderer/content_renderer_client.cc
index 4e168c7..32dbc4d 100644
--- a/content/public/renderer/content_renderer_client.cc
+++ b/content/public/renderer/content_renderer_client.cc
@@ -252,4 +252,8 @@
   return absl::nullopt;
 }
 
+void ContentRendererClient::AppendContentSecurityPolicy(
+    const blink::WebURL& url,
+    blink::WebVector<blink::WebContentSecurityPolicyHeader>* csp) {}
+
 }  // namespace content
diff --git a/content/public/renderer/content_renderer_client.h b/content/public/renderer/content_renderer_client.h
index 6016334..bb987f3a 100644
--- a/content/public/renderer/content_renderer_client.h
+++ b/content/public/renderer/content_renderer_client.h
@@ -50,6 +50,7 @@
 class WebServiceWorkerContextProxy;
 class WebURL;
 class WebURLRequest;
+struct WebContentSecurityPolicyHeader;
 struct WebPluginParams;
 struct WebURLError;
 enum class ProtocolHandlerSecurityLevel;
@@ -398,6 +399,12 @@
   virtual absl::optional<::media::AudioRendererAlgorithmParameters>
   GetAudioRendererAlgorithmParameters(
       ::media::AudioParameters audio_parameters);
+
+  // Appends to `csp`, the default CSP which should be applied to the given
+  // `url`. This allows the embedder to customize the applied CSP.
+  virtual void AppendContentSecurityPolicy(
+      const blink::WebURL& url,
+      blink::WebVector<blink::WebContentSecurityPolicyHeader>* csp);
 };
 
 }  // namespace content
diff --git a/content/renderer/renderer_blink_platform_impl.cc b/content/renderer/renderer_blink_platform_impl.cc
index 2b4cf709..5ccc942 100644
--- a/content/renderer/renderer_blink_platform_impl.cc
+++ b/content/renderer/renderer_blink_platform_impl.cc
@@ -1123,4 +1123,10 @@
   return std::make_unique<V8ValueConverterImpl>();
 }
 
+void RendererBlinkPlatformImpl::AppendContentSecurityPolicy(
+    const blink::WebURL& url,
+    blink::WebVector<blink::WebContentSecurityPolicyHeader>* csp) {
+  GetContentClient()->renderer()->AppendContentSecurityPolicy(url, csp);
+}
+
 }  // namespace content
diff --git a/content/renderer/renderer_blink_platform_impl.h b/content/renderer/renderer_blink_platform_impl.h
index 8275928..d2e127a 100644
--- a/content/renderer/renderer_blink_platform_impl.h
+++ b/content/renderer/renderer_blink_platform_impl.h
@@ -41,6 +41,7 @@
 class WebGraphicsContext3DProvider;
 class WebSecurityOrigin;
 enum class ProtocolHandlerSecurityLevel;
+struct WebContentSecurityPolicyHeader;
 }  // namespace blink
 
 namespace gpu {
@@ -254,6 +255,9 @@
   SkBitmap* GetSadPageBitmap() override;
   std::unique_ptr<blink::WebV8ValueConverter> CreateWebV8ValueConverter()
       override;
+  void AppendContentSecurityPolicy(
+      const blink::WebURL& url,
+      blink::WebVector<blink::WebContentSecurityPolicyHeader>* csp) override;
 
   // Tells this platform that the renderer is locked to a site (i.e., a scheme
   // plus eTLD+1, such as https://google.com), or to a more specific origin.
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index f4dd1039..a1debdc 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1959,6 +1959,7 @@
     "../browser/indexed_db/mock_mojo_indexed_db_database_callbacks.h",
     "../browser/interest_group/auction_runner_unittest.cc",
     "../browser/interest_group/auction_url_loader_factory_proxy_unittest.cc",
+    "../browser/interest_group/interest_group_service_impl_unittest.cc",
     "../browser/interest_group/interest_group_storage_unittest.cc",
     "../browser/loader/cors_origin_pattern_setter_unittest.cc",
     "../browser/loader/file_url_loader_factory_unittest.cc",
diff --git a/content/test/data/accessibility/event/same-page-link-navigation-expected-uia-win.txt b/content/test/data/accessibility/event/same-page-link-navigation-expected-uia-win.txt
index 8a55ab0b..3347120 100644
--- a/content/test/data/accessibility/event/same-page-link-navigation-expected-uia-win.txt
+++ b/content/test/data/accessibility/event/same-page-link-navigation-expected-uia-win.txt
@@ -1,6 +1,6 @@
-ActiveTextPositionChanged on role=link, name=Section 1 content
+ActiveTextPositionChanged on role=document
 Invoke_Invoked on role=link, name=Section 1
 ScrollVerticalScrollPercent changed on role=document
 === Start Continuation ===
-ActiveTextPositionChanged on role=link, name=Section 3 content
-Invoke_Invoked on role=link, name=Section 3
+ActiveTextPositionChanged on role=document
+Invoke_Invoked on role=link, name=Section 3
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/same-page-link-navigation-expected-win.txt b/content/test/data/accessibility/event/same-page-link-navigation-expected-win.txt
index 0567d76..b1f6014 100644
--- a/content/test/data/accessibility/event/same-page-link-navigation-expected-win.txt
+++ b/content/test/data/accessibility/event/same-page-link-navigation-expected-win.txt
@@ -1,3 +1,3 @@
-EVENT_SYSTEM_SCROLLINGSTART on <a> role=ROLE_SYSTEM_LINK name="Section 1 content"
+EVENT_SYSTEM_SCROLLINGSTART on <a> role=ROLE_SYSTEM_GROUPING
 === Start Continuation ===
-EVENT_SYSTEM_SCROLLINGSTART on <a> role=ROLE_SYSTEM_LINK name="Section 3 content"
+EVENT_SYSTEM_SCROLLINGSTART on <a> role=ROLE_SYSTEM_GROUPING
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/a-name-expected-auralinux.txt b/content/test/data/accessibility/html/a-name-expected-auralinux.txt
index 8194f69..709c1ca 100644
--- a/content/test/data/accessibility/html/a-name-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/a-name-expected-auralinux.txt
@@ -1,5 +1,5 @@
 [document web]
-++[link] name='named anchor'
+++[section]
 ++++[static] name='named anchor'
 ++[link] name='both a named anchor and a link'
-++++[static] name='both a named anchor and a link'
+++++[static] name='both a named anchor and a link'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/a-name-expected-blink.txt b/content/test/data/accessibility/html/a-name-expected-blink.txt
index a1f23e9..3337a26 100644
--- a/content/test/data/accessibility/html/a-name-expected-blink.txt
+++ b/content/test/data/accessibility/html/a-name-expected-blink.txt
@@ -1,9 +1,9 @@
 rootWebArea
 ++genericContainer ignored
 ++++genericContainer ignored
-++++++anchor name='named anchor'
+++++++genericContainer
 ++++++++staticText name='named anchor'
 ++++++++++inlineTextBox name='named anchor'
 ++++++link name='both a named anchor and a link'
 ++++++++staticText name='both a named anchor and a link'
-++++++++++inlineTextBox name='both a named anchor and a link'
+++++++++++inlineTextBox name='both a named anchor and a link'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/a-name-expected-mac.txt b/content/test/data/accessibility/html/a-name-expected-mac.txt
index 0d05f4eb..f18b1bf 100644
--- a/content/test/data/accessibility/html/a-name-expected-mac.txt
+++ b/content/test/data/accessibility/html/a-name-expected-mac.txt
@@ -1,5 +1,5 @@
 AXWebArea
-++AXGroup AXTitle='named anchor'
+++AXGroup
 ++++AXStaticText AXValue='named anchor'
 ++AXLink AXDescription='both a named anchor and a link'
-++++AXStaticText AXValue='both a named anchor and a link'
+++++AXStaticText AXValue='both a named anchor and a link'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/a-name-expected-uia-win.txt b/content/test/data/accessibility/html/a-name-expected-uia-win.txt
index a0d54cc..1b9ef17 100644
--- a/content/test/data/accessibility/html/a-name-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/a-name-expected-uia-win.txt
@@ -1,5 +1,5 @@
 Document
-++Hyperlink Name='named anchor'
+++Group IsControlElement=false
 ++++Text Name='named anchor'
 ++Hyperlink Name='both a named anchor and a link'
-++++Text Name='both a named anchor and a link' IsControlElement=false
+++++Text Name='both a named anchor and a link' IsControlElement=false
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/a-name-expected-win.txt b/content/test/data/accessibility/html/a-name-expected-win.txt
index 28fef56..4c2453c 100644
--- a/content/test/data/accessibility/html/a-name-expected-win.txt
+++ b/content/test/data/accessibility/html/a-name-expected-win.txt
@@ -1,5 +1,5 @@
 ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
-++ROLE_SYSTEM_LINK name='named anchor'
+++IA2_ROLE_SECTION
 ++++ROLE_SYSTEM_STATICTEXT name='named anchor'
 ++ROLE_SYSTEM_LINK name='both a named anchor and a link' FOCUSABLE LINKED
-++++ROLE_SYSTEM_STATICTEXT name='both a named anchor and a link' LINKED
+++++ROLE_SYSTEM_STATICTEXT name='both a named anchor and a link' LINKED
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/continuations-expected-auralinux.txt b/content/test/data/accessibility/html/continuations-expected-auralinux.txt
index 73427c7..23dcfc44 100644
--- a/content/test/data/accessibility/html/continuations-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/continuations-expected-auralinux.txt
@@ -10,11 +10,11 @@
 ++++[section]
 ++++++[static] name='After'
 ++[panel] name='Group 3'
-++++[link] name='Before'
+++++[section]
 ++++++[section]
 ++++++++[static] name='Before'
 ++++[section]
-++++++[link] name='Ever '
+++++++[section]
 ++++++++[static] name='Ever '
 ++++++[link] name='After'
 ++++++++[static] name='After'
@@ -31,4 +31,4 @@
 ++++[section]
 ++++++[static] name='Wow, another block!'
 ++++[static] name='More italic and bold text'
-++++[static] name=' More italic text'
+++++[static] name=' More italic text'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/continuations-expected-blink.txt b/content/test/data/accessibility/html/continuations-expected-blink.txt
index e862d93..eed57a0 100644
--- a/content/test/data/accessibility/html/continuations-expected-blink.txt
+++ b/content/test/data/accessibility/html/continuations-expected-blink.txt
@@ -17,12 +17,12 @@
 ++++++++++staticText display='block' name='After'
 ++++++++++++inlineTextBox display='block' name='After'
 ++++++group display='block' name='Group 3' isLineBreakingObject=true
-++++++++anchor display='inline' name='Before'
+++++++++genericContainer display='inline'
 ++++++++++genericContainer display='block' isLineBreakingObject=true
 ++++++++++++staticText display='block' name='Before'
 ++++++++++++++inlineTextBox display='block' name='Before'
 ++++++++genericContainer display='block' isLineBreakingObject=true
-++++++++++anchor display='inline' name='Ever '
+++++++++++genericContainer display='inline'
 ++++++++++++staticText display='inline' name='Ever '
 ++++++++++++++inlineTextBox display='inline' name='Ever '
 ++++++++++link display='inline' name='After'
diff --git a/content/test/data/accessibility/html/continuations-parser-splits-markup-expected-blink.txt b/content/test/data/accessibility/html/continuations-parser-splits-markup-expected-blink.txt
new file mode 100644
index 0000000..6829cda
--- /dev/null
+++ b/content/test/data/accessibility/html/continuations-parser-splits-markup-expected-blink.txt
@@ -0,0 +1,15 @@
+rootWebArea isLineBreakingObject=true
+++genericContainer ignored isLineBreakingObject=true
+++++genericContainer ignored isLineBreakingObject=true
+++++++genericContainer ignored isLineBreakingObject=true
+++++++++genericContainer className='copied-element' display='inline'
+++++++++++genericContainer className='before' display='block' isLineBreakingObject=true
+++++++++++++staticText display='block' name='Before'
+++++++++++++++inlineTextBox display='block' name='Before'
+++++++++genericContainer className='ever' display='block' isLineBreakingObject=true
+++++++++++genericContainer className='copied-element' display='inline'
+++++++++++++staticText display='inline' name='Ever '
+++++++++++++++inlineTextBox display='inline' name='Ever '
+++++++++++link className='after' display='inline' name='After'
+++++++++++++staticText display='inline' name='After'
+++++++++++++++inlineTextBox display='inline' name='After'
diff --git a/content/test/data/accessibility/html/continuations-parser-splits-markup.html b/content/test/data/accessibility/html/continuations-parser-splits-markup.html
new file mode 100644
index 0000000..b630ac7
--- /dev/null
+++ b/content/test/data/accessibility/html/continuations-parser-splits-markup.html
@@ -0,0 +1,34 @@
+<!--
+@BLINK-ALLOW:display*
+@BLINK-ALLOW:tag*
+@BLINK-ALLOW:class*
+@BLINK-ALLOW:isLineBreakingObject*
+-->
+<!-- The parser does not actually create the structure below, but rather
+  duplicates <a name="a">! both as a first child of the <span>, and a first
+  child of <div#ever>. The final DOM looks like:
+<div>
+  <span>
+    <a name="a" class="copied-element">
+      <div id="before" class="before">Before</div>
+    </a>
+    <div id="ever" class="ever">
+      <a name="a" class="copied-element">
+        Ever
+      </a>
+      <a href="#" class="after">After</a>
+    </div>
+  </span>
+</div>
+-->
+<div>
+  <span>
+    <a name="a" class="copied-element">
+      <div id="before" class="before">Before</div>
+      <div id="ever" class="ever">
+        Ever
+        <a href="#" class="after">After</a>
+      </div>
+    </a>
+  </span>
+</div>
diff --git a/content/test/data/accessibility/html/in-page-links-expected-auralinux.txt b/content/test/data/accessibility/html/in-page-links-expected-auralinux.txt
index 03dc4387..943a572 100644
--- a/content/test/data/accessibility/html/in-page-links-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/in-page-links-expected-auralinux.txt
@@ -17,13 +17,13 @@
 ++[link] name='Paragraph with content'
 ++++[static] name='Paragraph with content'
 ++[paragraph]
-++++[link]
+++++[section]
 ++++[static] name='After empty anchor'
 ++[paragraph]
-++++[link] name='Anchor with content'
+++++[section]
 ++++++[static] name='Anchor with content'
 ++[paragraph]
-++++[link] name='Anchor with ID'
+++++[section]
 ++++++[static] name='Anchor with ID'
 ++[paragraph]
 ++++[section]
@@ -32,4 +32,4 @@
 ++++[section]
 ++++++[static] name='Span with content'
 ++[paragraph]
-++++[static] name='Paragraph with content'
+++++[static] name='Paragraph with content'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/in-page-links-expected-blink.txt b/content/test/data/accessibility/html/in-page-links-expected-blink.txt
index fd0f501d..b415386 100644
--- a/content/test/data/accessibility/html/in-page-links-expected-blink.txt
+++ b/content/test/data/accessibility/html/in-page-links-expected-blink.txt
@@ -1,17 +1,17 @@
 rootWebArea
 ++genericContainer ignored
 ++++genericContainer ignored
-++++++link name='Empty anchor' defaultActionVerb=jump inPageLinkTargetId=anchor
+++++++link name='Empty anchor' defaultActionVerb=jump inPageLinkTargetId=genericContainer
 ++++++++staticText name='Empty anchor' defaultActionVerb=clickAncestor
 ++++++++++inlineTextBox name='Empty anchor'
 ++++++staticText name=' '
 ++++++++inlineTextBox name=' '
-++++++link name='Anchor with content' defaultActionVerb=jump inPageLinkTargetId=anchor
+++++++link name='Anchor with content' defaultActionVerb=jump inPageLinkTargetId=genericContainer
 ++++++++staticText name='Anchor with content' defaultActionVerb=clickAncestor
 ++++++++++inlineTextBox name='Anchor with content'
 ++++++staticText name=' '
 ++++++++inlineTextBox name=' '
-++++++link name='Anchor with ID' defaultActionVerb=jump inPageLinkTargetId=anchor
+++++++link name='Anchor with ID' defaultActionVerb=jump inPageLinkTargetId=genericContainer
 ++++++++staticText name='Anchor with ID' defaultActionVerb=clickAncestor
 ++++++++++inlineTextBox name='Anchor with ID'
 ++++++staticText name=' '
@@ -30,15 +30,15 @@
 ++++++++staticText name='Paragraph with content' defaultActionVerb=clickAncestor
 ++++++++++inlineTextBox name='Paragraph with content'
 ++++++paragraph
-++++++++anchor
+++++++++genericContainer
 ++++++++staticText name='After empty anchor'
 ++++++++++inlineTextBox name='After empty anchor'
 ++++++paragraph
-++++++++anchor name='Anchor with content'
+++++++++genericContainer
 ++++++++++staticText name='Anchor with content'
 ++++++++++++inlineTextBox name='Anchor with content'
 ++++++paragraph
-++++++++anchor name='Anchor with ID'
+++++++++genericContainer
 ++++++++++staticText name='Anchor with ID'
 ++++++++++++inlineTextBox name='Anchor with ID'
 ++++++paragraph
@@ -51,4 +51,4 @@
 ++++++++++++inlineTextBox name='Span with content'
 ++++++paragraph
 ++++++++staticText name='Paragraph with content'
-++++++++++inlineTextBox name='Paragraph with content'
+++++++++++inlineTextBox name='Paragraph with content'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/in-page-links-expected-mac.txt b/content/test/data/accessibility/html/in-page-links-expected-mac.txt
index e93e6636..7a159d8 100644
--- a/content/test/data/accessibility/html/in-page-links-expected-mac.txt
+++ b/content/test/data/accessibility/html/in-page-links-expected-mac.txt
@@ -20,10 +20,10 @@
 ++++AXGroup
 ++++AXStaticText AXValue='After empty anchor'
 ++AXGroup
-++++AXGroup AXTitle='Anchor with content'
+++++AXGroup
 ++++++AXStaticText AXValue='Anchor with content'
 ++AXGroup
-++++AXGroup AXTitle='Anchor with ID'
+++++AXGroup
 ++++++AXStaticText AXValue='Anchor with ID'
 ++AXGroup
 ++++AXGroup
@@ -32,4 +32,4 @@
 ++++AXGroup
 ++++++AXStaticText AXValue='Span with content'
 ++AXGroup
-++++AXStaticText AXValue='Paragraph with content'
+++++AXStaticText AXValue='Paragraph with content'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/in-page-links-expected-uia-win.txt b/content/test/data/accessibility/html/in-page-links-expected-uia-win.txt
index be7be40..2f6f3fc 100644
--- a/content/test/data/accessibility/html/in-page-links-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/in-page-links-expected-uia-win.txt
@@ -17,13 +17,13 @@
 ++Hyperlink Name='Paragraph with content'
 ++++Text Name='Paragraph with content' IsControlElement=false
 ++Group IsControlElement=false
-++++Hyperlink IsControlElement=false
+++++Group IsControlElement=false
 ++++Text Name='After empty anchor'
 ++Group IsControlElement=false
-++++Hyperlink Name='Anchor with content'
+++++Group IsControlElement=false
 ++++++Text Name='Anchor with content'
 ++Group IsControlElement=false
-++++Hyperlink Name='Anchor with ID'
+++++Group IsControlElement=false
 ++++++Text Name='Anchor with ID'
 ++Group IsControlElement=false
 ++++Group IsControlElement=false
diff --git a/content/test/data/accessibility/html/in-page-links-expected-win.txt b/content/test/data/accessibility/html/in-page-links-expected-win.txt
index e91e432..c49a99db 100644
--- a/content/test/data/accessibility/html/in-page-links-expected-win.txt
+++ b/content/test/data/accessibility/html/in-page-links-expected-win.txt
@@ -17,13 +17,13 @@
 ++ROLE_SYSTEM_LINK name='Paragraph with content' FOCUSABLE LINKED
 ++++ROLE_SYSTEM_STATICTEXT name='Paragraph with content' LINKED
 ++IA2_ROLE_PARAGRAPH
-++++ROLE_SYSTEM_LINK
+++++IA2_ROLE_SECTION
 ++++ROLE_SYSTEM_STATICTEXT name='After empty anchor'
 ++IA2_ROLE_PARAGRAPH
-++++ROLE_SYSTEM_LINK name='Anchor with content'
+++++IA2_ROLE_SECTION
 ++++++ROLE_SYSTEM_STATICTEXT name='Anchor with content'
 ++IA2_ROLE_PARAGRAPH
-++++ROLE_SYSTEM_LINK name='Anchor with ID'
+++++IA2_ROLE_SECTION
 ++++++ROLE_SYSTEM_STATICTEXT name='Anchor with ID'
 ++IA2_ROLE_PARAGRAPH
 ++++IA2_ROLE_SECTION
diff --git a/content/test/data/accessibility/html/svg-expected-blink.txt b/content/test/data/accessibility/html/svg-expected-blink.txt
index efe48f9..1d54bcd 100644
--- a/content/test/data/accessibility/html/svg-expected-blink.txt
+++ b/content/test/data/accessibility/html/svg-expected-blink.txt
@@ -1,7 +1,7 @@
 rootWebArea
 ++genericContainer ignored
 ++++genericContainer
-++++++svgRoot name='svg'
+++++++svgRoot name='svg' tooltip='SVG Title Tag'
 ++++++++genericContainer
 ++++++++++staticText name='Test'
 ++++++++++++inlineTextBox name='Test'
diff --git a/content/test/data/accessibility/html/svg.html b/content/test/data/accessibility/html/svg.html
index 9b5328d..0d069dc 100644
--- a/content/test/data/accessibility/html/svg.html
+++ b/content/test/data/accessibility/html/svg.html
@@ -1,4 +1,5 @@
 <!--
+@BLINK-ALLOW:tooltip*
 @UIA-WIN-ALLOW:HelpText*
 @MAC-ALLOW:AXDescription='svg'
 -->
diff --git a/content/test/data/conversions/databases/version_6.sql b/content/test/data/conversions/databases/version_6.sql
index 190c2c0..6c9a6d2 100644
--- a/content/test/data/conversions/databases/version_6.sql
+++ b/content/test/data/conversions/databases/version_6.sql
@@ -30,4 +30,32 @@
 
 CREATE INDEX rate_limit_impression_id_idx ON rate_limits(impression_id);
 
+INSERT INTO impressions
+VALUES (1,
+        '9357e17751666f64',
+        'https://a.impression.test',
+        'https://conversion.test',
+        'https://report.test',
+        13245278349693988,
+        13247870349693988,
+        0,
+        1,
+        'https://conversion.test/',
+        0,
+        1,
+        3),
+       (2,
+        '9357e17751666f64',
+        'https://b.impression.test',
+        'https://conversion.test',
+        'https://report.test',
+        13245278349693988,
+        13247870349693988,
+        0,
+        1,
+        'https://conversion.test/',
+        0,
+        1,
+        4);
+
 COMMIT;
diff --git a/content/test/data/conversions/databases/version_7.sql b/content/test/data/conversions/databases/version_7.sql
new file mode 100644
index 0000000..a981811
--- /dev/null
+++ b/content/test/data/conversions/databases/version_7.sql
@@ -0,0 +1,35 @@
+PRAGMA foreign_keys=OFF;
+
+BEGIN TRANSACTION;
+
+CREATE TABLE impressions(impression_id INTEGER PRIMARY KEY,impression_data TEXT NOT NULL,impression_origin TEXT NOT NULL,conversion_origin TEXT NOT NULL,reporting_origin TEXT NOT NULL,impression_time INTEGER NOT NULL,expiry_time INTEGER NOT NULL,num_conversions INTEGER DEFAULT 0,active INTEGER DEFAULT 1,conversion_destination TEXT NOT NULL,source_type INTEGER NOT NULL,attributed_truthfully INTEGER NOT NULL,priority INTEGER NOT NULL,impression_site TEXT NOT NULL);
+
+CREATE TABLE conversions (conversion_id INTEGER PRIMARY KEY, impression_id INTEGER, conversion_data TEXT NOT NULL, conversion_time INTEGER NOT NULL, report_time INTEGER NOT NULL);
+
+CREATE TABLE rate_limits(rate_limit_id INTEGER PRIMARY KEY,attribution_type INTEGER NOT NULL,impression_id INTEGER NOT NULL,impression_site TEXT NOT NULL,impression_origin TEXT NOT NULL,conversion_destination TEXT NOT NULL,conversion_origin TEXT NOT NULL,conversion_time INTEGER NOT NULL);
+
+CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY, value LONGVARCHAR);
+
+INSERT INTO meta VALUES('mmap_status','-1');
+INSERT INTO meta VALUES('version','7');
+INSERT INTO meta VALUES('last_compatible_version','7');
+
+CREATE INDEX conversion_destination_idx ON impressions(active, conversion_destination, reporting_origin);
+
+CREATE INDEX impression_expiry_idx ON impressions(expiry_time);
+
+CREATE INDEX impression_origin_idx ON impressions(impression_origin);
+
+CREATE INDEX impression_site_idx ON impressions(active, impression_site, source_type);
+
+CREATE INDEX conversion_report_idx ON conversions(report_time);
+
+CREATE INDEX conversion_impression_id_idx ON conversions(impression_id);
+
+CREATE INDEX rate_limit_impression_site_type_idx ON rate_limits(attribution_type, conversion_destination, impression_site, conversion_time);
+
+CREATE INDEX rate_limit_conversion_time_idx ON rate_limits(conversion_time);
+
+CREATE INDEX rate_limit_impression_id_idx ON rate_limits(impression_id);
+
+COMMIT;
diff --git a/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
index f3b45557..625f03b 100644
--- a/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/gpu_process_expectations.txt
@@ -76,6 +76,9 @@
 [ fuchsia ] GpuProcess_feature_status_under_swiftshader [ Skip ]
 [ fuchsia ] GpuProcess_swiftshader_for_webgl [ Skip ]
 
+# Fuchsia flakes.
+crbug.com/1207726 [ fuchsia ] GpuProcess_canvas2d [ RetryOnFailure ]
+
 # SwiftShader GL does not work on CrOS, wait for it to be replaced by SwANGLE.
 crbug.com/1084794 [ chromeos ] GpuProcess_feature_status_under_swiftshader [ Skip ]
 crbug.com/1084794 [ chromeos ] GpuProcess_swiftshader_for_webgl [ Skip ]
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index 95f1cb3..fe15e2e 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -355,6 +355,7 @@
 crbug.com/1152619 [ win amd-0x7340 angle-d3d9 ] conformance/textures/misc/texture-npot-video.html [ Failure ]
 crbug.com/1152621 [ win amd-0x7340 angle-vulkan ] conformance/attribs/gl-vertexattribpointer-offsets.html [ Failure ]
 crbug.com/1152623 [ win amd-0x7340 angle-vulkan ] conformance/extensions/webgl-draw-buffers.html [ Failure ]
+crbug.com/1215624 [ win amd-0x7340 angle-d3d11 ] conformance/textures/video/tex-2d-rgba-rgba-unsigned_byte.html [ RetryOnFailure ]
 
 # Win / D3D9 failures
 # Skipping these two tests because they're causing assertion failures.
diff --git a/content/test/web_contents_observer_consistency_checker.cc b/content/test/web_contents_observer_consistency_checker.cc
index be23074..f5a030f 100644
--- a/content/test/web_contents_observer_consistency_checker.cc
+++ b/content/test/web_contents_observer_consistency_checker.cc
@@ -16,9 +16,11 @@
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/site_instance.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "content/public/test/test_utils.h"
 #include "net/base/net_errors.h"
 
 namespace content {
@@ -83,6 +85,7 @@
         << "not a current RenderFrameHost. Only the current frame should be "
         << "spawning children.";
   }
+  AddInputEventObserver(render_frame_host);
 }
 
 void WebContentsObserverConsistencyChecker::RenderFrameDeleted(
@@ -116,6 +119,7 @@
   // All players should have been paused by this point.
   for (const auto& id : active_media_players_)
     CHECK_NE(RenderFrameHost::FromID(id.frame_routing_id), render_frame_host);
+  RemoveInputEventObserver(render_frame_host);
 }
 
 void WebContentsObserverConsistencyChecker::RenderFrameHostChanged(
@@ -457,4 +461,63 @@
   return false;
 }
 
+class WebContentsObserverConsistencyChecker::TestInputEventObserver
+    : public RenderWidgetHost::InputEventObserver {
+ public:
+  explicit TestInputEventObserver(RenderFrameHost& render_frame_host)
+      : render_frame_host_wrapper_(&render_frame_host),
+        render_widget_host_(static_cast<RenderWidgetHostImpl*>(
+                                render_frame_host.GetRenderWidgetHost())
+                                ->GetWeakPtr()) {
+    render_widget_host_->AddInputEventObserver(this);
+  }
+  ~TestInputEventObserver() override {
+    if (render_widget_host_)
+      render_widget_host_->RemoveInputEventObserver(this);
+  }
+
+  void OnInputEvent(const blink::WebInputEvent&) override {
+    EnsureRenderFrameHostNotPrerendered();
+  }
+  void OnInputEventAck(blink::mojom::InputEventResultSource source,
+                       blink::mojom::InputEventResultState state,
+                       const blink::WebInputEvent&) override {
+    EnsureRenderFrameHostNotPrerendered();
+  }
+
+ private:
+  void EnsureRenderFrameHostNotPrerendered() {
+    if (render_frame_host_wrapper_.IsDestroyed())
+      return;
+
+    // TODO(crbug.com/1183639): Use RenderFrameHost::GetLifecycleState() if it
+    // is possible.
+    int frame_tree_node_id =
+        content::RenderFrameHost::GetFrameTreeNodeIdForRoutingId(
+            render_frame_host_wrapper_->GetProcess()->GetID(),
+            render_frame_host_wrapper_->GetRoutingID());
+    CHECK(!FrameTreeNode::GloballyFindByID(frame_tree_node_id)
+               ->frame_tree()
+               ->is_prerendering());
+  }
+
+  RenderFrameHostWrapper render_frame_host_wrapper_;
+  base::WeakPtr<RenderWidgetHostImpl> render_widget_host_;
+};
+
+void WebContentsObserverConsistencyChecker::AddInputEventObserver(
+    RenderFrameHost* render_frame_host) {
+  auto result = input_observer_map_.insert(std::make_pair(
+      render_frame_host,
+      std::make_unique<TestInputEventObserver>(*render_frame_host)));
+  CHECK(result.second);
+}
+
+void WebContentsObserverConsistencyChecker::RemoveInputEventObserver(
+    RenderFrameHost* render_frame_host) {
+  auto it = input_observer_map_.find(render_frame_host);
+  CHECK(it != input_observer_map_.end());
+  input_observer_map_.erase(it);
+}
+
 }  // namespace content
diff --git a/content/test/web_contents_observer_consistency_checker.h b/content/test/web_contents_observer_consistency_checker.h
index 0e4ea66..6525173 100644
--- a/content/test/web_contents_observer_consistency_checker.h
+++ b/content/test/web_contents_observer_consistency_checker.h
@@ -82,6 +82,8 @@
   void DidStopLoading() override;
 
  private:
+  class TestInputEventObserver;
+
   explicit WebContentsObserverConsistencyChecker(WebContents* web_contents);
 
   std::string Format(RenderFrameHost* render_frame_host);
@@ -93,6 +95,9 @@
   void EnsureStableParentValue(RenderFrameHost* render_frame_host);
   bool HasAnyChildren(RenderFrameHost* render_frame_host);
 
+  void AddInputEventObserver(RenderFrameHost* render_frame_host);
+  void RemoveInputEventObserver(RenderFrameHost* render_frame_host);
+
   std::map<int64_t, RenderFrameHost*> ready_to_commit_hosts_;
   std::set<GlobalRoutingID> current_hosts_;
   std::set<GlobalRoutingID> live_routes_;
@@ -101,6 +106,9 @@
   std::set<NavigationHandle*> ongoing_navigations_;
   std::vector<MediaPlayerId> active_media_players_;
 
+  std::map<RenderFrameHost*, std::unique_ptr<TestInputEventObserver>>
+      input_observer_map_;
+
   // Remembers parents to make sure RenderFrameHost::GetParent() never changes.
   std::map<GlobalRoutingID, GlobalRoutingID> parent_ids_;
 
diff --git a/crypto/ec_private_key.cc b/crypto/ec_private_key.cc
index e4feb04b..2807804 100644
--- a/crypto/ec_private_key.cc
+++ b/crypto/ec_private_key.cc
@@ -151,22 +151,16 @@
 bool ECPrivateKey::ExportRawPublicKey(std::string* output) const {
   OpenSSLErrStackTracer err_tracer(FROM_HERE);
 
-  // Export the x and y field elements as 32-byte, big-endian numbers. (This is
-  // the same as X9.62 uncompressed form without the leading 0x04 byte.)
+  std::array<uint8_t, 65> buf;
   EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(key_.get());
-  bssl::UniquePtr<BIGNUM> x(BN_new());
-  bssl::UniquePtr<BIGNUM> y(BN_new());
-  uint8_t buf[64];
-  if (!x || !y ||
-      !EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ec_key),
-                                           EC_KEY_get0_public_key(ec_key),
-                                           x.get(), y.get(), nullptr) ||
-      !BN_bn2bin_padded(buf, 32, x.get()) ||
-      !BN_bn2bin_padded(buf + 32, 32, y.get())) {
+  if (!EC_POINT_point2oct(EC_KEY_get0_group(ec_key),
+                          EC_KEY_get0_public_key(ec_key),
+                          POINT_CONVERSION_UNCOMPRESSED, buf.data(), buf.size(),
+                          /*ctx=*/nullptr)) {
     return false;
   }
 
-  output->assign(reinterpret_cast<const char*>(buf), sizeof(buf));
+  output->assign(buf.begin(), buf.end());
   return true;
 }
 
diff --git a/crypto/ec_private_key.h b/crypto/ec_private_key.h
index 3e26599..50d9877f 100644
--- a/crypto/ec_private_key.h
+++ b/crypto/ec_private_key.h
@@ -69,7 +69,8 @@
   // Exports the public key to an X.509 SubjectPublicKeyInfo block.
   bool ExportPublicKey(std::vector<uint8_t>* output) const;
 
-  // Exports the public key as an EC point in the uncompressed point format.
+  // Exports the public key as an EC point in X9.62 uncompressed form. Note this
+  // includes the leading 0x04 byte.
   bool ExportRawPublicKey(std::string* output) const;
 
  private:
diff --git a/crypto/ec_private_key_unittest.cc b/crypto/ec_private_key_unittest.cc
index cfec13c..c4662d09 100644
--- a/crypto/ec_private_key_unittest.cc
+++ b/crypto/ec_private_key_unittest.cc
@@ -97,12 +97,12 @@
       0x94, 0x2d, 0x4b, 0xcf, 0x72, 0x22, 0xc1,
   };
   static const uint8_t kRawPublicKey[] = {
-      0xe6, 0x2b, 0x69, 0xe2, 0xbf, 0x65, 0x9f, 0x97, 0xbe, 0x2f, 0x1e,
-      0x0d, 0x94, 0x8a, 0x4c, 0xd5, 0x97, 0x6b, 0xb7, 0xa9, 0x1e, 0x0d,
-      0x46, 0xfb, 0xdd, 0xa9, 0xa9, 0x1e, 0x9d, 0xdc, 0xba, 0x5a, 0x01,
-      0xe7, 0xd6, 0x97, 0xa8, 0x0a, 0x18, 0xf9, 0xc3, 0xc4, 0xa3, 0x1e,
-      0x56, 0xe2, 0x7c, 0x83, 0x48, 0xdb, 0x16, 0x1a, 0x1c, 0xf5, 0x1d,
-      0x7e, 0xf1, 0x94, 0x2d, 0x4b, 0xcf, 0x72, 0x22, 0xc1,
+      0x04, 0xe6, 0x2b, 0x69, 0xe2, 0xbf, 0x65, 0x9f, 0x97, 0xbe, 0x2f,
+      0x1e, 0x0d, 0x94, 0x8a, 0x4c, 0xd5, 0x97, 0x6b, 0xb7, 0xa9, 0x1e,
+      0x0d, 0x46, 0xfb, 0xdd, 0xa9, 0xa9, 0x1e, 0x9d, 0xdc, 0xba, 0x5a,
+      0x01, 0xe7, 0xd6, 0x97, 0xa8, 0x0a, 0x18, 0xf9, 0xc3, 0xc4, 0xa3,
+      0x1e, 0x56, 0xe2, 0x7c, 0x83, 0x48, 0xdb, 0x16, 0x1a, 0x1c, 0xf5,
+      0x1d, 0x7e, 0xf1, 0x94, 0x2d, 0x4b, 0xcf, 0x72, 0x22, 0xc1,
   };
 
   std::unique_ptr<crypto::ECPrivateKey> key =
@@ -242,12 +242,12 @@
       0xff, 0xab, 0x4d, 0xb5, 0x7e, 0x25, 0x3d,
   };
   static const uint8_t kOpenSSLRawPublicKey[] = {
-      0xb9, 0xda, 0x0d, 0x71, 0x60, 0xb3, 0x63, 0x28, 0x22, 0x67, 0xe7,
-      0xe0, 0xa3, 0xf8, 0x00, 0x8e, 0x4c, 0x89, 0xed, 0x31, 0x34, 0xf6,
-      0xdb, 0xc4, 0xfe, 0x0b, 0x5d, 0xe1, 0x11, 0x39, 0x49, 0xa6, 0x50,
-      0xa8, 0xe3, 0x4a, 0xc0, 0x40, 0x88, 0xb8, 0x38, 0x3f, 0x56, 0xfb,
-      0x33, 0x8d, 0xd4, 0x64, 0x91, 0xd6, 0x15, 0x77, 0x42, 0x27, 0xc5,
-      0xaa, 0x44, 0xff, 0xab, 0x4d, 0xb5, 0x7e, 0x25, 0x3d,
+      0x04, 0xb9, 0xda, 0x0d, 0x71, 0x60, 0xb3, 0x63, 0x28, 0x22, 0x67,
+      0xe7, 0xe0, 0xa3, 0xf8, 0x00, 0x8e, 0x4c, 0x89, 0xed, 0x31, 0x34,
+      0xf6, 0xdb, 0xc4, 0xfe, 0x0b, 0x5d, 0xe1, 0x11, 0x39, 0x49, 0xa6,
+      0x50, 0xa8, 0xe3, 0x4a, 0xc0, 0x40, 0x88, 0xb8, 0x38, 0x3f, 0x56,
+      0xfb, 0x33, 0x8d, 0xd4, 0x64, 0x91, 0xd6, 0x15, 0x77, 0x42, 0x27,
+      0xc5, 0xaa, 0x44, 0xff, 0xab, 0x4d, 0xb5, 0x7e, 0x25, 0x3d,
   };
 
   std::unique_ptr<crypto::ECPrivateKey> keypair_openssl(
diff --git a/device/fido/cable/fido_ble_uuids.cc b/device/fido/cable/fido_ble_uuids.cc
index f01930e..3dff9ed 100644
--- a/device/fido/cable/fido_ble_uuids.cc
+++ b/device/fido/cable/fido_ble_uuids.cc
@@ -24,4 +24,10 @@
     0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb,
 };
 
+const char kFIDOCableUUID128[] = "0000fff9-0000-1000-8000-00805f9b34fb";
+const uint8_t kFIDOCableUUID[16] = {
+    0x00, 0x00, 0xff, 0xf9, 0x00, 0x00, 0x10, 0x00,
+    0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb,
+};
+
 }  // namespace device
diff --git a/device/fido/cable/fido_ble_uuids.h b/device/fido/cable/fido_ble_uuids.h
index 856ccff6..71d4d8c 100644
--- a/device/fido/cable/fido_ble_uuids.h
+++ b/device/fido/cable/fido_ble_uuids.h
@@ -34,6 +34,11 @@
 // |kGoogleCableUUID128|, the UUID allocated to Google for caBLE adverts.
 COMPONENT_EXPORT(DEVICE_FIDO) extern const uint8_t kGoogleCableUUID[16];
 
+// kFIDOCableUUID128 is a 16-bit UUID assigned to FIDO that is used for
+// caBLEv2. (For now, caBLEv2 devices can also use |kGoogleCableUUID|.)
+COMPONENT_EXPORT(DEVICE_FIDO) extern const char kFIDOCableUUID128[];
+COMPONENT_EXPORT(DEVICE_FIDO) extern const uint8_t kFIDOCableUUID[16];
+
 }  // namespace device
 
 #endif  // DEVICE_FIDO_CABLE_FIDO_BLE_UUIDS_H_
diff --git a/device/fido/cable/fido_cable_discovery.cc b/device/fido/cable/fido_cable_discovery.cc
index 7cb1290..81e55e5 100644
--- a/device/fido/cable/fido_cable_discovery.cc
+++ b/device/fido/cable/fido_cable_discovery.cc
@@ -110,7 +110,11 @@
 
 static bool IsCableUUID(const CableEidArray& eid) {
   static_assert(sizeof(kGoogleCableUUID) == EXTENT(eid), "");
-  return memcmp(eid.data(), kGoogleCableUUID, sizeof(kGoogleCableUUID)) == 0;
+  static_assert(sizeof(kFIDOCableUUID) == EXTENT(eid), "");
+
+  return (memcmp(eid.data(), kGoogleCableUUID, sizeof(kGoogleCableUUID)) ==
+          0) ||
+         (memcmp(eid.data(), kFIDOCableUUID, sizeof(kFIDOCableUUID)) == 0);
 }
 
 }  // namespace
@@ -217,16 +221,23 @@
 
 // static
 const BluetoothUUID& FidoCableDiscovery::GoogleCableUUID() {
-  static const base::NoDestructor<BluetoothUUID> service_uuid(
-      kGoogleCableUUID128);
-  return *service_uuid;
+  static const base::NoDestructor<BluetoothUUID> kUUID(kGoogleCableUUID128);
+  return *kUUID;
+}
+
+const BluetoothUUID& FidoCableDiscovery::FIDOCableUUID() {
+  static const base::NoDestructor<BluetoothUUID> kUUID(kFIDOCableUUID128);
+  return *kUUID;
 }
 
 // static
 bool FidoCableDiscovery::IsCableDevice(const BluetoothDevice* device) {
-  const auto& uuid = GoogleCableUUID();
-  return base::Contains(device->GetServiceData(), uuid) ||
-         base::Contains(device->GetUUIDs(), uuid);
+  const auto& uuid1 = GoogleCableUUID();
+  const auto& uuid2 = FIDOCableUUID();
+  return base::Contains(device->GetServiceData(), uuid1) ||
+         base::Contains(device->GetUUIDs(), uuid1) ||
+         base::Contains(device->GetServiceData(), uuid2) ||
+         base::Contains(device->GetUUIDs(), uuid2);
 }
 
 void FidoCableDiscovery::OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter) {
@@ -560,8 +571,11 @@
 
 absl::optional<FidoCableDiscovery::V1DiscoveryDataAndEID>
 FidoCableDiscovery::GetCableDiscoveryData(const BluetoothDevice* device) {
-  const std::vector<uint8_t>* const service_data =
+  const std::vector<uint8_t>* service_data =
       device->GetServiceDataForUUID(GoogleCableUUID());
+  if (!service_data) {
+    service_data = device->GetServiceDataForUUID(FIDOCableUUID());
+  }
   absl::optional<CableEidArray> maybe_eid_from_service_data =
       MaybeGetEidFromServiceData(device);
   std::vector<CableEidArray> uuids = GetUUIDs(device);
diff --git a/device/fido/cable/fido_cable_discovery.h b/device/fido/cable/fido_cable_discovery.h
index 43696722..a73b2c6 100644
--- a/device/fido/cable/fido_cable_discovery.h
+++ b/device/fido/cable/fido_cable_discovery.h
@@ -78,6 +78,7 @@
   };
 
   static const BluetoothUUID& GoogleCableUUID();
+  static const BluetoothUUID& FIDOCableUUID();
   static bool IsCableDevice(const BluetoothDevice* device);
 
   // ResultDebugString returns a string containing a hex dump of |eid| and a
diff --git a/device/gamepad/raw_input_gamepad_device_win.cc b/device/gamepad/raw_input_gamepad_device_win.cc
index 29a6812..2ca55e2 100644
--- a/device/gamepad/raw_input_gamepad_device_win.cc
+++ b/device/gamepad/raw_input_gamepad_device_win.cc
@@ -26,29 +26,21 @@
 
 namespace {
 
-float NormalizeAxis(long value, long min, long max) {
-  return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
-}
+constexpr uint32_t kGenericDesktopUsagePage = 0x01;
+constexpr uint32_t kGameControlsUsagePage = 0x05;
+constexpr uint32_t kButtonUsagePage = 0x09;
+constexpr uint32_t kConsumerUsagePage = 0x0c;
 
-unsigned long GetBitmask(unsigned short bits) {
-  return (1 << bits) - 1;
-}
-
-const uint32_t kGenericDesktopUsagePage = 0x01;
-const uint32_t kGameControlsUsagePage = 0x05;
-const uint32_t kButtonUsagePage = 0x09;
-const uint32_t kConsumerUsagePage = 0x0c;
-
-const uint32_t kAxisMinimumUsageNumber = 0x30;
-const uint32_t kSystemMainMenuUsageNumber = 0x85;
-const uint32_t kPowerUsageNumber = 0x30;
-const uint32_t kSearchUsageNumber = 0x0221;
-const uint32_t kHomeUsageNumber = 0x0223;
-const uint32_t kBackUsageNumber = 0x0224;
+constexpr uint32_t kAxisMinimumUsageNumber = 0x30;
+constexpr uint32_t kSystemMainMenuUsageNumber = 0x85;
+constexpr uint32_t kPowerUsageNumber = 0x30;
+constexpr uint32_t kSearchUsageNumber = 0x0221;
+constexpr uint32_t kHomeUsageNumber = 0x0223;
+constexpr uint32_t kBackUsageNumber = 0x0224;
 
 // The fetcher will collect all HID usages from the Button usage page and any
 // additional usages listed below.
-struct SpecialUsages {
+constexpr struct SpecialUsages {
   const uint16_t usage_page;
   const uint16_t usage;
 } kSpecialUsages[] = {
@@ -63,7 +55,19 @@
     {kConsumerUsagePage, kHomeUsageNumber},
     {kConsumerUsagePage, kBackUsageNumber},
 };
-const size_t kSpecialUsagesLen = base::size(kSpecialUsages);
+constexpr size_t kSpecialUsagesLen = base::size(kSpecialUsages);
+
+// Scales |value| from the range |min| <= x <= |max| to a Standard Gamepad axis
+// value in the range -1.0 <= x <= 1.0.
+template <class T>
+float NormalizeAxis(T value, T min, T max) {
+  return (2.0f * (value - min) / static_cast<float>(max - min)) - 1.0f;
+}
+
+// Returns a 32-bit mask with the lowest |bits| bits set.
+unsigned long GetBitmask(unsigned short bits) {
+  return (1 << bits) - 1;
+}
 
 }  // namespace
 
@@ -175,37 +179,9 @@
     }
   }
 
-  // Query axis state.
-  ULONG axis_value = 0;
-  LONG scaled_axis_value = 0;
-  for (uint32_t i = 0; i < axes_length_; i++) {
-    RawGamepadAxis* axis = &axes_[i];
-
-    // If the min is < 0 we have to query the scaled value, otherwise we need
-    // the normal unscaled value.
-    if (axis->caps.LogicalMin < 0) {
-      status = HidP_GetScaledUsageValue(
-          HidP_Input, axis->caps.UsagePage, 0, axis->caps.Range.UsageMin,
-          &scaled_axis_value, preparsed_data_,
-          reinterpret_cast<PCHAR>(input->data.hid.bRawData),
-          input->data.hid.dwSizeHid);
-      if (status == HIDP_STATUS_SUCCESS) {
-        axis->value = NormalizeAxis(scaled_axis_value, axis->caps.PhysicalMin,
-                                    axis->caps.PhysicalMax);
-      }
-    } else {
-      status = HidP_GetUsageValue(
-          HidP_Input, axis->caps.UsagePage, 0, axis->caps.Range.UsageMin,
-          &axis_value, preparsed_data_,
-          reinterpret_cast<PCHAR>(input->data.hid.bRawData),
-          input->data.hid.dwSizeHid);
-      if (status == HIDP_STATUS_SUCCESS) {
-        axis->value = NormalizeAxis(axis_value & axis->bitmask,
-                                    axis->caps.LogicalMin & axis->bitmask,
-                                    axis->caps.LogicalMax & axis->bitmask);
-      }
-    }
-  }
+  // Update axis state.
+  for (uint32_t axis_index = 0; axis_index < axes_length_; ++axis_index)
+    UpdateAxisValue(axis_index, *input);
 
   last_update_timestamp_ = GamepadDataFetcher::CurrentTimeInMicroseconds();
 }
@@ -418,62 +394,55 @@
 
 void RawInputGamepadDeviceWin::QueryButtonCapabilities(uint16_t button_count) {
   if (button_count > 0) {
-    std::unique_ptr<HIDP_BUTTON_CAPS[]> button_caps(
-        new HIDP_BUTTON_CAPS[button_count]);
-    NTSTATUS status = HidP_GetButtonCaps(HidP_Input, button_caps.get(),
+    std::vector<HIDP_BUTTON_CAPS> button_caps(button_count);
+    NTSTATUS status = HidP_GetButtonCaps(HidP_Input, button_caps.data(),
                                          &button_count, preparsed_data_);
     DCHECK_EQ(HIDP_STATUS_SUCCESS, status);
 
     // Collect all inputs from the Button usage page.
-    QueryNormalButtonCapabilities(button_caps.get(), button_count);
+    QueryNormalButtonCapabilities(button_caps);
 
     // Check for common gamepad buttons that are not on the Button usage page.
-    QuerySpecialButtonCapabilities(button_caps.get(), button_count);
+    QuerySpecialButtonCapabilities(button_caps);
   }
 }
 
 void RawInputGamepadDeviceWin::QueryNormalButtonCapabilities(
-    HIDP_BUTTON_CAPS button_caps[],
-    uint16_t button_count) {
-  DCHECK(button_caps);
-
+    base::span<const HIDP_BUTTON_CAPS> button_caps) {
   // Collect all inputs from the Button usage page and assign button indices
   // based on the usage value.
-  for (size_t i = 0; i < button_count; ++i) {
-    uint16_t usage_page = button_caps[i].UsagePage;
-    uint16_t usage_min = button_caps[i].Range.UsageMin;
-    uint16_t usage_max = button_caps[i].Range.UsageMax;
+  for (const auto& item : button_caps) {
+    uint16_t usage_min = item.Range.UsageMin;
+    uint16_t usage_max = item.Range.UsageMax;
     if (usage_min == 0 || usage_max == 0)
       continue;
     size_t button_index_min = size_t{usage_min - 1};
     size_t button_index_max = size_t{usage_max - 1};
-    if (usage_page == kButtonUsagePage &&
+    if (item.UsagePage == kButtonUsagePage &&
         button_index_min < Gamepad::kButtonsLengthCap) {
       button_index_max =
           std::min(Gamepad::kButtonsLengthCap - 1, button_index_max);
       buttons_length_ = std::max(buttons_length_, button_index_max + 1);
-      for (size_t j = button_index_min; j <= button_index_max; ++j)
-        button_indices_used_[j] = true;
+      for (size_t button_index = button_index_min;
+           button_index <= button_index_max; ++button_index) {
+        button_indices_used_[button_index] = true;
+      }
     }
   }
 }
 
 void RawInputGamepadDeviceWin::QuerySpecialButtonCapabilities(
-    HIDP_BUTTON_CAPS button_caps[],
-    uint16_t button_count) {
-  DCHECK(button_caps);
-
+    base::span<const HIDP_BUTTON_CAPS> button_caps) {
   // Check for common gamepad buttons that are not on the Button usage page.
   std::vector<bool> has_special_usage(kSpecialUsagesLen, false);
   size_t unmapped_button_count = 0;
-  for (size_t i = 0; i < button_count; ++i) {
-    uint16_t usage_page = button_caps[i].UsagePage;
-    uint16_t usage_min = button_caps[i].Range.UsageMin;
-    uint16_t usage_max = button_caps[i].Range.UsageMax;
+  for (const auto& item : button_caps) {
+    uint16_t usage_min = item.Range.UsageMin;
+    uint16_t usage_max = item.Range.UsageMax;
     for (size_t special_index = 0; special_index < kSpecialUsagesLen;
          ++special_index) {
       const auto& special = kSpecialUsages[special_index];
-      if (usage_page == special.usage_page && usage_min <= special.usage &&
+      if (item.UsagePage == special.usage_page && usage_min <= special.usage &&
           usage_max >= special.usage) {
         has_special_usage[special_index] = true;
         ++unmapped_button_count;
@@ -557,6 +526,62 @@
   }
 }
 
+void RawInputGamepadDeviceWin::UpdateAxisValue(size_t axis_index,
+                                               RAWINPUT& input) {
+  DCHECK_LT(axis_index, Gamepad::kAxesLengthCap);
+  // RawInput gamepad axes are normalized according to the information provided
+  // in the HID report descriptor. Each HID report item must specify a Logical
+  // Minimum and Logical Maximum to define the domain of allowable values for
+  // the item. An item may optionally specify a Units definition to indicate
+  // that the item represents a real-world value measured in those units. Items
+  // with a Units definition should also specify Physical Minimum and Physical
+  // Maximum, which are the Logical bounds transformed into Physical units.
+  //
+  // For gamepads, it is common for joystick and trigger axis items to not
+  // specify Units. However, D-pad items typically do specify Units. An 8-way
+  // directional pad, when implemented as a Hat Switch axis, reports a logical
+  // value from 0 to 7 that corresponds to a physical value from 0 degrees (N)
+  // clockwise to 315 degrees (NW).
+  //
+  // When a Hat Switch is in its Null State (no interaction) it reports a value
+  // outside the Logical range, often 8. Normalizing the out-of-bounds Null
+  // State value yields an invalid axis value greater than +1.0. This invalid
+  // axis value must be preserved so that downstream consumers can detect when
+  // the Hat Switch is reporting a Null State value.
+  //
+  // When an item provides Physical bounds, prefer to use
+  // HidP_GetScaledUsageValue to retrieve the item's value in real-world units
+  // and normalize using the Physical bounds. If the Physical bounds are invalid
+  // or HidP_GetScaledUsageValue fails, use HidP_GetUsageValue to retrieve the
+  // logical value and normalize using the Logical bounds.
+  auto& axis = axes_[axis_index];
+  if (axis.caps.PhysicalMin < axis.caps.PhysicalMax) {
+    LONG scaled_axis_value = 0;
+    if (HidP_GetScaledUsageValue(
+            HidP_Input, axis.caps.UsagePage, /*LinkCollection=*/0,
+            axis.caps.Range.UsageMin, &scaled_axis_value, preparsed_data_,
+            reinterpret_cast<PCHAR>(input.data.hid.bRawData),
+            input.data.hid.dwSizeHid) == HIDP_STATUS_SUCCESS) {
+      axis.value = NormalizeAxis(scaled_axis_value, axis.caps.PhysicalMin,
+                                 axis.caps.PhysicalMax);
+      return;
+    }
+  }
+
+  ULONG axis_value = 0;
+  if (HidP_GetUsageValue(HidP_Input, axis.caps.UsagePage, /*LinkCollection=*/0,
+                         axis.caps.Range.UsageMin, &axis_value, preparsed_data_,
+                         reinterpret_cast<PCHAR>(input.data.hid.bRawData),
+                         input.data.hid.dwSizeHid) == HIDP_STATUS_SUCCESS) {
+    axis.value = NormalizeAxis(axis_value & axis.bitmask,
+                               axis.caps.LogicalMin & axis.bitmask,
+                               axis.caps.LogicalMax & axis.bitmask);
+    return;
+  }
+
+  axis.value = 0.0f;
+}
+
 base::WeakPtr<AbstractHapticGamepad> RawInputGamepadDeviceWin::GetWeakPtr() {
   return weak_factory_.GetWeakPtr();
 }
diff --git a/device/gamepad/raw_input_gamepad_device_win.h b/device/gamepad/raw_input_gamepad_device_win.h
index 25ea942b..aba234b89 100644
--- a/device/gamepad/raw_input_gamepad_device_win.h
+++ b/device/gamepad/raw_input_gamepad_device_win.h
@@ -95,12 +95,16 @@
   // on the device.
   bool QueryDeviceCapabilities();
   void QueryButtonCapabilities(uint16_t button_count);
-  void QueryNormalButtonCapabilities(HIDP_BUTTON_CAPS button_caps[],
-                                     uint16_t button_count);
-  void QuerySpecialButtonCapabilities(HIDP_BUTTON_CAPS button_caps[],
-                                      uint16_t button_count);
+  void QueryNormalButtonCapabilities(
+      base::span<const HIDP_BUTTON_CAPS> button_caps);
+  void QuerySpecialButtonCapabilities(
+      base::span<const HIDP_BUTTON_CAPS> button_caps);
   void QueryAxisCapabilities(uint16_t axis_count);
 
+  // Reads the value of the axis at index |axis_index| from |input| and scales
+  // to the range [-1.0,+1.0].
+  void UpdateAxisValue(size_t axis_index, RAWINPUT& input);
+
   // True if the device described by this object is a valid RawInput gamepad.
   bool is_valid_ = false;
 
diff --git a/docs/accessibility/brltty.md b/docs/accessibility/brltty.md
index e05f1d7..cd921d01 100644
--- a/docs/accessibility/brltty.md
+++ b/docs/accessibility/brltty.md
@@ -169,6 +169,14 @@
 
 matches the one in the new brltty.
 
+#### ChromeVox
+ChromeVox keeps a list of bluetooth braille display names
+(search for bluetooth_display_manager.js).
+
+Within the brltty sources (as of 6.3), one can find all bluetooth display names
+in:
+brltty/Programs/bluetooth_names.c
+
 ### Testing
 
 Firstly, try to test against brltty on linux. This involves building brltty at
diff --git a/docs/gpu/gpu_testing.md b/docs/gpu/gpu_testing.md
index 67e4ce65f..8be60b7 100644
--- a/docs/gpu/gpu_testing.md
+++ b/docs/gpu/gpu_testing.md
@@ -368,7 +368,7 @@
 The easiest way to do this is to find the ID of the swarming task and use
 "swarming.py reproduce" to re-run it:
 
-*   `./src/tools/swarming_client/swarming.py reproduce -S https://chromium-swarm.appspot.com [task ID]`
+*   `./src/tools/luci-go/swarming reproduce -S https://chromium-swarm.appspot.com [task ID]`
 
 The task ID can be found in the stdio for the "trigger" step for the test. For
 example, look at a recent build from the [Mac Release (Intel)] bot, and
@@ -405,8 +405,7 @@
 to access the isolate server. Full instructions can be [found
 here][isolate-server-credentials]. For most cases, you can simply run:
 
-*   `./src/tools/swarming_client/auth.py login
-    --service=https://isolateserver.appspot.com`
+*   `./src/tools/luci-go/isolate login`
 
 The above link requires that you log in with your @google.com credentials. It's
 not known at the present time whether this works with @chromium.org accounts.
@@ -420,7 +419,7 @@
 
 Be sure to use the correct swarming dimensions for your desired GPU e.g. "1002:6613" instead of "AMD Radeon R7 240 (1002:6613)" which is how it appears on swarming task page.  You can query bots in the chromium.tests.gpu pool to find the correct dimensions:
 
-*   `vpython tools\swarming_client\swarming.py bots -S chromium-swarm.appspot.com -d pool chromium.tests.gpu`
+*   `tools\luci-go\swarming bots -S chromium-swarm.appspot.com -d pool=chromium.tests.gpu`
 
 [Swarming documentation]: https://www.chromium.org/developers/testing/isolated-testing/for-swes#TOC-Run-a-test-built-locally-on-Swarming
 
@@ -492,11 +491,7 @@
 See the documentation from the GPU bot details on [adding new isolated
 tests][new-isolates] for the gn args and authentication needed to upload
 isolates to the isolate server. Most likely the new test will be Telemetry
-based, and included in the `telemetry_gpu_test_run` isolate. You can then
-invoke it via:
-
-*   `./src/tools/swarming_client/run_isolated.py -s [HASH]
-    -I https://isolateserver.appspot.com -- [TEST_NAME] [TEST_ARGUMENTS]`
+based, and included in the `telemetry_gpu_test_run` isolate.
 
 [new-isolates]: gpu_testing_bot_details.md#Adding-a-new-isolated-test-to-the-bots
 
diff --git a/docs/gpu/gpu_testing_bot_details.md b/docs/gpu/gpu_testing_bot_details.md
index 850d860c..04186934 100644
--- a/docs/gpu/gpu_testing_bot_details.md
+++ b/docs/gpu/gpu_testing_bot_details.md
@@ -46,7 +46,7 @@
 7-like NVIDIA bots in the pool, which necessitates the OS specifier.)
 
 Details about the bots can be found on [chromium-swarm.appspot.com] and by
-using `src/tools/swarming_client/swarming.py`, for example `swarming.py bots`.
+using `src/tools/luci-go/swarming`, for example `swarming bots`.
 If you are authenticated with @google.com credentials you will be able to make
 queries of the bots and see, for example, which GPUs are available.
 
@@ -120,11 +120,8 @@
 
 1.  `./tools/mb/mb.py isolate //out/Release [target name]`
     *   For example: `./tools/mb/mb.py isolate //out/Release angle_end2end_tests`
-1.  `python tools/swarming_client/isolate.py batcharchive -I https://isolateserver.appspot.com out/Release/[target name].isolated.gen.json`
-    *   For example: `python tools/swarming_client/isolate.py batcharchive -I https://isolateserver.appspot.com out/Release/angle_end2end_tests.isolated.gen.json`
-1.  This will write a hash to stdout. You can run it via:
-    `python tools/swarming_client/run_isolated.py -I https://isolateserver.appspot.com -s [HASH] -- [any additional args for the isolate]`
-
+1.  `./tools/luci-go/isolate batcharchive -I https://isolateserver.appspot.com out/Release/[target name].isolated.gen.json`
+    *   For example: `./tools/luci-go/isolate batcharchive -I https://isolateserver.appspot.com out/Release/angle_end2end_tests.isolated.gen.json`
 See the section below on [isolate server credentials](#Isolate-server-credentials).
 
 ### Adding your new isolate to the tests that are run on the bots
@@ -212,7 +209,7 @@
     *   Definitions of how bots are organized on the waterfall,
         how builds are triggered, which VMs or machines are used for the
         builder itself, i.e. for compilation and scheduling swarmed tasks
-        on GPU hardware. See 
+        on GPU hardware. See
         [README.md](https://chromium.googlesource.com/chromium/src/+/main/infra/config/README.md)
         in this directory for up to date information.
 
@@ -331,7 +328,7 @@
        GCEs from this small pool.
     1. Run [`main.star`][main.star] to regenerate
        `configs/chromium-swarm/bots.cfg` and `configs/gce-provider/vms.cfg`.
-       Double-check your work there.  
+       Double-check your work there.
        Note that previously [`vms.cfg`][vms.cfg] had to be edited manually.
        Part of the difficulty was in choosing a zone. This should soon no
        longer be necessary per [crbug.com/942301](http://crbug.com/942301),
@@ -341,7 +338,7 @@
        [dashboard](https://viceroy.corp.google.com/chrome_infra/Quota/chrome?duration=7d).
     1. Get this reviewed and landed. This step associates the VM or pool of VMs
        with the bot's name on the waterfall for "builderful" bots or increases
-       swarmed pool capacity for "builderless" bots.  
+       swarmed pool capacity for "builderless" bots.
        Note: CR+1 is not sticky in this repo, so you'll have to ping for
        re-review after every change, like rebase.
 
@@ -369,7 +366,7 @@
 
 1.  Work with the Chrome Infrastructure Labs team to get the (minimum 4)
     physical machines added to the Swarming pool. Use
-    [chromium-swarm.appspot.com] or `src/tools/swarming_client/swarming.py bots`
+    [chromium-swarm.appspot.com] or `src/tools/luci-go/swarming bots`
     to determine the PCI IDs of the GPUs in the bots. (These instructions will
     need to be updated for Android bots which don't have PCI buses.)
 
@@ -732,8 +729,7 @@
 To upload and download isolates you must first authenticate to the isolate
 server. From a Chromium checkout, run:
 
-*   `./src/tools/swarming_client/auth.py login
-    --service=https://isolateserver.appspot.com`
+*   `./src/tools/luci-go/isolate login`
 
 This will open a web browser to complete the authentication flow. A @google.com
 email address is required in order to properly authenticate.
@@ -744,14 +740,6 @@
 
 [Running Binaries from the Bots Locally]: https://www.chromium.org/developers/testing/gpu-testing#TOC-Running-Binaries-from-the-Bots-Locally
 
-If authentication succeeded, this will silently download a file called
-`delete_me` into the current working directory. If it failed, the script will
-report multiple authentication errors. In this case, use the following command
-to log out and then try again:
-
-*   `./src/tools/swarming_client/auth.py logout
-    --service=https://isolateserver.appspot.com`
-
 ### Swarming server credentials
 
 The swarming server uses the same `auth.py` script as the isolate server. You
diff --git a/docs/security/lookalikes/lookalike-domains.md b/docs/security/lookalikes/lookalike-domains.md
index 6e1c28f..0415cce 100644
--- a/docs/security/lookalikes/lookalike-domains.md
+++ b/docs/security/lookalikes/lookalike-domains.md
@@ -1,5 +1,9 @@
 # "Lookalike" Warnings in Google Chrome
 
+[TOC]
+
+## What are lookalike warnings?
+
 "Lookalike" domains are domains that are crafted to impersonate the URLs of
 other sites in order to trick users into believing they're on a different site.
 These domains are used in social engineering attacks, from phishing to retail
@@ -82,5 +86,4 @@
 new sites in Chrome to ensure that their new domain does not trigger warnings.
 
 If you are a site operator and would like to request an appeal, please fill out
-a
-[request](https://bugs.chromium.org/p/chromium/issues/entry?template=Safety+Tips+Appeals).
+a [request](https://forms.gle/BxV3JGbCbRjucDxq6).
diff --git a/docs/testing/chromeos_debugging_tips.md b/docs/testing/chromeos_debugging_tips.md
index 26f5927..aa0627a 100644
--- a/docs/testing/chromeos_debugging_tips.md
+++ b/docs/testing/chromeos_debugging_tips.md
@@ -73,7 +73,7 @@
 into a tmp directory and run:
 ```
 $CHROME_DIR/tools/luci-go/isolated download -I https://chrome-isolated.appspot.com --namespace default-gzip -isolated 64919fee8b02d826df2401544a9dc0f7dfa2172d -output-dir input
-python $CHROME_DIR/tools/swarming_client/swarming.py collect -S chrome-swarming.appspot.com 506a01dd12c8a610 --task-output-dir output
+$CHROME_DIR/tools/luci-go/swarming collect -S chrome-swarming.appspot.com -output-dir output 506a01dd12c8a610
 ```
 
 Once both isolates have been fetched you must then generate the breakpad
diff --git a/docs/workflow/debugging-with-swarming.md b/docs/workflow/debugging-with-swarming.md
index b0845a7..a656301 100644
--- a/docs/workflow/debugging-with-swarming.md
+++ b/docs/workflow/debugging-with-swarming.md
@@ -153,14 +153,9 @@
 ## Authenticating
 
 You may need to log in to `https://chromium-swarm.appspot.com` to do this
-(for now you need to authenticate with python too,
-TODO(https://crbug.com/984869): remove this):
-
 
 ```
 $ tools/luci-go/isolate login
-$ python tools/swarming_client/auth.py login \
-      --service=https://chromium-swarm.appspot.com
 ```
 
 Use your google.com account for this.
diff --git a/extensions/browser/api/web_request/web_request_api.cc b/extensions/browser/api/web_request/web_request_api.cc
index 4da01c1..b7d804c 100644
--- a/extensions/browser/api/web_request/web_request_api.cc
+++ b/extensions/browser/api/web_request/web_request_api.cc
@@ -816,6 +816,11 @@
   return web_request_extension_count_ > 0;
 }
 
+bool WebRequestAPI::HasExtraHeadersListenerForTesting() {
+  return ExtensionWebRequestEventRouter::GetInstance()
+      ->HasAnyExtraHeadersListener(browser_context_);
+}
+
 void WebRequestAPI::UpdateMayHaveProxies() {
   bool may_have_proxies = MayHaveProxies();
   if (!may_have_proxies_ && may_have_proxies) {
diff --git a/extensions/browser/api/web_request/web_request_api.h b/extensions/browser/api/web_request/web_request_api.h
index 5f04379..c68c1830 100644
--- a/extensions/browser/api/web_request/web_request_api.h
+++ b/extensions/browser/api/web_request/web_request_api.h
@@ -233,6 +233,8 @@
   // installed to support the API.
   bool MayHaveProxies() const;
 
+  bool HasExtraHeadersListenerForTesting();
+
  private:
   friend class BrowserContextKeyedAPIFactory<WebRequestAPI>;
 
diff --git a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
index 613c704..356de36 100644
--- a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
+++ b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
@@ -203,7 +203,15 @@
       factory_->IsForServiceWorkerScript(), factory_->navigation_id_,
       ukm_source_id_));
 
+  // The value of `has_any_extra_headers_listeners_` is constant for the
+  // lifetime of InProgressRequest and determines whether the request is made
+  // with the network::mojom::kURLLoadOptionUseHeaderClient option. To prevent
+  // the redirected request from getting into a state where
+  // `current_request_uses_header_client_` is true but the request is not made
+  // with the kURLLoadOptionUseHeaderClient option, also check
+  // `has_any_extra_headers_listeners_` here. See http://crbug.com/1074282.
   current_request_uses_header_client_ =
+      has_any_extra_headers_listeners_ &&
       factory_->url_loader_header_client_receiver_.is_bound() &&
       (request_.url.SchemeIsHTTPOrHTTPS() ||
        request_.url.SchemeIs(url::kUrnScheme)) &&
diff --git a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h
index e835898..15c56f1 100644
--- a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h
+++ b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h
@@ -218,7 +218,7 @@
     // network::mojom::TrustedURLLoaderHeaderClient binding on the factory. This
     // is only set to true if there is a listener that needs to view or modify
     // headers set in the network process.
-    bool has_any_extra_headers_listeners_ = false;
+    const bool has_any_extra_headers_listeners_ = false;
     bool current_request_uses_header_client_ = false;
     OnBeforeSendHeadersCallback on_before_send_headers_callback_;
     OnHeadersReceivedCallback on_headers_received_callback_;
diff --git a/extensions/browser/extension_dialog_auto_confirm.cc b/extensions/browser/extension_dialog_auto_confirm.cc
index e17fa6a..44daf5a 100644
--- a/extensions/browser/extension_dialog_auto_confirm.cc
+++ b/extensions/browser/extension_dialog_auto_confirm.cc
@@ -2,28 +2,49 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <utility>
+
 #include "extensions/browser/extension_dialog_auto_confirm.h"
 
 namespace extensions {
 
 namespace {
-ScopedTestDialogAutoConfirm::AutoConfirm g_extension_dialog_auto_confirm =
+ScopedTestDialogAutoConfirm::AutoConfirm g_extension_dialog_auto_confirm_value =
     ScopedTestDialogAutoConfirm::NONE;
+int g_extension_dialog_option_to_select = 0;
 }
 
 ScopedTestDialogAutoConfirm::ScopedTestDialogAutoConfirm(
-    ScopedTestDialogAutoConfirm::AutoConfirm override_value)
-    : old_value_(g_extension_dialog_auto_confirm) {
-  g_extension_dialog_auto_confirm = override_value;
-}
+    ScopedTestDialogAutoConfirm::AutoConfirm override_confirm_value)
+    : old_auto_confirm_value_(
+          std::exchange(g_extension_dialog_auto_confirm_value,
+                        override_confirm_value)),
+      // Assign a default value if unspecified.
+      old_option_to_select_(0) {}
+
+ScopedTestDialogAutoConfirm::ScopedTestDialogAutoConfirm(
+    ScopedTestDialogAutoConfirm::AutoConfirm override_confirm_value,
+    int override_option_to_select)
+    : old_auto_confirm_value_(
+          std::exchange(g_extension_dialog_auto_confirm_value,
+                        override_confirm_value)),
+      old_option_to_select_(std::exchange(g_extension_dialog_option_to_select,
+                                          override_option_to_select)) {}
 
 ScopedTestDialogAutoConfirm::~ScopedTestDialogAutoConfirm() {
-  g_extension_dialog_auto_confirm = old_value_;
+  g_extension_dialog_auto_confirm_value = old_auto_confirm_value_;
+  g_extension_dialog_option_to_select = old_option_to_select_;
 }
 
+// static
 ScopedTestDialogAutoConfirm::AutoConfirm
 ScopedTestDialogAutoConfirm::GetAutoConfirmValue() {
-  return g_extension_dialog_auto_confirm;
+  return g_extension_dialog_auto_confirm_value;
+}
+
+// static
+int ScopedTestDialogAutoConfirm::GetOptionSelected() {
+  return g_extension_dialog_option_to_select;
 }
 
 }  // namespace extensions
diff --git a/extensions/browser/extension_dialog_auto_confirm.h b/extensions/browser/extension_dialog_auto_confirm.h
index 5d0c29e..693d635 100644
--- a/extensions/browser/extension_dialog_auto_confirm.h
+++ b/extensions/browser/extension_dialog_auto_confirm.h
@@ -20,13 +20,32 @@
     CANCEL,             // The prompt will always cancel.
   };
 
-  explicit ScopedTestDialogAutoConfirm(AutoConfirm override_value);
+  // Set up auto confirm value to |override_confirm_value| so the dialog is
+  // automatically shown, accepted, or cancelled.
+  explicit ScopedTestDialogAutoConfirm(AutoConfirm override_confirm_value);
+
+  // Set up auto confirm value to |override_confirm_value| so the dialog is
+  // automatically shown, accepted, or cancelled. In addition, if a dialog is
+  // accepted and an option can be selected, accept the option specified by
+  // |override_option_to_select|.
+  ScopedTestDialogAutoConfirm(AutoConfirm override_confirm_value,
+                              int override_option_to_select);
   ~ScopedTestDialogAutoConfirm();
 
+  // Return whether the dialog should be showed, accepted, or cancelled.
   static AutoConfirm GetAutoConfirmValue();
 
+  // Return which option is selected for the dialog.
+  static int GetOptionSelected();
+
  private:
-  AutoConfirm old_value_;
+  // Preserve the old auto confirm value so it can be reset when the dialog
+  // goes out of scope.
+  const AutoConfirm old_auto_confirm_value_;
+
+  // Preserve the old option to select so it can be reset when the dialog goes
+  // out of scope.
+  const int old_option_to_select_;
 
   DISALLOW_COPY_AND_ASSIGN(ScopedTestDialogAutoConfirm);
 };
diff --git a/extensions/common/api/automation.idl b/extensions/common/api/automation.idl
index 15f2411..e49ff6f5 100644
--- a/extensions/common/api/automation.idl
+++ b/extensions/common/api/automation.idl
@@ -136,7 +136,6 @@
     abbr,
     alert,
     alertDialog,
-    anchor,
     application,
     article,
     audio,
diff --git a/extensions/common/manifest_handlers/csp_info.cc b/extensions/common/manifest_handlers/csp_info.cc
index 209c373..a3844ae 100644
--- a/extensions/common/manifest_handlers/csp_info.cc
+++ b/extensions/common/manifest_handlers/csp_info.cc
@@ -119,6 +119,12 @@
 }
 
 // static
+const std::string& CSPInfo::GetDefaultMV3ExtensionPagesCSP() {
+  static const base::NoDestructor<std::string> default_csp(kDefaultSecureCSP);
+  return *default_csp;
+}
+
+// static
 const std::string* CSPInfo::GetIsolatedWorldCSP(const Extension& extension) {
   if (extension.manifest_version() >= 3) {
     // The isolated world will use its own CSP which blocks remotely hosted
diff --git a/extensions/common/manifest_handlers/csp_info.h b/extensions/common/manifest_handlers/csp_info.h
index a2ba725..b22cf82 100644
--- a/extensions/common/manifest_handlers/csp_info.h
+++ b/extensions/common/manifest_handlers/csp_info.h
@@ -37,6 +37,9 @@
   // shouldn't be returned for those cases.
   static const std::string& GetExtensionPagesCSP(const Extension* extension);
 
+  // Returns the default extension pages CSP for Manifest V3 extensions.
+  static const std::string& GetDefaultMV3ExtensionPagesCSP();
+
   // Returns the Content Security Policy to be used for extension isolated
   // worlds or null if there is no defined CSP.
   static const std::string* GetIsolatedWorldCSP(const Extension& extension);
diff --git a/extensions/docs/security_faq.md b/extensions/docs/security_faq.md
index 6662d4a0..4fa9ba9 100644
--- a/extensions/docs/security_faq.md
+++ b/extensions/docs/security_faq.md
@@ -375,11 +375,15 @@
 a bug in Chromium. However, they may be covered by the
 [Google Vulnerability Reward Program](https://www.google.com/about/appsecurity/reward-program/).
 
-**Other Extensions:** A security bug in a third-party extension would not be
-considered a security bug in Chromium. Some third-party extensions may have
-their own vulnerability reward programs; please check with the extension
-developer. It may also be eligible for a reward through the Developer Data
-Protection Reward Program; visit
+**Other Extensions:** A security bug in a third-party extension _would not_ be
+considered a security bug in Chromium. This is true even if the extension has
+sensitive and powerful permissions, which could leak user data  or allow
+cross-site scripting attacks
+([example](https://bugs.chromium.org/p/chromium/issues/detail?id=1213523)).
+Some third-party extensions may have their own vulnerability reward programs;
+please check with the extension developer. It may also be eligible for a reward
+through the Developer Data Protection Reward Program (though this typically
+targets abuse, rather than vulnerabilities); visit
 [this site](https://www.google.com/about/appsecurity/ddprp/) for more
 information.
 
diff --git a/extensions/renderer/native_extension_bindings_system.cc b/extensions/renderer/native_extension_bindings_system.cc
index 19f91ba..9a823a4 100644
--- a/extensions/renderer/native_extension_bindings_system.cc
+++ b/extensions/renderer/native_extension_bindings_system.cc
@@ -372,78 +372,6 @@
   return false;
 }
 
-// Logs the amount of time taken to update the bindings for a given context
-// (i.e., UpdateBindingsForContext()).
-void LogUpdateBindingsForContextTime(Feature::Context context_type,
-                                     bool is_for_service_worker,
-                                     base::TimeDelta elapsed) {
-  constexpr int kHistogramBucketCount = 50;
-  static const int kTenSecondsInMicroseconds = 10000000;
-  switch (context_type) {
-    case Feature::UNSPECIFIED_CONTEXT:
-      break;
-    case Feature::WEB_PAGE_CONTEXT:
-      UMA_HISTOGRAM_CUSTOM_COUNTS(
-          "Extensions.Bindings.UpdateBindingsForContextTime.WebPageContext",
-          elapsed.InMicroseconds(), 1, kTenSecondsInMicroseconds,
-          kHistogramBucketCount);
-      break;
-    case Feature::BLESSED_WEB_PAGE_CONTEXT:
-      UMA_HISTOGRAM_CUSTOM_COUNTS(
-          "Extensions.Bindings.UpdateBindingsForContextTime."
-          "BlessedWebPageContext",
-          elapsed.InMicroseconds(), 1, kTenSecondsInMicroseconds,
-          kHistogramBucketCount);
-      break;
-    case Feature::BLESSED_EXTENSION_CONTEXT:
-      if (is_for_service_worker) {
-        UMA_HISTOGRAM_CUSTOM_COUNTS(
-            "Extensions.Bindings.UpdateBindingsForContextTime."
-            "ServiceWorkerContext",
-            elapsed.InMicroseconds(), 1, kTenSecondsInMicroseconds,
-            kHistogramBucketCount);
-      } else {
-        UMA_HISTOGRAM_CUSTOM_COUNTS(
-            "Extensions.Bindings.UpdateBindingsForContextTime."
-            "BlessedExtensionContext",
-            elapsed.InMicroseconds(), 1, kTenSecondsInMicroseconds,
-            kHistogramBucketCount);
-      }
-      break;
-    case Feature::LOCK_SCREEN_EXTENSION_CONTEXT:
-      UMA_HISTOGRAM_CUSTOM_COUNTS(
-          "Extensions.Bindings.UpdateBindingsForContextTime."
-          "LockScreenExtensionContext",
-          elapsed.InMicroseconds(), 1, kTenSecondsInMicroseconds,
-          kHistogramBucketCount);
-      break;
-    case Feature::UNBLESSED_EXTENSION_CONTEXT:
-      UMA_HISTOGRAM_CUSTOM_COUNTS(
-          "Extensions.Bindings.UpdateBindingsForContextTime."
-          "UnblessedExtensionContext",
-          elapsed.InMicroseconds(), 1, kTenSecondsInMicroseconds,
-          kHistogramBucketCount);
-      break;
-    case Feature::CONTENT_SCRIPT_CONTEXT:
-      UMA_HISTOGRAM_CUSTOM_COUNTS(
-          "Extensions.Bindings.UpdateBindingsForContextTime."
-          "ContentScriptContext",
-          elapsed.InMicroseconds(), 1, kTenSecondsInMicroseconds,
-          kHistogramBucketCount);
-      break;
-    case Feature::WEBUI_CONTEXT:
-      UMA_HISTOGRAM_CUSTOM_COUNTS(
-          "Extensions.Bindings.UpdateBindingsForContextTime.WebUIContext",
-          elapsed.InMicroseconds(), 1, kTenSecondsInMicroseconds,
-          kHistogramBucketCount);
-      break;
-    case Feature::WEBUI_UNTRUSTED_CONTEXT:
-      // Extension APIs in untrusted WebUIs are temporary so don't bother
-      // recording metrics for them.
-      break;
-  }
-}
-
 // The APIs that could potentially be available to webpage-like contexts.
 // This is the list of possible features; most web pages will not have access
 // to these APIs.
@@ -544,7 +472,6 @@
 
 void NativeExtensionBindingsSystem::UpdateBindingsForContext(
     ScriptContext* context) {
-  base::ElapsedTimer timer;
   v8::Isolate* isolate = context->isolate();
   v8::HandleScope handle_scope(isolate);
   v8::Local<v8::Context> v8_context = context->v8_context();
@@ -599,9 +526,6 @@
     if (IsRuntimeAvailableToContext(context) && !set_accessor("runtime"))
       LOG(ERROR) << "Failed to create API on Chrome object.";
 
-    LogUpdateBindingsForContextTime(context->context_type(),
-                                    context->IsForServiceWorker(),
-                                    timer.Elapsed());
     UpdateContentCapabilities(context);
     return;
   }
@@ -634,9 +558,6 @@
       return;
     }
   }
-
-  LogUpdateBindingsForContextTime(
-      context->context_type(), context->IsForServiceWorker(), timer.Elapsed());
 }
 
 void NativeExtensionBindingsSystem::DispatchEventInContext(
diff --git a/fuchsia/engine/browser/frame_impl_browsertest.cc b/fuchsia/engine/browser/frame_impl_browsertest.cc
index 5c5f882..b0ae16a 100644
--- a/fuchsia/engine/browser/frame_impl_browsertest.cc
+++ b/fuchsia/engine/browser/frame_impl_browsertest.cc
@@ -1328,6 +1328,8 @@
       frame.ptr().get(), "window.devicePixelRatio");
   ASSERT_TRUE(default_dpr);
 
+  EXPECT_EQ(default_dpr->GetDouble(), 1.0f);
+
   // Update scale and verify that devicePixelRatio is updated accordingly.
   const float kZoomInScale = 1.5;
   frame->SetPageScale(kZoomInScale);
@@ -1336,8 +1338,7 @@
       frame.ptr().get(), "window.devicePixelRatio");
   ASSERT_TRUE(scaled_dpr);
 
-  EXPECT_NEAR(scaled_dpr->GetDouble() / default_dpr->GetDouble(), kZoomInScale,
-              1e-6);
+  EXPECT_EQ(scaled_dpr->GetDouble(), kZoomInScale);
 
   // Navigate to the same page on http://localhost. This is a different site,
   // so it will be loaded in a new renderer process. Page scale value should be
@@ -1356,14 +1357,14 @@
 
   EXPECT_EQ(dpr_after_navigation, scaled_dpr);
 
-  // Reset the scale to 1.0 (default) and verify that reported DPR is equal to
-  // the same as when the frame was created.
+  // Reset the scale to 1.0 (default) and verify that reported DPR is updated
+  // to 1.0.
   frame->SetPageScale(1.0);
   absl::optional<base::Value> dpr_after_reset = cr_fuchsia::ExecuteJavaScript(
       frame.ptr().get(), "window.devicePixelRatio");
   ASSERT_TRUE(dpr_after_reset);
 
-  EXPECT_EQ(dpr_after_reset.value(), default_dpr.value());
+  EXPECT_EQ(dpr_after_reset->GetDouble(), 1.0);
 
   // Zoom out by setting scale to 0.5.
   const float kZoomOutScale = 0.5;
@@ -1373,8 +1374,7 @@
       frame.ptr().get(), "window.devicePixelRatio");
   ASSERT_TRUE(zoomed_out_dpr);
 
-  EXPECT_NEAR(zoomed_out_dpr->GetDouble() / default_dpr->GetDouble(),
-              kZoomOutScale, 1e-6);
+  EXPECT_EQ(zoomed_out_dpr->GetDouble(), kZoomOutScale);
 
   // Create another frame. Verify that the scale factor is not applied to the
   // new frame.
@@ -1395,7 +1395,7 @@
       frame2.ptr().get(), "window.devicePixelRatio");
   ASSERT_TRUE(frame2_dpr);
 
-  EXPECT_EQ(frame2_dpr.value(), default_dpr.value());
+  EXPECT_EQ(frame2_dpr->GetDouble(), 1.0);
 }
 
 // Send a MessagePort to the content, then perform bidirectional messaging
diff --git a/gpu/command_buffer/client/gles2_implementation.cc b/gpu/command_buffer/client/gles2_implementation.cc
index a3044fd3..ff13b21 100644
--- a/gpu/command_buffer/client/gles2_implementation.cc
+++ b/gpu/command_buffer/client/gles2_implementation.cc
@@ -26,8 +26,8 @@
 #include "base/bits.h"
 #include "base/compiler_specific.h"
 #include "base/containers/span.h"
+#include "base/cxx17_backports.h"
 #include "base/numerics/safe_math.h"
-#include "base/stl_util.h"
 #include "base/strings/string_split.h"
 #include "base/system/sys_info.h"
 #include "base/threading/thread_task_runner_handle.h"
diff --git a/gpu/command_buffer/client/gles2_implementation_unittest.cc b/gpu/command_buffer/client/gles2_implementation_unittest.cc
index 1f4f495..71f83799 100644
--- a/gpu/command_buffer/client/gles2_implementation_unittest.cc
+++ b/gpu/command_buffer/client/gles2_implementation_unittest.cc
@@ -18,7 +18,7 @@
 
 #include "base/bind.h"
 #include "base/compiler_specific.h"
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "gpu/command_buffer/client/client_test_helper.h"
 #include "gpu/command_buffer/client/gles2_cmd_helper.h"
 #include "gpu/command_buffer/client/mock_transfer_buffer.h"
diff --git a/gpu/command_buffer/client/program_info_manager_unittest.cc b/gpu/command_buffer/client/program_info_manager_unittest.cc
index c9e652d..9ddaa9e4 100644
--- a/gpu/command_buffer/client/program_info_manager_unittest.cc
+++ b/gpu/command_buffer/client/program_info_manager_unittest.cc
@@ -7,7 +7,7 @@
 
 #include <memory>
 
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "gpu/command_buffer/client/program_info_manager.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/gpu/command_buffer/client/query_tracker_unittest.cc b/gpu/command_buffer/client/query_tracker_unittest.cc
index 0b3982d..2015578 100644
--- a/gpu/command_buffer/client/query_tracker_unittest.cc
+++ b/gpu/command_buffer/client/query_tracker_unittest.cc
@@ -13,7 +13,7 @@
 #include <memory>
 #include <vector>
 
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "gpu/command_buffer/client/client_test_helper.h"
 #include "gpu/command_buffer/client/gles2_cmd_helper.h"
 #include "gpu/command_buffer/client/mapped_memory.h"
diff --git a/gpu/command_buffer/client/raster_implementation_unittest.cc b/gpu/command_buffer/client/raster_implementation_unittest.cc
index 53af6b4a..629ad614 100644
--- a/gpu/command_buffer/client/raster_implementation_unittest.cc
+++ b/gpu/command_buffer/client/raster_implementation_unittest.cc
@@ -16,8 +16,8 @@
 
 #include "base/bind.h"
 #include "base/compiler_specific.h"
+#include "base/cxx17_backports.h"
 #include "base/memory/ptr_util.h"
-#include "base/stl_util.h"
 #include "cc/paint/raw_memory_transfer_cache_entry.h"
 #include "cc/paint/transfer_cache_serialize_helper.h"
 #include "gpu/command_buffer/client/client_test_helper.h"
diff --git a/gpu/command_buffer/client/vertex_array_object_manager_unittest.cc b/gpu/command_buffer/client/vertex_array_object_manager_unittest.cc
index f11e84de..b536128 100644
--- a/gpu/command_buffer/client/vertex_array_object_manager_unittest.cc
+++ b/gpu/command_buffer/client/vertex_array_object_manager_unittest.cc
@@ -12,7 +12,7 @@
 
 #include <memory>
 
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace gpu {
diff --git a/gpu/command_buffer/common/gles2_cmd_format.cc b/gpu/command_buffer/common/gles2_cmd_format.cc
index 6371553..b66cf96 100644
--- a/gpu/command_buffer/common/gles2_cmd_format.cc
+++ b/gpu/command_buffer/common/gles2_cmd_format.cc
@@ -12,7 +12,7 @@
 
 #include <stddef.h>
 
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 
 namespace gpu {
 namespace gles2 {
diff --git a/gpu/command_buffer/common/gles2_cmd_utils.cc b/gpu/command_buffer/common/gles2_cmd_utils.cc
index 0b6e26a..0de243f 100644
--- a/gpu/command_buffer/common/gles2_cmd_utils.cc
+++ b/gpu/command_buffer/common/gles2_cmd_utils.cc
@@ -16,9 +16,9 @@
 #include <sstream>
 
 #include "base/check_op.h"
+#include "base/cxx17_backports.h"
 #include "base/notreached.h"
 #include "base/numerics/safe_math.h"
-#include "base/stl_util.h"
 
 namespace gpu {
 namespace gles2 {
diff --git a/gpu/command_buffer/common/mailbox.cc b/gpu/command_buffer/common/mailbox.cc
index f31a517..da8fda3 100644
--- a/gpu/command_buffer/common/mailbox.cc
+++ b/gpu/command_buffer/common/mailbox.cc
@@ -9,8 +9,8 @@
 #include <string.h>
 
 #include "base/check.h"
+#include "base/cxx17_backports.h"
 #include "base/rand_util.h"
-#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 
 namespace gpu {
diff --git a/gpu/command_buffer/common/raster_cmd_format.cc b/gpu/command_buffer/common/raster_cmd_format.cc
index 6729d13c..dab0c9e 100644
--- a/gpu/command_buffer/common/raster_cmd_format.cc
+++ b/gpu/command_buffer/common/raster_cmd_format.cc
@@ -11,7 +11,7 @@
 
 #include <stddef.h>
 
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 
 namespace gpu {
 namespace raster {
diff --git a/gpu/command_buffer/common/webgpu_cmd_format.cc b/gpu/command_buffer/common/webgpu_cmd_format.cc
index 73e21dcf..468d828 100644
--- a/gpu/command_buffer/common/webgpu_cmd_format.cc
+++ b/gpu/command_buffer/common/webgpu_cmd_format.cc
@@ -7,7 +7,7 @@
 
 // We explicitly do NOT include webgpu_cmd_format.h here because client side
 // and service side have different requirements.
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "gpu/command_buffer/common/cmd_buffer_common.h"
 
 namespace gpu {
diff --git a/gpu/command_buffer/service/buffer_manager_unittest.cc b/gpu/command_buffer/service/buffer_manager_unittest.cc
index 6968432..cffb866 100644
--- a/gpu/command_buffer/service/buffer_manager_unittest.cc
+++ b/gpu/command_buffer/service/buffer_manager_unittest.cc
@@ -7,7 +7,7 @@
 
 #include <memory>
 
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "gpu/command_buffer/service/buffer_manager.h"
 #include "gpu/command_buffer/service/error_state_mock.h"
 #include "gpu/command_buffer/service/feature_info.h"
diff --git a/gpu/command_buffer/service/common_decoder.cc b/gpu/command_buffer/service/common_decoder.cc
index e47dbf5..82dea1c 100644
--- a/gpu/command_buffer/service/common_decoder.cc
+++ b/gpu/command_buffer/service/common_decoder.cc
@@ -9,8 +9,8 @@
 
 #include <algorithm>
 
+#include "base/cxx17_backports.h"
 #include "base/numerics/safe_math.h"
-#include "base/stl_util.h"
 #include "gpu/command_buffer/service/command_buffer_service.h"
 #include "gpu/command_buffer/service/decoder_client.h"
 #include "ui/gfx/ipc/color/gfx_param_traits.h"
diff --git a/gpu/command_buffer/service/external_vk_image_backing.cc b/gpu/command_buffer/service/external_vk_image_backing.cc
index ae998ca..18ca533 100644
--- a/gpu/command_buffer/service/external_vk_image_backing.cc
+++ b/gpu/command_buffer/service/external_vk_image_backing.cc
@@ -7,7 +7,7 @@
 #include <utility>
 #include <vector>
 
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "build/build_config.h"
 #include "components/viz/common/resources/resource_sizes.h"
 #include "gpu/command_buffer/service/external_vk_image_gl_representation.h"
diff --git a/gpu/command_buffer/service/feature_info.cc b/gpu/command_buffer/service/feature_info.cc
index d8137c45..8ef9b9ee 100644
--- a/gpu/command_buffer/service/feature_info.cc
+++ b/gpu/command_buffer/service/feature_info.cc
@@ -12,9 +12,9 @@
 
 #include "base/command_line.h"
 #include "base/containers/contains.h"
+#include "base/cxx17_backports.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
-#include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
 #include "build/build_config.h"
diff --git a/gpu/command_buffer/service/framebuffer_manager_unittest.cc b/gpu/command_buffer/service/framebuffer_manager_unittest.cc
index c783a2e..5dfcffb 100644
--- a/gpu/command_buffer/service/framebuffer_manager_unittest.cc
+++ b/gpu/command_buffer/service/framebuffer_manager_unittest.cc
@@ -7,7 +7,7 @@
 
 #include <memory>
 
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "gpu/command_buffer/client/client_test_helper.h"
 #include "gpu/command_buffer/service/error_state_mock.h"
 #include "gpu/command_buffer/service/feature_info.h"
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 3612e8e..139d4ea 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -25,6 +25,7 @@
 #include "base/containers/flat_set.h"
 #include "base/containers/queue.h"
 #include "base/containers/span.h"
+#include "base/cxx17_backports.h"
 #include "base/debug/alias.h"
 #include "base/debug/dump_without_crashing.h"
 #include "base/hash/legacy_hash.h"
@@ -32,7 +33,6 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/ranges.h"
 #include "base/numerics/safe_math.h"
-#include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/trace_event/trace_event.h"
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
index 8d6eb403..81d27a9 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
@@ -10,7 +10,7 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "base/strings/string_split.h"
 #include "build/build_config.h"
 #include "gpu/command_buffer/service/command_buffer_service.h"
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
index 4f8d623..baa0e9c 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
@@ -9,7 +9,7 @@
 
 #include "base/bind.h"
 #include "base/command_line.h"
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "base/strings/string_number_conversions.h"
 #include "gpu/command_buffer/common/gles2_cmd_format.h"
 #include "gpu/command_buffer/common/gles2_cmd_utils.h"
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_attribs.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_attribs.cc
index de2a399..2c9b7702 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_attribs.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_attribs.cc
@@ -7,7 +7,7 @@
 #include <stddef.h>
 
 #include "base/command_line.h"
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "base/strings/string_number_conversions.h"
 #include "gpu/command_buffer/common/gles2_cmd_format.h"
 #include "gpu/command_buffer/common/gles2_cmd_utils.h"
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
index 906f062..049becc 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
@@ -13,7 +13,7 @@
 #include <vector>
 
 #include "base/command_line.h"
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
 #include "gpu/command_buffer/common/gles2_cmd_format.h"
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc
index 04ecdd60..83082aa 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc
@@ -11,8 +11,8 @@
 #include <memory>
 
 #include "base/command_line.h"
+#include "base/cxx17_backports.h"
 #include "base/numerics/ranges.h"
-#include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "gpu/command_buffer/common/gles2_cmd_format.h"
 #include "gpu/command_buffer/common/gles2_cmd_utils.h"
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_programs.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_programs.cc
index 6ec6b2fa..f0e6375 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_programs.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_programs.cc
@@ -8,7 +8,7 @@
 #include <stdint.h>
 
 #include "base/command_line.h"
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "base/strings/string_number_conversions.h"
 #include "gpu/command_buffer/common/gles2_cmd_format.h"
 #include "gpu/command_buffer/common/gles2_cmd_utils.h"
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
index 1e3c9b6..197ce21 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
@@ -8,7 +8,7 @@
 #include <stdint.h>
 
 #include "base/command_line.h"
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "base/strings/string_number_conversions.h"
 #include "components/viz/common/resources/resource_format_utils.h"
 #include "gpu/command_buffer/common/gles2_cmd_format.h"
diff --git a/gpu/command_buffer/service/gpu_tracer.cc b/gpu/command_buffer/service/gpu_tracer.cc
index f6c29308..7ee9654 100644
--- a/gpu/command_buffer/service/gpu_tracer.cc
+++ b/gpu/command_buffer/service/gpu_tracer.cc
@@ -8,9 +8,9 @@
 #include <stdint.h>
 
 #include "base/bind.h"
+#include "base/cxx17_backports.h"
 #include "base/location.h"
 #include "base/single_thread_task_runner.h"
-#include "base/stl_util.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_task_runner_handle.h"
diff --git a/gpu/command_buffer/service/program_manager.cc b/gpu/command_buffer/service/program_manager.cc
index e3985f92..ae6d27d5 100644
--- a/gpu/command_buffer/service/program_manager.cc
+++ b/gpu/command_buffer/service/program_manager.cc
@@ -15,10 +15,10 @@
 #include <vector>
 
 #include "base/command_line.h"
+#include "base/cxx17_backports.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/safe_math.h"
-#include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/time/time.h"
diff --git a/gpu/command_buffer/service/program_manager_unittest.cc b/gpu/command_buffer/service/program_manager_unittest.cc
index f627ad8..696f66a 100644
--- a/gpu/command_buffer/service/program_manager_unittest.cc
+++ b/gpu/command_buffer/service/program_manager_unittest.cc
@@ -11,7 +11,7 @@
 #include <memory>
 
 #include "base/command_line.h"
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "gpu/command_buffer/common/gles2_cmd_format.h"
diff --git a/gpu/command_buffer/service/raster_decoder.cc b/gpu/command_buffer/service/raster_decoder.cc
index d258855..8433fbd 100644
--- a/gpu/command_buffer/service/raster_decoder.cc
+++ b/gpu/command_buffer/service/raster_decoder.cc
@@ -16,11 +16,11 @@
 #include "base/bind.h"
 #include "base/bits.h"
 #include "base/containers/flat_map.h"
+#include "base/cxx17_backports.h"
 #include "base/debug/crash_logging.h"
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
-#include "base/stl_util.h"
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "cc/paint/paint_cache.h"
diff --git a/gpu/command_buffer/service/scheduler.cc b/gpu/command_buffer/service/scheduler.cc
index 60c27948..2fe4798 100644
--- a/gpu/command_buffer/service/scheduler.cc
+++ b/gpu/command_buffer/service/scheduler.cc
@@ -95,14 +95,15 @@
 Scheduler::PerThreadState& Scheduler::PerThreadState::operator=(
     PerThreadState&& other) = default;
 
-Scheduler::Sequence::Sequence(Scheduler* scheduler,
-                              SequenceId sequence_id,
-                              base::PlatformThreadId thread_id,
-                              SchedulingPriority priority,
-                              scoped_refptr<SyncPointOrderData> order_data)
+Scheduler::Sequence::Sequence(
+    Scheduler* scheduler,
+    SequenceId sequence_id,
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+    SchedulingPriority priority,
+    scoped_refptr<SyncPointOrderData> order_data)
     : scheduler_(scheduler),
       sequence_id_(sequence_id),
-      thread_id_(thread_id),
+      task_runner_(std::move(task_runner)),
       default_priority_(priority),
       current_priority_(priority),
       order_data_(std::move(order_data)) {}
@@ -154,7 +155,7 @@
 }
 
 bool Scheduler::Sequence::ShouldYieldTo(const Sequence* other) const {
-  if (thread_id() != other->thread_id())
+  if (task_runner() != other->task_runner())
     return false;
   if (!running() || !other->scheduled())
     return false;
@@ -393,20 +394,25 @@
     DCHECK(!per_thread_state.second.running);
 }
 
-SequenceId Scheduler::CreateSequence(SchedulingPriority priority) {
+SequenceId Scheduler::CreateSequence(
+    SchedulingPriority priority,
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
   base::AutoLock auto_lock(lock_);
   scoped_refptr<SyncPointOrderData> order_data =
       sync_point_manager_->CreateSyncPointOrderData();
   SequenceId sequence_id = order_data->sequence_id();
-  auto task_runner = base::ThreadTaskRunnerHandle::Get();
-  auto thread_id = base::PlatformThread::CurrentId();
-  auto sequence = std::make_unique<Sequence>(this, sequence_id, thread_id,
-                                             priority, std::move(order_data));
+  auto sequence =
+      std::make_unique<Sequence>(this, sequence_id, std::move(task_runner),
+                                 priority, std::move(order_data));
   sequence_map_.emplace(sequence_id, std::move(sequence));
-  per_thread_state_map_[thread_id].task_runner = task_runner;
   return sequence_id;
 }
 
+SequenceId Scheduler::CreateSequenceForTesting(SchedulingPriority priority) {
+  // This will create the sequence on the thread on which this method is called.
+  return CreateSequence(priority, base::ThreadTaskRunnerHandle::Get());
+}
+
 void Scheduler::DestroySequence(SequenceId sequence_id) {
   base::circular_deque<Sequence::Task> tasks_to_be_destroyed;
   {
@@ -415,7 +421,7 @@
     Sequence* sequence = GetSequence(sequence_id);
     DCHECK(sequence);
     if (sequence->scheduled()) {
-      per_thread_state_map_[sequence->thread_id()].rebuild_scheduling_queue =
+      per_thread_state_map_[sequence->task_runner()].rebuild_scheduling_queue =
           true;
     }
 
@@ -479,7 +485,7 @@
   Sequence* sequence = GetSequence(sequence_id);
   DCHECK(sequence);
 
-  auto task_runner = per_thread_state_map_[sequence->thread_id()].task_runner;
+  auto* task_runner = sequence->task_runner();
   uint32_t order_num = sequence->ScheduleTask(std::move(task.closure),
                                               std::move(task.report_callback));
 
@@ -507,7 +513,7 @@
   base::AutoLock auto_lock(lock_);
   Sequence* sequence = GetSequence(sequence_id);
   DCHECK(sequence);
-  DCHECK_EQ(base::PlatformThread::CurrentId(), sequence->thread_id());
+  DCHECK(sequence->task_runner()->BelongsToCurrentThread());
   sequence->ContinueTask(std::move(closure));
 }
 
@@ -517,10 +523,10 @@
   Sequence* running_sequence = GetSequence(sequence_id);
   DCHECK(running_sequence);
   DCHECK(running_sequence->running());
-  DCHECK_EQ(base::PlatformThread::CurrentId(), running_sequence->thread_id());
+  DCHECK(running_sequence->task_runner()->BelongsToCurrentThread());
 
   const auto& scheduling_queue =
-      RebuildSchedulingQueueIfNeeded(running_sequence->thread_id());
+      RebuildSchedulingQueueIfNeeded(running_sequence->task_runner());
 
   if (scheduling_queue.empty())
     return false;
@@ -546,8 +552,8 @@
 void Scheduler::TryScheduleSequence(Sequence* sequence) {
   lock_.AssertAcquired();
 
-  auto thread_id = sequence->thread_id();
-  auto& thread_state = per_thread_state_map_[thread_id];
+  auto* task_runner = sequence->task_runner();
+  auto& thread_state = per_thread_state_map_[task_runner];
 
   if (sequence->running()) {
     // Update priority of running sequence because of sync token releases.
@@ -557,11 +563,12 @@
     // Rebuild scheduling queue if priority changed for a scheduled sequence.
     DCHECK(thread_state.running);
     DCHECK(sequence->IsRunnable());
-    per_thread_state_map_[thread_id].rebuild_scheduling_queue = true;
+    per_thread_state_map_[task_runner].rebuild_scheduling_queue = true;
   } else if (!sequence->scheduled() && sequence->IsRunnable()) {
     // Insert into scheduling queue if sequence isn't already scheduled.
     SchedulingState scheduling_state = sequence->SetScheduled();
-    auto& scheduling_queue = per_thread_state_map_[thread_id].scheduling_queue;
+    auto& scheduling_queue =
+        per_thread_state_map_[task_runner].scheduling_queue;
     scheduling_queue.push_back(scheduling_state);
     std::push_heap(scheduling_queue.begin(), scheduling_queue.end(),
                    &SchedulingState::Comparator);
@@ -569,18 +576,18 @@
       TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("gpu", "Scheduler::Running", this);
       thread_state.running = true;
       run_next_task_scheduled_ = base::TimeTicks::Now();
-      thread_state.task_runner->PostTask(
-          FROM_HERE,
-          base::BindOnce(&Scheduler::RunNextTask, base::Unretained(this)));
+      task_runner->PostTask(FROM_HERE, base::BindOnce(&Scheduler::RunNextTask,
+                                                      base::Unretained(this)));
     }
   }
 }
 
 std::vector<Scheduler::SchedulingState>&
-Scheduler::RebuildSchedulingQueueIfNeeded(base::PlatformThreadId thread_id) {
+Scheduler::RebuildSchedulingQueueIfNeeded(
+    base::SingleThreadTaskRunner* task_runner) {
   lock_.AssertAcquired();
 
-  auto& thread_state = per_thread_state_map_[thread_id];
+  auto& thread_state = per_thread_state_map_[task_runner];
   auto& scheduling_queue = thread_state.scheduling_queue;
 
   if (!thread_state.rebuild_scheduling_queue)
@@ -591,7 +598,7 @@
   for (const auto& kv : sequence_map_) {
     Sequence* sequence = kv.second.get();
     if (!sequence->IsRunnable() || sequence->running() ||
-        sequence->thread_id() != thread_id) {
+        sequence->task_runner() != task_runner) {
       continue;
     }
     SchedulingState scheduling_state = sequence->SetScheduled();
@@ -610,14 +617,14 @@
       base::TimeTicks::Now() - run_next_task_scheduled_,
       base::TimeDelta::FromMicroseconds(10), base::TimeDelta::FromSeconds(30),
       100);
-  auto thread_id = base::PlatformThread::CurrentId();
+  auto* task_runner = base::ThreadTaskRunnerHandle::Get().get();
 
   SchedulingState state;
   {
-    auto& scheduling_queue = RebuildSchedulingQueueIfNeeded(thread_id);
+    auto& scheduling_queue = RebuildSchedulingQueueIfNeeded(task_runner);
     if (scheduling_queue.empty()) {
       TRACE_EVENT_NESTABLE_ASYNC_END0("gpu", "Scheduler::Running", this);
-      per_thread_state_map_[thread_id].running = false;
+      per_thread_state_map_[task_runner].running = false;
       return;
     }
 
@@ -631,7 +638,7 @@
 
   Sequence* sequence = GetSequence(state.sequence_id);
   DCHECK(sequence);
-  DCHECK_EQ(sequence->thread_id(), thread_id);
+  DCHECK_EQ(sequence->task_runner(), task_runner);
 
   UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
       "GPU.Scheduler.TaskDependencyTime",
@@ -688,7 +695,7 @@
     sequence->FinishTask();
     if (sequence->IsRunnable()) {
       auto& scheduling_queue =
-          per_thread_state_map_[thread_id].scheduling_queue;
+          per_thread_state_map_[task_runner].scheduling_queue;
 
       SchedulingState scheduling_state = sequence->SetScheduled();
       scheduling_queue.push_back(scheduling_state);
@@ -703,17 +710,16 @@
       100);
 
   // Avoid scheduling another RunNextTask if we're done with all tasks.
-  auto& scheduling_queue = RebuildSchedulingQueueIfNeeded(thread_id);
+  auto& scheduling_queue = RebuildSchedulingQueueIfNeeded(task_runner);
   if (scheduling_queue.empty()) {
     TRACE_EVENT_NESTABLE_ASYNC_END0("gpu", "Scheduler::Running", this);
-    per_thread_state_map_[thread_id].running = false;
+    per_thread_state_map_[task_runner].running = false;
     return;
   }
 
   run_next_task_scheduled_ = base::TimeTicks::Now();
-  per_thread_state_map_[thread_id].task_runner->PostTask(
-      FROM_HERE,
-      base::BindOnce(&Scheduler::RunNextTask, base::Unretained(this)));
+  task_runner->PostTask(FROM_HERE, base::BindOnce(&Scheduler::RunNextTask,
+                                                  base::Unretained(this)));
 }
 
 base::TimeDelta Scheduler::TakeTotalBlockingTime() {
@@ -727,9 +733,7 @@
 base::SingleThreadTaskRunner* Scheduler::GetTaskRunnerForTesting(
     SequenceId sequence_id) {
   base::AutoLock auto_lock(lock_);
-  return (per_thread_state_map_[GetSequence(sequence_id)->thread_id()]
-              .task_runner)
-      .get();
+  return GetSequence(sequence_id)->task_runner();
 }
 
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/scheduler.h b/gpu/command_buffer/service/scheduler.h
index 92cdcbb..de327231 100644
--- a/gpu/command_buffer/service/scheduler.h
+++ b/gpu/command_buffer/service/scheduler.h
@@ -64,8 +64,13 @@
   // Create a sequence with given priority. Returns an identifier for the
   // sequence that can be used with SyncPointManager for creating sync point
   // release clients. Sequences start off as enabled (see |EnableSequence|).
-  // Sequence could be created outside of GPU thread.
-  SequenceId CreateSequence(SchedulingPriority priority);
+  // Sequence is bound to the provided |task_runner|.
+  SequenceId CreateSequence(
+      SchedulingPriority priority,
+      scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+  // Should be only used for tests.
+  SequenceId CreateSequenceForTesting(SchedulingPriority priority);
 
   // Destroy the sequence and run any scheduled tasks immediately. Sequence
   // could be destroyed outside of GPU thread.
@@ -135,7 +140,7 @@
    public:
     Sequence(Scheduler* scheduler,
              SequenceId sequence_id,
-             base::PlatformThreadId thread_id,
+             scoped_refptr<base::SingleThreadTaskRunner> task_runner,
              SchedulingPriority priority,
              scoped_refptr<SyncPointOrderData> order_data);
 
@@ -147,7 +152,9 @@
       return order_data_;
     }
 
-    base::PlatformThreadId thread_id() const { return thread_id_; }
+    base::SingleThreadTaskRunner* task_runner() const {
+      return task_runner_.get();
+    }
 
     bool enabled() const { return enabled_; }
 
@@ -312,7 +319,7 @@
 
     Scheduler* const scheduler_;
     const SequenceId sequence_id_;
-    const base::PlatformThreadId thread_id_;
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 
     const SchedulingPriority default_priority_;
     SchedulingPriority current_priority_;
@@ -351,7 +358,7 @@
   // If the scheduling queue needs to be rebuild because a sequence changed
   // priority.
   std::vector<SchedulingState>& RebuildSchedulingQueueIfNeeded(
-      base::PlatformThreadId thread_id);
+      base::SingleThreadTaskRunner* task_runner);
 
   Sequence* GetSequence(SequenceId sequence_id);
 
@@ -373,11 +380,10 @@
     // SchedulingState with highest priority (lowest order) in front.
     std::vector<SchedulingState> scheduling_queue;
     bool rebuild_scheduling_queue = false;
-
-    scoped_refptr<base::SingleThreadTaskRunner> task_runner;
     bool running = false;
   };
-  base::flat_map<base::PlatformThreadId, PerThreadState> per_thread_state_map_;
+  base::flat_map<base::SingleThreadTaskRunner*, PerThreadState>
+      per_thread_state_map_;
 
   // Accumulated time the thread was blocked during running task
   base::TimeDelta total_blocked_time_;
diff --git a/gpu/command_buffer/service/scheduler_unittest.cc b/gpu/command_buffer/service/scheduler_unittest.cc
index 3babe97..e921d556 100644
--- a/gpu/command_buffer/service/scheduler_unittest.cc
+++ b/gpu/command_buffer/service/scheduler_unittest.cc
@@ -45,7 +45,7 @@
 
   void RunAllPendingTasks() {
     SequenceId sequence_id =
-        scheduler()->CreateSequence(SchedulingPriority::kLow);
+        scheduler()->CreateSequenceForTesting(SchedulingPriority::kLow);
     scheduler()->ScheduleTask(Scheduler::Task(
         sequence_id, run_loop_.QuitClosure(), std::vector<SyncToken>()));
     run_loop_.Run();
@@ -62,7 +62,7 @@
 
 TEST_F(SchedulerTest, ScheduledTasksRunInOrder) {
   SequenceId sequence_id =
-      scheduler()->CreateSequence(SchedulingPriority::kNormal);
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kNormal);
 
   static int count = 0;
   int ran1 = 0;
@@ -87,7 +87,7 @@
 
 TEST_F(SchedulerTest, ScheduledTasksRunAfterReporting) {
   SequenceId sequence_id =
-      scheduler()->CreateSequence(SchedulingPriority::kNormal);
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kNormal);
 
   bool ran = false;
   bool reported = false;
@@ -113,7 +113,7 @@
 
 TEST_F(SchedulerTest, ContinuedTasksRunFirst) {
   SequenceId sequence_id =
-      scheduler()->CreateSequence(SchedulingPriority::kNormal);
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kNormal);
 
   static int count = 0;
   int ran1 = 0;
@@ -153,7 +153,7 @@
 
  protected:
   void CreateSequence(int sequence_key, SchedulingPriority priority) {
-    SequenceId sequence_id = scheduler()->CreateSequence(priority);
+    SequenceId sequence_id = scheduler()->CreateSequenceForTesting(priority);
     CommandBufferId command_buffer_id =
         CommandBufferId::FromUnsafeValue(sequence_key);
     scoped_refptr<SyncPointClientState> release_state =
@@ -482,7 +482,7 @@
 
 TEST_F(SchedulerTest, ReleaseSequenceShouldYield) {
   SequenceId sequence_id1 =
-      scheduler()->CreateSequence(SchedulingPriority::kLow);
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kLow);
   CommandBufferNamespace namespace_id = CommandBufferNamespace::GPU_IO;
   CommandBufferId command_buffer_id = CommandBufferId::FromUnsafeValue(1);
   scoped_refptr<SyncPointClientState> release_state =
@@ -504,7 +504,7 @@
   int ran2 = 0;
   SyncToken sync_token(namespace_id, command_buffer_id, release);
   SequenceId sequence_id2 =
-      scheduler()->CreateSequence(SchedulingPriority::kHigh);
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kHigh);
   scheduler()->ScheduleTask(Scheduler::Task(
       sequence_id2, GetClosure([&] { ran2 = ++count; }), {sync_token}));
 
@@ -521,7 +521,7 @@
 
 TEST_F(SchedulerTest, ReentrantEnableSequenceShouldNotDeadlock) {
   SequenceId sequence_id1 =
-      scheduler()->CreateSequence(SchedulingPriority::kHigh);
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kHigh);
   CommandBufferNamespace namespace_id = CommandBufferNamespace::GPU_IO;
   CommandBufferId command_buffer_id1 = CommandBufferId::FromUnsafeValue(1);
   scoped_refptr<SyncPointClientState> release_state1 =
@@ -529,7 +529,7 @@
           namespace_id, command_buffer_id1, sequence_id1);
 
   SequenceId sequence_id2 =
-      scheduler()->CreateSequence(SchedulingPriority::kNormal);
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kNormal);
   CommandBufferId command_buffer_id2 = CommandBufferId::FromUnsafeValue(2);
   scoped_refptr<SyncPointClientState> release_state2 =
       sync_point_manager()->CreateSyncPointClientState(
@@ -576,11 +576,11 @@
 
 TEST_F(SchedulerTest, ClientWaitIsPrioritized) {
   SequenceId sequence_id1 =
-      scheduler()->CreateSequence(SchedulingPriority::kNormal);
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kNormal);
   SequenceId sequence_id2 =
-      scheduler()->CreateSequence(SchedulingPriority::kLow);
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kLow);
   SequenceId sequence_id3 =
-      scheduler()->CreateSequence(SchedulingPriority::kHigh);
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kHigh);
 
   CommandBufferId command_buffer_id = CommandBufferId::FromUnsafeValue(1);
 
@@ -625,7 +625,7 @@
   // schedule the task.
   base::RunLoop run_loop_temp;
   SequenceId sequence_id_run_loop =
-      scheduler()->CreateSequence(SchedulingPriority::kLow);
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kLow);
   scheduler()->ScheduleTask(Scheduler::Task(sequence_id_run_loop,
                                             run_loop_temp.QuitClosure(),
                                             std::vector<SyncToken>()));
@@ -642,9 +642,12 @@
 }
 
 TEST_F(SchedulerTest, StreamPriorities) {
-  SequenceId seq_id1 = scheduler()->CreateSequence(SchedulingPriority::kLow);
-  SequenceId seq_id2 = scheduler()->CreateSequence(SchedulingPriority::kNormal);
-  SequenceId seq_id3 = scheduler()->CreateSequence(SchedulingPriority::kHigh);
+  SequenceId seq_id1 =
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kLow);
+  SequenceId seq_id2 =
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kNormal);
+  SequenceId seq_id3 =
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kHigh);
 
   CommandBufferNamespace namespace_id = CommandBufferNamespace::GPU_IO;
   CommandBufferId command_buffer_id1 = CommandBufferId::FromUnsafeValue(1);
@@ -705,9 +708,12 @@
 }
 
 TEST_F(SchedulerTest, StreamDestroyRemovesPriorities) {
-  SequenceId seq_id1 = scheduler()->CreateSequence(SchedulingPriority::kLow);
-  SequenceId seq_id2 = scheduler()->CreateSequence(SchedulingPriority::kNormal);
-  SequenceId seq_id3 = scheduler()->CreateSequence(SchedulingPriority::kHigh);
+  SequenceId seq_id1 =
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kLow);
+  SequenceId seq_id2 =
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kNormal);
+  SequenceId seq_id3 =
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kHigh);
 
   CommandBufferNamespace namespace_id = CommandBufferNamespace::GPU_IO;
   CommandBufferId command_buffer_id1 = CommandBufferId::FromUnsafeValue(1);
@@ -755,9 +761,12 @@
 
 // crbug.com/781585#5: Test RemoveWait/AddWait/RemoveWait sequence.
 TEST_F(SchedulerTest, StreamPriorityChangeWhileReleasing) {
-  SequenceId seq_id1 = scheduler()->CreateSequence(SchedulingPriority::kLow);
-  SequenceId seq_id2 = scheduler()->CreateSequence(SchedulingPriority::kNormal);
-  SequenceId seq_id3 = scheduler()->CreateSequence(SchedulingPriority::kHigh);
+  SequenceId seq_id1 =
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kLow);
+  SequenceId seq_id2 =
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kNormal);
+  SequenceId seq_id3 =
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kHigh);
 
   CommandBufferNamespace namespace_id = CommandBufferNamespace::GPU_IO;
   CommandBufferId command_buffer_id1 = CommandBufferId::FromUnsafeValue(1);
@@ -812,9 +821,12 @@
 }
 
 TEST_F(SchedulerTest, CircularPriorities) {
-  SequenceId seq_id1 = scheduler()->CreateSequence(SchedulingPriority::kHigh);
-  SequenceId seq_id2 = scheduler()->CreateSequence(SchedulingPriority::kLow);
-  SequenceId seq_id3 = scheduler()->CreateSequence(SchedulingPriority::kNormal);
+  SequenceId seq_id1 =
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kHigh);
+  SequenceId seq_id2 =
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kLow);
+  SequenceId seq_id3 =
+      scheduler()->CreateSequenceForTesting(SchedulingPriority::kNormal);
 
   CommandBufferNamespace namespace_id = CommandBufferNamespace::GPU_IO;
   CommandBufferId command_buffer_id2 = CommandBufferId::FromUnsafeValue(2);
diff --git a/gpu/command_buffer/service/service_utils.cc b/gpu/command_buffer/service/service_utils.cc
index 209f0a40..5c9dddb 100644
--- a/gpu/command_buffer/service/service_utils.cc
+++ b/gpu/command_buffer/service/service_utils.cc
@@ -160,6 +160,8 @@
   gpu_preferences.enable_webgpu =
       command_line->HasSwitch(switches::kEnableUnsafeWebGPU) ||
       command_line->HasSwitch(switches::kEnableUnsafeWebGPUService);
+  gpu_preferences.enable_webgpu_spirv =
+      command_line->HasSwitch(switches::kEnableUnsafeWebGPU);
   if (command_line->HasSwitch(switches::kEnableDawnBackendValidation)) {
     auto value = command_line->GetSwitchValueASCII(
         switches::kEnableDawnBackendValidation);
diff --git a/gpu/command_buffer/service/sync_point_manager.cc b/gpu/command_buffer/service/sync_point_manager.cc
index cede4071..6ae530b 100644
--- a/gpu/command_buffer/service/sync_point_manager.cc
+++ b/gpu/command_buffer/service/sync_point_manager.cc
@@ -9,11 +9,11 @@
 #include <stdint.h>
 
 #include "base/bind.h"
+#include "base/cxx17_backports.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "base/single_thread_task_runner.h"
-#include "base/stl_util.h"
 
 namespace gpu {
 
diff --git a/gpu/command_buffer/service/test_helper.cc b/gpu/command_buffer/service/test_helper.cc
index da9d7c4c..316a094 100644
--- a/gpu/command_buffer/service/test_helper.cc
+++ b/gpu/command_buffer/service/test_helper.cc
@@ -10,7 +10,7 @@
 #include <algorithm>
 #include <string>
 
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "base/strings/string_number_conversions.h"
 #include "gpu/command_buffer/service/buffer_manager.h"
 #include "gpu/command_buffer/service/error_state_mock.h"
diff --git a/gpu/command_buffer/service/texture_manager.cc b/gpu/command_buffer/service/texture_manager.cc
index 528a13b..3e0b11b4 100644
--- a/gpu/command_buffer/service/texture_manager.cc
+++ b/gpu/command_buffer/service/texture_manager.cc
@@ -13,9 +13,9 @@
 #include <utility>
 
 #include "base/bits.h"
+#include "base/cxx17_backports.h"
 #include "base/format_macros.h"
 #include "base/lazy_instance.h"
-#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/memory_dump_manager.h"
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc
index 63b4e115..fdf6ad6 100644
--- a/gpu/command_buffer/service/webgpu_decoder_impl.cc
+++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
@@ -418,6 +418,7 @@
   std::unique_ptr<dawn_native::Instance> dawn_instance_;
   std::vector<dawn_native::Adapter> dawn_adapters_;
 
+  bool allow_spirv_ = false;
   std::vector<std::string> force_enabled_toggles_;
   std::vector<std::string> force_disabled_toggles_;
 
@@ -503,6 +504,7 @@
       break;
   }
 
+  allow_spirv_ = gpu_preferences.enable_webgpu_spirv;
   force_enabled_toggles_ = gpu_preferences.enabled_dawn_features_list;
   force_disabled_toggles_ = gpu_preferences.disabled_dawn_features_list;
 
@@ -564,6 +566,12 @@
         "not_supported_extension_for_test");
   }
 
+  // Disallows usage of SPIR-V by default for security (we only ensure that WGSL
+  // is secure), unless --enable-unsafe-webgpu is used.
+  if (!allow_spirv_) {
+    device_descriptor.forceEnabledToggles.push_back("disallow_spirv");
+  }
+
   for (const std::string& toggles : force_enabled_toggles_) {
     device_descriptor.forceEnabledToggles.push_back(toggles.c_str());
   }
diff --git a/gpu/command_buffer/tests/fuzzer_main.cc b/gpu/command_buffer/tests/fuzzer_main.cc
index d8fb127..c3546d1 100644
--- a/gpu/command_buffer/tests/fuzzer_main.cc
+++ b/gpu/command_buffer/tests/fuzzer_main.cc
@@ -12,10 +12,10 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/command_line.h"
+#include "base/cxx17_backports.h"
 #include "base/i18n/icu_util.h"
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
-#include "base/stl_util.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "build/build_config.h"
diff --git a/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc b/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc
index 5b7a3855..5fbf4e2 100644
--- a/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc
+++ b/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc
@@ -13,8 +13,8 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include "base/cxx17_backports.h"
 #include "base/logging.h"
-#include "base/stl_util.h"
 #include "build/build_config.h"
 #include "gpu/command_buffer/tests/gl_manager.h"
 #include "gpu/command_buffer/tests/gl_test_utils.h"
diff --git a/gpu/command_buffer/tests/gl_cube_map_texture_unittest.cc b/gpu/command_buffer/tests/gl_cube_map_texture_unittest.cc
index 2fde0c5c..4c092dc 100644
--- a/gpu/command_buffer/tests/gl_cube_map_texture_unittest.cc
+++ b/gpu/command_buffer/tests/gl_cube_map_texture_unittest.cc
@@ -7,7 +7,7 @@
 
 #include <memory>
 
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "gpu/command_buffer/tests/gl_manager.h"
 #include "gpu/command_buffer/tests/gl_test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/gpu/command_buffer/tests/gl_depth_texture_unittest.cc b/gpu/command_buffer/tests/gl_depth_texture_unittest.cc
index d720005..df76a54 100644
--- a/gpu/command_buffer/tests/gl_depth_texture_unittest.cc
+++ b/gpu/command_buffer/tests/gl_depth_texture_unittest.cc
@@ -7,7 +7,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "gpu/command_buffer/tests/gl_manager.h"
 #include "gpu/command_buffer/tests/gl_test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
diff --git a/gpu/command_buffer/tests/gl_helper_benchmark.cc b/gpu/command_buffer/tests/gl_helper_benchmark.cc
index 85b7d0b9..86325de 100644
--- a/gpu/command_buffer/tests/gl_helper_benchmark.cc
+++ b/gpu/command_buffer/tests/gl_helper_benchmark.cc
@@ -21,9 +21,9 @@
 
 #include "base/at_exit.h"
 #include "base/command_line.h"
+#include "base/cxx17_backports.h"
 #include "base/files/file_util.h"
 #include "base/run_loop.h"
-#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/task_environment.h"
 #include "base/threading/thread_task_runner_handle.h"
diff --git a/gpu/command_buffer/tests/gl_helper_unittest.cc b/gpu/command_buffer/tests/gl_helper_unittest.cc
index d0fb916..5c40d81 100644
--- a/gpu/command_buffer/tests/gl_helper_unittest.cc
+++ b/gpu/command_buffer/tests/gl_helper_unittest.cc
@@ -17,10 +17,10 @@
 #include <vector>
 
 #include "base/bind.h"
+#include "base/cxx17_backports.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/numerics/ranges.h"
 #include "base/run_loop.h"
-#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/thread_task_runner_handle.h"
diff --git a/gpu/command_buffer/tests/webgpu_test.cc b/gpu/command_buffer/tests/webgpu_test.cc
index cab561f..999708b 100644
--- a/gpu/command_buffer/tests/webgpu_test.cc
+++ b/gpu/command_buffer/tests/webgpu_test.cc
@@ -329,4 +329,38 @@
   GetNewDevice();
 }
 
+TEST_F(WebGPUTest, SPIRVIsDisallowed) {
+  auto ExpectSPIRVDisallowedError = [](WGPUErrorType type, const char* message,
+                                       void* userdata) {
+    // We match on this string to make sure the shader module creation fails
+    // because SPIR-V is disallowed and not because codeSize=0.
+    EXPECT_NE(std::string(message).find("SPIR-V is disallowed"),
+              std::string::npos);
+    EXPECT_EQ(type, WGPUErrorType_Validation);
+    *static_cast<bool*>(userdata) = true;
+  };
+
+  // The initialization code doesn't set GpuPreferences::enable_webgpu_spirv so
+  // it stays at the default value of "false".
+  Initialize(WebGPUTest::Options());
+  wgpu::Device device = GetNewDevice();
+
+  // Make a invalid ShaderModuleDescriptor because it contains SPIR-V.
+  wgpu::ShaderModuleSPIRVDescriptor spirvDesc;
+  spirvDesc.codeSize = 0;
+  spirvDesc.code = nullptr;
+
+  wgpu::ShaderModuleDescriptor desc;
+  desc.nextInChain = &spirvDesc;
+
+  // Make sure creation fails, and for the correct reason.
+  device.PushErrorScope(wgpu::ErrorFilter::Validation);
+  device.CreateShaderModule(&desc);
+  bool got_error = false;
+  device.PopErrorScope(ExpectSPIRVDisallowedError, &got_error);
+
+  WaitForCompletion(device);
+  EXPECT_TRUE(got_error);
+}
+
 }  // namespace gpu
diff --git a/gpu/config/gpu_control_list_entry_unittest.cc b/gpu/config/gpu_control_list_entry_unittest.cc
index 4ff3570..931b991e 100644
--- a/gpu/config/gpu_control_list_entry_unittest.cc
+++ b/gpu/config/gpu_control_list_entry_unittest.cc
@@ -4,7 +4,7 @@
 
 #include <stddef.h>
 
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "build/build_config.h"
 #include "gpu/config/gpu_control_list.h"
 #include "gpu/config/gpu_control_list_testing_data.h"
diff --git a/gpu/config/gpu_driver_bug_list.cc b/gpu/config/gpu_driver_bug_list.cc
index ebf579a..f2c2333 100644
--- a/gpu/config/gpu_driver_bug_list.cc
+++ b/gpu/config/gpu_driver_bug_list.cc
@@ -5,7 +5,7 @@
 #include "gpu/config/gpu_driver_bug_list.h"
 
 #include "base/check_op.h"
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "gpu/config/gpu_driver_bug_list_autogen.h"
 #include "gpu/config/gpu_driver_bug_workaround_type.h"
 #include "gpu/config/gpu_switches.h"
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index 84f8ed45d..09dfc6e 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -3693,6 +3693,21 @@
       "features": [
         "disallow_vp9_resilient_dxva_decoding"
       ]
+    },
+    {
+      "id": 375,
+      "cr_bugs": [1212825],
+      "description": "Always force direct composition full damage on older Windows 10 releases",
+      "os": {
+        "type": "win",
+        "version": {
+          "op": "<",
+          "value": "10.0.19041.508"
+        }
+      },
+      "features": [
+        "force_direct_composition_full_damage_always"
+      ]
     }
   ]
 }
diff --git a/gpu/config/gpu_dx_diagnostics_win.cc b/gpu/config/gpu_dx_diagnostics_win.cc
index 43562b0..becf06e7 100644
--- a/gpu/config/gpu_dx_diagnostics_win.cc
+++ b/gpu/config/gpu_dx_diagnostics_win.cc
@@ -9,7 +9,7 @@
 #include <dxdiag.h>
 #include <windows.h>
 
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/win/com_init_util.h"
diff --git a/gpu/config/gpu_info_collector.cc b/gpu/config/gpu_info_collector.cc
index 15af5dff..878c8da 100644
--- a/gpu/config/gpu_info_collector.cc
+++ b/gpu/config/gpu_info_collector.cc
@@ -12,9 +12,9 @@
 #include <vector>
 
 #include "base/command_line.h"
+#include "base/cxx17_backports.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_functions.h"
-#include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_split.h"
diff --git a/gpu/config/gpu_preferences.h b/gpu/config/gpu_preferences.h
index a5fa582..c5733e52 100644
--- a/gpu/config/gpu_preferences.h
+++ b/gpu/config/gpu_preferences.h
@@ -252,6 +252,10 @@
   // Enable the WebGPU command buffer.
   bool enable_webgpu = false;
 
+  // Enable usage of SPIR-V with WebGPU. This is unsafe since SPIR-V from the
+  // renderer process isn't fully validated.
+  bool enable_webgpu_spirv = false;
+
   // Enable validation layers in Dawn backends.
   DawnBackendValidationLevel enable_dawn_backend_validation =
       DawnBackendValidationLevel::kDisabled;
diff --git a/gpu/config/gpu_workaround_list.txt b/gpu/config/gpu_workaround_list.txt
index 74efadc1..c9103bc 100644
--- a/gpu/config/gpu_workaround_list.txt
+++ b/gpu/config/gpu_workaround_list.txt
@@ -69,6 +69,7 @@
 flush_on_framebuffer_change
 force_cube_complete
 force_cube_map_positive_x_allocation
+force_direct_composition_full_damage_always
 force_enable_color_buffer_float
 force_enable_color_buffer_float_except_rgb32f
 force_gl_flush_on_swap_buffers
diff --git a/gpu/gles2_conform_support/egl/display.cc b/gpu/gles2_conform_support/egl/display.cc
index 62ab1be..d7752a2 100644
--- a/gpu/gles2_conform_support/egl/display.cc
+++ b/gpu/gles2_conform_support/egl/display.cc
@@ -6,7 +6,7 @@
 
 #include <memory>
 
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "build/build_config.h"
 #include "gpu/gles2_conform_support/egl/config.h"
 #include "gpu/gles2_conform_support/egl/context.h"
diff --git a/gpu/ipc/client/gpu_channel_host.cc b/gpu/ipc/client/gpu_channel_host.cc
index 105c328..a1e981a 100644
--- a/gpu/ipc/client/gpu_channel_host.cc
+++ b/gpu/ipc/client/gpu_channel_host.cc
@@ -52,16 +52,9 @@
           static_cast<int32_t>(
               GpuChannelReservedRoutes::kImageDecodeAccelerator)) {
   mojo::PendingAssociatedRemote<mojom::GpuChannel> channel;
-  auto receiver = channel.InitWithNewEndpointAndPassReceiver();
-  if (io_thread_->BelongsToCurrentThread()) {
-    listener_->Initialize(std::move(handle), std::move(receiver), io_thread_);
-  } else {
-    io_thread_->PostTask(
-        FROM_HERE,
-        base::BindOnce(&Listener::Initialize, base::Unretained(listener_.get()),
-                       std::move(handle), std::move(receiver), io_thread_));
-  }
-
+  listener_->Initialize(std::move(handle),
+                        channel.InitWithNewEndpointAndPassReceiver(),
+                        io_thread_);
   gpu_channel_ = mojo::SharedAssociatedRemote<mojom::GpuChannel>(
       std::move(channel), io_thread_);
 
@@ -297,8 +290,7 @@
     scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) {
   channel_ = IPC::ChannelMojo::Create(
       std::move(handle), IPC::Channel::MODE_CLIENT, this, io_task_runner,
-      base::ThreadTaskRunnerHandle::Get(),
-      mojo::internal::MessageQuotaChecker::MaybeCreate());
+      io_task_runner, mojo::internal::MessageQuotaChecker::MaybeCreate());
   DCHECK(channel_);
   bool result = channel_->Connect();
   DCHECK(result);
diff --git a/gpu/ipc/client/gpu_channel_host.h b/gpu/ipc/client/gpu_channel_host.h
index d42478d3..89b3b5a 100644
--- a/gpu/ipc/client/gpu_channel_host.h
+++ b/gpu/ipc/client/gpu_channel_host.h
@@ -166,7 +166,7 @@
     Listener();
     ~Listener() override;
 
-    // Called on the IO thread.
+    // Called on the GpuChannelHost's thread.
     void Initialize(mojo::ScopedMessagePipeHandle handle,
                     mojo::PendingAssociatedReceiver<mojom::GpuChannel> receiver,
                     scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);
diff --git a/gpu/ipc/client/image_decode_accelerator_proxy_unittest.cc b/gpu/ipc/client/image_decode_accelerator_proxy_unittest.cc
index 2a90e2f..75139ae9 100644
--- a/gpu/ipc/client/image_decode_accelerator_proxy_unittest.cc
+++ b/gpu/ipc/client/image_decode_accelerator_proxy_unittest.cc
@@ -6,7 +6,7 @@
 #include <utility>
 #include <vector>
 
-#include "base/stl_util.h"
+#include "base/cxx17_backports.h"
 #include "base/test/task_environment.h"
 #include "cc/paint/paint_image.h"
 #include "gpu/ipc/client/gpu_channel_host.h"
diff --git a/gpu/ipc/common/gpu_preferences.mojom b/gpu/ipc/common/gpu_preferences.mojom
index b24af43..d0871c7 100644
--- a/gpu/ipc/common/gpu_preferences.mojom
+++ b/gpu/ipc/common/gpu_preferences.mojom
@@ -92,6 +92,7 @@
   bool enable_metal;
   bool enable_gpu_benchmarking_extension;
   bool enable_webgpu;
+  bool enable_webgpu_spirv;
   DawnBackendValidationLevel enable_dawn_backend_validation;
   array<string> enabled_dawn_features_list;
   array<string> disabled_dawn_features_list;
diff --git a/gpu/ipc/common/gpu_preferences_mojom_traits.h b/gpu/ipc/common/gpu_preferences_mojom_traits.h
index e7b79cf..69d8209 100644
--- a/gpu/ipc/common/gpu_preferences_mojom_traits.h
+++ b/gpu/ipc/common/gpu_preferences_mojom_traits.h
@@ -209,6 +209,7 @@
     out->enable_gpu_benchmarking_extension =
         prefs.enable_gpu_benchmarking_extension();
     out->enable_webgpu = prefs.enable_webgpu();
+    out->enable_webgpu_spirv = prefs.enable_webgpu_spirv();
     if (!prefs.ReadEnableDawnBackendValidation(
             &out->enable_dawn_backend_validation))
       return false;
@@ -400,6 +401,9 @@
   static bool enable_webgpu(const gpu::GpuPreferences& prefs) {
     return prefs.enable_webgpu;
   }
+  static bool enable_webgpu_spirv(const gpu::GpuPreferences& prefs) {
+    return prefs.enable_webgpu_spirv;
+  }
   static gpu::DawnBackendValidationLevel enable_dawn_backend_validation(
       const gpu::GpuPreferences& prefs) {
     return prefs.enable_dawn_backend_validation;
diff --git a/gpu/ipc/gpu_in_process_thread_service.cc b/gpu/ipc/gpu_in_process_thread_service.cc
index 24cf2d3b..2d71838 100644
--- a/gpu/ipc/gpu_in_process_thread_service.cc
+++ b/gpu/ipc/gpu_in_process_thread_service.cc
@@ -53,28 +53,7 @@
 
 std::unique_ptr<SingleTaskSequence>
 GpuInProcessThreadService::CreateSequence() {
-  // Create the SingleTaskSequence on the gpu thread. This is important since
-  // the sequences are now run on the thread it was created on.
-  std::unique_ptr<SingleTaskSequence> sequence;
-  if (task_runner_->BelongsToCurrentThread()) {
-    sequence = std::make_unique<SchedulerSequence>(scheduler_);
-  } else {
-    base::WaitableEvent completion;
-    task_runner_->PostTask(
-        FROM_HERE,
-        base::BindOnce(&GpuInProcessThreadService::CreateSequenceOnThread,
-                       base::Unretained(this), &sequence, &completion));
-    completion.Wait();
-  }
-  return sequence;
-}
-
-void GpuInProcessThreadService::CreateSequenceOnThread(
-    std::unique_ptr<SingleTaskSequence>* sequence,
-    base::WaitableEvent* completion) {
-  DCHECK(task_runner_->BelongsToCurrentThread());
-  *sequence = std::make_unique<SchedulerSequence>(scheduler_);
-  completion->Signal();
+  return std::make_unique<SchedulerSequence>(scheduler_, task_runner_);
 }
 
 void GpuInProcessThreadService::ScheduleOutOfOrderTask(base::OnceClosure task) {
diff --git a/gpu/ipc/gpu_in_process_thread_service.h b/gpu/ipc/gpu_in_process_thread_service.h
index 45ea2ca1..442861a 100644
--- a/gpu/ipc/gpu_in_process_thread_service.h
+++ b/gpu/ipc/gpu_in_process_thread_service.h
@@ -69,9 +69,6 @@
   scoped_refptr<gl::GLShareGroup> GetShareGroup() override;
 
  private:
-  void CreateSequenceOnThread(std::unique_ptr<SingleTaskSequence>* sequence,
-                              base::WaitableEvent* completion);
-
   GpuInProcessThreadServiceDelegate* const delegate_;
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
   Scheduler* scheduler_;
diff --git a/gpu/ipc/scheduler_sequence.cc b/gpu/ipc/scheduler_sequence.cc
index 16dbb85..11fedf5 100644
--- a/gpu/ipc/scheduler_sequence.cc
+++ b/gpu/ipc/scheduler_sequence.cc
@@ -44,10 +44,13 @@
 #endif
 }
 
-SchedulerSequence::SchedulerSequence(Scheduler* scheduler)
+SchedulerSequence::SchedulerSequence(
+    Scheduler* scheduler,
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner)
     : SingleTaskSequence(),
       scheduler_(scheduler),
-      sequence_id_(scheduler->CreateSequence(SchedulingPriority::kHigh)) {}
+      sequence_id_(scheduler->CreateSequence(SchedulingPriority::kHigh,
+                                             std::move(task_runner))) {}
 
 // Note: this drops tasks not executed yet.
 SchedulerSequence::~SchedulerSequence() {
diff --git a/gpu/ipc/scheduler_sequence.h b/gpu/ipc/scheduler_sequence.h
index 6745c9c1..effb371e 100644
--- a/gpu/ipc/scheduler_sequence.h
+++ b/gpu/ipc/scheduler_sequence.h
@@ -11,6 +11,7 @@
 #include "base/callback.h"
 #include "base/check_op.h"
 #include "base/macros.h"
+#include "base/single_thread_task_runner.h"
 #include "gpu/command_buffer/common/sync_token.h"
 #include "gpu/command_buffer/service/sequence_id.h"
 #include "gpu/ipc/gl_in_process_context_export.h"
@@ -62,7 +63,8 @@
   // allow ScheduleTask.
   static void DefaultDisallowScheduleTaskOnCurrentThread();
 
-  explicit SchedulerSequence(Scheduler* scheduler);
+  SchedulerSequence(Scheduler* scheduler,
+                    scoped_refptr<base::SingleThreadTaskRunner> task_runner);
 
   // Note: this drops tasks not executed yet.
   ~SchedulerSequence() override;
diff --git a/gpu/ipc/service/gpu_channel.cc b/gpu/ipc/service/gpu_channel.cc
index f3909c0..69bcfd5 100644
--- a/gpu/ipc/service/gpu_channel.cc
+++ b/gpu/ipc/service/gpu_channel.cc
@@ -903,7 +903,8 @@
 
   SequenceId sequence_id = stream_sequences_[stream_id];
   if (sequence_id.is_null()) {
-    sequence_id = scheduler_->CreateSequence(init_params->stream_priority);
+    sequence_id =
+        scheduler_->CreateSequence(init_params->stream_priority, task_runner_);
     stream_sequences_[stream_id] = sequence_id;
   }
 
diff --git a/gpu/ipc/service/image_decode_accelerator_stub.cc b/gpu/ipc/service/image_decode_accelerator_stub.cc
index 7559c7e..5c39728 100644
--- a/gpu/ipc/service/image_decode_accelerator_stub.cc
+++ b/gpu/ipc/service/image_decode_accelerator_stub.cc
@@ -99,7 +99,8 @@
     int32_t route_id)
     : worker_(worker),
       channel_(channel),
-      sequence_(channel->scheduler()->CreateSequence(SchedulingPriority::kLow)),
+      sequence_(channel->scheduler()->CreateSequence(SchedulingPriority::kLow,
+                                                     channel->task_runner())),
       sync_point_client_state_(
           channel->sync_point_manager()->CreateSyncPointClientState(
               CommandBufferNamespace::GPU_IO,
diff --git a/gpu/ipc/service/image_transport_surface_win.cc b/gpu/ipc/service/image_transport_surface_win.cc
index a3a0937..916ac85 100644
--- a/gpu/ipc/service/image_transport_surface_win.cc
+++ b/gpu/ipc/service/image_transport_surface_win.cc
@@ -34,6 +34,8 @@
   settings.force_root_surface_full_damage =
       features::IsUsingSkiaRenderer() &&
       gl::ShouldForceDirectCompositionRootSurfaceFullDamage();
+  settings.force_root_surface_full_damage_always =
+      workarounds.force_direct_composition_full_damage_always;
   return settings;
 }
 }  // namespace
diff --git a/gpu/ipc/service/shared_image_stub.cc b/gpu/ipc/service/shared_image_stub.cc
index d52d07d..0ebfe75 100644
--- a/gpu/ipc/service/shared_image_stub.cc
+++ b/gpu/ipc/service/shared_image_stub.cc
@@ -31,7 +31,8 @@
     : channel_(channel),
       command_buffer_id_(
           CommandBufferIdFromChannelAndRoute(channel->client_id(), route_id)),
-      sequence_(channel->scheduler()->CreateSequence(SchedulingPriority::kLow)),
+      sequence_(channel->scheduler()->CreateSequence(SchedulingPriority::kLow,
+                                                     channel_->task_runner())),
       sync_point_client_state_(
           channel->sync_point_manager()->CreateSyncPointClientState(
               CommandBufferNamespace::GPU_IO,
diff --git a/gpu/ipc/service/stream_texture_android.cc b/gpu/ipc/service/stream_texture_android.cc
index 6a3c3f7..7d4d68c3 100644
--- a/gpu/ipc/service/stream_texture_android.cc
+++ b/gpu/ipc/service/stream_texture_android.cc
@@ -94,8 +94,8 @@
       route_id_(route_id),
       has_listener_(false),
       context_state_(std::move(context_state)),
-      sequence_(
-          channel_->scheduler()->CreateSequence(SchedulingPriority::kLow)),
+      sequence_(channel_->scheduler()->CreateSequence(SchedulingPriority::kLow,
+                                                      channel_->task_runner())),
       sync_point_client_state_(
           channel_->sync_point_manager()->CreateSyncPointClientState(
               CommandBufferNamespace::GPU_IO,
diff --git a/gpu/vulkan/vulkan_surface.cc b/gpu/vulkan/vulkan_surface.cc
index d5b8097..94355f2 100644
--- a/gpu/vulkan/vulkan_surface.cc
+++ b/gpu/vulkan/vulkan_surface.cc
@@ -8,9 +8,9 @@
 
 #include <algorithm>
 
+#include "base/cxx17_backports.h"
 #include "base/logging.h"
 #include "base/macros.h"
-#include "base/stl_util.h"
 #include "base/threading/scoped_blocking_call.h"
 #include "gpu/vulkan/vulkan_device_queue.h"
 #include "gpu/vulkan/vulkan_function_pointers.h"
diff --git a/ios/chrome/app/BUILD.gn b/ios/chrome/app/BUILD.gn
index 0348334..904689d 100644
--- a/ios/chrome/app/BUILD.gn
+++ b/ios/chrome/app/BUILD.gn
@@ -187,14 +187,26 @@
   sources = [
     "first_run_app_state_agent.h",
     "first_run_app_state_agent.mm",
+    "first_run_app_state_agent_testing.h",
   ]
   deps = [
+    ":tests_hook",
     "//base",
+    "//ios/chrome/app:blocking_scene_commands",
     "//ios/chrome/app/application_delegate:app_state_header",
     "//ios/chrome/app/application_delegate:application_delegate_internal",
+    "//ios/chrome/browser",
+    "//ios/chrome/browser/browsing_data",
+    "//ios/chrome/browser/geolocation",
+    "//ios/chrome/browser/policy",
     "//ios/chrome/browser/ui:feature_flags",
+    "//ios/chrome/browser/ui/blocking_overlay",
+    "//ios/chrome/browser/ui/browser_view",
+    "//ios/chrome/browser/ui/commands:commands",
+    "//ios/chrome/browser/ui/first_run",
+    "//ios/chrome/browser/ui/first_run:field_trial",
     "//ios/chrome/browser/ui/first_run:utils",
-    "//ios/chrome/browser/ui/main:main",
+    "//ios/chrome/browser/ui/main:browser_interface_provider",
     "//ios/chrome/browser/ui/main:observing_scene_agent",
     "//ios/chrome/browser/ui/main:scene",
   ]
diff --git a/ios/chrome/app/first_run_app_state_agent.h b/ios/chrome/app/first_run_app_state_agent.h
index bd83dcf..cd53d56 100644
--- a/ios/chrome/app/first_run_app_state_agent.h
+++ b/ios/chrome/app/first_run_app_state_agent.h
@@ -12,7 +12,6 @@
 // App state agent that displays the first run UI when needed and handles the
 // InitStageFirstRun stage.
 @interface FirstRunAppAgent : NSObject <AppStateAgent>
-
 @end
 
 #endif  // IOS_CHROME_APP_FIRST_RUN_APP_STATE_AGENT_H_
diff --git a/ios/chrome/app/first_run_app_state_agent.mm b/ios/chrome/app/first_run_app_state_agent.mm
index b3e83a7..cc9fa4a 100644
--- a/ios/chrome/app/first_run_app_state_agent.mm
+++ b/ios/chrome/app/first_run_app_state_agent.mm
@@ -5,30 +5,71 @@
 #import "ios/chrome/app/first_run_app_state_agent.h"
 
 #import "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
 #import "ios/chrome/app/application_delegate/app_state.h"
 #import "ios/chrome/app/application_delegate/app_state_observer.h"
 #include "ios/chrome/app/application_delegate/startup_information.h"
-#include "ios/chrome/browser/ui/first_run/first_run_util.h"
+#import "ios/chrome/app/tests_hook.h"
+#import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
+#import "ios/chrome/browser/main/browser.h"
+#import "ios/chrome/browser/policy/policy_watcher_browser_agent.h"
+#import "ios/chrome/browser/policy/policy_watcher_browser_agent_observer_bridge.h"
+#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
+#import "ios/chrome/browser/ui/commands/browsing_data_commands.h"
+#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
+#import "ios/chrome/browser/ui/first_run/first_run_util.h"
+#import "ios/chrome/browser/ui/first_run/location_permissions_field_trial.h"
+#import "ios/chrome/browser/ui/first_run/orientation_limiting_navigation_controller.h"
+#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
+#import "ios/chrome/browser/ui/main/browser_interface_provider.h"
 #import "ios/chrome/browser/ui/main/scene_controller.h"
 #import "ios/chrome/browser/ui/main/scene_state.h"
 #import "ios/chrome/browser/ui/main/scene_state_observer.h"
+#import "ios/chrome/browser/ui/scoped_ui_blocker/scoped_ui_blocker.h"
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
-@interface FirstRunAppAgent () <AppStateObserver>
+namespace {
+// Histogram enum values for showing the experiment arms of the location
+// permissions experiment. These values are persisted to logs. Entries should
+// not be renumbered and numeric values should never be reused.
+enum class LocationPermissionsUI {
+  // The First Run native location prompt was not shown.
+  kFirstRunPromptNotShown = 0,
+  // The First Run location permissions modal was shown.
+  kFirstRunModal = 1,
+  // kMaxValue should share the value of the highest enumerator.
+  kMaxValue = kFirstRunModal,
+};
+}
+
+@interface FirstRunAppAgent () <AppStateObserver,
+                                PolicyWatcherBrowserAgentObserving>
 
 // The app state for the app.
 @property(nonatomic, weak, readonly) AppState* appState;
 
+@property(nonatomic, weak)
+    WelcomeToChromeViewController* welcomeToChromeController;
+
 // The scene that is chosen for presenting the FRE on.
 @property(nonatomic, strong) SceneState* presentingSceneState;
 
 @end
 
-@implementation FirstRunAppAgent
+@implementation FirstRunAppAgent {
+  // UI blocker used while the FRE UI is shown in the scene controlled by this
+  // object.
+  std::unique_ptr<ScopedUIBlocker> _firstRunUIBlocker;
+
+  // Observer for the sign-out policy changes.
+  std::unique_ptr<PolicyWatcherBrowserAgentObserverBridge>
+      _policyWatcherObserverBridge;
+}
 
 - (void)dealloc {
   [_appState removeObserver:self];
@@ -76,7 +117,7 @@
     return;
   }
 
-  [self showFirstRun:self.presentingSceneState];
+  [self showFirstRunUI];
 }
 
 - (void)appState:(AppState*)appState
@@ -94,21 +135,175 @@
     return;
   }
 
-  [self showFirstRun:sceneState];
+  [self showFirstRunUI];
 }
 
 #pragma mark - internal
 
-- (void)showFirstRun:(SceneState*)sceneState {
-  DCHECK(self.appState.initStage == InitStageFirstRun);
+- (void)setUpPolicyWatcher {
+  _policyWatcherObserverBridge =
+      std::make_unique<PolicyWatcherBrowserAgentObserverBridge>(self);
+
+  Browser* mainBrowser =
+      self.presentingSceneState.interfaceProvider.mainInterface.browser;
+  PolicyWatcherBrowserAgent* policyWatcherAgent =
+      PolicyWatcherBrowserAgent::FromBrowser(mainBrowser);
+
+  // Sanity check that there is a PolicyWatcherBrowserAgent agent stashed in
+  // the browser. This considers that the main browser for the scene was
+  // initialized before showing the FRE, which should always be the case.
+  DCHECK(policyWatcherAgent);
+
+  policyWatcherAgent->AddObserver(_policyWatcherObserverBridge.get());
+}
+
+- (void)tearDownPolicyWatcher {
+  PolicyWatcherBrowserAgent::FromBrowser(
+      self.presentingSceneState.interfaceProvider.mainInterface.browser)
+      ->RemoveObserver(_policyWatcherObserverBridge.get());
+}
+
+- (void)showFirstRunUI {
+  if (![self ignoreFirstRunStageForTesting]) {
+    DCHECK(self.appState.initStage == InitStageFirstRun);
+  }
+
   // There must be a designated presenting scene before showing the first run
   // UI.
   DCHECK(self.presentingSceneState);
 
+  [self setUpPolicyWatcher];
+
   if (base::FeatureList::IsEnabled(kEnableFREUIModuleIOS)) {
-    [sceneState.controller showFirstRunUI];
+    [self.presentingSceneState.controller showFirstRunUI];
   } else {
-    [sceneState.controller showLegacyFirstRunUI];
+    [self showLegacyFirstRunUI];
+  }
+}
+
+// Presents the first run UI to the user.
+- (void)showLegacyFirstRunUI {
+  DCHECK(![self.presentingSceneState.controller isPresentingSigninView]);
+
+  // This should not be necessary because now it's an app-level object doing
+  // this.
+  DCHECK(!_firstRunUIBlocker);
+  _firstRunUIBlocker =
+      std::make_unique<ScopedUIBlocker>(self.presentingSceneState);
+  // Register for the first run dismissal notification to reset
+  // |sceneState.presentingFirstRunUI| flag;
+  [[NSNotificationCenter defaultCenter]
+      addObserver:self
+         selector:@selector(handleFirstRunUIWillFinish)
+             name:kChromeFirstRunUIWillFinishNotification
+           object:nil];
+  [[NSNotificationCenter defaultCenter]
+      addObserver:self
+         selector:@selector(handleFirstRunUIDidFinish)
+             name:kChromeFirstRunUIDidFinishNotification
+           object:nil];
+
+  Browser* browser =
+      self.presentingSceneState.interfaceProvider.mainInterface.browser;
+  id<ApplicationCommands, BrowsingDataCommands> welcomeHandler =
+      static_cast<id<ApplicationCommands, BrowsingDataCommands>>(
+          browser->GetCommandDispatcher());
+
+  WelcomeToChromeViewController* welcomeToChrome =
+      [[WelcomeToChromeViewController alloc]
+          initWithBrowser:browser
+                presenter:self.presentingSceneState.interfaceProvider
+                              .currentInterface.bvc
+               dispatcher:welcomeHandler];
+  self.welcomeToChromeController = welcomeToChrome;
+  UINavigationController* navController =
+      [[OrientationLimitingNavigationController alloc]
+          initWithRootViewController:welcomeToChrome];
+  [navController setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
+  navController.modalPresentationStyle = UIModalPresentationFullScreen;
+  CGRect appFrame = [[UIScreen mainScreen] bounds];
+  [[navController view] setFrame:appFrame];
+  self.presentingSceneState.presentingFirstRunUI = YES;
+  [self.presentingSceneState.interfaceProvider.currentInterface.viewController
+      presentViewController:navController
+                   animated:NO
+                 completion:nil];
+}
+
+- (void)handleFirstRunUIWillFinish {
+  DCHECK(self.presentingSceneState.presentingFirstRunUI);
+  _firstRunUIBlocker.reset();
+  self.presentingSceneState.presentingFirstRunUI = NO;
+  [self tearDownPolicyWatcher];
+  [[NSNotificationCenter defaultCenter]
+      removeObserver:self
+                name:kChromeFirstRunUIWillFinishNotification
+              object:nil];
+}
+
+// All of this can be triggered from will transition from init stage, or did
+// transition to init stage, once the rest is done.
+- (void)handleFirstRunUIDidFinish {
+  [[NSNotificationCenter defaultCenter]
+      removeObserver:self
+                name:kChromeFirstRunUIDidFinishNotification
+              object:nil];
+
+  self.welcomeToChromeController = nil;
+
+  [self maybePromptLocationWithSystemAlert];
+
+  if (![self ignoreFirstRunStageForTesting]) {
+    [self.appState queueTransitionToNextInitStage];
+  }
+}
+
+- (void)logLocationPermissionsExperimentForGroupShown:
+    (LocationPermissionsUI)experimentGroup {
+  UMA_HISTOGRAM_ENUMERATION("IOS.LocationPermissionsUI", experimentGroup);
+}
+
+- (void)maybePromptLocationWithSystemAlert {
+  if (!location_permissions_field_trial::IsInRemoveFirstRunPromptGroup() &&
+      !location_permissions_field_trial::IsInFirstRunModalGroup()) {
+    [self logLocationPermissionsExperimentForGroupShown:
+              LocationPermissionsUI::kFirstRunPromptNotShown];
+    // As soon as First Run has finished, give OmniboxGeolocationController an
+    // opportunity to present the iOS system location alert.
+    [[OmniboxGeolocationController sharedInstance] triggerSystemPrompt];
+  } else if (location_permissions_field_trial::
+                 IsInRemoveFirstRunPromptGroup()) {
+    // If in RemoveFirstRunPrompt group, the system prompt will be delayed until
+    // the site requests location information.
+    [[OmniboxGeolocationController sharedInstance]
+        systemPromptSkippedForNewUser];
+  }
+}
+
+// TODO(crbug.com/1210246): Remove this hook once the chrome test fixture is
+// adapted to startup testing.
+//
+// Determines whether the First Run stage has to be ignored because of
+// testing. When testing with first_run_egtest.mm, the First Run UI is
+// manually triggered after the browser is fully initialized, in which
+// case the code that assumes that the app is in the First Run stage when
+// showing the FRE has to be ignored to avoid unexepted failures (e.g., DCHECKs,
+// unexpected init stage transition).
+- (BOOL)ignoreFirstRunStageForTesting {
+  return tests_hook::DisableFirstRun();
+}
+
+#pragma mark - PolicyWatcherBrowserAgentObserving
+
+- (void)policyWatcherBrowserAgentNotifySignInDisabled:
+    (PolicyWatcherBrowserAgent*)policyWatcher {
+  auto signinInterrupted = ^{
+    policyWatcher->SignInUIDismissed();
+  };
+
+  if (self.welcomeToChromeController) {
+    [self.welcomeToChromeController
+        interruptSigninCoordinatorWithCompletion:signinInterrupted];
   }
 }
 
diff --git a/ios/chrome/app/first_run_app_state_agent_testing.h b/ios/chrome/app/first_run_app_state_agent_testing.h
new file mode 100644
index 0000000..b660352
--- /dev/null
+++ b/ios/chrome/app/first_run_app_state_agent_testing.h
@@ -0,0 +1,20 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_APP_FIRST_RUN_APP_STATE_AGENT_TESTING_H_
+#define IOS_CHROME_APP_FIRST_RUN_APP_STATE_AGENT_TESTING_H_
+
+#import "ios/chrome/app/first_run_app_state_agent.h"
+
+// Interface for testing FirstRunAppAgent.
+@interface FirstRunAppAgent (TestingOnly)
+
+// TODO(crbug.com/1210246): Remove this once the chrome test fixture is adapted
+// to startup testing.
+// Shows the First Run UI for testing purpose.
+- (void)showFirstRunUI;
+
+@end
+
+#endif  // IOS_CHROME_APP_FIRST_RUN_APP_STATE_AGENT_TESTING_H_
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index 32f4805..5a7e2c1b 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -289,6 +289,8 @@
   // List of closure to run as part of shutdown. The closure will be called
   // in reverse order of registration.
   std::vector<base::OnceClosure> _cleanupClosures;
+
+  FirstRunAppAgent* _firstRunAppAgent;
 }
 
 // Handles collecting metrics on user triggered screenshots
@@ -644,7 +646,10 @@
 - (void)addPostSafeModeAgents {
   [self.appState addAgent:[[ContentSuggestionsSchedulerAppAgent alloc] init]];
   [self.appState addAgent:[[IncognitoUsageAppStateAgent alloc] init]];
-  [self.appState addAgent:[[FirstRunAppAgent alloc] init]];
+
+  FirstRunAppAgent* firstRunAppAgent = [[FirstRunAppAgent alloc] init];
+  _firstRunAppAgent = firstRunAppAgent;
+  [self.appState addAgent:firstRunAppAgent];
 }
 
 #pragma mark - Property implementation.
@@ -1290,6 +1295,10 @@
 
 @implementation MainController (TestingOnly)
 
+- (FirstRunAppAgent*)firstRunAppAgent {
+  return _firstRunAppAgent;
+}
+
 - (void)setStartupParametersWithURL:(const GURL&)launchURL {
   NSString* sourceApplication = @"Fake App";
   SceneState* sceneState = self.appState.foregroundActiveScene;
diff --git a/ios/chrome/app/main_controller_private.h b/ios/chrome/app/main_controller_private.h
index 1550252..88237cf 100644
--- a/ios/chrome/app/main_controller_private.h
+++ b/ios/chrome/app/main_controller_private.h
@@ -13,6 +13,7 @@
 
 class GURL;
 @protocol TabSwitcher;
+@class FirstRunAppAgent;
 
 // Private methods and protocols that are made visible here for tests.
 @interface MainController ()
@@ -25,6 +26,11 @@
 // Methods that only exist for tests.
 @interface MainController (TestingOnly)
 
+// TODO(crbug.com/1210246): Remove this once the chrome test fixture is adapted
+// to startup testing.
+// Returns the FirstRunAppAgent.
+- (FirstRunAppAgent*)firstRunAppAgent;
+
 // Sets the internal startup state to indicate that the launch was triggered
 // by an external app opening the given URL.
 - (void)setStartupParametersWithURL:(const GURL&)launchURL;
diff --git a/ios/chrome/browser/translate/language_detection_javascript_unittest.mm b/ios/chrome/browser/translate/language_detection_javascript_unittest.mm
index ed91175..c78b04a 100644
--- a/ios/chrome/browser/translate/language_detection_javascript_unittest.mm
+++ b/ios/chrome/browser/translate/language_detection_javascript_unittest.mm
@@ -59,10 +59,10 @@
     });
   }
 
-  // Verifies if translation was allowed or not on the page based on
+  // Verifies if the notranslate meta tag is present or not on the page based on
   // |expected_value|.
-  void ExpectTranslationAllowed(BOOL expected_value) {
-    InjectJsAndVerify(@"__gCrWeb.languageDetection.translationAllowed();",
+  void ExpectHasNoTranslate(BOOL expected_value) {
+    InjectJsAndVerify(@"__gCrWeb.languageDetection.hasNoTranslate();",
                       @(expected_value));
   }
 
@@ -91,20 +91,20 @@
   }
 };
 
-// Tests |__gCrWeb.languageDetection.translationAllowed| JS call.
-TEST_F(JsLanguageDetectionManagerTest, IsTranslationAllowed) {
+// Tests |__gCrWeb.languageDetection.hasNoTranslate| JS call.
+TEST_F(JsLanguageDetectionManagerTest, PageHasNoTranslate) {
   LoadHtml(@"<html></html>");
-  ExpectTranslationAllowed(YES);
+  ExpectHasNoTranslate(NO);
 
   LoadHtml(@"<html><head>"
             "<meta name='google' content='notranslate'>"
             "</head></html>");
-  ExpectTranslationAllowed(NO);
+  ExpectHasNoTranslate(YES);
 
   LoadHtml(@"<html><head>"
             "<meta name='google' value='notranslate'>"
             "</head></html>");
-  ExpectTranslationAllowed(NO);
+  ExpectHasNoTranslate(YES);
 }
 
 // Tests correctness of |document.documentElement.lang| attribute.
@@ -261,11 +261,16 @@
   base::CallbackListSubscription subscription_;
 };
 
-// Tests if |__gCrWeb.languageDetection.detectLanguage| correctly informs the
-// native side when translation is not allowed.
+// Tests if |__gCrWeb.languageDetection.hasNoTranslate| correctly informs the
+// native side when the notranslate meta tag is specified.
 TEST_F(JsLanguageDetectionManagerDetectLanguageTest,
-       DetectLanguageTranslationNotAllowed) {
-  LoadHtml(@"<html></html>");
+       DetectLanguageWithNoTranslateMeta) {
+  // A simple page using the notranslate meta tag.
+  NSString* html = @"<html><head>"
+                   @"<meta http-equiv='content-language' content='en'>"
+                   @"<meta name='google' content='notranslate'>"
+                   @"</head></html>";
+  LoadHtml(html);
   ExecuteJavaScript(@"__gCrWeb.languageDetection.detectLanguage()");
   // Wait until the original injection has received a command.
   base::test::ios::WaitUntilCondition(^bool() {
@@ -275,30 +280,25 @@
 
   commands_received_.clear();
 
-  // Stub out translationAllowed.
-  NSString* const kTranslationAllowedJS =
-      @"__gCrWeb.languageDetection.translationAllowed = function() {"
-      @"  return false;"
-      @"}";
-  ExecuteJavaScript(kTranslationAllowedJS);
-  ConditionBlock commands_recieved_block = ^bool {
-    return commands_received_.size();
-  };
-  InjectJSAndWaitUntilCondition(@"__gCrWeb.languageDetection.detectLanguage()",
-                                commands_recieved_block);
+  ExecuteJavaScript(@"__gCrWeb.languageDetection.detectLanguage()");
+  base::test::ios::WaitUntilCondition(^bool() {
+    return !commands_received_.empty();
+  });
   ASSERT_EQ(1U, commands_received_.size());
   const base::Value& value = commands_received_[0];
-  absl::optional<bool> translation_allowed =
-      value.FindBoolKey("translationAllowed");
-  ASSERT_TRUE(translation_allowed);
-  EXPECT_FALSE(*translation_allowed);
+  absl::optional<bool> has_notranslate = value.FindBoolKey("hasNoTranslate");
+  ASSERT_TRUE(has_notranslate);
+  EXPECT_TRUE(value.FindKey("captureTextTime"));
+  EXPECT_TRUE(value.FindKey("htmlLang"));
+  EXPECT_TRUE(value.FindKey("httpContentLanguage"));
+  EXPECT_TRUE(*has_notranslate);
 }
 
 // Tests if |__gCrWeb.languageDetection.detectLanguage| correctly informs the
-// native side when translation is allowed with the right parameters.
+// native side when no notranslate meta tag is specified.
 TEST_F(JsLanguageDetectionManagerDetectLanguageTest,
-       DetectLanguageTranslationAllowed) {
-  // A simple page that allows translation.
+       DetectLanguageWithoutNoTranslateMeta) {
+  // A simple page with no notranslate meta tag.
   NSString* html = @"<html><head>"
                    @"<meta http-equiv='content-language' content='en'>"
                    @"</head></html>";
@@ -319,12 +319,11 @@
   ASSERT_EQ(1U, commands_received_.size());
   const base::Value& value = commands_received_[0];
 
-  absl::optional<bool> translation_allowed =
-      value.FindBoolKey("translationAllowed");
+  absl::optional<bool> has_notranslate = value.FindBoolKey("hasNoTranslate");
 
-  ASSERT_TRUE(translation_allowed);
+  ASSERT_TRUE(has_notranslate);
   EXPECT_TRUE(value.FindKey("captureTextTime"));
   EXPECT_TRUE(value.FindKey("htmlLang"));
   EXPECT_TRUE(value.FindKey("httpContentLanguage"));
-  EXPECT_TRUE(*translation_allowed);
+  EXPECT_FALSE(*has_notranslate);
 }
diff --git a/ios/chrome/browser/ui/first_run/BUILD.gn b/ios/chrome/browser/ui/first_run/BUILD.gn
index 4fe567a..29ef22d 100644
--- a/ios/chrome/browser/ui/first_run/BUILD.gn
+++ b/ios/chrome/browser/ui/first_run/BUILD.gn
@@ -223,6 +223,7 @@
     "//components/metrics",
     "//components/prefs",
     "//ios/chrome/app:app_internal",
+    "//ios/chrome/app:first_run_app_state_agent",
     "//ios/chrome/browser",
     "//ios/chrome/browser/sync",
     "//ios/chrome/browser/ui/main:scene",
diff --git a/ios/chrome/browser/ui/first_run/first_run_app_interface.h b/ios/chrome/browser/ui/first_run/first_run_app_interface.h
index ab7b2c9..15c1c95 100644
--- a/ios/chrome/browser/ui/first_run/first_run_app_interface.h
+++ b/ios/chrome/browser/ui/first_run/first_run_app_interface.h
@@ -13,7 +13,7 @@
 @interface FirstRunAppInterface : NSObject
 
 // Triggers the display of the first run UI.
-+ (void)showLegacyFirstRunUI;
++ (void)showFirstRunUI;
 
 // Resets the UMA collection enabled pref to |enabled|.
 + (void)setUMACollectionEnabled:(BOOL)enabled;
diff --git a/ios/chrome/browser/ui/first_run/first_run_app_interface.mm b/ios/chrome/browser/ui/first_run/first_run_app_interface.mm
index 7c8c592..92b0b26 100644
--- a/ios/chrome/browser/ui/first_run/first_run_app_interface.mm
+++ b/ios/chrome/browser/ui/first_run/first_run_app_interface.mm
@@ -7,7 +7,10 @@
 #include "components/metrics/metrics_pref_names.h"
 #include "components/metrics/metrics_reporting_default_state.h"
 #include "components/prefs/pref_service.h"
+#include "ios/chrome/app/first_run_app_state_agent_testing.h"
 #import "ios/chrome/app/main_controller.h"
+#include "ios/chrome/app/main_controller.h"
+#import "ios/chrome/app/main_controller_private.h"
 #include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/sync/sync_setup_service.h"
 #include "ios/chrome/browser/sync/sync_setup_service_factory.h"
@@ -22,8 +25,8 @@
 
 @implementation FirstRunAppInterface
 
-+ (void)showLegacyFirstRunUI {
-  [chrome_test_util::GetForegroundActiveSceneController() showLegacyFirstRunUI];
++ (void)showFirstRunUI {
+  [[chrome_test_util::GetMainController() firstRunAppAgent] showFirstRunUI];
 }
 
 + (void)setUMACollectionEnabled:(BOOL)enabled {
diff --git a/ios/chrome/browser/ui/first_run/first_run_egtest.mm b/ios/chrome/browser/ui/first_run/first_run_egtest.mm
index b4f9b82..abaa8d4 100644
--- a/ios/chrome/browser/ui/first_run/first_run_egtest.mm
+++ b/ios/chrome/browser/ui/first_run/first_run_egtest.mm
@@ -63,12 +63,13 @@
 - (AppLaunchConfiguration)appConfigurationForTestCase {
   AppLaunchConfiguration config;
   config.features_disabled.push_back(kLocationPermissionsPrompt);
+  config.features_disabled.push_back(kEnableFREUIModuleIOS);
   return config;
 }
 
 // Navigates to the terms of service and back.
 - (void)testTermsAndConditions {
-  [FirstRunAppInterface showLegacyFirstRunUI];
+  [FirstRunAppInterface showFirstRunUI];
 
   id<GREYMatcher> termsOfServiceLink =
       grey_accessibilityLabel(@"Terms of Service");
@@ -101,7 +102,7 @@
 
 // Toggle the UMA checkbox.
 - (void)testToggleMetricsOn {
-  [FirstRunAppInterface showLegacyFirstRunUI];
+  [FirstRunAppInterface showFirstRunUI];
 
   id<GREYMatcher> metrics =
       grey_accessibilityID(first_run::kUMAMetricsButtonAccessibilityIdentifier);
@@ -122,7 +123,7 @@
 
 // Dismisses the first run screens.
 - (void)testDismissFirstRun {
-  [FirstRunAppInterface showLegacyFirstRunUI];
+  [FirstRunAppInterface showFirstRunUI];
 
   [[EarlGrey selectElementWithMatcher:FirstRunOptInAcceptButton()]
       performAction:grey_tap()];
@@ -144,7 +145,7 @@
   [SigninEarlGrey addFakeIdentity:fakeIdentity];
 
   // Launch First Run and accept tems of services.
-  [FirstRunAppInterface showLegacyFirstRunUI];
+  [FirstRunAppInterface showFirstRunUI];
   [[EarlGrey selectElementWithMatcher:FirstRunOptInAcceptButton()]
       performAction:grey_tap()];
 
@@ -170,7 +171,7 @@
   if (![ChromeEarlGrey areMultipleWindowsSupported])
     EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
 
-  [FirstRunAppInterface showLegacyFirstRunUI];
+  [FirstRunAppInterface showFirstRunUI];
 
   [ChromeEarlGrey openNewWindow];
   [ChromeEarlGrey waitForForegroundWindowCount:2];
diff --git a/ios/chrome/browser/ui/infobars/translate_infobar_egtest.mm b/ios/chrome/browser/ui/infobars/translate_infobar_egtest.mm
index bf968f3..aa7d856 100644
--- a/ios/chrome/browser/ui/infobars/translate_infobar_egtest.mm
+++ b/ios/chrome/browser/ui/infobars/translate_infobar_egtest.mm
@@ -300,8 +300,8 @@
                                   translate::kUnknownLanguageCode)];
 }
 
-// Tests that language detection is not performed when the page specifies that
-// it should not be translated.
+// Tests that language detection is still performed when the page specifies the
+// notranslate meta tag.
 - (void)testLanguageDetectionNoTranslate {
   // Start the HTTP server.
   std::unique_ptr<web::DataResponseProvider> provider(new TestResponseProvider);
@@ -315,16 +315,16 @@
   // Load some french page with |content="notranslate"| meta tag.
   [ChromeEarlGrey loadURL:noTranslateContentURL];
 
-  // Check that no language has been detected.
-  GREYAssertFalse([self waitForLanguageDetection],
-                  @"A language has been detected");
+  // Check that the language has been detected.
+  GREYAssertTrue([self waitForLanguageDetection],
+                 @"A language has been detected");
 
   // Load some french page with |value="notranslate"| meta tag.
   [ChromeEarlGrey loadURL:noTranslateValueURL];
 
-  // Check that no language has been detected.
-  GREYAssertFalse([self waitForLanguageDetection],
-                  @"A language has been detected");
+  // Check that the language has been detected.
+  GREYAssertTrue([self waitForLanguageDetection],
+                 @"A language has been detected");
 }
 
 // Tests that history.pushState triggers a new detection.
diff --git a/ios/chrome/browser/ui/main/scene_controller.h b/ios/chrome/browser/ui/main/scene_controller.h
index 146dc90..f7fb10b 100644
--- a/ios/chrome/browser/ui/main/scene_controller.h
+++ b/ios/chrome/browser/ui/main/scene_controller.h
@@ -33,22 +33,21 @@
 @property(nonatomic, strong, readonly) id<BrowserInterfaceProvider>
     interfaceProvider;
 
+// YES if incognito mode is forced by enterprise policy.
+@property(nonatomic, readonly, getter=isIncognitoForced) BOOL incognitoForced;
+
+// YES if the scene is presenting the signin view.
+@property(nonatomic, readonly) BOOL isPresentingSigninView;
+
 // Handler for the UIWindowSceneDelegate callback with the same selector.
 - (void)performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
                    completionHandler:(void (^)(BOOL succeeded))completionHandler
     API_AVAILABLE(ios(13));
 
-// Return YES if incognito mode is forced by enterprise policy.
-- (BOOL)isIncognitoForced;
-
 // TODO(crbug.com/1210256): Remove this once it is migrated to the agent.
 // Shows the new first run UI.
 - (void)showFirstRunUI;
 
-// TODO(crbug.com/1210256): Remove this once it is migrated to the agent.
-// Shows the legacy first run UI.
-- (void)showLegacyFirstRunUI;
-
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_MAIN_SCENE_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/main/scene_controller.mm b/ios/chrome/browser/ui/main/scene_controller.mm
index 1e5ab32..1b8e7db 100644
--- a/ios/chrome/browser/ui/main/scene_controller.mm
+++ b/ios/chrome/browser/ui/main/scene_controller.mm
@@ -382,6 +382,10 @@
   }
 }
 
+- (BOOL)isPresentingSigninView {
+  return self.signinCoordinator != nil;
+}
+
 #pragma mark - SceneStateObserver
 
 - (void)sceneState:(SceneState*)sceneState
@@ -835,7 +839,8 @@
 
   // Only create the restoration helper if the session with the current session
   // id was backed up successfully.
-  if (self.sceneState.appState.sessionRestorationRequired) {
+  if (self.sceneState.appState.sessionRestorationRequired &&
+      !self.sceneState.appState.startupInformation.isFirstRun) {
     Browser* mainBrowser = self.mainInterface.browser;
     if (!base::ios::IsMultiwindowSupported() ||
         [CrashRestoreHelper
@@ -1162,48 +1167,6 @@
 
 #pragma mark - First Run
 
-// Initializes the first run UI and presents it to the user.
-- (void)showLegacyFirstRunUI {
-  DCHECK(!self.signinCoordinator);
-  DCHECK(!_firstRunUIBlocker);
-  _firstRunUIBlocker = std::make_unique<ScopedUIBlocker>(self.sceneState);
-  // Register for the first run dismissal notification to reset
-  // |sceneState.presentingFirstRunUI| flag;
-  [[NSNotificationCenter defaultCenter]
-      addObserver:self
-         selector:@selector(handleFirstRunUIWillFinish)
-             name:kChromeFirstRunUIWillFinishNotification
-           object:nil];
-  [[NSNotificationCenter defaultCenter]
-      addObserver:self
-         selector:@selector(handleFirstRunUIDidFinish)
-             name:kChromeFirstRunUIDidFinishNotification
-           object:nil];
-
-  Browser* browser = self.mainInterface.browser;
-  id<ApplicationCommands, BrowsingDataCommands> welcomeHandler =
-      static_cast<id<ApplicationCommands, BrowsingDataCommands>>(
-          browser->GetCommandDispatcher());
-
-  WelcomeToChromeViewController* welcomeToChrome =
-      [[WelcomeToChromeViewController alloc]
-          initWithBrowser:browser
-                presenter:self.currentInterface.bvc
-               dispatcher:welcomeHandler];
-  self.welcomeToChromeController = welcomeToChrome;
-  UINavigationController* navController =
-      [[OrientationLimitingNavigationController alloc]
-          initWithRootViewController:welcomeToChrome];
-  [navController setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
-  navController.modalPresentationStyle = UIModalPresentationFullScreen;
-  CGRect appFrame = [[UIScreen mainScreen] bounds];
-  [[navController view] setFrame:appFrame];
-  self.sceneState.presentingFirstRunUI = YES;
-  [self.currentInterface.viewController presentViewController:navController
-                                                     animated:NO
-                                                   completion:nil];
-}
-
 // Shows the first run UI.
 - (void)showFirstRunUI {
   DCHECK(!_firstRunUIBlocker);
@@ -1252,34 +1215,6 @@
               object:nil];
 }
 
-// Handles the notification that first run modal dialog UI completed.
-- (void)handleFirstRunUIDidFinish {
-  [[NSNotificationCenter defaultCenter]
-      removeObserver:self
-                name:kChromeFirstRunUIDidFinishNotification
-              object:nil];
-
-  self.welcomeToChromeController = nil;
-
-  if (!location_permissions_field_trial::IsInRemoveFirstRunPromptGroup() &&
-      !location_permissions_field_trial::IsInFirstRunModalGroup()) {
-    [self logLocationPermissionsExperimentForGroupShown:
-              LocationPermissionsUI::kFirstRunPromptNotShown];
-    // As soon as First Run has finished, give OmniboxGeolocationController an
-    // opportunity to present the iOS system location alert.
-    [[OmniboxGeolocationController sharedInstance] triggerSystemPrompt];
-  } else if (location_permissions_field_trial::
-                 IsInRemoveFirstRunPromptGroup()) {
-    // If in RemoveFirstRunPrompt group, the system prompt will be delayed until
-    // the site requests location information.
-    [[OmniboxGeolocationController sharedInstance]
-        systemPromptSkippedForNewUser];
-  }
-  if (![self ignoreFirstRunStageForTesting]) {
-    [self.sceneState.appState queueTransitionToNextInitStage];
-  }
-}
-
 // Presents the sign-in upgrade promo if is relevant and possible.
 // Returns YES if the promo is shown.
 - (BOOL)presentSigninUpgradePromoIfPossible {
@@ -3154,10 +3089,6 @@
     [self interruptSigninCoordinatorAnimated:YES completion:signinInterrupted];
     UMA_HISTOGRAM_BOOLEAN(
         "Enterprise.BrowserSigninIOS.SignInInterruptedByPolicy", true);
-  } else if (self.sceneState.presentingFirstRunUI &&
-             self.welcomeToChromeController) {
-    [self.welcomeToChromeController
-        interruptSigninCoordinatorWithCompletion:signinInterrupted];
   }
 }
 
diff --git a/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm b/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
index f83d94b..df1fc1c 100644
--- a/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
+++ b/ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.mm
@@ -714,7 +714,7 @@
                              IDS_IOS_GOOGLE_SERVICES_SETTINGS_AUTOCOMPLETE_SEARCHES_AND_URLS_TEXT
                        detailStringID:
                            IDS_IOS_GOOGLE_SERVICES_SETTINGS_AUTOCOMPLETE_SEARCHES_AND_URLS_DETAIL
-                               status:self.autocompleteSearchPreference];
+                               status:self.autocompleteSearchPreference.value];
       [items addObject:autocompleteItem];
     } else {
       SyncSwitchItem* autocompleteItem = [self
@@ -735,7 +735,7 @@
                              IDS_IOS_GOOGLE_SERVICES_SETTINGS_SAFE_BROWSING_TEXT
                        detailStringID:
                            IDS_IOS_GOOGLE_SERVICES_SETTINGS_SAFE_BROWSING_DETAIL
-                               status:self.safeBrowsingPreference];
+                               status:self.safeBrowsingPreference.value];
       [items addObject:safeBrowsingManagedItem];
     } else {
       SyncSwitchItem* safeBrowsingItem = [self
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
index 0cb1c43..88c44227 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
@@ -1592,12 +1592,16 @@
       googleSyncItem.iconImageName = kSyncAndGoogleServicesSyncOffImageName;
       break;
     }
-    case kSyncOff:
-    case kSyncEnabledWithNoSelectedTypes: {
+    case kSyncOff: {
       googleSyncItem.detailText = l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
       googleSyncItem.iconImageName = kSyncAndGoogleServicesSyncOffImageName;
       break;
     }
+    case kSyncEnabledWithNoSelectedTypes: {
+      googleSyncItem.detailText = nil;
+      googleSyncItem.iconImageName = kSyncAndGoogleServicesSyncOffImageName;
+      break;
+    }
     case kSyncEnabledWithError: {
       SyncSetupService* syncSetupService =
           SyncSetupServiceFactory::GetForBrowserState(_browserState);
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller_mice_unittest.mm b/ios/chrome/browser/ui/settings/settings_table_view_controller_mice_unittest.mm
index b4454fb..4bbe7b2 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller_mice_unittest.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller_mice_unittest.mm
@@ -236,9 +236,10 @@
               sync_item.detailText);
 }
 
-// Verifies that the Sync icon displays the off state when the user has
-// completed the sign-in and sync flow then explcitly turned off all data types
-// in the Sync settings.
+// Verifies that the Sync icon displays the off state (and no detail text) when
+// the user has completed the sign-in and sync flow then explcitly turned off
+// all data types in the Sync settings.
+// This case can only happen for pre-MICE users who migrated with MICE.
 TEST_F(SettingsTableViewControllerMICETest,
        DisablesAllSyncSettingsAfterFirstSetup) {
   ON_CALL(*sync_service_mock_->GetMockUserSettings(), GetSelectedTypes())
@@ -261,6 +262,5 @@
       static_cast<TableViewDetailIconItem*>(account_items[1]);
   ASSERT_NSEQ(l10n_util::GetNSString(IDS_IOS_GOOGLE_SYNC_SETTINGS_TITLE),
               sync_item.text);
-  ASSERT_NSEQ(l10n_util::GetNSString(IDS_IOS_SETTING_OFF),
-              sync_item.detailText);
+  ASSERT_EQ(nil, sync_item.detailText);
 }
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
index e3f410d..49297ac9 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-74f26b6069b4e8166fab915ffcaf9df3ca65f2bc
\ No newline at end of file
+a74cc9413758120b7bc2f7c0ecd4e992128f2609
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
index 8dc1409..514af577 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-9afa9743ed0d528ee1d07e03f556734fd81b87c5
\ No newline at end of file
+5afe4773af8dc6bb2c95769478f413f5a1be81c5
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
index 4550cbe4..c0a5cdb 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-63ef4ffa6d0c6bd613de7f1011696a61ac3cc539
\ No newline at end of file
+86e71244c37d9693bf9687ff78eb2b523ea48bcd
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
index de93bcf..c458979 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-c3e99ca14823c5ec0fb32cbddff8653ece7e1b75
\ No newline at end of file
+bec573cefb452413f030cc3c8505b19836df8de9
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
index 535a4e8..2a015e8 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-66b6e4106a979ba78d7e6d2c5f03399b9e4a3e6f
\ No newline at end of file
+1a8f6a4560d9d2f95034a69d5bcf33f39767cd74
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
index d34ab3e..d6b7730 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-720152502285633607d3229421db879f14a2a0be
\ No newline at end of file
+a71129e8bed9af8e877e580cfbdb9a34734dc906
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
index c3dd7e7..1051648 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-a50b8d176d51c429635dfb2d99729d3049585ff6
\ No newline at end of file
+a613af75638e18aa756ffa4bf3f9567a248014bf
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
index 1d69676..2e4a1df 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-b6aaeb512599cc9b412e9629bad47da704aebd81
\ No newline at end of file
+9658304b579055f7a3fd328e820c9337319ea597
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
index 485a7675..5f5c3882 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@
-2630343a8c71f8b5b614eda3b9ab4c9d79eebddd
\ No newline at end of file
+067f45484084e17ea599214e896b63462cdea0e3
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
index c9c8f74..01ec24af 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@
-e3b951ad1079099f2d8a6d73dec57e98b401202b
\ No newline at end of file
+c85d87c12e09e4b0bb238ddb3fa762b0ac42df22
\ No newline at end of file
diff --git a/ipc/ipc_channel_mojo.cc b/ipc/ipc_channel_mojo.cc
index bdb19d6..16f7058 100644
--- a/ipc/ipc_channel_mojo.cc
+++ b/ipc/ipc_channel_mojo.cc
@@ -186,21 +186,31 @@
 }
 
 bool ChannelMojo::Connect() {
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
-
   WillConnect();
 
-  mojo::AssociatedRemote<mojom::Channel> sender;
+  mojo::PendingAssociatedRemote<mojom::Channel> sender;
   mojo::PendingAssociatedReceiver<mojom::Channel> receiver;
   bootstrap_->Connect(&sender, &receiver);
 
   DCHECK(!message_reader_);
-  sender->SetPeerPid(GetSelfPID());
   message_reader_ = std::make_unique<internal::MessagePipeReader>(
-      pipe_, std::move(sender), std::move(receiver), this);
+      pipe_, std::move(sender), std::move(receiver), task_runner_, this);
+
+  if (task_runner_->RunsTasksInCurrentSequence()) {
+    FinishConnectOnIOThread();
+  } else {
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&ChannelMojo::FinishConnectOnIOThread, weak_ptr_));
+  }
   return true;
 }
 
+void ChannelMojo::FinishConnectOnIOThread() {
+  DCHECK(message_reader_);
+  message_reader_->FinishInitializationOnIOThread(GetSelfPID());
+}
+
 void ChannelMojo::Pause() {
   bootstrap_->Pause();
 }
@@ -375,6 +385,12 @@
     const std::string& name,
     mojo::ScopedInterfaceEndpointHandle handle) {
   if (message_reader_) {
+    if (!task_runner_->RunsTasksInCurrentSequence()) {
+      message_reader_->thread_safe_sender().GetAssociatedInterface(
+          name, mojo::PendingAssociatedReceiver<mojom::GenericInterface>(
+                    std::move(handle)));
+      return;
+    }
     message_reader_->GetRemoteInterface(name, std::move(handle));
   } else {
     // Attach the associated interface to a disconnected pipe, so that the
diff --git a/ipc/ipc_channel_mojo.h b/ipc/ipc_channel_mojo.h
index d99bb2f..3379466 100644
--- a/ipc/ipc_channel_mojo.h
+++ b/ipc/ipc_channel_mojo.h
@@ -116,6 +116,8 @@
       const std::string& name,
       mojo::ScopedInterfaceEndpointHandle handle) override;
 
+  void FinishConnectOnIOThread();
+
   base::WeakPtr<ChannelMojo> weak_ptr_;
 
   // A TaskRunner which runs tasks on the ChannelMojo's owning thread.
diff --git a/ipc/ipc_message_pipe_reader.cc b/ipc/ipc_message_pipe_reader.cc
index eb290ab..8deecd3 100644
--- a/ipc/ipc_message_pipe_reader.cc
+++ b/ipc/ipc_message_pipe_reader.cc
@@ -19,29 +19,83 @@
 #include "base/trace_event/trace_event.h"
 #include "ipc/ipc_channel_mojo.h"
 #include "mojo/public/cpp/bindings/message.h"
+#include "mojo/public/cpp/bindings/thread_safe_proxy.h"
 
 namespace IPC {
 namespace internal {
 
+namespace {
+
+class ThreadSafeProxy : public mojo::ThreadSafeProxy {
+ public:
+  using Forwarder = base::RepeatingCallback<void(mojo::Message)>;
+
+  ThreadSafeProxy(scoped_refptr<base::SequencedTaskRunner> task_runner,
+                  Forwarder forwarder,
+                  mojo::AssociatedGroupController& group_controller)
+      : task_runner_(std::move(task_runner)),
+        forwarder_(std::move(forwarder)),
+        group_controller_(group_controller) {}
+
+  // mojo::ThreadSafeProxy:
+  void SendMessage(mojo::Message& message) override {
+    message.SerializeHandles(&group_controller_);
+    task_runner_->PostTask(FROM_HERE,
+                           base::BindOnce(forwarder_, std::move(message)));
+  }
+
+  void SendMessageWithResponder(
+      mojo::Message& message,
+      std::unique_ptr<mojo::MessageReceiver> responder) override {
+    // We don't bother supporting this because it's not used in practice.
+    NOTREACHED();
+  }
+
+ private:
+  ~ThreadSafeProxy() override = default;
+
+  const scoped_refptr<base::SequencedTaskRunner> task_runner_;
+  const Forwarder forwarder_;
+  mojo::AssociatedGroupController& group_controller_;
+};
+
+}  // namespace
+
 MessagePipeReader::MessagePipeReader(
     mojo::MessagePipeHandle pipe,
-    mojo::AssociatedRemote<mojom::Channel> sender,
+    mojo::PendingAssociatedRemote<mojom::Channel> sender,
     mojo::PendingAssociatedReceiver<mojom::Channel> receiver,
+    scoped_refptr<base::SequencedTaskRunner> task_runner,
     MessagePipeReader::Delegate* delegate)
     : delegate_(delegate),
-      sender_(std::move(sender)),
-      receiver_(this, std::move(receiver)) {
+      sender_(std::move(sender), task_runner),
+      receiver_(this, std::move(receiver), task_runner) {
+  thread_safe_sender_ =
+      std::make_unique<mojo::ThreadSafeForwarder<mojom::Channel>>(
+          base::MakeRefCounted<ThreadSafeProxy>(
+              task_runner,
+              base::BindRepeating(&MessagePipeReader::ForwardMessage,
+                                  weak_ptr_factory_.GetWeakPtr()),
+              *sender_.internal_state()->associated_group()->GetController()));
+
+  thread_checker_.DetachFromThread();
+}
+
+MessagePipeReader::~MessagePipeReader() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  // The pipe should be closed before deletion.
+}
+
+void MessagePipeReader::FinishInitializationOnIOThread(
+    base::ProcessId self_pid) {
   sender_.set_disconnect_handler(
       base::BindOnce(&MessagePipeReader::OnPipeError, base::Unretained(this),
                      MOJO_RESULT_FAILED_PRECONDITION));
   receiver_.set_disconnect_handler(
       base::BindOnce(&MessagePipeReader::OnPipeError, base::Unretained(this),
                      MOJO_RESULT_FAILED_PRECONDITION));
-}
 
-MessagePipeReader::~MessagePipeReader() {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  // The pipe should be closed before deletion.
+  sender_->SetPeerPid(self_pid);
 }
 
 void MessagePipeReader::Close() {
@@ -128,5 +182,9 @@
     delegate_->OnPipeError();
 }
 
+void MessagePipeReader::ForwardMessage(mojo::Message message) {
+  sender_.internal_state()->ForwardMessage(std::move(message));
+}
+
 }  // namespace internal
 }  // namespace IPC
diff --git a/ipc/ipc_message_pipe_reader.h b/ipc/ipc_message_pipe_reader.h
index b7f73d2..1894bf3 100644
--- a/ipc/ipc_message_pipe_reader.h
+++ b/ipc/ipc_message_pipe_reader.h
@@ -14,6 +14,7 @@
 #include "base/compiler_specific.h"
 #include "base/component_export.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/process/process_handle.h"
 #include "base/threading/thread_checker.h"
 #include "ipc/ipc.mojom.h"
@@ -22,6 +23,7 @@
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h"
+#include "mojo/public/cpp/bindings/shared_remote.h"
 #include "mojo/public/cpp/system/core.h"
 #include "mojo/public/cpp/system/message_pipe.h"
 
@@ -68,11 +70,14 @@
   //
   // Note that MessagePipeReader doesn't delete |delegate|.
   MessagePipeReader(mojo::MessagePipeHandle pipe,
-                    mojo::AssociatedRemote<mojom::Channel> sender,
+                    mojo::PendingAssociatedRemote<mojom::Channel> sender,
                     mojo::PendingAssociatedReceiver<mojom::Channel> receiver,
+                    scoped_refptr<base::SequencedTaskRunner> task_runner,
                     Delegate* delegate);
   ~MessagePipeReader() override;
 
+  void FinishInitializationOnIOThread(base::ProcessId self_pid);
+
   // Close and destroy the MessagePipe.
   void Close();
 
@@ -88,6 +93,7 @@
                           mojo::ScopedInterfaceEndpointHandle handle);
 
   mojo::AssociatedRemote<mojom::Channel>& sender() { return sender_; }
+  mojom::Channel& thread_safe_sender() { return thread_safe_sender_->proxy(); }
 
  protected:
   void OnPipeClosed();
@@ -102,11 +108,16 @@
       mojo::PendingAssociatedReceiver<mojom::GenericInterface> receiver)
       override;
 
+  void ForwardMessage(mojo::Message message);
+
   // |delegate_| is null once the message pipe is closed.
   Delegate* delegate_;
   mojo::AssociatedRemote<mojom::Channel> sender_;
+  std::unique_ptr<mojo::ThreadSafeForwarder<mojom::Channel>>
+      thread_safe_sender_;
   mojo::AssociatedReceiver<mojom::Channel> receiver_;
   base::ThreadChecker thread_checker_;
+  base::WeakPtrFactory<MessagePipeReader> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(MessagePipeReader);
 };
diff --git a/ipc/ipc_mojo_bootstrap.cc b/ipc/ipc_mojo_bootstrap.cc
index 4c848850..35d6112 100644
--- a/ipc/ipc_mojo_bootstrap.cc
+++ b/ipc/ipc_mojo_bootstrap.cc
@@ -164,11 +164,8 @@
   }
 
   void Bind(mojo::ScopedMessagePipeHandle handle) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    DCHECK(task_runner_->BelongsToCurrentThread());
-
     connector_ = std::make_unique<mojo::Connector>(
-        std::move(handle), mojo::Connector::SINGLE_THREADED_SEND, task_runner_,
+        std::move(handle), mojo::Connector::SINGLE_THREADED_SEND,
         "IPC Channel");
     connector_->set_incoming_receiver(&dispatcher_);
     connector_->set_connection_error_handler(
@@ -185,6 +182,8 @@
     // operation would only introduce a redundant scheduling step for most
     // messages.
     connector_->set_force_immediate_dispatch(true);
+
+    connector_->StartReceiving(task_runner_);
   }
 
   void Pause() {
@@ -211,7 +210,7 @@
   }
 
   void CreateChannelEndpoints(
-      mojo::AssociatedRemote<mojom::Channel>* sender,
+      mojo::PendingAssociatedRemote<mojom::Channel>* sender,
       mojo::PendingAssociatedReceiver<mojom::Channel>* receiver) {
     mojo::InterfaceId sender_id, receiver_id;
     if (set_interface_id_namespace_bit_) {
@@ -237,8 +236,8 @@
     mojo::ScopedInterfaceEndpointHandle receiver_handle =
         CreateScopedInterfaceEndpointHandle(receiver_id);
 
-    sender->Bind(mojo::PendingAssociatedRemote<mojom::Channel>(
-        std::move(sender_handle), 0));
+    *sender = mojo::PendingAssociatedRemote<mojom::Channel>(
+        std::move(sender_handle), 0);
     *receiver = mojo::PendingAssociatedReceiver<mojom::Channel>(
         std::move(receiver_handle));
   }
@@ -511,6 +510,11 @@
       return client_;
     }
 
+    void CountDroppedMessage() {
+      controller_->lock_.AssertAcquired();
+      ++num_dropped_messages_;
+    }
+
     void AttachClient(mojo::InterfaceEndpointClient* client,
                       scoped_refptr<base::SequencedTaskRunner> runner) {
       controller_->lock_.AssertAcquired();
@@ -520,6 +524,15 @@
 
       task_runner_ = std::move(runner);
       client_ = client;
+
+      CHECK_EQ(num_dropped_messages_, 0u)
+          << "A Channel-associated interface endpoint for "
+          << client->interface_name() << " received undeliverable messages "
+          << "prior to being bound. This means the endpoint was held in a "
+          << "pending state longer than allowed. Channel-associated interface "
+          << "endpoints must be bound ASAP once received; either immediately "
+          << "on the IO or main thread, or after a single task hop from the IO "
+          << "thread to main thread where applicable.";
     }
 
     void DetachClient() {
@@ -673,6 +686,7 @@
     std::unique_ptr<mojo::SequenceLocalSyncEventWatcher> sync_watcher_;
     base::queue<std::pair<uint32_t, MessageWrapper>> sync_messages_;
     uint32_t next_sync_message_id_ = 0;
+    size_t num_dropped_messages_ = 0;
 
     DISALLOW_COPY_AND_ASSIGN(Endpoint);
   };
@@ -933,8 +947,10 @@
       return;
 
     mojo::InterfaceEndpointClient* client = endpoint->client();
-    if (!client)
+    if (!client) {
+      endpoint->CountDroppedMessage();
       return;
+    }
 
     // Using client->interface_name() is safe here because this is a static
     // string defined for each mojo interface.
@@ -1104,7 +1120,7 @@
 
  private:
   void Connect(
-      mojo::AssociatedRemote<mojom::Channel>* sender,
+      mojo::PendingAssociatedRemote<mojom::Channel>* sender,
       mojo::PendingAssociatedReceiver<mojom::Channel>* receiver) override {
     controller_->Bind(std::move(handle_));
     controller_->CreateChannelEndpoints(sender, receiver);
@@ -1144,7 +1160,7 @@
     const scoped_refptr<base::SingleThreadTaskRunner>& proxy_task_runner,
     const scoped_refptr<mojo::internal::MessageQuotaChecker>& quota_checker) {
   return std::make_unique<MojoBootstrapImpl>(
-      std::move(handle), new ChannelAssociatedGroupController(
+      std::move(handle), base::MakeRefCounted<ChannelAssociatedGroupController>(
                              mode == Channel::MODE_SERVER, ipc_task_runner,
                              proxy_task_runner, quota_checker));
 }
diff --git a/ipc/ipc_mojo_bootstrap.h b/ipc/ipc_mojo_bootstrap.h
index d231ab2c..8d9b5745 100644
--- a/ipc/ipc_mojo_bootstrap.h
+++ b/ipc/ipc_mojo_bootstrap.h
@@ -48,7 +48,7 @@
 
   // Start the handshake over the underlying message pipe.
   virtual void Connect(
-      mojo::AssociatedRemote<mojom::Channel>* sender,
+      mojo::PendingAssociatedRemote<mojom::Channel>* sender,
       mojo::PendingAssociatedReceiver<mojom::Channel>* receiver) = 0;
 
   // Stop transmitting messages and start queueing them instead.
diff --git a/ipc/ipc_mojo_bootstrap_unittest.cc b/ipc/ipc_mojo_bootstrap_unittest.cc
index eddcde20..ddf1604 100644
--- a/ipc/ipc_mojo_bootstrap_unittest.cc
+++ b/ipc/ipc_mojo_bootstrap_unittest.cc
@@ -26,7 +26,9 @@
   explicit Connection(std::unique_ptr<IPC::MojoBootstrap> bootstrap,
                       int32_t sender_id)
       : bootstrap_(std::move(bootstrap)) {
-    bootstrap_->Connect(&sender_, &receiver_);
+    mojo::PendingAssociatedRemote<IPC::mojom::Channel> sender;
+    bootstrap_->Connect(&sender, &receiver_);
+    sender_.Bind(std::move(sender));
     sender_->SetPeerPid(sender_id);
   }
 
diff --git a/media/audio/android/aaudio_output.cc b/media/audio/android/aaudio_output.cc
index 9e9b53ab..6ea70c6 100644
--- a/media/audio/android/aaudio_output.cc
+++ b/media/audio/android/aaudio_output.cc
@@ -4,7 +4,10 @@
 
 #include "media/audio/android/aaudio_output.h"
 
+#include "base/callback_helpers.h"
 #include "base/logging.h"
+#include "base/thread_annotations.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "media/audio/android/aaudio_stubs.h"
 #include "media/audio/android/audio_manager_android.h"
@@ -12,14 +15,47 @@
 
 namespace media {
 
+// Used to circumvent issues where the AAudio thread callbacks continue
+// after AAudioStream_requestStop() completes. See crbug.com/1183255.
+class LOCKABLE AAudioDestructionHelper {
+ public:
+  explicit AAudioDestructionHelper(AAudioOutputStream* stream)
+      : stream_(stream) {}
+
+  AAudioOutputStream* GetAndLockStream() EXCLUSIVE_LOCK_FUNCTION() {
+    lock_.Acquire();
+    return stream_;
+  }
+
+  void UnlockStream() UNLOCK_FUNCTION() { lock_.Release(); }
+
+  void OnStreamDestroyed() {
+    base::AutoLock al(lock_);
+    stream_ = nullptr;
+  }
+
+ private:
+  base::Lock lock_;
+  AAudioOutputStream* stream_ GUARDED_BY(lock_) = nullptr;
+};
+
 static aaudio_data_callback_result_t OnAudioDataRequestedCallback(
     AAudioStream* stream,
     void* user_data,
     void* audio_data,
     int32_t num_frames) {
-  AAudioOutputStream* output_stream =
-      reinterpret_cast<AAudioOutputStream*>(user_data);
-  return output_stream->OnAudioDataRequested(audio_data, num_frames);
+  AAudioDestructionHelper* destruction_helper =
+      reinterpret_cast<AAudioDestructionHelper*>(user_data);
+
+  AAudioOutputStream* output_stream = destruction_helper->GetAndLockStream();
+
+  aaudio_data_callback_result_t result = AAUDIO_CALLBACK_RESULT_STOP;
+  if (output_stream)
+    result = output_stream->OnAudioDataRequested(audio_data, num_frames);
+
+  destruction_helper->UnlockStream();
+
+  return result;
 }
 
 static void OnStreamErrorCallback(AAudioStream* stream,
@@ -38,7 +74,8 @@
       usage_(usage),
       performance_mode_(AAUDIO_PERFORMANCE_MODE_NONE),
       ns_per_frame_(base::Time::kNanosecondsPerSecond /
-                    static_cast<double>(params.sample_rate())) {
+                    static_cast<double>(params.sample_rate())),
+      destruction_helper_(std::make_unique<AAudioDestructionHelper>(this)) {
   DCHECK(manager);
   DCHECK(params.IsValid());
 
@@ -66,6 +103,21 @@
 
 AAudioOutputStream::~AAudioOutputStream() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  // In R and earlier, it is possible for callbacks to still be running even
+  // after calling AAudioStream_close(). The code below is a mitigation to work
+  // around this issue. See crbug.com/1183255.
+
+  // |destruction_helper_->GetStreamAndLock()| will return nullptr after this.
+  destruction_helper_->OnStreamDestroyed();
+
+  // Keep |destruction_helper_| alive longer than |this|, so the |user_data|
+  // bound to the callback stays valid until the callbacks stop.
+  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(
+          base::DoNothing::Once<std::unique_ptr<AAudioDestructionHelper>>(),
+          std::move(destruction_helper_)),
+      base::TimeDelta::FromSeconds(1));
 }
 
 void AAudioOutputStream::Flush() {}
@@ -90,7 +142,7 @@
 
   // Callbacks
   AAudioStreamBuilder_setDataCallback(builder, OnAudioDataRequestedCallback,
-                                      this);
+                                      destruction_helper_.get());
   AAudioStreamBuilder_setErrorCallback(builder, OnStreamErrorCallback, this);
 
   result = AAudioStreamBuilder_openStream(builder, &aaudio_stream_);
diff --git a/media/audio/android/aaudio_output.h b/media/audio/android/aaudio_output.h
index 5c6b7b2..35343369 100644
--- a/media/audio/android/aaudio_output.h
+++ b/media/audio/android/aaudio_output.h
@@ -15,6 +15,7 @@
 
 namespace media {
 
+class AAudioDestructionHelper;
 class AudioManagerAndroid;
 
 class AAudioOutputStream : public MuteableAudioOutputStream {
@@ -60,6 +61,10 @@
 
   AAudioStream* aaudio_stream_ = nullptr;
 
+  // Bound to the audio data callback. Outlives |this| in case the callbacks
+  // continue after |this| is destroyed. See crbug.com/1183255.
+  std::unique_ptr<AAudioDestructionHelper> destruction_helper_;
+
   // Lock protects all members below which may be read concurrently from the
   // audio manager thread and the OS provided audio thread.
   base::Lock lock_;
@@ -74,4 +79,4 @@
 
 }  // namespace media
 
-#endif  // MEDIA_AUDIO_ANDROID_AAUDIO_OUTPUT_H_
\ No newline at end of file
+#endif  // MEDIA_AUDIO_ANDROID_AAUDIO_OUTPUT_H_
diff --git a/media/base/android/media_drm_bridge.cc b/media/base/android/media_drm_bridge.cc
index cfa68cdd..c3a47f85 100644
--- a/media/base/android/media_drm_bridge.cc
+++ b/media/base/android/media_drm_bridge.cc
@@ -747,8 +747,10 @@
   DVLOG(2) << __func__;
   std::string session_id;
   JavaByteArrayToString(env, j_session_id, &session_id);
+  // TODO(crbug.com/1208618): Support other closed reasons.
   task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(session_closed_cb_, std::move(session_id)));
+      FROM_HERE, base::BindOnce(session_closed_cb_, std::move(session_id),
+                                CdmSessionClosedReason::kClose));
 }
 
 void MediaDrmBridge::OnSessionKeysChange(
diff --git a/media/base/cdm_session_tracker.cc b/media/base/cdm_session_tracker.cc
index da6f7b3..edd35f6 100644
--- a/media/base/cdm_session_tracker.cc
+++ b/media/base/cdm_session_tracker.cc
@@ -24,12 +24,13 @@
 }
 
 void CdmSessionTracker::CloseRemainingSessions(
-    const SessionClosedCB& session_closed_cb) {
+    const SessionClosedCB& session_closed_cb,
+    CdmSessionClosedReason reason) {
   std::unordered_set<std::string> session_ids;
   session_ids.swap(session_ids_);
 
   for (const auto& session_id : session_ids)
-    session_closed_cb.Run(session_id);
+    session_closed_cb.Run(session_id, reason);
 }
 
 bool CdmSessionTracker::HasRemainingSessions() const {
diff --git a/media/base/cdm_session_tracker.h b/media/base/cdm_session_tracker.h
index 2d90264..504b0e3 100644
--- a/media/base/cdm_session_tracker.h
+++ b/media/base/cdm_session_tracker.h
@@ -21,15 +21,16 @@
   CdmSessionTracker();
   ~CdmSessionTracker();
 
-  // Adds |session_id| to the list of sessions being tracked.
+  // Adds `session_id` to the list of sessions being tracked.
   void AddSession(const std::string& session_id);
 
-  // Removes |session_id| from the list of sessions being tracked.
+  // Removes `session_id` from the list of sessions being tracked.
   void RemoveSession(const std::string& session_id);
 
-  // Calls |session_closed_cb| on any remaining sessions in the list and then
-  // clear the list.
-  void CloseRemainingSessions(const SessionClosedCB& session_closed_cb);
+  // Calls `session_closed_cb` with `reason` on any remaining sessions in the
+  // list and then clear the list.
+  void CloseRemainingSessions(const SessionClosedCB& session_closed_cb,
+                              CdmSessionClosedReason reason);
 
   // Returns whether there are any remaining sessions being tracked.
   bool HasRemainingSessions() const;
diff --git a/media/base/content_decryption_module.h b/media/base/content_decryption_module.h
index 31e1c85..1b4720ec 100644
--- a/media/base/content_decryption_module.h
+++ b/media/base/content_decryption_module.h
@@ -71,6 +71,17 @@
   kMaxValue = kHdcpVersion2_3
 };
 
+// Reasons for CDM session closed.
+enum class CdmSessionClosedReason {
+  kUnknown,         // Anything not listed below.
+  kClose,           // Reaction to MediaKeySession close().
+  kCdmUnavailable,  // CDM is no longer usable, e.g. the CDM was disconnected or
+                    // had fatal internal-error.
+  kHardwareContextReset,  // As a result of hardware context reset.
+  kResourceEvicted,  // The CDM resource was evicted, e.g. by newer sessions.
+  kMaxValue = kResourceEvicted
+};
+
 // An interface that represents the Content Decryption Module (CDM) in the
 // Encrypted Media Extensions (EME) spec in Chromium.
 // See http://w3c.github.io/encrypted-media/#cdm
@@ -191,12 +202,14 @@
                                  CdmMessageType message_type,
                                  const std::vector<uint8_t>& message)>;
 
-// Called when the session specified by |session_id| is closed. Note that the
+// Called when the session specified by `session_id` is closed. Note that the
 // CDM may close a session at any point, such as in response to a CloseSession()
 // call, when the session is no longer needed, or when system resources are
-// lost. See http://w3c.github.io/encrypted-media/#session-close
+// lost, as specified by `reason`.
+// See http://w3c.github.io/encrypted-media/#session-close
 using SessionClosedCB =
-    base::RepeatingCallback<void(const std::string& session_id)>;
+    base::RepeatingCallback<void(const std::string& session_id,
+                                 CdmSessionClosedReason reason)>;
 
 // Called when there has been a change in the keys in the session or their
 // status. See http://w3c.github.io/encrypted-media/#dom-evt-keystatuseschange
diff --git a/media/base/mock_filters.cc b/media/base/mock_filters.cc
index 981c436..9ed57758 100644
--- a/media/base/mock_filters.cc
+++ b/media/base/mock_filters.cc
@@ -258,8 +258,9 @@
   session_message_cb_.Run(session_id, message_type, message);
 }
 
-void MockCdm::CallSessionClosedCB(const std::string& session_id) {
-  session_closed_cb_.Run(session_id);
+void MockCdm::CallSessionClosedCB(const std::string& session_id,
+                                  CdmSessionClosedReason reason) {
+  session_closed_cb_.Run(session_id, reason);
 }
 
 void MockCdm::CallSessionKeysChangeCB(const std::string& session_id,
diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h
index e70cb8c6..3e67b8a 100644
--- a/media/base/mock_filters.h
+++ b/media/base/mock_filters.h
@@ -573,7 +573,9 @@
                void(const std::string& session_id,
                     CdmMessageType message_type,
                     const std::vector<uint8_t>& message));
-  MOCK_METHOD1(OnSessionClosed, void(const std::string& session_id));
+  MOCK_METHOD2(OnSessionClosed,
+               void(const std::string& session_id,
+                    CdmSessionClosedReason reason));
 
   // Add OnSessionKeysChangeCalled() function so we can store |keys_info|.
   MOCK_METHOD2(OnSessionKeysChangeCalled,
@@ -740,7 +742,8 @@
   void CallSessionMessageCB(const std::string& session_id,
                             CdmMessageType message_type,
                             const std::vector<uint8_t>& message);
-  void CallSessionClosedCB(const std::string& session_id);
+  void CallSessionClosedCB(const std::string& session_id,
+                           CdmSessionClosedReason reason);
   void CallSessionKeysChangeCB(const std::string& session_id,
                                bool has_additional_usable_key,
                                CdmKeysInfo keys_info);
diff --git a/media/blink/cdm_session_adapter.cc b/media/blink/cdm_session_adapter.cc
index e378e62..dc425419 100644
--- a/media/blink/cdm_session_adapter.cc
+++ b/media/blink/cdm_session_adapter.cc
@@ -245,12 +245,14 @@
   }
 }
 
-void CdmSessionAdapter::OnSessionClosed(const std::string& session_id) {
+void CdmSessionAdapter::OnSessionClosed(const std::string& session_id,
+                                        CdmSessionClosedReason /*reason*/) {
   WebContentDecryptionModuleSessionImpl* session = GetSession(session_id);
   DLOG_IF(WARNING, !session) << __func__ << " for unknown session "
                              << session_id;
   if (session) {
     DVLOG(2) << __func__ << ": session_id = " << session_id;
+    // TODO(crbug.com/1208618): Plumb `reason` to blink.
     session->OnSessionClosed();
   }
 }
diff --git a/media/blink/cdm_session_adapter.h b/media/blink/cdm_session_adapter.h
index 3325504..c4d9438 100644
--- a/media/blink/cdm_session_adapter.h
+++ b/media/blink/cdm_session_adapter.h
@@ -134,7 +134,8 @@
                            CdmKeysInfo keys_info);
   void OnSessionExpirationUpdate(const std::string& session_id,
                                  base::Time new_expiry_time);
-  void OnSessionClosed(const std::string& session_id);
+  void OnSessionClosed(const std::string& session_id,
+                       CdmSessionClosedReason reason);
 
   // Helper function of the callbacks.
   WebContentDecryptionModuleSessionImpl* GetSession(
diff --git a/media/cdm/aes_decryptor.cc b/media/cdm/aes_decryptor.cc
index 417552dc1..800be34b 100644
--- a/media/cdm/aes_decryptor.cc
+++ b/media/cdm/aes_decryptor.cc
@@ -367,7 +367,7 @@
 
   // 5.3. Queue a task to run the following steps:
   // 5.3.1. Run the Session Closed algorithm on the session.
-  session_closed_cb_.Run(session_id);
+  session_closed_cb_.Run(session_id, CdmSessionClosedReason::kClose);
   // 5.3.2. Resolve promise.
   promise->resolve();
 }
diff --git a/media/cdm/aes_decryptor_unittest.cc b/media/cdm/aes_decryptor_unittest.cc
index cc6e245..9abcd5f 100644
--- a/media/cdm/aes_decryptor_unittest.cc
+++ b/media/cdm/aes_decryptor_unittest.cc
@@ -381,7 +381,8 @@
 
   // Closes the session specified by |session_id|.
   void CloseSession(const std::string& session_id) {
-    EXPECT_CALL(cdm_client_, OnSessionClosed(session_id));
+    EXPECT_CALL(cdm_client_,
+                OnSessionClosed(session_id, CdmSessionClosedReason::kClose));
     cdm_->CloseSession(session_id, CreatePromise(RESOLVED));
   }
 
diff --git a/media/cdm/cdm_adapter.cc b/media/cdm/cdm_adapter.cc
index c0a849ae..c3ceb95 100644
--- a/media/cdm/cdm_adapter.cc
+++ b/media/cdm/cdm_adapter.cc
@@ -816,7 +816,8 @@
   std::string session_id_str(session_id, session_id_size);
   TRACE_EVENT1("media", "CdmAdapter::OnSessionClosed", "session_id",
                session_id_str);
-  session_closed_cb_.Run(session_id_str);
+  // Library CDMs typically only close sessions as a result of `CloseSession()`.
+  session_closed_cb_.Run(session_id_str, CdmSessionClosedReason::kClose);
 }
 
 void CdmAdapter::SendPlatformChallenge(const char* service_id,
diff --git a/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.cc b/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.cc
index ae1a929..4bbaa85 100644
--- a/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.cc
+++ b/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.cc
@@ -888,7 +888,8 @@
                                        keys_vector.data(), keys_vector.size());
 }
 
-void ClearKeyCdm::OnSessionClosed(const std::string& session_id) {
+void ClearKeyCdm::OnSessionClosed(const std::string& session_id,
+                                  CdmSessionClosedReason /*reason*/) {
   cdm_host_proxy_->OnSessionClosed(session_id.data(), session_id.length());
 }
 
diff --git a/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.h b/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.h
index 79e86d52..b48293f 100644
--- a/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.h
+++ b/media/cdm/library_cdm/clear_key_cdm/clear_key_cdm.h
@@ -109,7 +109,8 @@
   void OnSessionKeysChange(const std::string& session_id,
                            bool has_additional_usable_key,
                            CdmKeysInfo keys_info);
-  void OnSessionClosed(const std::string& session_id);
+  void OnSessionClosed(const std::string& session_id,
+                       CdmSessionClosedReason reason);
   void OnSessionExpirationUpdate(const std::string& session_id,
                                  base::Time new_expiry_time);
 
diff --git a/media/cdm/library_cdm/clear_key_cdm/clear_key_persistent_session_cdm.cc b/media/cdm/library_cdm/clear_key_cdm/clear_key_persistent_session_cdm.cc
index cc71d26d..a6adbf0d 100644
--- a/media/cdm/library_cdm/clear_key_cdm/clear_key_persistent_session_cdm.cc
+++ b/media/cdm/library_cdm/clear_key_cdm/clear_key_persistent_session_cdm.cc
@@ -352,9 +352,10 @@
 }
 
 void ClearKeyPersistentSessionCdm::OnSessionClosed(
-    const std::string& session_id) {
+    const std::string& session_id,
+    CdmSessionClosedReason reason) {
   persistent_sessions_.erase(session_id);
-  session_closed_cb_.Run(session_id);
+  session_closed_cb_.Run(session_id, reason);
 }
 
 void ClearKeyPersistentSessionCdm::OnSessionMessage(
diff --git a/media/cdm/library_cdm/clear_key_cdm/clear_key_persistent_session_cdm.h b/media/cdm/library_cdm/clear_key_cdm/clear_key_persistent_session_cdm.h
index b2ef8ee..5063180 100644
--- a/media/cdm/library_cdm/clear_key_cdm/clear_key_persistent_session_cdm.h
+++ b/media/cdm/library_cdm/clear_key_cdm/clear_key_persistent_session_cdm.h
@@ -99,7 +99,8 @@
 
   // When the session is closed, remove it from the list of open persistent
   // sessions if it was a persistent session.
-  void OnSessionClosed(const std::string& session_id);
+  void OnSessionClosed(const std::string& session_id,
+                       CdmSessionClosedReason reason);
 
   void OnSessionMessage(const std::string& session_id,
                         CdmMessageType message_type,
diff --git a/media/cdm/win/media_foundation_cdm.cc b/media/cdm/win/media_foundation_cdm.cc
index 629aa4b..ec92442 100644
--- a/media/cdm/win/media_foundation_cdm.cc
+++ b/media/cdm/win/media_foundation_cdm.cc
@@ -402,36 +402,8 @@
     std::unique_ptr<SimpleCdmPromise> promise) {
   DVLOG_FUNC(1);
 
-  if (!mf_cdm_) {
-    promise->reject(Exception::INVALID_STATE_ERROR, 0, "CDM Unavailable");
-    return;
-  }
-
-  // Validate that this is a reference to an open session. close() shouldn't
-  // be called if the session is already closed. However, the operation is
-  // asynchronous, so there is a window where close() was called a second time
-  // just before the closed event arrives. As a result it is possible that the
-  // session is already closed, so assume that the session is closed if it
-  // doesn't exist. https://github.com/w3c/encrypted-media/issues/365.
-  //
-  // close() is called from a MediaKeySession object, so it is unlikely that
-  // this method will be called with a previously unseen |session_id|.
-  auto* session = GetSession(session_id);
-  if (!session) {
-    promise->resolve();
-    return;
-  }
-
-  if (FAILED(session->Close())) {
-    sessions_.erase(session_id);
-    promise->reject(Exception::INVALID_STATE_ERROR, 0, "Close failed");
-    return;
-  }
-
-  // EME requires running session closed algorithm before resolving the promise.
-  sessions_.erase(session_id);
-  session_closed_cb_.Run(session_id);
-  promise->resolve();
+  CloseSessionInternal(session_id, CdmSessionClosedReason::kClose,
+                       std::move(promise));
 }
 
 void MediaFoundationCdm::RemoveSession(
@@ -519,6 +491,44 @@
   return itr->second.get();
 }
 
+void MediaFoundationCdm::CloseSessionInternal(
+    const std::string& session_id,
+    CdmSessionClosedReason reason,
+    std::unique_ptr<SimpleCdmPromise> promise) {
+  DVLOG_FUNC(1);
+
+  if (!mf_cdm_) {
+    promise->reject(Exception::INVALID_STATE_ERROR, 0, "CDM Unavailable");
+    return;
+  }
+
+  // Validate that this is a reference to an open session. close() shouldn't
+  // be called if the session is already closed. However, the operation is
+  // asynchronous, so there is a window where close() was called a second time
+  // just before the closed event arrives. As a result it is possible that the
+  // session is already closed, so assume that the session is closed if it
+  // doesn't exist. https://github.com/w3c/encrypted-media/issues/365.
+  //
+  // close() is called from a MediaKeySession object, so it is unlikely that
+  // this method will be called with a previously unseen |session_id|.
+  auto* session = GetSession(session_id);
+  if (!session) {
+    promise->resolve();
+    return;
+  }
+
+  if (FAILED(session->Close())) {
+    sessions_.erase(session_id);
+    promise->reject(Exception::INVALID_STATE_ERROR, 0, "Close failed");
+    return;
+  }
+
+  // EME requires running session closed algorithm before resolving the promise.
+  sessions_.erase(session_id);
+  session_closed_cb_.Run(session_id, reason);
+  promise->resolve();
+}
+
 // When hardware context is reset, all sessions are in a bad state. Close all
 // the sessions and hopefully the player will create new sessions to resume.
 void MediaFoundationCdm::OnHardwareContextReset() {
@@ -530,8 +540,11 @@
   for (const auto& s : sessions_)
     session_ids.push_back(s.first);
 
-  for (const auto& session_id : session_ids)
-    CloseSession(session_id, std::make_unique<DoNothingCdmPromise<>>());
+  for (const auto& session_id : session_ids) {
+    CloseSessionInternal(session_id,
+                         CdmSessionClosedReason::kHardwareContextReset,
+                         std::make_unique<DoNothingCdmPromise<>>());
+  }
 
   cdm_proxy_.reset();
 
diff --git a/media/cdm/win/media_foundation_cdm.h b/media/cdm/win/media_foundation_cdm.h
index b49d287..bf97ad75 100644
--- a/media/cdm/win/media_foundation_cdm.h
+++ b/media/cdm/win/media_foundation_cdm.h
@@ -88,7 +88,11 @@
 
   MediaFoundationCdmSession* GetSession(const std::string& session_id);
 
-  // Closes all outstanding sessions.
+  void CloseSessionInternal(const std::string& session_id,
+                            CdmSessionClosedReason reason,
+                            std::unique_ptr<SimpleCdmPromise> promise);
+
+  // Called when hardware context reset happens.
   void OnHardwareContextReset();
 
   // Callback to create `mf_cdm_`.
diff --git a/media/cdm/win/media_foundation_cdm_unittest.cc b/media/cdm/win/media_foundation_cdm_unittest.cc
index 0c57dae..bb37379e 100644
--- a/media/cdm/win/media_foundation_cdm_unittest.cc
+++ b/media/cdm/win/media_foundation_cdm_unittest.cc
@@ -379,7 +379,8 @@
   CreateSessionAndGenerateRequest();
 
   COM_EXPECT_CALL(mf_cdm_session_, Close()).WillOnce(Return(S_OK));
-  EXPECT_CALL(cdm_client_, OnSessionClosed(kSessionId));
+  EXPECT_CALL(cdm_client_,
+              OnSessionClosed(kSessionId, CdmSessionClosedReason::kClose));
 
   cdm_->CloseSession(session_id_,
                      std::make_unique<MockCdmPromise>(/*expect_success=*/true));
@@ -433,7 +434,9 @@
   ASSERT_TRUE(mf_cdm_proxy_);
 
   COM_EXPECT_CALL(mf_cdm_session_, Close()).WillOnce(Return(S_OK));
-  EXPECT_CALL(cdm_client_, OnSessionClosed(kSessionId));
+  EXPECT_CALL(cdm_client_,
+              OnSessionClosed(kSessionId,
+                              CdmSessionClosedReason::kHardwareContextReset));
   mf_cdm_proxy_->OnHardwareContextReset();
 
   // Create a new session and expect success.
@@ -454,7 +457,9 @@
   can_initialize_ = false;
 
   COM_EXPECT_CALL(mf_cdm_session_, Close()).WillOnce(Return(S_OK));
-  EXPECT_CALL(cdm_client_, OnSessionClosed(kSessionId));
+  EXPECT_CALL(cdm_client_,
+              OnSessionClosed(kSessionId,
+                              CdmSessionClosedReason::kHardwareContextReset));
   mf_cdm_proxy_->OnHardwareContextReset();
 
   std::vector<uint8_t> init_data = StringToVector("init_data");
diff --git a/media/fuchsia/cdm/fuchsia_cdm.cc b/media/fuchsia/cdm/fuchsia_cdm.cc
index a4692f43..61a8c3fd 100644
--- a/media/fuchsia/cdm/fuchsia_cdm.cc
+++ b/media/fuchsia/cdm/fuchsia_cdm.cc
@@ -143,8 +143,10 @@
   }
 
   ~CdmSession() {
-    if (!session_id_.empty())
-      session_callbacks_->closed_cb.Run(session_id_);
+    if (!session_id_.empty()) {
+      session_callbacks_->closed_cb.Run(
+          session_id_, CdmSessionClosedReason::kCdmUnavailable);
+    }
   }
 
   fidl::InterfaceRequest<fuchsia::media::drm::LicenseSession> NewRequest() {
diff --git a/media/gpu/command_buffer_helper.cc b/media/gpu/command_buffer_helper.cc
index 36c9aab..6d88256 100644
--- a/media/gpu/command_buffer_helper.cc
+++ b/media/gpu/command_buffer_helper.cc
@@ -49,7 +49,8 @@
 #else
         gpu::SchedulingPriority::kNormal
 #endif  // defined(OS_MAC)
-    );
+        ,
+        stub_->channel()->task_runner());
     decoder_helper_ = GLES2DecoderHelper::Create(stub_->decoder_context());
   }
 
diff --git a/media/gpu/mac/vt_video_decode_accelerator_mac.cc b/media/gpu/mac/vt_video_decode_accelerator_mac.cc
index af3230f..88702de 100644
--- a/media/gpu/mac/vt_video_decode_accelerator_mac.cc
+++ b/media/gpu/mac/vt_video_decode_accelerator_mac.cc
@@ -517,11 +517,11 @@
   // called already).
   for (const auto& it : picture_info_map_) {
     PictureInfo* picture_info = it.second.get();
-    if (picture_info->gl_image) {
+    for (const auto& gl_image : picture_info->gl_images) {
       std::string dump_name =
           base::StringPrintf("media/vt_video_decode_accelerator_%d/picture_%d",
                              memory_dump_id_, picture_info->bitstream_id);
-      picture_info->gl_image->OnMemoryDump(pmd, 0, dump_name);
+      gl_image->OnMemoryDump(pmd, 0, dump_name);
     }
   }
 
@@ -1285,13 +1285,13 @@
   // Drop references to allow the underlying buffer to be released.
   PictureInfo* picture_info = it->second.get();
   if (picture_info->uses_shared_images) {
-    picture_info->scoped_shared_image = nullptr;
+    picture_info->scoped_shared_images.clear();
   } else {
     gl_client_.bind_image.Run(picture_info->client_texture_id,
                               gpu::GetPlatformSpecificTextureTarget(), nullptr,
                               false);
   }
-  picture_info->gl_image = nullptr;
+  picture_info->gl_images.clear();
   picture_info->bitstream_id = 0;
 
   // Mark the picture as available and try to complete pending output work.
@@ -1461,38 +1461,38 @@
   // If the next pending flush is for a reset, then the frame will be dropped.
   bool resetting = !pending_flush_tasks_.empty() &&
                    pending_flush_tasks_.front() == TASK_RESET;
+  if (resetting)
+    return true;
 
-  if (!resetting) {
-    DCHECK(frame.image.get());
-    // If the |image_size| has changed, request new picture buffers and then
-    // wait for them.
-    //
-    // TODO(sandersd): When used by GpuVideoDecoder, we don't need to bother
-    // with this. We can tell that is the case when we also have a timestamp.
-    if (picture_size_ != frame.image_size) {
-      // Dismiss current pictures.
-      for (int32_t picture_id : assigned_picture_ids_) {
-        DVLOG(3) << "DismissPictureBuffer(" << picture_id << ")";
-        client_->DismissPictureBuffer(picture_id);
-      }
-      assigned_picture_ids_.clear();
-      picture_info_map_.clear();
-      available_picture_ids_.clear();
-
-      // Request new pictures.
-      picture_size_ = frame.image_size;
-      DVLOG(3) << "ProvidePictureBuffers(" << kNumPictureBuffers
-               << frame.image_size.ToString() << ")";
-      client_->ProvidePictureBuffers(kNumPictureBuffers, PIXEL_FORMAT_UNKNOWN,
-                                     1, frame.image_size,
-                                     gpu::GetPlatformSpecificTextureTarget());
-      return false;
+  DCHECK(frame.image.get());
+  // If the |image_size| has changed, request new picture buffers and then
+  // wait for them.
+  //
+  // TODO(sandersd): When used by GpuVideoDecoder, we don't need to bother
+  // with this. We can tell that is the case when we also have a timestamp.
+  if (picture_size_ != frame.image_size) {
+    // Dismiss current pictures.
+    for (int32_t picture_id : assigned_picture_ids_) {
+      DVLOG(3) << "DismissPictureBuffer(" << picture_id << ")";
+      client_->DismissPictureBuffer(picture_id);
     }
-    if (!SendFrame(frame))
-      return false;
-  }
+    assigned_picture_ids_.clear();
+    picture_info_map_.clear();
+    available_picture_ids_.clear();
 
-  return true;
+    // Request new pictures.
+    picture_size_ = frame.image_size;
+    // TODO(https://crbug.com/1210994): Remove XRGB support, and expose only
+    // PIXEL_FORMAT_NV12 and PIXEL_FORMAT_YUV420P10.
+    picture_format_ = PIXEL_FORMAT_XRGB;
+    DVLOG(3) << "ProvidePictureBuffers(" << kNumPictureBuffers
+             << frame.image_size.ToString() << ")";
+    client_->ProvidePictureBuffers(kNumPictureBuffers, picture_format_, 1,
+                                   frame.image_size,
+                                   gpu::GetPlatformSpecificTextureTarget());
+    return false;
+  }
+  return SendFrame(frame);
 }
 
 bool VTVideoDecodeAccelerator::SendFrame(const Frame& frame) {
@@ -1508,100 +1508,124 @@
   auto it = picture_info_map_.find(picture_id);
   DCHECK(it != picture_info_map_.end());
   PictureInfo* picture_info = it->second.get();
-  DCHECK(!picture_info->gl_image);
+  DCHECK(picture_info->gl_images.empty());
 
   const gfx::BufferFormat buffer_format =
       config_.profile == VP9PROFILE_PROFILE2
           ? gfx::BufferFormat::P010
           : gfx::BufferFormat::YUV_420_BIPLANAR;
-  // TODO(https://crbug.com/1108909): BGRA is not an appropriate value for
-  // these parameters.
-  const GLenum gl_format = GL_BGRA_EXT;
-  const viz::ResourceFormat viz_resource_format =
-      viz::ResourceFormat::BGRA_8888;
-
-  scoped_refptr<gl::GLImageIOSurface> gl_image(
-      gl::GLImageIOSurface::Create(frame.image_size, gl_format));
-  if (!gl_image->InitializeWithCVPixelBuffer(
-          frame.image.get(),
-          gfx::GenericSharedMemoryId(g_cv_pixel_buffer_ids.GetNext()),
-          buffer_format)) {
-    NOTIFY_STATUS("Failed to initialize GLImageIOSurface", PLATFORM_FAILURE,
-                  SFT_PLATFORM_ERROR);
-  }
-  gl_image->DisableInUseByWindowServer();
-
   gfx::ColorSpace color_space = GetImageBufferColorSpace(frame.image);
-  gl_image->SetColorSpaceForYUVToRGBConversion(color_space);
-  gl_image->SetColorSpaceShallow(color_space);
 
-  scoped_refptr<Picture::ScopedSharedImage> scoped_shared_image;
-  if (picture_info->uses_shared_images) {
-    gpu::SharedImageStub* shared_image_stub = client_->GetSharedImageStub();
-    DCHECK(shared_image_stub);
-    const uint32_t shared_image_usage =
-        gpu::SHARED_IMAGE_USAGE_DISPLAY | gpu::SHARED_IMAGE_USAGE_SCANOUT;
-    gpu::Mailbox mailbox = gpu::Mailbox::GenerateForSharedImage();
-
-    gpu::SharedImageBackingGLCommon::InitializeGLTextureParams gl_params;
-    // ANGLE-on-Metal exposes IOSurfaces via GL_TEXTURE_2D. Be robust to that.
-    gl_params.target = gl_client_.supports_arb_texture_rectangle
-                           ? GL_TEXTURE_RECTANGLE_ARB
-                           : GL_TEXTURE_2D;
-    gl_params.internal_format = gl_format;
-    gl_params.format = gl_format;
-    gl_params.type = GL_UNSIGNED_BYTE;
-    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, frame.image_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);
-    if (!success) {
-      DLOG(ERROR) << "Failed to register shared image";
-      NotifyError(PLATFORM_FAILURE, SFT_PLATFORM_ERROR);
-      return false;
-    }
-
-    // Wrap the destroy callback in a lambda that ensures that it be called on
-    // the appropriate thread.
-    auto destroy_shared_image_lambda =
-        [](gpu::SharedImageStub::SharedImageDestructionCallback callback,
-           scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
-          task_runner->PostTask(
-              FROM_HERE, base::BindOnce(std::move(callback), gpu::SyncToken()));
-        };
-    auto destroy_shared_image_callback = base::BindOnce(
-        destroy_shared_image_lambda,
-        shared_image_stub->GetSharedImageDestructionCallback(mailbox),
-        gpu_task_runner_);
-    scoped_shared_image = scoped_refptr<Picture::ScopedSharedImage>(
-        new Picture::ScopedSharedImage(
-            mailbox, gl_params.target,
-            std::move(destroy_shared_image_callback)));
-  } else {
-    if (!gl_client_.bind_image.Run(picture_info->client_texture_id,
-                                   gpu::GetPlatformSpecificTextureTarget(),
-                                   gl_image, false)) {
-      DLOG(ERROR) << "Failed to bind image";
-      NotifyError(PLATFORM_FAILURE, SFT_PLATFORM_ERROR);
-      return false;
-    }
+  std::vector<gfx::BufferPlane> planes;
+  switch (picture_format_) {
+    case PIXEL_FORMAT_NV12:
+    case PIXEL_FORMAT_YUV420P10:
+      planes.push_back(gfx::BufferPlane::Y);
+      planes.push_back(gfx::BufferPlane::UV);
+      break;
+    case PIXEL_FORMAT_XRGB:
+      planes.push_back(gfx::BufferPlane::DEFAULT);
+      break;
+    default:
+      NOTREACHED();
+      break;
   }
-  picture_info->gl_image = gl_image;
+
+  for (size_t plane = 0; plane < planes.size(); ++plane) {
+    const gfx::Size plane_size(
+        CVPixelBufferGetWidthOfPlane(frame.image.get(), plane),
+        CVPixelBufferGetHeightOfPlane(frame.image.get(), plane));
+    gfx::BufferFormat plane_buffer_format =
+        gpu::GetPlaneBufferFormat(planes[plane], buffer_format);
+    // TODO(https://crbug.com/1108909): BGRA is not an appropriate value for
+    // these parameters.
+    const viz::ResourceFormat viz_resource_format =
+        (picture_format_ == PIXEL_FORMAT_XRGB)
+            ? viz::ResourceFormat::BGRA_8888
+            : viz::GetResourceFormat(plane_buffer_format);
+    const GLenum gl_format = viz::GLDataFormat(viz_resource_format);
+
+    scoped_refptr<gl::GLImageIOSurface> gl_image(
+        gl::GLImageIOSurface::Create(plane_size, gl_format));
+    if (!gl_image->InitializeWithCVPixelBuffer(
+            frame.image.get(), plane,
+            gfx::GenericSharedMemoryId(g_cv_pixel_buffer_ids.GetNext()),
+            plane_buffer_format)) {
+      NOTIFY_STATUS("Failed to initialize GLImageIOSurface", PLATFORM_FAILURE,
+                    SFT_PLATFORM_ERROR);
+    }
+    gl_image->DisableInUseByWindowServer();
+    gl_image->SetColorSpaceForYUVToRGBConversion(color_space);
+    gl_image->SetColorSpaceShallow(color_space);
+
+    if (picture_info->uses_shared_images) {
+      gpu::SharedImageStub* shared_image_stub = client_->GetSharedImageStub();
+      DCHECK(shared_image_stub);
+      const uint32_t shared_image_usage =
+          gpu::SHARED_IMAGE_USAGE_DISPLAY | gpu::SHARED_IMAGE_USAGE_SCANOUT;
+      gpu::Mailbox mailbox = gpu::Mailbox::GenerateForSharedImage();
+
+      gpu::SharedImageBackingGLCommon::InitializeGLTextureParams gl_params;
+      // ANGLE-on-Metal exposes IOSurfaces via GL_TEXTURE_2D. Be robust to that.
+      gl_params.target = gl_client_.supports_arb_texture_rectangle
+                             ? GL_TEXTURE_RECTANGLE_ARB
+                             : GL_TEXTURE_2D;
+      gl_params.internal_format = gl_format;
+      gl_params.format = gl_format;
+      gl_params.type = GL_UNSIGNED_BYTE;
+      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);
+      if (!success) {
+        DLOG(ERROR) << "Failed to register shared image";
+        NotifyError(PLATFORM_FAILURE, SFT_PLATFORM_ERROR);
+        return false;
+      }
+
+      // Wrap the destroy callback in a lambda that ensures that it be called on
+      // the appropriate thread.
+      auto destroy_shared_image_lambda =
+          [](gpu::SharedImageStub::SharedImageDestructionCallback callback,
+             scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+            task_runner->PostTask(FROM_HERE, base::BindOnce(std::move(callback),
+                                                            gpu::SyncToken()));
+          };
+      auto destroy_shared_image_callback = base::BindOnce(
+          destroy_shared_image_lambda,
+          shared_image_stub->GetSharedImageDestructionCallback(mailbox),
+          gpu_task_runner_);
+      picture_info->scoped_shared_images.push_back(
+          scoped_refptr<Picture::ScopedSharedImage>(
+              new Picture::ScopedSharedImage(
+                  mailbox, gl_params.target,
+                  std::move(destroy_shared_image_callback))));
+    } else {
+      if (!gl_client_.bind_image.Run(picture_info->client_texture_id,
+                                     gpu::GetPlatformSpecificTextureTarget(),
+                                     gl_image, false)) {
+        DLOG(ERROR) << "Failed to bind image";
+        NotifyError(PLATFORM_FAILURE, SFT_PLATFORM_ERROR);
+        return false;
+      }
+    }
+    picture_info->gl_images.push_back(gl_image);
+  }
   picture_info->bitstream_id = frame.bitstream_id;
   available_picture_ids_.pop_back();
 
@@ -1621,7 +1645,10 @@
   // we don't need to use them when the image is never bound? Bindings are
   // typically only created when WebGL is in use.
   picture.set_read_lock_fences_enabled(true);
-  picture.set_scoped_shared_image(scoped_shared_image);
+  for (size_t plane = 0; plane < planes.size(); ++plane) {
+    picture.set_scoped_shared_image(picture_info->scoped_shared_images[plane],
+                                    plane);
+  }
   client_->PictureReady(std::move(picture));
   return true;
 }
diff --git a/media/gpu/mac/vt_video_decode_accelerator_mac.h b/media/gpu/mac/vt_video_decode_accelerator_mac.h
index c7dc49bb..3afcdab 100644
--- a/media/gpu/mac/vt_video_decode_accelerator_mac.h
+++ b/media/gpu/mac/vt_video_decode_accelerator_mac.h
@@ -144,12 +144,12 @@
     PictureInfo(uint32_t client_texture_id, uint32_t service_texture_id);
     ~PictureInfo();
 
-    // If true, then |scoped_shared_image| is used and |client_texture_id| and
+    // If true, then |scoped_shared_images| is used and |client_texture_id| and
     // |service_texture_id| are not used.
     const bool uses_shared_images;
 
     // Information about the currently bound image, for OnMemoryDump().
-    scoped_refptr<gl::GLImageIOSurface> gl_image;
+    std::vector<scoped_refptr<gl::GLImageIOSurface>> gl_images;
     int32_t bitstream_id = 0;
 
     // Texture IDs for the image buffer.
@@ -157,7 +157,7 @@
     const uint32_t service_texture_id = 0;
 
     // The shared image holder that will be passed to the client.
-    scoped_refptr<Picture::ScopedSharedImage> scoped_shared_image;
+    std::vector<scoped_refptr<Picture::ScopedSharedImage>> scoped_shared_images;
 
    private:
     DISALLOW_COPY_AND_ASSIGN(PictureInfo);
@@ -246,6 +246,9 @@
   // Size of assigned picture buffers.
   gfx::Size picture_size_;
 
+  // Format of the assigned picture buffers.
+  VideoPixelFormat picture_format_ = PIXEL_FORMAT_UNKNOWN;
+
   // Frames that have not yet been decoded, keyed by bitstream ID; maintains
   // ownership of Frame objects while they flow through VideoToolbox.
   std::map<int32_t, std::unique_ptr<Frame>> pending_frames_;
diff --git a/media/gpu/mac/vt_video_encode_accelerator_mac.cc b/media/gpu/mac/vt_video_encode_accelerator_mac.cc
index a7e1c74..a59cadf3 100644
--- a/media/gpu/mac/vt_video_encode_accelerator_mac.cc
+++ b/media/gpu/mac/vt_video_encode_accelerator_mac.cc
@@ -163,7 +163,10 @@
   client_ptr_factory_ = std::make_unique<base::WeakPtrFactory<Client>>(client);
   client_ = client_ptr_factory_->GetWeakPtr();
   input_visible_size_ = config.input_visible_size;
-  frame_rate_ = kMaxFrameRateNumerator / kMaxFrameRateDenominator;
+  if (config.initial_framerate.has_value())
+    frame_rate_ = config.initial_framerate.value();
+  else
+    frame_rate_ = kMaxFrameRateNumerator / kMaxFrameRateDenominator;
   initial_bitrate_ = config.initial_bitrate;
   bitstream_buffer_size_ = config.input_visible_size.GetArea();
   require_low_delay_ = config.require_low_delay;
@@ -339,13 +342,11 @@
     return;
   }
 
-  if (framerate != static_cast<uint32_t>(frame_rate_)) {
-    video_toolbox::SessionPropertySetter session_property_setter(
-        compression_session_);
-    session_property_setter.Set(kVTCompressionPropertyKey_ExpectedFrameRate,
-                                frame_rate_);
-  }
-
+  frame_rate_ = framerate;
+  video_toolbox::SessionPropertySetter session_property_setter(
+      compression_session_);
+  session_property_setter.Set(kVTCompressionPropertyKey_ExpectedFrameRate,
+                              frame_rate_);
   if (bitrate != static_cast<uint32_t>(target_bitrate_) && bitrate > 0) {
     target_bitrate_ = bitrate;
     bitrate_adjuster_.SetTargetBitrateBps(target_bitrate_);
diff --git a/media/mojo/clients/mojo_cdm.cc b/media/mojo/clients/mojo_cdm.cc
index 3a7e883e..ad33cb1a 100644
--- a/media/mojo/clients/mojo_cdm.cc
+++ b/media/mojo/clients/mojo_cdm.cc
@@ -87,7 +87,8 @@
 
   // Reject any outstanding promises and close all the existing sessions.
   cdm_promise_adapter_.Clear(CdmPromiseAdapter::ClearReason::kDestruction);
-  cdm_session_tracker_.CloseRemainingSessions(session_closed_cb_);
+  cdm_session_tracker_.CloseRemainingSessions(
+      session_closed_cb_, CdmSessionClosedReason::kCdmUnavailable);
 }
 
 // Using base::Unretained(this) below is safe because |this| owns |remote_cdm_|,
@@ -107,7 +108,8 @@
   // As communication with the remote CDM is broken, reject any outstanding
   // promises and close all the existing sessions.
   cdm_promise_adapter_.Clear(CdmPromiseAdapter::ClearReason::kConnectionError);
-  cdm_session_tracker_.CloseRemainingSessions(session_closed_cb_);
+  cdm_session_tracker_.CloseRemainingSessions(
+      session_closed_cb_, CdmSessionClosedReason::kCdmUnavailable);
 }
 
 void MojoCdm::SetServerCertificate(const std::vector<uint8_t>& certificate,
@@ -293,12 +295,13 @@
   session_message_cb_.Run(session_id, message_type, message);
 }
 
-void MojoCdm::OnSessionClosed(const std::string& session_id) {
+void MojoCdm::OnSessionClosed(const std::string& session_id,
+                              CdmSessionClosedReason reason) {
   DVLOG(2) << __func__;
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   cdm_session_tracker_.RemoveSession(session_id);
-  session_closed_cb_.Run(session_id);
+  session_closed_cb_.Run(session_id, reason);
 }
 
 void MojoCdm::OnSessionKeysChange(
diff --git a/media/mojo/clients/mojo_cdm.h b/media/mojo/clients/mojo_cdm.h
index ee3ae28..4417e04 100644
--- a/media/mojo/clients/mojo_cdm.h
+++ b/media/mojo/clients/mojo_cdm.h
@@ -94,7 +94,8 @@
   void OnSessionMessage(const std::string& session_id,
                         MessageType message_type,
                         const std::vector<uint8_t>& message) final;
-  void OnSessionClosed(const std::string& session_id) final;
+  void OnSessionClosed(const std::string& session_id,
+                       CdmSessionClosedReason reason) final;
   void OnSessionKeysChange(
       const std::string& session_id,
       bool has_additional_usable_key,
diff --git a/media/mojo/clients/mojo_cdm_unittest.cc b/media/mojo/clients/mojo_cdm_unittest.cc
index bf9a0d8f..5475007 100644
--- a/media/mojo/clients/mojo_cdm_unittest.cc
+++ b/media/mojo/clients/mojo_cdm_unittest.cc
@@ -215,8 +215,11 @@
 
       // MojoCdm expects the session to be closed, so invoke SessionClosedCB
       // to "close" it.
-      EXPECT_CALL(cdm_client_, OnSessionClosed(session_id));
-      remote_cdm_->CallSessionClosedCB(session_id);
+      EXPECT_CALL(
+          cdm_client_,
+          OnSessionClosed(session_id, CdmSessionClosedReason::kUnknown));
+      remote_cdm_->CallSessionClosedCB(session_id,
+                                       CdmSessionClosedReason::kUnknown);
       base::RunLoop().RunUntilIdle();
     }
   }
@@ -426,7 +429,9 @@
   CreateSessionAndExpect(session_id, SUCCESS);
 
   // Created session should always be closed!
-  EXPECT_CALL(cdm_client_, OnSessionClosed(session_id));
+  EXPECT_CALL(
+      cdm_client_,
+      OnSessionClosed(session_id, CdmSessionClosedReason::kCdmUnavailable));
 }
 
 TEST_F(MojoCdmTest, CreateSession_Failure) {
diff --git a/media/mojo/mojom/BUILD.gn b/media/mojo/mojom/BUILD.gn
index 78d210c..7e3d085 100644
--- a/media/mojo/mojom/BUILD.gn
+++ b/media/mojo/mojom/BUILD.gn
@@ -552,6 +552,15 @@
     {
       types = [
         {
+          mojom = "media.mojom.CdmSessionClosedReason"
+          cpp = "::media::CdmSessionClosedReason"
+        },
+      ]
+      traits_headers = [ "media_types_enum_mojom_traits.h" ]
+    },
+    {
+      types = [
+        {
           mojom = "media.mojom.RendererType"
           cpp = "::media::RendererType"
         },
@@ -560,6 +569,36 @@
     },
   ]
 
+  if (!is_android) {
+    cpp_typemaps += [
+      {
+        types = [
+          {
+            mojom = "media.mojom.HypothesisParts"
+            cpp = "::media::HypothesisParts"
+          },
+          {
+            mojom = "media.mojom.TimingInformation"
+            cpp = "::media::TimingInformation"
+          },
+          {
+            mojom = "media.mojom.SpeechRecognitionResult"
+            cpp = "::media::SpeechRecognitionResult"
+          },
+        ]
+        traits_headers = [
+          "speech_recognition_result_mojom_traits.h",
+          "speech_recognition_result.h",
+        ]
+        traits_sources = [
+          "speech_recognition_result.cc",
+          "speech_recognition_result_mojom_traits.cc",
+        ]
+        traits_public_deps = [ "//base" ]
+      },
+    ]
+  }
+
   cpp_typemaps += shared_typemaps
   blink_cpp_typemaps = shared_typemaps
 
@@ -615,6 +654,10 @@
     "video_frame_mojom_traits_unittest.cc",
   ]
 
+  if (!is_android) {
+    sources += [ "speech_recognition_result_mojom_traits_unittest.cc" ]
+  }
+
   deps = [
     "//base",
     "//base/test:test_support",
diff --git a/media/mojo/mojom/content_decryption_module.mojom b/media/mojo/mojom/content_decryption_module.mojom
index c0e8e251..eda8b0b86 100644
--- a/media/mojo/mojom/content_decryption_module.mojom
+++ b/media/mojo/mojom/content_decryption_module.mojom
@@ -5,6 +5,7 @@
 module media.mojom;
 
 import "media/mojo/mojom/decryptor.mojom";
+import "media/mojo/mojom/media_types.mojom";
 import "url/mojom/url.mojom";
 import "mojo/public/mojom/base/unguessable_token.mojom";
 
@@ -65,12 +66,18 @@
 };
 
 // An interface that represents a CDM in the Encrypted Media Extensions (EME)
-// spec (https://w3c.github.io/encrypted-media/). For security reason, the CDM
-// is running in its own CDM process as the CDM contains untrusted code and
-// handles arbitrary data.
-// See media/base/content_decryption_module.h
+// spec (https://w3c.github.io/encrypted-media/).
+// - Process Model: For security reason, the CDM is running in its own process
+// as the CDM contains untrusted code and handles arbitrary data. The exact
+// process model varies on various platforms. For example, on desktop, the CDM
+// runs in a CDM (utility) process. On Android, the CDM runs in the GPU process.
+// The client of the CDM is MojoCdm, which runs in the render process.
+// - Session ID: Passed as `session_id` in some methods, it is a unique string
+// identifier generated by the CDM that can be used to identify CDM sessions.
+// A ContentDecryptionModule can manage mutilple sessions per the EME spec. See
+// https://www.w3.org/TR/encrypted-media/#session-id.
+// - Also see media/base/content_decryption_module.h and media/mojo/README.md.
 interface ContentDecryptionModule {
-
   // Sets ContentDecryptionModuleClient. Must be called before any other calls.
   // Use associated interface to ensure ordering, e.g. events on the client
   // interface and promise fulfillment.
@@ -117,17 +124,32 @@
   RemoveSession(string session_id) => (CdmPromiseResult result);
 };
 
-// Session callbacks. See media/base/content_decryption_module.h for details.
+// Session callbacks. The implementation is MojoCdm that runs in the render
+// process. The caller is the remote CDM (mojom::ContentDecryptionModule). See
+// comments above for more details on CDM process model and session ID.
+// Also see media/base/content_decryption_module.h and media/mojo/README.md.
 interface ContentDecryptionModuleClient {
+  // Called when the CDM needs to queue a message event to the session object.
+  // See http://w3c.github.io/encrypted-media/#dom-evt-message
   OnSessionMessage(string session_id, CdmMessageType message_type,
                    array<uint8> message);
 
-  OnSessionClosed(string session_id);
+  // Called when the session specified by |session_id| is closed. Note that the
+  // CDM may close a session at any point, e.g. in response to `CloseSession()`,
+  // when the session is no longer needed, or when system resources are lost.
+  // After `OnSessionClosed()` is called for a `session_id`, no methods in this
+  // interface should be called with the same `session_id`.
+  // See http://w3c.github.io/encrypted-media/#session-close
+  OnSessionClosed(string session_id, CdmSessionClosedReason reason);
 
+  // Called when there has been a change in the keys in the session or their
+  // status.
+  // See http://w3c.github.io/encrypted-media/#dom-evt-keystatuseschange
   OnSessionKeysChange(string session_id, bool has_additional_usable_key,
                       array<CdmKeyInformation> keys_info);
 
-  // Provide session expiration update for |session_id|.
+  // Called when the CDM changes the expiration time of a session.
+  // See http://w3c.github.io/encrypted-media/#update-expiration
   // |new_expiry_time_sec| is the number of seconds since epoch (Jan 1, 1970).
   OnSessionExpirationUpdate(string session_id, double new_expiry_time_sec);
 };
diff --git a/media/mojo/mojom/media_types.mojom b/media/mojo/mojom/media_types.mojom
index bf134ce..a2946185 100644
--- a/media/mojo/mojom/media_types.mojom
+++ b/media/mojo/mojom/media_types.mojom
@@ -151,7 +151,6 @@
   RangeID range;
 };
 
-
 // This defines a mojo transport format for media::AudioDecoderConfig.
 // See media/base/audio_decoder_config.h for descriptions.
 struct AudioDecoderConfig {
@@ -187,6 +186,14 @@
 [Native]
 struct SubsampleEntry;
 
+enum CdmSessionClosedReason {
+  kUnknown,
+  kClose,
+  kCdmUnavailable,
+  kHardwareContextReset,
+  kResourceEvicted,
+};
+
 // This defines a mojo transport format for media::DecryptConfig.
 // See media/base/decrypt_config.h for descriptions.
 struct DecryptConfig {
diff --git a/media/mojo/mojom/media_types_enum_mojom_traits.h b/media/mojo/mojom/media_types_enum_mojom_traits.h
index ec91fe6a..cf6e3ea 100644
--- a/media/mojo/mojom/media_types_enum_mojom_traits.h
+++ b/media/mojo/mojom/media_types_enum_mojom_traits.h
@@ -18,6 +18,56 @@
 namespace mojo {
 
 template <>
+struct EnumTraits<media::mojom::CdmSessionClosedReason,
+                  ::media::CdmSessionClosedReason> {
+  static media::mojom::CdmSessionClosedReason ToMojom(
+      ::media::CdmSessionClosedReason input) {
+    switch (input) {
+      case ::media::CdmSessionClosedReason::kUnknown:
+        return media::mojom::CdmSessionClosedReason::kUnknown;
+      case ::media::CdmSessionClosedReason::kClose:
+        return media::mojom::CdmSessionClosedReason::kClose;
+      case ::media::CdmSessionClosedReason::kCdmUnavailable:
+        return media::mojom::CdmSessionClosedReason::kCdmUnavailable;
+      case ::media::CdmSessionClosedReason::kHardwareContextReset:
+        return media::mojom::CdmSessionClosedReason::kHardwareContextReset;
+      case ::media::CdmSessionClosedReason::kResourceEvicted:
+        return media::mojom::CdmSessionClosedReason::kResourceEvicted;
+    }
+
+    NOTREACHED();
+    return static_cast<media::mojom::CdmSessionClosedReason>(input);
+  }
+
+  // Returning false results in deserialization failure and causes the
+  // message pipe receiving it to be disconnected.
+  static bool FromMojom(media::mojom::CdmSessionClosedReason input,
+                        ::media::CdmSessionClosedReason* output) {
+    switch (input) {
+      case media::mojom::CdmSessionClosedReason::kUnknown:
+        *output = ::media::CdmSessionClosedReason::kUnknown;
+        return true;
+      case media::mojom::CdmSessionClosedReason::kClose:
+        *output = ::media::CdmSessionClosedReason::kClose;
+        return true;
+      case media::mojom::CdmSessionClosedReason::kCdmUnavailable:
+        *output = ::media::CdmSessionClosedReason::kCdmUnavailable;
+        return true;
+      case media::mojom::CdmSessionClosedReason::kHardwareContextReset:
+        *output = ::media::CdmSessionClosedReason::kHardwareContextReset;
+        return true;
+      case media::mojom::CdmSessionClosedReason::kResourceEvicted:
+        *output = ::media::CdmSessionClosedReason::kResourceEvicted;
+        return true;
+    }
+
+    NOTREACHED();
+    *output = static_cast<::media::CdmSessionClosedReason>(input);
+    return false;
+  }
+};
+
+template <>
 struct EnumTraits<media::mojom::VideoRotation, ::media::VideoRotation> {
   static media::mojom::VideoRotation ToMojom(::media::VideoRotation input) {
     switch (input) {
diff --git a/media/mojo/mojom/speech_recognition_result.cc b/media/mojo/mojom/speech_recognition_result.cc
new file mode 100644
index 0000000..eb99456
--- /dev/null
+++ b/media/mojo/mojom/speech_recognition_result.cc
@@ -0,0 +1,60 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/mojo/mojom/speech_recognition_result.h"
+
+namespace media {
+
+HypothesisParts::HypothesisParts() = default;
+HypothesisParts::HypothesisParts(const std::vector<std::string> part,
+                                 base::TimeDelta offset)
+    : text(part), hypothesis_part_offset(offset) {}
+
+HypothesisParts::HypothesisParts(const HypothesisParts&) = default;
+HypothesisParts::HypothesisParts(HypothesisParts&&) = default;
+HypothesisParts& HypothesisParts::operator=(const HypothesisParts&) = default;
+HypothesisParts& HypothesisParts::operator=(HypothesisParts&&) = default;
+HypothesisParts::~HypothesisParts() = default;
+
+bool HypothesisParts::operator==(const HypothesisParts& rhs) const {
+  return text == rhs.text &&
+         hypothesis_part_offset == rhs.hypothesis_part_offset;
+}
+
+TimingInformation::TimingInformation() = default;
+TimingInformation::TimingInformation(const TimingInformation&) = default;
+TimingInformation::TimingInformation(TimingInformation&&) = default;
+TimingInformation& TimingInformation::operator=(const TimingInformation&) =
+    default;
+TimingInformation& TimingInformation::operator=(TimingInformation&&) = default;
+TimingInformation::~TimingInformation() = default;
+
+bool TimingInformation::operator==(const TimingInformation& rhs) const {
+  return audio_start_time == rhs.audio_start_time &&
+         audio_end_time == rhs.audio_end_time &&
+         hypothesis_parts == rhs.hypothesis_parts;
+}
+
+SpeechRecognitionResult::SpeechRecognitionResult() = default;
+SpeechRecognitionResult::SpeechRecognitionResult(const std::string transcript,
+                                                 bool is_final)
+    : transcription(transcript), is_final(is_final) {}
+
+SpeechRecognitionResult::SpeechRecognitionResult(
+    const SpeechRecognitionResult&) = default;
+SpeechRecognitionResult::SpeechRecognitionResult(SpeechRecognitionResult&&) =
+    default;
+SpeechRecognitionResult& SpeechRecognitionResult::operator=(
+    const SpeechRecognitionResult&) = default;
+SpeechRecognitionResult& SpeechRecognitionResult::operator=(
+    SpeechRecognitionResult&&) = default;
+SpeechRecognitionResult::~SpeechRecognitionResult() = default;
+
+bool SpeechRecognitionResult::operator==(
+    const SpeechRecognitionResult& rhs) const {
+  return transcription == rhs.transcription && is_final == rhs.is_final &&
+         timing_information == rhs.timing_information;
+}
+
+}  // namespace media
diff --git a/media/mojo/mojom/speech_recognition_result.h b/media/mojo/mojom/speech_recognition_result.h
new file mode 100644
index 0000000..269388b5
--- /dev/null
+++ b/media/mojo/mojom/speech_recognition_result.h
@@ -0,0 +1,91 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_MOJO_MOJOM_SPEECH_RECOGNITION_RESULT_H_
+#define MEDIA_MOJO_MOJOM_SPEECH_RECOGNITION_RESULT_H_
+
+#include <string>
+#include <vector>
+
+#include "base/time/time.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace media {
+
+struct HypothesisParts {
+  HypothesisParts();
+  HypothesisParts(const std::vector<std::string> part, base::TimeDelta offset);
+  HypothesisParts(const HypothesisParts&);
+  HypothesisParts(HypothesisParts&&);
+  HypothesisParts& operator=(const HypothesisParts&);
+  HypothesisParts& operator=(HypothesisParts&&);
+  ~HypothesisParts();
+
+  bool operator==(const HypothesisParts& rhs) const;
+
+  // A section of the final transcription text. Either an entire word or single
+  // character (depending on the language) with adjacent punctuation. There will
+  // usually only be one value here. If formatting is enabled in the speech
+  // recognition, then the raw text will be included as the second element.
+  std::vector<std::string> text;
+
+  // Time offset from this event's |audio_start_time| defined below. Time
+  // offset from this event's |audio_start_time| defined below. We enforce the
+  // following invariant: 0 <= hypothesis_part_offset < |audio_end_time -
+  // audio_start_time|.
+  base::TimeDelta hypothesis_part_offset;
+};
+
+struct TimingInformation {
+  TimingInformation();
+  TimingInformation(const TimingInformation&);
+  TimingInformation(TimingInformation&&);
+  TimingInformation& operator=(const TimingInformation&);
+  TimingInformation& operator=(TimingInformation&&);
+  ~TimingInformation();
+
+  bool operator==(const TimingInformation& rhs) const;
+
+  // Start time in audio time from the start of the SODA session.
+  // This time measures the amount of audio input into SODA.
+  base::TimeDelta audio_start_time;
+
+  // Elapsed processed audio from first frame after preamble.
+  base::TimeDelta audio_end_time;
+
+  // The timing information for each word/letter in the transription.
+  // HypothesisPartsInResult was introduced in min version 1 in
+  // chromeos/services/machine_learning/public/mojom/soda.mojom. Therefore, it
+  // must be optional. Hypothesis parts maybe non-empty optional containing a
+  // zero length vector if no words were spoken during the event's time span.
+  absl::optional<std::vector<HypothesisParts>> hypothesis_parts;
+};
+
+// A speech recognition result created by the speech service and passed to the
+// SpeechRecognitionRecognizerClient.
+struct SpeechRecognitionResult {
+  SpeechRecognitionResult();
+  SpeechRecognitionResult(const std::string transcript, bool is_final);
+  SpeechRecognitionResult(const SpeechRecognitionResult&);
+  SpeechRecognitionResult(SpeechRecognitionResult&&);
+  SpeechRecognitionResult& operator=(const SpeechRecognitionResult&);
+  SpeechRecognitionResult& operator=(SpeechRecognitionResult&&);
+  ~SpeechRecognitionResult();
+
+  bool operator==(const SpeechRecognitionResult& rhs) const;
+
+  std::string transcription;
+
+  // A flag indicating whether the result is final. If true, the result is
+  // locked in and the next result returned will not overlap with the previous
+  // final result.
+  bool is_final = false;
+
+  // Timing information for the current transcription.
+  absl::optional<TimingInformation> timing_information;
+};
+
+}  // namespace media
+
+#endif  // MEDIA_MOJO_MOJOM_SPEECH_RECOGNITION_RESULT_H_
diff --git a/media/mojo/mojom/speech_recognition_result_mojom_traits.cc b/media/mojo/mojom/speech_recognition_result_mojom_traits.cc
new file mode 100644
index 0000000..e5205a39
--- /dev/null
+++ b/media/mojo/mojom/speech_recognition_result_mojom_traits.cc
@@ -0,0 +1,90 @@
+// 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/speech_recognition_result_mojom_traits.h"
+
+namespace mojo {
+
+namespace {
+
+constexpr base::TimeDelta kZeroTime = base::TimeDelta::FromSeconds(0);
+
+}  // namespace
+
+// static
+bool StructTraits<
+    media::mojom::HypothesisPartsDataView,
+    media::HypothesisParts>::Read(media::mojom::HypothesisPartsDataView data,
+                                  media::HypothesisParts* out) {
+  std::vector<std::string> text;
+  base::TimeDelta offset = kZeroTime;
+
+  if (!data.ReadText(&text) || !data.ReadHypothesisPartOffset(&offset))
+    return false;
+  if (offset < kZeroTime)
+    return false;
+
+  out->text = std::move(text);
+  out->hypothesis_part_offset = offset;
+  return true;
+}
+
+bool StructTraits<media::mojom::TimingInformationDataView,
+                  media::TimingInformation>::
+    Read(media::mojom::TimingInformationDataView data,
+         media::TimingInformation* out) {
+  base::TimeDelta audio_start_time = kZeroTime;
+  base::TimeDelta audio_end_time = kZeroTime;
+  absl::optional<std::vector<media::HypothesisParts>> hypothesis_parts;
+
+  if (!data.ReadAudioStartTime(&audio_start_time) ||
+      !data.ReadAudioEndTime(&audio_end_time) ||
+      !data.ReadHypothesisParts(&hypothesis_parts)) {
+    return false;
+  }
+
+  if (audio_start_time < kZeroTime || audio_end_time < audio_start_time)
+    return false;
+
+  if (hypothesis_parts.has_value() && hypothesis_parts->size() > 0) {
+    base::TimeDelta prev_offset = kZeroTime;
+    base::TimeDelta max_offset = audio_end_time - audio_start_time;
+    for (const auto& part : *hypothesis_parts) {
+      if (part.hypothesis_part_offset < prev_offset ||
+          part.hypothesis_part_offset >= max_offset) {
+        return false;
+      }
+      prev_offset = part.hypothesis_part_offset;
+    }
+  }
+
+  out->audio_start_time = audio_start_time;
+  out->audio_end_time = audio_end_time;
+  out->hypothesis_parts = std::move(hypothesis_parts);
+  return true;
+}
+
+bool StructTraits<media::mojom::SpeechRecognitionResultDataView,
+                  media::SpeechRecognitionResult>::
+    Read(media::mojom::SpeechRecognitionResultDataView data,
+         media::SpeechRecognitionResult* out) {
+  std::string transcription;
+  absl::optional<media::TimingInformation> timing_information;
+
+  if (!data.ReadTranscription(&transcription) ||
+      !data.ReadTimingInformation(&timing_information)) {
+    return false;
+  }
+
+  // Timing information is provided only for final results.
+  if (!data.is_final() && timing_information.has_value())
+    return false;
+
+  out->transcription = std::move(transcription);
+  out->is_final = data.is_final();
+  out->timing_information = std::move(timing_information);
+  return true;
+}
+
+}  // namespace mojo
diff --git a/media/mojo/mojom/speech_recognition_result_mojom_traits.h b/media/mojo/mojom/speech_recognition_result_mojom_traits.h
new file mode 100644
index 0000000..c3eb1509
--- /dev/null
+++ b/media/mojo/mojom/speech_recognition_result_mojom_traits.h
@@ -0,0 +1,80 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_MOJO_MOJOM_SPEECH_RECOGNITION_RESULT_MOJOM_TRAITS_H_
+#define MEDIA_MOJO_MOJOM_SPEECH_RECOGNITION_RESULT_MOJOM_TRAITS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/time/time.h"
+#include "media/mojo/mojom/speech_recognition_result.h"
+#include "media/mojo/mojom/speech_recognition_service.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace mojo {
+
+template <>
+class StructTraits<media::mojom::HypothesisPartsDataView,
+                   media::HypothesisParts> {
+ public:
+  static const std::vector<std::string>& text(const media::HypothesisParts& r) {
+    return r.text;
+  }
+
+  static base::TimeDelta hypothesis_part_offset(
+      const media::HypothesisParts& r) {
+    return r.hypothesis_part_offset;
+  }
+
+  static bool Read(media::mojom::HypothesisPartsDataView data,
+                   media::HypothesisParts* out);
+};
+
+template <>
+class StructTraits<media::mojom::TimingInformationDataView,
+                   media::TimingInformation> {
+ public:
+  static base::TimeDelta audio_start_time(const media::TimingInformation& r) {
+    return r.audio_start_time;
+  }
+
+  static base::TimeDelta audio_end_time(const media::TimingInformation& r) {
+    return r.audio_end_time;
+  }
+
+  static const ::absl::optional<std::vector<media::HypothesisParts>>&
+  hypothesis_parts(const media::TimingInformation& r) {
+    return r.hypothesis_parts;
+  }
+
+  static bool Read(media::mojom::TimingInformationDataView data,
+                   media::TimingInformation* out);
+};
+
+template <>
+class StructTraits<media::mojom::SpeechRecognitionResultDataView,
+                   media::SpeechRecognitionResult> {
+ public:
+  static const std::string& transcription(
+      const media::SpeechRecognitionResult& r) {
+    return r.transcription;
+  }
+
+  static bool is_final(const media::SpeechRecognitionResult& r) {
+    return r.is_final;
+  }
+
+  static const ::absl::optional<media::TimingInformation>& timing_information(
+      const media::SpeechRecognitionResult& r) {
+    return r.timing_information;
+  }
+
+  static bool Read(media::mojom::SpeechRecognitionResultDataView data,
+                   media::SpeechRecognitionResult* out);
+};
+
+}  // namespace mojo
+
+#endif  // MEDIA_MOJO_MOJOM_SPEECH_RECOGNITION_RESULT_MOJOM_TRAITS_H_
diff --git a/media/mojo/mojom/speech_recognition_result_mojom_traits_unittest.cc b/media/mojo/mojom/speech_recognition_result_mojom_traits_unittest.cc
new file mode 100644
index 0000000..42a83fb
--- /dev/null
+++ b/media/mojo/mojom/speech_recognition_result_mojom_traits_unittest.cc
@@ -0,0 +1,115 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/mojo/mojom/speech_recognition_result_mojom_traits.h"
+
+#include <vector>
+
+#include "base/time/time.h"
+#include "media/mojo/mojom/speech_recognition_result.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+namespace {
+
+base::TimeDelta kZeroTime = base::TimeDelta::FromSeconds(0);
+
+}
+
+TEST(SpeechRecognitionResultStructTraitsTest, NoTimingInformation) {
+  media::SpeechRecognitionResult result("hello world", true);
+  std::vector<uint8_t> data =
+      media::mojom::SpeechRecognitionResult::Serialize(&result);
+  media::SpeechRecognitionResult output;
+  EXPECT_TRUE(media::mojom::SpeechRecognitionResult::Deserialize(
+      std::move(data), &output));
+  EXPECT_EQ(result, output);
+}
+
+TEST(SpeechRecognitionResultStructTraitsTest, WithTimingInformation) {
+  media::SpeechRecognitionResult invalid_result("hello world", true);
+  invalid_result.timing_information = media::TimingInformation();
+  invalid_result.timing_information->audio_start_time = kZeroTime;
+  invalid_result.timing_information->audio_end_time =
+      base::TimeDelta::FromSeconds(-1);
+  std::vector<uint8_t> data =
+      media::mojom::SpeechRecognitionResult::Serialize(&invalid_result);
+  media::SpeechRecognitionResult output;
+  EXPECT_FALSE(media::mojom::SpeechRecognitionResult::Deserialize(
+      std::move(data), &output));
+
+  media::SpeechRecognitionResult valid_result("hello world", true);
+  valid_result.timing_information = media::TimingInformation();
+  valid_result.timing_information->audio_start_time = kZeroTime;
+  valid_result.timing_information->audio_end_time =
+      base::TimeDelta::FromSeconds(1);
+  std::vector<uint8_t> valid_data =
+      media::mojom::SpeechRecognitionResult::Serialize(&valid_result);
+  media::SpeechRecognitionResult valid_output;
+  EXPECT_TRUE(media::mojom::SpeechRecognitionResult::Deserialize(
+      std::move(valid_data), &valid_output));
+  EXPECT_EQ(valid_result, valid_output);
+}
+
+TEST(SpeechRecognitionResultStructTraitsTest,
+     PartialResultWithTimingInformation) {
+  media::SpeechRecognitionResult invalid_result("hello world", false);
+  invalid_result.timing_information = media::TimingInformation();
+  invalid_result.timing_information->audio_start_time = kZeroTime;
+  invalid_result.timing_information->audio_end_time =
+      base::TimeDelta::FromSeconds(1);
+  std::vector<uint8_t> invalid_data =
+      media::mojom::SpeechRecognitionResult::Serialize(&invalid_result);
+  media::SpeechRecognitionResult invalid_output;
+
+  // Partial results shouldn't have timing information.
+  EXPECT_FALSE(media::mojom::SpeechRecognitionResult::Deserialize(
+      std::move(invalid_data), &invalid_output));
+}
+
+TEST(SpeechRecognitionResultStructTraitsTest, WithInvalidHypothesisParts) {
+  media::SpeechRecognitionResult invalid_result("hello world", true);
+  invalid_result.timing_information = media::TimingInformation();
+  invalid_result.timing_information->audio_start_time = kZeroTime;
+  invalid_result.timing_information->audio_end_time =
+      base::TimeDelta::FromSeconds(1);
+  invalid_result.timing_information->hypothesis_parts =
+      std::vector<media::HypothesisParts>();
+  auto& hypothesis_parts =
+      invalid_result.timing_information->hypothesis_parts.value();
+  hypothesis_parts.emplace_back(std::vector<std::string>({"hello"}),
+                                base::TimeDelta::FromSeconds(-1));
+  hypothesis_parts.emplace_back(std::vector<std::string>({"world"}),
+                                base::TimeDelta::FromSeconds(1));
+  std::vector<uint8_t> data =
+      media::mojom::SpeechRecognitionResult::Serialize(&invalid_result);
+  media::SpeechRecognitionResult output;
+  EXPECT_FALSE(media::mojom::SpeechRecognitionResult::Deserialize(
+      std::move(data), &output));
+}
+
+TEST(SpeechRecognitionResultStructTraitsTest, WithValidHypothesisParts) {
+  media::SpeechRecognitionResult valid_result("hello world", true);
+  valid_result.timing_information = media::TimingInformation();
+  valid_result.timing_information->audio_start_time = kZeroTime;
+  valid_result.timing_information->audio_end_time =
+      base::TimeDelta::FromSeconds(2);
+  valid_result.timing_information->hypothesis_parts =
+      std::vector<media::HypothesisParts>();
+  auto& hypothesis_parts =
+      valid_result.timing_information->hypothesis_parts.value();
+  hypothesis_parts.emplace_back(std::vector<std::string>({"hello"}),
+                                base::TimeDelta::FromSeconds(0));
+  hypothesis_parts.emplace_back(std::vector<std::string>({"world"}),
+                                base::TimeDelta::FromSeconds(1));
+  std::vector<uint8_t> data =
+      media::mojom::SpeechRecognitionResult::Serialize(&valid_result);
+  media::SpeechRecognitionResult output;
+  EXPECT_TRUE(media::mojom::SpeechRecognitionResult::Deserialize(
+      std::move(data), &output));
+  EXPECT_EQ(valid_result, output);
+}
+
+}  // namespace media
diff --git a/media/mojo/mojom/speech_recognition_service.mojom b/media/mojo/mojom/speech_recognition_service.mojom
index d5ba43b0..0e9bede 100644
--- a/media/mojo/mojom/speech_recognition_service.mojom
+++ b/media/mojo/mojom/speech_recognition_service.mojom
@@ -119,6 +119,38 @@
   OnLanguageIdentificationEvent(LanguageIdentificationEvent event);
 };
 
+// The hypothesis parts that provides timing information for each word in
+// recognized speech.
+struct HypothesisParts {
+  // A section of the final transcription text. Either an entire word or single
+  // character (depending on the language) with adjacent punctuation. There will
+  // usually only be one value here. If formatting is enabled in the speech
+  // recognition, then the raw text will be included as the second element.
+  array<string> text;
+
+  // Time offset from this event's |audio_start_time| defined below. We enforce
+  // the following invariant: 0 <= hypothesis_part_offset < |audio_end_time -
+  // audio_start_time|.
+  mojo_base.mojom.TimeDelta hypothesis_part_offset;
+};
+
+// The timing information for the transcript.
+struct TimingInformation {
+  // Start time in audio time from the start of the SODA session.
+  // This time measures the amount of audio input into SODA.
+  mojo_base.mojom.TimeDelta audio_start_time;
+
+  // Elapsed processed audio from first frame after preamble.
+  mojo_base.mojom.TimeDelta audio_end_time;
+
+  // The timing information for each word/letter in the transription.
+  // HypothesisPartsInResult was introduced in min version 1 in
+  // chromeos/services/machine_learning/public/mojom/soda.mojom. Therefore, it
+  // must be optional. Hypothesis parts maybe non-empty optional containing a
+  // zero length vector if no words were spoken during the event's time span.
+  array<HypothesisParts> ? hypothesis_parts;
+};
+
 // A speech recognition result created by the speech service and passed to the
 // browser.
 struct SpeechRecognitionResult {
@@ -128,6 +160,12 @@
   // locked in and the next result returned will not overlap with the previous
   // final result.
   bool is_final;
+
+  // Timing information for the current transcription. |timing_information| is
+  // expected to be valid if:
+  //   1. speech recognition is provided by |CrosSodaClient| and
+  //   2. |is_final| is true.
+  TimingInformation? timing_information;
 };
 
 // A language identification event created by the speech recognition service
diff --git a/media/mojo/services/mojo_cdm_service.cc b/media/mojo/services/mojo_cdm_service.cc
index 785f2c9..265b7b3 100644
--- a/media/mojo/services/mojo_cdm_service.cc
+++ b/media/mojo/services/mojo_cdm_service.cc
@@ -220,10 +220,11 @@
   }
 }
 
-void MojoCdmService::OnSessionClosed(const std::string& session_id) {
+void MojoCdmService::OnSessionClosed(const std::string& session_id,
+                                     CdmSessionClosedReason reason) {
   DVLOG(2) << __func__;
   if (client_) {
-    client_->OnSessionClosed(session_id);
+    client_->OnSessionClosed(session_id, reason);
   }
 }
 
diff --git a/media/mojo/services/mojo_cdm_service.h b/media/mojo/services/mojo_cdm_service.h
index d6c32d1..bedc547 100644
--- a/media/mojo/services/mojo_cdm_service.h
+++ b/media/mojo/services/mojo_cdm_service.h
@@ -98,7 +98,8 @@
                            CdmKeysInfo keys_info);
   void OnSessionExpirationUpdate(const std::string& session_id,
                                  base::Time new_expiry_time);
-  void OnSessionClosed(const std::string& session_id);
+  void OnSessionClosed(const std::string& session_id,
+                       CdmSessionClosedReason reason);
 
   // Callback for when |decryptor_| loses connectivity.
   void OnDecryptorConnectionError();
diff --git a/media/test/fake_encrypted_media.cc b/media/test/fake_encrypted_media.cc
index cd5320c1..cb312065 100644
--- a/media/test/fake_encrypted_media.cc
+++ b/media/test/fake_encrypted_media.cc
@@ -43,8 +43,9 @@
   app_->OnSessionMessage(session_id, message_type, message, decryptor_.get());
 }
 
-void FakeEncryptedMedia::OnSessionClosed(const std::string& session_id) {
-  app_->OnSessionClosed(session_id);
+void FakeEncryptedMedia::OnSessionClosed(const std::string& session_id,
+                                         CdmSessionClosedReason reason) {
+  app_->OnSessionClosed(session_id, reason);
 }
 
 void FakeEncryptedMedia::OnSessionKeysChange(const std::string& session_id,
diff --git a/media/test/fake_encrypted_media.h b/media/test/fake_encrypted_media.h
index 9e1e643a..8c942b1 100644
--- a/media/test/fake_encrypted_media.h
+++ b/media/test/fake_encrypted_media.h
@@ -26,7 +26,8 @@
                                   const std::vector<uint8_t>& message,
                                   AesDecryptor* decryptor) = 0;
 
-    virtual void OnSessionClosed(const std::string& session_id) = 0;
+    virtual void OnSessionClosed(const std::string& session_id,
+                                 CdmSessionClosedReason reason) = 0;
 
     virtual void OnSessionKeysChange(const std::string& session_id,
                                      bool has_additional_usable_key,
@@ -43,11 +44,13 @@
   FakeEncryptedMedia(AppBase* app);
   ~FakeEncryptedMedia();
   CdmContext* GetCdmContext();
+
   // Callbacks for firing session events. Delegate to |app_|.
   void OnSessionMessage(const std::string& session_id,
                         CdmMessageType message_type,
                         const std::vector<uint8_t>& message);
-  void OnSessionClosed(const std::string& session_id);
+  void OnSessionClosed(const std::string& session_id,
+                       CdmSessionClosedReason reason);
   void OnSessionKeysChange(const std::string& session_id,
                            bool has_additional_usable_key,
                            CdmKeysInfo keys_info);
diff --git a/media/test/pipeline_integration_test.cc b/media/test/pipeline_integration_test.cc
index 220d3e0..a09045a 100644
--- a/media/test/pipeline_integration_test.cc
+++ b/media/test/pipeline_integration_test.cc
@@ -239,7 +239,8 @@
                              CreatePromise(RESOLVED));
   }
 
-  void OnSessionClosed(const std::string& session_id) override {
+  void OnSessionClosed(const std::string& session_id,
+                       CdmSessionClosedReason /*reason*/) override {
     EXPECT_EQ(current_session_id_, session_id);
   }
 
@@ -326,7 +327,8 @@
     FAIL() << "Unexpected Message";
   }
 
-  void OnSessionClosed(const std::string& session_id) override {
+  void OnSessionClosed(const std::string& session_id,
+                       CdmSessionClosedReason /*reason*/) override {
     EXPECT_FALSE(session_id.empty());
     FAIL() << "Unexpected Closed";
   }
diff --git a/mojo/public/cpp/bindings/connector.h b/mojo/public/cpp/bindings/connector.h
index 86ff9a7..3975d01 100644
--- a/mojo/public/cpp/bindings/connector.h
+++ b/mojo/public/cpp/bindings/connector.h
@@ -81,11 +81,21 @@
     kSerializeBeforeDispatchForTesting,
   };
 
-  // The Connector takes ownership of |message_pipe|.
+  // The Connector takes ownership of `message_pipe`. A Connector is essentially
+  // inert upon construction, though it may be used to send messages
+  // immediately. In order to receive incoming messages or error events,
+  // StartReceiving() must be called.
+  Connector(ScopedMessagePipeHandle message_pipe,
+            ConnectorConfig config,
+            const char* interface_name = "unknown interface");
+
+  // Same as above but automatically calls StartReceiving() with `runner` before
+  // returning.
   Connector(ScopedMessagePipeHandle message_pipe,
             ConnectorConfig config,
             scoped_refptr<base::SequencedTaskRunner> runner,
             const char* interface_name = "unknown interface");
+
   ~Connector() override;
 
   const char* interface_name() const { return interface_name_; }
@@ -131,6 +141,18 @@
     return error_;
   }
 
+  // Starts receiving on the Connector's message pipe, allowing incoming
+  // messages and error events to be dispatched. Once called, the Connector is
+  // effectively bound to `task_runner`. Initialization methods like
+  // `set_incoming_receiver` may be called before this, but if called after they
+  // must be called from the same sequence as `task_runner`.
+  //
+  // If `allow_woken_up_by_others` is true, the receiving sequence will allow
+  // this connector to process incoming messages during any sync wait by any
+  // Mojo object on the same sequence.
+  void StartReceiving(scoped_refptr<base::SequencedTaskRunner> task_runner,
+                      bool allow_woken_up_by_others = false);
+
   // Closes the pipe. The connector is put into a quiescent state.
   //
   // Please note that this method shouldn't be called unless it results from an
@@ -303,8 +325,10 @@
   // The quota checker associate with this connector, if any.
   scoped_refptr<internal::MessageQuotaChecker> quota_checker_;
 
-  base::Lock connected_lock_;
-  bool connected_ = true;
+  // Indicates whether the Connector is configured to actively read from its
+  // message pipe. As long as this is true, the Connector is only safe to
+  // destroy in sequence with `task_runner_` tasks.
+  bool is_receiving_ = false;
 
   // The tag used to track heap allocations that originated from a Watcher
   // notification.
diff --git a/mojo/public/cpp/bindings/lib/associated_receiver.cc b/mojo/public/cpp/bindings/lib/associated_receiver.cc
index 98f820cc..221fbf1d 100644
--- a/mojo/public/cpp/bindings/lib/associated_receiver.cc
+++ b/mojo/public/cpp/bindings/lib/associated_receiver.cc
@@ -74,7 +74,7 @@
 void AssociateWithDisconnectedPipe(ScopedInterfaceEndpointHandle handle) {
   MessagePipe pipe;
   scoped_refptr<internal::MultiplexRouter> router =
-      internal::MultiplexRouter::Create(
+      internal::MultiplexRouter::CreateAndStartReceiving(
           std::move(pipe.handle0), internal::MultiplexRouter::MULTI_INTERFACE,
           false, base::SequencedTaskRunnerHandle::Get());
   router->AssociateInterface(std::move(handle));
diff --git a/mojo/public/cpp/bindings/lib/binding_state.cc b/mojo/public/cpp/bindings/lib/binding_state.cc
index fd20c1e5..1efeb07 100644
--- a/mojo/public/cpp/bindings/lib/binding_state.cc
+++ b/mojo/public/cpp/bindings/lib/binding_state.cc
@@ -122,8 +122,9 @@
           : (has_sync_methods
                  ? MultiplexRouter::SINGLE_INTERFACE_WITH_SYNC_METHODS
                  : MultiplexRouter::SINGLE_INTERFACE);
-  router_ = MultiplexRouter::Create(std::move(receiver_state->pipe), config,
-                                    false, sequenced_runner, interface_name);
+  router_ = MultiplexRouter::CreateAndStartReceiving(
+      std::move(receiver_state->pipe), config, false, sequenced_runner,
+      interface_name);
   router_->SetConnectionGroup(std::move(receiver_state->connection_group));
 
   endpoint_client_ = std::make_unique<InterfaceEndpointClient>(
diff --git a/mojo/public/cpp/bindings/lib/connector.cc b/mojo/public/cpp/bindings/lib/connector.cc
index daae5a3..3223c784 100644
--- a/mojo/public/cpp/bindings/lib/connector.cc
+++ b/mojo/public/cpp/bindings/lib/connector.cc
@@ -148,10 +148,8 @@
 
 Connector::Connector(ScopedMessagePipeHandle message_pipe,
                      ConnectorConfig config,
-                     scoped_refptr<base::SequencedTaskRunner> runner,
                      const char* interface_name)
     : message_pipe_(std::move(message_pipe)),
-      task_runner_(std::move(runner)),
       error_(false),
       force_immediate_dispatch_(!EnableTaskPerMessage()),
       outgoing_serialization_mode_(g_default_outgoing_serialization_mode),
@@ -166,17 +164,14 @@
 #endif
 
   weak_self_ = weak_factory_.GetWeakPtr();
+}
 
-  DETACH_FROM_SEQUENCE(sequence_checker_);
-
-  // Even though we don't have an incoming receiver, we still want to monitor
-  // the message pipe to know if is closed or encounters an error.
-  if (task_runner_->RunsTasksInCurrentSequence()) {
-    WaitToReadMore();
-  } else {
-    task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(&Connector::WaitToReadMore, weak_self_));
-  }
+Connector::Connector(ScopedMessagePipeHandle message_pipe,
+                     ConnectorConfig config,
+                     scoped_refptr<base::SequencedTaskRunner> runner,
+                     const char* interface_name)
+    : Connector(std::move(message_pipe), config, interface_name) {
+  StartReceiving(std::move(runner));
 }
 
 Connector::~Connector() {
@@ -187,16 +182,10 @@
                             quota_checker_->GetMaxQuotaUsage());
   }
 
-  {
-    // Allow for quick destruction on any sequence if the pipe is already
-    // closed.
-    base::AutoLock lock(connected_lock_);
-    if (!connected_)
-      return;
+  if (is_receiving_) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    CancelWait();
   }
-
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  CancelWait();
 }
 
 void Connector::SetOutgoingSerializationMode(OutgoingSerializationMode mode) {
@@ -209,6 +198,22 @@
   incoming_serialization_mode_ = mode;
 }
 
+void Connector::StartReceiving(
+    scoped_refptr<base::SequencedTaskRunner> task_runner,
+    bool allow_woken_up_by_others) {
+  DCHECK(!task_runner_);
+  task_runner_ = std::move(task_runner);
+  allow_woken_up_by_others_ = allow_woken_up_by_others;
+  if (task_runner_->RunsTasksInCurrentSequence()) {
+    WaitToReadMore();
+  } else {
+    DETACH_FROM_SEQUENCE(sequence_checker_);
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&Connector::WaitToReadMore, weak_factory_.GetWeakPtr()));
+  }
+}
+
 void Connector::CloseMessagePipe() {
   // Throw away the returned message pipe.
   PassMessagePipe();
@@ -223,8 +228,6 @@
   weak_factory_.InvalidateWeakPtrs();
   sync_handle_watcher_callback_count_ = 0;
 
-  base::AutoLock lock(connected_lock_);
-  connected_ = false;
   return message_pipe;
 }
 
@@ -331,9 +334,11 @@
   if (!message->is_serialized()) {
     // The caller is sending an unserialized message. If we haven't set up a
     // remoteness tracker yet, do so now. See PrefersSerializedMessages() above
-    // for more details.
+    // for more details. Note that if the Connector is not yet bound to a
+    // TaskRunner and activaly reading the pipe, we don't bother setting this up
+    // yet.
     DCHECK_EQ(outgoing_serialization_mode_, OutgoingSerializationMode::kLazy);
-    if (!peer_remoteness_tracker_) {
+    if (!peer_remoteness_tracker_ && task_runner_) {
       peer_remoteness_tracker_.emplace(
           message_pipe_.get(), MOJO_HANDLE_SIGNAL_PEER_REMOTE, task_runner_);
     }
@@ -437,6 +442,7 @@
 }
 
 void Connector::WaitToReadMore() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   CHECK(!paused_);
   DCHECK(!handle_watcher_);
 
@@ -466,6 +472,8 @@
     EnsureSyncWatcherExists();
     sync_watcher_->AllowWokenUpBySyncWatchOnSameThread();
   }
+
+  is_receiving_ = true;
 }
 
 uint64_t Connector::QueryPendingMessageCount() const {
@@ -627,6 +635,8 @@
 }
 
 void Connector::CancelWait() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  is_receiving_ = false;
   peer_remoteness_tracker_.reset();
   handle_watcher_.reset();
   sync_watcher_.reset();
diff --git a/mojo/public/cpp/bindings/lib/interface_ptr_state.cc b/mojo/public/cpp/bindings/lib/interface_ptr_state.cc
index 7f095b7e..ea1c937 100644
--- a/mojo/public/cpp/bindings/lib/interface_ptr_state.cc
+++ b/mojo/public/cpp/bindings/lib/interface_ptr_state.cc
@@ -98,6 +98,14 @@
       // The version is only queried from the client so the value passed here
       // will not be used.
       0u, interface_name);
+
+  // Note that we defer this until after attaching the endpoint. This is in case
+  // `runner_` does not run tasks in the current sequence but MultiplexRouter is
+  // in SINGLE_INTERFACE mode. In that case, MultiplexRouter elides some
+  // internal synchronization, so we need to ensure that messages aren't
+  // processed by the router before the endpoint above is fully attached.
+  router_->StartReceiving();
+
   return true;
 }
 
diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.cc b/mojo/public/cpp/bindings/lib/multiplex_router.cc
index f6b62b0b..b6b7f46 100644
--- a/mojo/public/cpp/bindings/lib/multiplex_router.cc
+++ b/mojo/public/cpp/bindings/lib/multiplex_router.cc
@@ -338,16 +338,22 @@
     bool set_interface_id_namespace_bit,
     scoped_refptr<base::SequencedTaskRunner> runner,
     const char* primary_interface_name) {
-  auto router = base::MakeRefCounted<MultiplexRouter>(
+  return base::MakeRefCounted<MultiplexRouter>(
       base::PassKey<MultiplexRouter>(), std::move(message_pipe), config,
       set_interface_id_namespace_bit, runner, primary_interface_name);
-  if (runner->RunsTasksInCurrentSequence()) {
-    router->BindToCurrentSequence();
-  } else {
-    runner->PostTask(
-        FROM_HERE,
-        base::BindOnce(&MultiplexRouter::BindToCurrentSequence, router));
-  }
+}
+
+// static
+scoped_refptr<MultiplexRouter> MultiplexRouter::CreateAndStartReceiving(
+    ScopedMessagePipeHandle message_pipe,
+    Config config,
+    bool set_interface_id_namespace_bit,
+    scoped_refptr<base::SequencedTaskRunner> runner,
+    const char* primary_interface_name) {
+  auto router =
+      Create(std::move(message_pipe), config, set_interface_id_namespace_bit,
+             runner, primary_interface_name);
+  router->StartReceiving();
   return router;
 }
 
@@ -365,31 +371,13 @@
       connector_(std::move(message_pipe),
                  config == MULTI_INTERFACE ? Connector::MULTI_THREADED_SEND
                                            : Connector::SINGLE_THREADED_SEND,
-                 std::move(runner),
                  primary_interface_name),
       control_message_handler_(this),
       control_message_proxy_(&connector_) {
-  DETACH_FROM_SEQUENCE(sequence_checker_);
   if (config_ == MULTI_INTERFACE)
     lock_.emplace();
-}
 
-void MultiplexRouter::BindToCurrentSequence() {
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  if (config_ == SINGLE_INTERFACE_WITH_SYNC_METHODS ||
-      config_ == MULTI_INTERFACE) {
-    // Always participate in sync handle watching in multi-interface mode,
-    // because even if it doesn't expect sync requests during sync handle
-    // watching, it may still need to dispatch messages to associated endpoints
-    // on a different sequence.
-    connector_.AllowWokenUpBySyncWatchOnSameThread();
-  }
   connector_.set_incoming_receiver(&dispatcher_);
-  connector_.set_connection_error_handler(
-      base::BindOnce(&MultiplexRouter::OnPipeConnectionError,
-                     base::Unretained(this), false /* force_async_dispatch */));
 
   scoped_refptr<internal::MessageQuotaChecker> quota_checker =
       internal::MessageQuotaChecker::MaybeCreate();
@@ -401,7 +389,6 @@
   header_validator_ = header_validator.get();
   dispatcher_.SetValidator(std::move(header_validator));
 
-  const char* primary_interface_name = connector_.interface_name();
   if (primary_interface_name) {
     header_validator_->SetDescription(base::JoinString(
         {primary_interface_name, "[primary] MessageHeaderValidator"}, " "));
@@ -410,6 +397,23 @@
   }
 }
 
+void MultiplexRouter::StartReceiving() {
+  connector_.set_connection_error_handler(
+      base::BindOnce(&MultiplexRouter::OnPipeConnectionError,
+                     base::Unretained(this), false /* force_async_dispatch */));
+
+  // Always participate in sync handle watching in multi-interface mode,
+  // because even if it doesn't expect sync requests during sync handle
+  // watching, it may still need to dispatch messages to associated endpoints
+  // on a different sequence.
+  const bool allow_woken_up_by_others =
+      config_ == SINGLE_INTERFACE_WITH_SYNC_METHODS ||
+      config_ == MULTI_INTERFACE;
+
+  DETACH_FROM_SEQUENCE(sequence_checker_);
+  connector_.StartReceiving(task_runner_, allow_woken_up_by_others);
+}
+
 MultiplexRouter::~MultiplexRouter() {
   MayAutoLock locker(&lock_);
 
diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.h b/mojo/public/cpp/bindings/lib/multiplex_router.h
index d61f3ae..3d3bbb16 100644
--- a/mojo/public/cpp/bindings/lib/multiplex_router.h
+++ b/mojo/public/cpp/bindings/lib/multiplex_router.h
@@ -76,8 +76,10 @@
   // If `set_interface_id_namespace_bit` is true, the interface IDs generated by
   // this router will have the highest bit set.
   //
-  // NOTE: CloseMessagePipe() or PassMessagePipe() MUST be called on the
-  // `runner` sequence before this object is destroyed.
+  // Note that the MultiplexRouter will not initially receive any messages or
+  // disconnect events until StartReceiving() is explicitly called. To create a
+  // MultiplexRouter which calls this automatically at construction time, use
+  // CreateAndStartReceiving().
   static scoped_refptr<MultiplexRouter> Create(
       ScopedMessagePipeHandle message_pipe,
       Config config,
@@ -85,6 +87,23 @@
       scoped_refptr<base::SequencedTaskRunner> runner,
       const char* primary_interface_name = "unknown interface");
 
+  // Same as above, but automatically calls StartReceiving() before returning.
+  // If `runner` does not run tasks in sequence with the caller, the returned
+  // MultiplexRouter may already begin receiving messages and events on `runner`
+  // before this call returns.
+  static scoped_refptr<MultiplexRouter> CreateAndStartReceiving(
+      ScopedMessagePipeHandle message_pipe,
+      Config config,
+      bool set_interface_id_namespace_bit,
+      scoped_refptr<base::SequencedTaskRunner> runner,
+      const char* primary_interface_name = "unknown interface");
+
+  // Starts receiving messages on the MultiplexRouter. Once this is called,
+  // CloseMessagePipe() or PassMessagePipe() MUST be called in sequence with
+  // the MultiplexRouter's `task_runner_` prior to destroying the
+  // MultiplexRouter.
+  void StartReceiving();
+
   MultiplexRouter(base::PassKey<MultiplexRouter>,
                   ScopedMessagePipeHandle message_pipe,
                   Config config,
@@ -188,9 +207,6 @@
 
   ~MultiplexRouter() override;
 
-  // Completes initialization of the MultiplexRouter.
-  void BindToCurrentSequence();
-
   // Indicates whether `message` can unblock any active external sync waiter.
   bool CanUnblockExternalSyncWait(const Message& message);
 
diff --git a/mojo/public/cpp/bindings/pending_associated_receiver.h b/mojo/public/cpp/bindings/pending_associated_receiver.h
index 487cf5c..fb6837c 100644
--- a/mojo/public/cpp/bindings/pending_associated_receiver.h
+++ b/mojo/public/cpp/bindings/pending_associated_receiver.h
@@ -107,11 +107,11 @@
 
     MessagePipe pipe;
     scoped_refptr<internal::MultiplexRouter> router0 =
-        internal::MultiplexRouter::Create(
+        internal::MultiplexRouter::CreateAndStartReceiving(
             std::move(pipe.handle0), internal::MultiplexRouter::MULTI_INTERFACE,
             false, base::SequencedTaskRunnerHandle::Get());
     scoped_refptr<internal::MultiplexRouter> router1 =
-        internal::MultiplexRouter::Create(
+        internal::MultiplexRouter::CreateAndStartReceiving(
             std::move(pipe.handle1), internal::MultiplexRouter::MULTI_INTERFACE,
             true, base::SequencedTaskRunnerHandle::Get());
 
diff --git a/mojo/public/cpp/bindings/pending_associated_remote.h b/mojo/public/cpp/bindings/pending_associated_remote.h
index cd4d28c..bb68431 100644
--- a/mojo/public/cpp/bindings/pending_associated_remote.h
+++ b/mojo/public/cpp/bindings/pending_associated_remote.h
@@ -103,11 +103,11 @@
 
     MessagePipe pipe;
     scoped_refptr<internal::MultiplexRouter> router0 =
-        internal::MultiplexRouter::Create(
+        internal::MultiplexRouter::CreateAndStartReceiving(
             std::move(pipe.handle0), internal::MultiplexRouter::MULTI_INTERFACE,
             false, base::SequencedTaskRunnerHandle::Get());
     scoped_refptr<internal::MultiplexRouter> router1 =
-        internal::MultiplexRouter::Create(
+        internal::MultiplexRouter::CreateAndStartReceiving(
             std::move(pipe.handle1), internal::MultiplexRouter::MULTI_INTERFACE,
             true, base::SequencedTaskRunnerHandle::Get());
 
diff --git a/mojo/public/cpp/bindings/pending_receiver.h b/mojo/public/cpp/bindings/pending_receiver.h
index c3c74b6..9ca1d24 100644
--- a/mojo/public/cpp/bindings/pending_receiver.h
+++ b/mojo/public/cpp/bindings/pending_receiver.h
@@ -92,7 +92,7 @@
     return request;
   }
 
-  // Indicates whether the PendingReceiver is valid, meaning it can ne used to
+  // Indicates whether the PendingReceiver is valid, meaning it can be used to
   // bind a Receiver that wants to begin dispatching method calls made by the
   // entangled Remote.
   bool is_valid() const { return state_.pipe.is_valid(); }
diff --git a/mojo/public/cpp/bindings/receiver.h b/mojo/public/cpp/bindings/receiver.h
index 0888f0d..87818aa 100644
--- a/mojo/public/cpp/bindings/receiver.h
+++ b/mojo/public/cpp/bindings/receiver.h
@@ -52,7 +52,7 @@
   using ImplPointerType = typename ImplRefTraits::PointerType;
 
   // Constructs an unbound Receiver linked to |impl| for the duration of the
-  // Receive's lifetime. The Receiver can be bound later by calling |Bind()| or
+  // Receiver's lifetime. The Receiver can be bound later by calling |Bind()| or
   // |BindNewPipeAndPassRemote()|. An unbound Receiver does not schedule any
   // asynchronous tasks.
   explicit Receiver(ImplPointerType impl) : internal_state_(std::move(impl)) {}
@@ -139,7 +139,7 @@
     return remote;
   }
 
-  // Like above, but the returne PendingRemote has the version annotated.
+  // Like above, but the returned PendingRemote has the version annotated.
   PendingRemote<Interface> BindNewPipeAndPassRemoteWithVersion(
       scoped_refptr<base::SequencedTaskRunner> task_runner = nullptr)
       WARN_UNUSED_RESULT {
diff --git a/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc
index b503e77..0b50480b 100644
--- a/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc
@@ -117,12 +117,12 @@
   void CreateRouterPair(scoped_refptr<MultiplexRouter>* router0,
                         scoped_refptr<MultiplexRouter>* router1) {
     MessagePipe pipe;
-    *router0 = MultiplexRouter::Create(std::move(pipe.handle0),
-                                       MultiplexRouter::MULTI_INTERFACE, true,
-                                       main_runner_);
-    *router1 = MultiplexRouter::Create(std::move(pipe.handle1),
-                                       MultiplexRouter::MULTI_INTERFACE, false,
-                                       main_runner_);
+    *router0 = MultiplexRouter::CreateAndStartReceiving(
+        std::move(pipe.handle0), MultiplexRouter::MULTI_INTERFACE, true,
+        main_runner_);
+    *router1 = MultiplexRouter::CreateAndStartReceiving(
+        std::move(pipe.handle1), MultiplexRouter::MULTI_INTERFACE, false,
+        main_runner_);
   }
 
   void CreateIntegerSenderWithExistingRouters(
diff --git a/mojo/public/cpp/bindings/tests/bindings_perftest.cc b/mojo/public/cpp/bindings/tests/bindings_perftest.cc
index f4dfff31..41f7c26 100644
--- a/mojo/public/cpp/bindings/tests/bindings_perftest.cc
+++ b/mojo/public/cpp/bindings/tests/bindings_perftest.cc
@@ -189,11 +189,11 @@
 TEST_F(MojoBindingsPerftest, MultiplexRouterPingPong) {
   MessagePipe pipe;
   scoped_refptr<internal::MultiplexRouter> router0(
-      internal::MultiplexRouter::Create(
+      internal::MultiplexRouter::CreateAndStartReceiving(
           std::move(pipe.handle0), internal::MultiplexRouter::SINGLE_INTERFACE,
           true, base::ThreadTaskRunnerHandle::Get()));
   scoped_refptr<internal::MultiplexRouter> router1(
-      internal::MultiplexRouter::Create(
+      internal::MultiplexRouter::CreateAndStartReceiving(
           std::move(pipe.handle1), internal::MultiplexRouter::SINGLE_INTERFACE,
           false, base::ThreadTaskRunnerHandle::Get()));
 
diff --git a/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc
index ea697d8..af60dfd 100644
--- a/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc
+++ b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc
@@ -33,12 +33,12 @@
 
   void SetUp() override {
     MessagePipe pipe;
-    router0_ = MultiplexRouter::Create(std::move(pipe.handle0),
-                                       MultiplexRouter::MULTI_INTERFACE, false,
-                                       base::ThreadTaskRunnerHandle::Get());
-    router1_ = MultiplexRouter::Create(std::move(pipe.handle1),
-                                       MultiplexRouter::MULTI_INTERFACE, true,
-                                       base::ThreadTaskRunnerHandle::Get());
+    router0_ = MultiplexRouter::CreateAndStartReceiving(
+        std::move(pipe.handle0), MultiplexRouter::MULTI_INTERFACE, false,
+        base::ThreadTaskRunnerHandle::Get());
+    router1_ = MultiplexRouter::CreateAndStartReceiving(
+        std::move(pipe.handle1), MultiplexRouter::MULTI_INTERFACE, true,
+        base::ThreadTaskRunnerHandle::Get());
     ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(&endpoint0_,
                                                                 &endpoint1_);
     auto id = router0_->AssociateInterface(std::move(endpoint1_));
diff --git a/mojo/public/js/mojo_bindings_resources.grd b/mojo/public/js/mojo_bindings_resources.grd
index cca7888..81073dd 100644
--- a/mojo/public/js/mojo_bindings_resources.grd
+++ b/mojo/public/js/mojo_bindings_resources.grd
@@ -95,6 +95,11 @@
           use_base_dir="false"
           resource_path="mojo/mojo/public/mojom/base/text_direction.mojom-webui.js"
           type="BINDATA" />
+      <include name="IDR_MOJO_TOKEN_MOJOM_WEBUI_JS"
+          file="${root_gen_dir}/mojom-webui/mojo/public/mojom/base/token.mojom-webui.js"
+          use_base_dir="false"
+          resource_path="mojo/mojo/public/mojom/base/token.mojom-webui.js"
+          type="BINDATA" />
       <include name="IDR_MOJO_UNGUESSABLE_TOKEN_MOJOM_WEBUI_JS"
           file="${root_gen_dir}/mojom-webui/mojo/public/mojom/base/unguessable_token.mojom-webui.js"
           use_base_dir="false"
diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc
index 4183288f..ede99f58 100644
--- a/net/cookies/cookie_monster.cc
+++ b/net/cookies/cookie_monster.cc
@@ -619,7 +619,6 @@
     std::sort(cookie_ptrs.begin(), cookie_ptrs.end(), CookieSorter);
 
     included_cookies.reserve(cookie_ptrs.size());
-    std::vector<CanonicalCookie*> included_cookie_ptrs;
     FilterCookiesWithOptions(url, options, &cookie_ptrs, &included_cookies,
                              &excluded_cookies);
   }
diff --git a/net/quic/quic_chromium_client_session.cc b/net/quic/quic_chromium_client_session.cc
index ce53c75..8ef5331 100644
--- a/net/quic/quic_chromium_client_session.cc
+++ b/net/quic/quic_chromium_client_session.cc
@@ -817,6 +817,7 @@
 void QuicChromiumClientSession::ConnectionMigrationValidationResultDelegate::
     OnPathValidationFailure(
         std::unique_ptr<quic::QuicPathValidationContext> context) {
+  session_->connection()->OnPathValidationFailureAtClient();
   // Note that socket, packet writer, and packet reader in |context| will be
   // discarded.
   auto* chrome_context =
@@ -843,6 +844,7 @@
 void QuicChromiumClientSession::PortMigrationValidationResultDelegate::
     OnPathValidationFailure(
         std::unique_ptr<quic::QuicPathValidationContext> context) {
+  session_->connection()->OnPathValidationFailureAtClient();
   // Note that socket, packet writer, and packet reader in |context| will be
   // discarded.
   auto* chrome_context =
@@ -2183,9 +2185,13 @@
                      weak_factory_.GetWeakPtr(), error_code,
                      connection()->writer()));
 
-  // Store packet in the session since the actual migration and packet rewrite
-  // can happen via this posted task or via an async network notification.
-  packet_ = std::move(packet);
+  // Only save packet from the old path for retransmission on the new path when
+  // the connection ID does not change.
+  if (!connection()->connection_migration_use_new_cid()) {
+    // Store packet in the session since the actual migration and packet rewrite
+    // can happen via this posted task or via an async network notification.
+    packet_ = std::move(packet);
+  }
   ignore_read_error_ = true;
 
   // Cause the packet writer to return ERR_IO_PENDING and block so
@@ -2501,7 +2507,7 @@
                                                        /*is_success=*/false);
                     });
 
-  if (version().HasIetfQuicFrames() && connection()->use_path_validator()) {
+  if (connection()->connection_migration_use_new_cid()) {
     auto* context = static_cast<QuicChromiumPathValidationContext*>(
         connection()->GetPathValidationContext());
 
@@ -2580,7 +2586,7 @@
       "disconnected_network", disconnected_network);
 
   // Stop probing the disconnected network if there is one.
-  if (version().HasIetfQuicFrames() && connection()->use_path_validator()) {
+  if (connection()->connection_migration_use_new_cid()) {
     auto* context = static_cast<QuicChromiumPathValidationContext*>(
         connection()->GetPathValidationContext());
     if (context && context->network() == disconnected_network &&
@@ -2706,7 +2712,7 @@
   }
 
   // Cancel probing on |network| if there is any.
-  if (version().HasIetfQuicFrames() && connection()->use_path_validator()) {
+  if (connection()->connection_migration_use_new_cid()) {
     auto* context = static_cast<QuicChromiumPathValidationContext*>(
         connection()->GetPathValidationContext());
     if (context && context->network() == network &&
@@ -3041,7 +3047,8 @@
 
   // Abort probing if connection migration is disabled by config.
   if (config()->DisableConnectionMigration() ||
-      (version().HasIetfQuicFrames() && !connection()->use_path_validator())) {
+      (version().HasIetfQuicFrames() &&
+       !connection()->connection_migration_use_new_cid())) {
     DVLOG(1) << "Client disables probing network with connection migration "
              << "disabled by config";
     HistogramAndLogMigrationFailure(MIGRATION_STATUS_DISABLED_BY_CONFIG,
@@ -3057,7 +3064,7 @@
     NetworkChangeNotifier::NetworkHandle network,
     const quic::QuicSocketAddress& peer_address) {
   // Check if probing manager is probing the same path.
-  if (version().HasIetfQuicFrames() && connection()->use_path_validator()) {
+  if (connection()->connection_migration_use_new_cid()) {
     auto* context = static_cast<QuicChromiumPathValidationContext*>(
         connection()->GetPathValidationContext());
     if (context && context->network() == network &&
@@ -3097,7 +3104,8 @@
     rtt_ms = kDefaultRTTMilliSecs;
   int timeout_ms = rtt_ms * 2;
 
-  if (connection()->use_path_validator() && version().HasIetfQuicFrames()) {
+  if (connection()->connection_migration_use_new_cid() &&
+      version().HasIetfQuicFrames()) {
     probing_reader->StartReading();
     path_validation_writer_delegate_.set_network(network);
     path_validation_writer_delegate_.set_peer_address(peer_address);
@@ -3586,8 +3594,6 @@
   if (!MigrateToSocket(ToQuicSocketAddress(self_address),
                        ToQuicSocketAddress(peer_address), std::move(socket),
                        std::move(new_reader), std::move(new_writer))) {
-    HistogramAndLogMigrationFailure(MIGRATION_STATUS_TOO_MANY_CHANGES,
-                                    connection_id(), "Too many changes");
     if (close_session_on_error) {
       if (migrate_session_on_network_change_v2_) {
         CloseSessionOnErrorLater(
@@ -3619,6 +3625,8 @@
   // write error events, and path degrading on original network.
   if (!migrate_session_on_network_change_v2_ &&
       sockets_.size() >= kMaxReadersPerQuicSession) {
+    HistogramAndLogMigrationFailure(MIGRATION_STATUS_TOO_MANY_CHANGES,
+                                    connection_id(), "Too many changes");
     return false;
   }
 
@@ -3628,8 +3636,14 @@
   // WriteToNewSocket completes.
   DVLOG(1) << "Force blocking the packet writer";
   writer->set_force_write_blocked(true);
-  MigratePath(self_address, peer_address, writer.release(),
-              /*owns_writer=*/true);
+  if (!MigratePath(self_address, peer_address, writer.release(),
+                   /*owns_writer=*/true)) {
+    HistogramAndLogMigrationFailure(MIGRATION_STATUS_NO_UNUSED_CONNECTION_ID,
+                                    connection_id(),
+                                    "No unused server connection ID");
+    DVLOG(1) << "MigratePath fails as there is no CID available";
+    return false;
+  }
 
   // Post task to write the pending packet or a PING packet to the new
   // socket. This avoids reentrancy issues if there is a write error
diff --git a/net/quic/quic_chromium_client_session.h b/net/quic/quic_chromium_client_session.h
index 538ffda..853970a 100644
--- a/net/quic/quic_chromium_client_session.h
+++ b/net/quic/quic_chromium_client_session.h
@@ -121,6 +121,7 @@
   MIGRATION_STATUS_ON_WRITE_ERROR_DISABLED,
   MIGRATION_STATUS_PATH_DEGRADING_BEFORE_HANDSHAKE_CONFIRMED,
   MIGRATION_STATUS_IDLE_MIGRATION_TIMEOUT,
+  MIGRATION_STATUS_NO_UNUSED_CONNECTION_ID,
   MIGRATION_STATUS_MAX
 };
 
diff --git a/net/quic/quic_stream_factory_test.cc b/net/quic/quic_stream_factory_test.cc
index 6e5ea81..2d15f21 100644
--- a/net/quic/quic_stream_factory_test.cc
+++ b/net/quic/quic_stream_factory_test.cc
@@ -244,6 +244,18 @@
         &crypto_client_stream_factory_, &context_);
   }
 
+  void SetIetfConnectionMigrationFlagsAndConnectionOptions() {
+    FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = true;
+    FLAGS_quic_reloadable_flag_quic_send_path_response2 = true;
+    FLAGS_quic_reloadable_flag_quic_use_connection_id_on_default_path_v2 = true;
+    FLAGS_quic_reloadable_flag_quic_server_reverse_validate_new_path3 = true;
+    FLAGS_quic_reloadable_flag_quic_group_path_response_and_challenge_sending_closer =
+        true;
+    FLAGS_quic_reloadable_flag_quic_drop_unsent_path_response = true;
+    FLAGS_quic_reloadable_flag_quic_connection_migration_use_new_cid_v2 = true;
+    quic_params_->connection_options.push_back(quic::kRVCM);
+  }
+
   void InitializeConnectionMigrationV2Test(
       NetworkChangeNotifier::NetworkList connected_networks) {
     scoped_mock_network_change_notifier_ =
@@ -256,11 +268,25 @@
     quic_params_->migrate_sessions_early_v2 = true;
     quic_params_->allow_port_migration = false;
     socket_factory_ = std::make_unique<TestConnectionMigrationSocketFactory>();
-    FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = true;
-    FLAGS_quic_reloadable_flag_quic_send_path_response2 = true;
+    SetIetfConnectionMigrationFlagsAndConnectionOptions();
     Initialize();
   }
 
+  void MaybeMakeNewConnectionIdAvailableToSession(
+      const quic::QuicConnectionId& new_cid,
+      quic::QuicSession* session) {
+    if (version_.HasIetfQuicFrames()) {
+      quic::QuicNewConnectionIdFrame new_cid_frame;
+      new_cid_frame.connection_id = new_cid;
+      new_cid_frame.sequence_number = 1u;
+      new_cid_frame.retire_prior_to = 0u;
+      new_cid_frame.stateless_reset_token =
+          quic::QuicUtils::GenerateStatelessResetToken(
+              new_cid_frame.connection_id);
+      session->connection()->OnNewConnectionIdFrame(new_cid_frame);
+    }
+  }
+
   std::unique_ptr<HttpStream> CreateStream(QuicStreamRequest* request) {
     std::unique_ptr<QuicChromiumClientSession::Handle> session =
         request->ReleaseSessionHandle();
@@ -2697,6 +2723,11 @@
 
   // Set up the second socket data provider that is used after migration.
   // The response to the earlier request is read on the new socket.
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+  }
   MockQuicData quic_data2(version_);
   // Connectivity probe to be sent on the new path.
   quic_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(
@@ -2765,6 +2796,7 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream.
   HttpResponseInfo response;
@@ -2909,6 +2941,11 @@
 
   // Set up the second socket data provider that is used after migration.
   // The response to the earlier request is read on the new socket.
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+  }
   MockQuicData quic_data2(version_);
   // First connectivity probe to be sent on the new path.
   quic_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(
@@ -2930,10 +2967,13 @@
   if (VersionUsesHttp3(version_.transport_version)) {
     quic_data2.AddWrite(ASYNC, client_maker_.MakeAckAndRetransmissionPacket(
                                    packet_num++, 1, 1, 1, {1, 2}));
+    quic_data2.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeAckAndRetireConnectionIdPacket(
+                            packet_num++, true, 2, 1, 0u));
     quic_data2.AddWrite(
-        SYNCHRONOUS, client_maker_.MakeAckAndDataPacket(
-                         packet_num++, false, GetQpackDecoderStreamId(), 2, 2,
-                         false, StreamCancellationQpackDecoderInstruction(0)));
+        SYNCHRONOUS, client_maker_.MakeDataPacket(
+                         packet_num++, GetQpackDecoderStreamId(), false, false,
+                         StreamCancellationQpackDecoderInstruction(0)));
     quic_data2.AddWrite(
         SYNCHRONOUS,
         client_maker_.MakeRstPacket(
@@ -2978,6 +3018,7 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream.
   HttpResponseInfo response;
@@ -3201,6 +3242,13 @@
   }
 
   // Set up the second socket data provider that is used for probing.
+  quic::QuicConnectionId cid_on_old_path =
+      quic::QuicUtils::CreateRandomConnectionId(context_.random_generator());
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+  }
   MockQuicData quic_data1(version_);
   // Connectivity probe to be sent on the new path.
   quic_data1.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(
@@ -3232,7 +3280,15 @@
     // Ping packet to send after migration is completed.
     quic_data1.AddWrite(SYNCHRONOUS,
                         client_maker_.MakePingPacket(packet_num++, false));
+    if (VersionUsesHttp3(version_.transport_version)) {
+      quic_data1.AddWrite(
+          SYNCHRONOUS,
+          client_maker_.MakeRetireConnectionIdPacket(packet_num++, false, 0u));
+    }
   } else {
+    if (VersionUsesHttp3(version_.transport_version)) {
+      client_maker_.set_connection_id(cid_on_old_path);
+    }
     if (version_.UsesTls()) {
       if (VersionUsesHttp3(version_.transport_version)) {
         socket_data.AddWrite(
@@ -3306,6 +3362,7 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Trigger connection migration. Session will start to probe the alternative
   // network. Although there is a non-migratable stream, session will still be
@@ -3421,6 +3478,10 @@
   client_maker_.set_save_packet_frames(true);
 
   MockQuicData failed_socket_data(version_);
+  quic::QuicConnectionId cid_on_old_path =
+      quic::QuicUtils::CreateRandomConnectionId(context_.random_generator());
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
   MockQuicData socket_data(version_);
   if (migrate_idle_sessions) {
     failed_socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
@@ -3444,16 +3505,27 @@
     failed_socket_data.AddSocketDataToFactory(socket_factory_.get());
 
     // Set up second socket data provider that is used after migration.
+    if (VersionUsesHttp3(version_.transport_version)) {
+      client_maker_.set_connection_id(cid_on_new_path);
+    }
     socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
     if (version_.UsesHttp3()) {
       socket_data.AddWrite(SYNCHRONOUS,
                            client_maker_.MakeCombinedRetransmissionPacket(
                                {3, 1, 2}, packet_num++, true));
+      // Ping packet to send after migration.
+      socket_data.AddWrite(
+          SYNCHRONOUS, client_maker_.MakePingPacket(packet_num++,
+                                                    /*include_version=*/false));
+      socket_data.AddWrite(SYNCHRONOUS,
+                           client_maker_.MakeRetireConnectionIdPacket(
+                               packet_num++, /*include_version=*/false, 0u));
+    } else {
+      // Ping packet to send after migration.
+      socket_data.AddWrite(
+          SYNCHRONOUS,
+          client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
     }
-    // Ping packet to send after migration.
-    socket_data.AddWrite(
-        SYNCHRONOUS,
-        client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
     socket_data.AddSocketDataToFactory(socket_factory_.get());
   } else {
     socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
@@ -3499,11 +3571,12 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Trigger connection migration. Since there is a non-migratable stream,
   // this should cause a RST_STREAM frame to be emitted with
   // quic::QUIC_STREAM_CANCELLED error code.
-  // If migate idle session, the connection will then be migrated to the
+  // If migrate idle session, the connection will then be migrated to the
   // alternate network. Otherwise, the connection will be closed.
   scoped_mock_network_change_notifier_->mock_network_change_notifier()
       ->NotifyNetworkDisconnected(kDefaultNetworkForTests);
@@ -3615,8 +3688,13 @@
   }
   socket_data.AddSocketDataToFactory(socket_factory_.get());
 
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
   MockQuicData quic_data1(version_);
   if (migrate_idle_sessions) {
+    if (VersionUsesHttp3(version_.transport_version)) {
+      client_maker_.set_connection_id(cid_on_new_path);
+    }
     // Set up the second socket data provider that is used for probing.
     // Connectivity probe to be sent on the new path.
     quic_data1.AddWrite(
@@ -3632,6 +3710,9 @@
       // already sent on the new address, ping will no longer be sent.
       quic_data1.AddWrite(ASYNC, client_maker_.MakeAckAndRetransmissionPacket(
                                      packet_num++, 1, 1, 1, {1}));
+      quic_data1.AddWrite(
+          SYNCHRONOUS,
+          client_maker_.MakeRetireConnectionIdPacket(packet_num++, false, 0u));
     } else {
       // Ping packet to send after migration is completed.
       quic_data1.AddWrite(
@@ -3658,6 +3739,7 @@
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
   EXPECT_FALSE(session->HasActiveRequestStreams());
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Trigger connection migration.
   scoped_mock_network_change_notifier_->mock_network_change_notifier()
@@ -3703,7 +3785,12 @@
   default_socket_data.AddSocketDataToFactory(socket_factory_.get());
 
   MockQuicData alternate_socket_data(version_);
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
   if (migrate_idle_sessions) {
+    if (VersionUsesHttp3(version_.transport_version)) {
+      client_maker_.set_connection_id(cid_on_new_path);
+    }
     // Set up second socket data provider that is used after migration.
     alternate_socket_data.AddRead(SYNCHRONOUS,
                                   ERR_IO_PENDING);  // Hanging read.
@@ -3711,11 +3798,19 @@
       alternate_socket_data.AddWrite(
           SYNCHRONOUS,
           client_maker_.MakeRetransmissionPacket(1, packet_num++, true));
+      // Ping packet to send after migration.
+      alternate_socket_data.AddWrite(
+          SYNCHRONOUS, client_maker_.MakePingPacket(packet_num++,
+                                                    /*include_version=*/false));
+      alternate_socket_data.AddWrite(
+          SYNCHRONOUS,
+          client_maker_.MakeRetireConnectionIdPacket(packet_num++, true, 0u));
+    } else {
+      // Ping packet to send after migration.
+      alternate_socket_data.AddWrite(
+          SYNCHRONOUS,
+          client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
     }
-    // Ping packet to send after migration.
-    alternate_socket_data.AddWrite(
-        SYNCHRONOUS,
-        client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
     alternate_socket_data.AddSocketDataToFactory(socket_factory_.get());
   }
 
@@ -3733,7 +3828,8 @@
   EXPECT_TRUE(stream.get());
 
   // Ensure that session is active.
-  EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  auto* session = GetActiveSession(host_port_pair_);
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Trigger connection migration. Since there are no active streams,
   // the session will be closed.
@@ -3820,6 +3916,9 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream.
   HttpResponseInfo response;
@@ -3833,14 +3932,24 @@
   // Set up second socket data provider that is used after migration.
   // The response to the earlier request is read on this new socket.
   MockQuicData socket_data1(version_);
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+  }
   if (version_.UsesHttp3()) {
     socket_data1.AddWrite(SYNCHRONOUS,
                           client_maker_.MakeCombinedRetransmissionPacket(
                               {1, 2}, packet_number++, true));
+    socket_data1.AddWrite(
+        SYNCHRONOUS, client_maker_.MakePingPacket(packet_number++,
+                                                  /*include_version=*/false));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRetireConnectionIdPacket(packet_number++, false, 0u));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS, client_maker_.MakePingPacket(packet_number++,
+                                                  /*include_version=*/true));
   }
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakePingPacket(packet_number++, /*include_version=*/true));
   socket_data1.AddRead(
       ASYNC,
       ConstructOkResponsePacket(
@@ -3962,6 +4071,9 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream.
   HttpResponseInfo response;
@@ -3983,14 +4095,24 @@
   // Set up second socket data provider that is used after migration.
   // The response to the earlier request is read on this new socket.
   MockQuicData socket_data1(version_);
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+  }
   if (version_.UsesHttp3()) {
     socket_data1.AddWrite(SYNCHRONOUS,
                           client_maker_.MakeCombinedRetransmissionPacket(
                               {1, 2}, packet_num++, true));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/false));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRetireConnectionIdPacket(packet_num++, false, 0u));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
   }
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
   socket_data1.AddRead(
       ASYNC,
       ConstructOkResponsePacket(
@@ -4092,6 +4214,11 @@
   // Set up the second socket data provider that is used for probing on the
   // alternate network.
   MockQuicData quic_data2(version_);
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+  }
   // Connectivity probe to be sent on the new path.
   quic_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(
                                        packet_number++, true));
@@ -4110,6 +4237,9 @@
   if (version_.UsesHttp3()) {
     quic_data2.AddWrite(ASYNC, client_maker_.MakeAckAndRetransmissionPacket(
                                    packet_number++, 1, 4, 1, {1, 2}));
+    quic_data2.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeRetireConnectionIdPacket(
+                            packet_number++, /*include_version=*/false, 0u));
   } else {
     quic_data2.AddWrite(ASYNC,
                         client_maker_.MakeAckPacket(packet_number++, 4, 1));
@@ -4165,6 +4295,7 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream.
   HttpResponseInfo response;
@@ -4308,6 +4439,11 @@
   // Set up the second socket data provider that is used after migration.
   // The response to the earlier request is read on the new socket.
   MockQuicData quic_data2(version_);
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  if (version_.UsesHttp3()) {
+    client_maker_.set_connection_id(cid_on_new_path);
+  }
   // Connectivity probe to be sent on the new path.
   quic_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(
                                        packet_number++, true));
@@ -4320,6 +4456,8 @@
     // already sent on the new address, ping will no longer be sent.
     quic_data2.AddWrite(ASYNC, client_maker_.MakeAckAndRetransmissionPacket(
                                    packet_number++, 1, 1, 1, {1, 2}));
+    quic_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeRetireConnectionIdPacket(
+                                         packet_number++, true, 0u));
   } else {
     // Ping packet to send after migration is completed.
     quic_data2.AddWrite(ASYNC, client_maker_.MakeAckAndPingPacket(
@@ -4376,6 +4514,7 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream.
   HttpResponseInfo response;
@@ -4509,15 +4648,34 @@
       ConstructOkResponsePacket(
           1, GetNthClientInitiatedBidirectionalStreamId(0), false, true));
   quic_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
-  quic_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Set up the second socket data provider that is used for path validation.
   MockQuicData quic_data2(version_);
+  quic::QuicConnectionId cid_on_old_path =
+      quic::QuicUtils::CreateRandomConnectionId(context_.random_generator());
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+  }
   // Connectivity probe to be sent on the new path.
   quic_data2.AddWrite(SYNCHRONOUS, ERR_ADDRESS_UNREACHABLE);
+  ++packet_number;  // Account for the packet encountering write error.
   quic_data2.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
   quic_data2.AddRead(ASYNC,
                      server_maker_.MakeConnectivityProbingPacket(1, false));
+
+  // Connection ID is retired on the old path.
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_old_path);
+    quic_data1.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeAckAndRetireConnectionIdPacket(
+                            packet_number++, /*include_version=*/false,
+                            /*largest_received=*/1, /*smallest_received=*/1,
+                            /*sequence_number=*/1u));
+  }
+
+  quic_data1.AddSocketDataToFactory(socket_factory_.get());
   quic_data2.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -4546,6 +4704,7 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream.
   HttpResponseInfo response;
@@ -4615,6 +4774,10 @@
   scoped_mock_network_change_notifier_->mock_network_change_notifier()
       ->QueueNetworkMadeDefault(kDefaultNetworkForTests);
 
+  quic::QuicConnectionId cid_on_path1 =
+      quic::QuicUtils::CreateRandomConnectionId(context_.random_generator());
+  quic::QuicConnectionId cid_on_path2 = quic::test::TestConnectionId(12345678);
+
   int packet_number = 1;
   MockQuicData quic_data1(version_);
   quic_data1.AddWrite(SYNCHRONOUS,
@@ -4630,26 +4793,34 @@
       ConstructOkResponsePacket(
           1, GetNthClientInitiatedBidirectionalStreamId(0), false, true));
   quic_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
-  quic_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Set up the second socket data provider that is used for path validation.
   MockQuicData quic_data2(version_);
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_path2);
+  }
   // Connectivity probe to be sent on the new path.
   quic_data2.AddWrite(SYNCHRONOUS, ERR_ADDRESS_UNREACHABLE);
   quic_data2.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
   quic_data2.AddRead(ASYNC,
                      server_maker_.MakeConnectivityProbingPacket(1, false));
-  quic_data2.AddSocketDataToFactory(socket_factory_.get());
+  packet_number++;  // Account for packet encountering write error.
 
-  packet_number++;
+  // Connection ID is retired on the old path.
+  client_maker_.set_connection_id(cid_on_path1);
+  quic_data1.AddWrite(SYNCHRONOUS,
+                      client_maker_.MakeAckAndRetireConnectionIdPacket(
+                          packet_number++, /*include_version=*/false,
+                          /*largest_received=*/1, /*smallest_received=*/1,
+                          /*sequence_number=*/1u));
 
+  // A socket will be created for a new path, but there would be no write
+  // due to lack of new connection ID.
   MockQuicData quic_data3(version_);
-  // Connectivity probe to be sent on the new path.
-  quic_data3.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(
-                                       packet_number++, true));
-  quic_data3.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
-  quic_data3.AddRead(ASYNC,
-                     server_maker_.MakeConnectivityProbingPacket(1, false));
+  quic_data3.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Pause
+
+  quic_data1.AddSocketDataToFactory(socket_factory_.get());
+  quic_data2.AddSocketDataToFactory(socket_factory_.get());
   quic_data3.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -4678,6 +4849,7 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_path2, session);
 
   // Send GET request on stream.
   HttpResponseInfo response;
@@ -4711,10 +4883,10 @@
   base::TimeDelta next_task_delay = task_runner->NextPendingTaskDelay();
   EXPECT_EQ(base::TimeDelta(), next_task_delay);
   task_runner->FastForwardBy(next_task_delay);
-  // Verify that the task is executed, but since a new path validation is under
-  // way, it won't be cancelled.
+  // Verify that the task is executed.
   EXPECT_EQ(0u, task_runner->GetPendingTaskCount());
-  EXPECT_TRUE(session->connection()->HasPendingPathValidation());
+  // No pending path validation as there is no connection ID available.
+  EXPECT_FALSE(session->connection()->HasPendingPathValidation());
 
   EXPECT_EQ(1u, QuicStreamFactoryPeer::GetNumDegradingSessions(factory_.get()));
   quic_data1.Resume();
@@ -4739,8 +4911,7 @@
     return;
   }
   quic_params_->allow_port_migration = true;
-  FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = true;
-  FLAGS_quic_reloadable_flag_quic_send_path_response2 = true;
+  SetIetfConnectionMigrationFlagsAndConnectionOptions();
   socket_factory_ = std::make_unique<TestPortMigrationSocketFactory>();
   Initialize();
 
@@ -4905,8 +5076,7 @@
     return;
   }
   quic_params_->allow_port_migration = true;
-  FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = true;
-  FLAGS_quic_reloadable_flag_quic_send_path_response2 = true;
+  SetIetfConnectionMigrationFlagsAndConnectionOptions();
   socket_factory_ = std::make_unique<TestPortMigrationSocketFactory>();
   Initialize();
   ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
@@ -4940,6 +5110,11 @@
 
   // Set up the second socket data provider that is used for migration probing.
   MockQuicData quic_data2(version_);
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+  }
   // Connectivity probe to be sent on the new path.
   quic_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(
                                        packet_number, true));
@@ -4974,6 +5149,7 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream.
   HttpResponseInfo response;
@@ -5032,8 +5208,7 @@
   mock_ncn->ForceNetworkHandlesSupported();
   mock_ncn->SetConnectedNetworksList({kDefaultNetworkForTests});
   quic_params_->allow_port_migration = true;
-  FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = true;
-  FLAGS_quic_reloadable_flag_quic_send_path_response2 = true;
+  SetIetfConnectionMigrationFlagsAndConnectionOptions();
   socket_factory_ = std::make_unique<TestPortMigrationSocketFactory>();
   Initialize();
 
@@ -5104,8 +5279,7 @@
   // Enable migration on network change.
   quic_params_->migrate_sessions_on_network_change_v2 = true;
   quic_params_->allow_port_migration = true;
-  FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator = true;
-  FLAGS_quic_reloadable_flag_quic_send_path_response2 = true;
+  SetIetfConnectionMigrationFlagsAndConnectionOptions();
   socket_factory_ = std::make_unique<TestPortMigrationSocketFactory>();
   Initialize();
 
@@ -5141,6 +5315,12 @@
   // Set up the second socket data provider that is used after migration.
   // The response to the earlier request is read on the new socket.
   MockQuicData quic_data2(version_);
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  if (VersionUsesHttp3(version_.transport_version) &&
+      FLAGS_quic_reloadable_flag_quic_connection_migration_use_new_cid_v2) {
+    client_maker_.set_connection_id(cid_on_new_path);
+  }
   // Connectivity probe to be sent on the new path.
   quic_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(
                                        packet_number++, true));
@@ -5157,11 +5337,27 @@
           2, GetNthClientInitiatedBidirectionalStreamId(0), false, false));
   quic_data2.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   if (VersionUsesHttp3(version_.transport_version)) {
-    quic_data2.AddWrite(
-        SYNCHRONOUS,
-        client_maker_.MakeAckAndDataPacket(
-            packet_number++, false, GetQpackDecoderStreamId(), 2, 2, false,
-            StreamCancellationQpackDecoderInstruction(0)));
+    if (FLAGS_quic_reloadable_flag_quic_connection_migration_use_new_cid_v2) {
+      quic_data2.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeAckAndRetireConnectionIdPacket(
+                              packet_number++, /*include_version=*/false,
+                              /*largest_received=*/2,
+                              /*smallest_received=*/1, /*sequence_number=*/0u));
+      quic_data2.AddWrite(
+          SYNCHRONOUS,
+          client_maker_.MakeDataPacket(
+              packet_number++, GetQpackDecoderStreamId(),
+              /*should_include_version=*/false,
+              /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
+    } else {
+      quic_data2.AddWrite(
+          SYNCHRONOUS,
+          client_maker_.MakeAckAndDataPacket(
+              packet_number++, /*include_version=*/false,
+              GetQpackDecoderStreamId(),
+              /*largest_received=*/2, /*smallest_received=*/2, /*fin=*/false,
+              StreamCancellationQpackDecoderInstruction(0)));
+    }
     quic_data2.AddWrite(SYNCHRONOUS,
                         client_maker_.MakeRstPacket(
                             packet_number++, false,
@@ -5202,6 +5398,7 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream.
   HttpResponseInfo response;
@@ -5270,6 +5467,9 @@
   EXPECT_EQ(base::TimeDelta(), next_task_delay);
   task_runner->FastForwardBy(next_task_delay);
 
+  // Fire any outstanding quic alarms.
+  base::RunLoop().RunUntilIdle();
+
   // Response headers are received over the new port.
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
   EXPECT_EQ(200, response.headers->response_code());
@@ -5776,6 +5976,11 @@
 
   // Set up the second socket data provider that is used after migration.
   MockQuicData quic_data2(version_);
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+  }
   // Connectivity probe to be sent on the new path.
   quic_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(
                                        packet_number++, false));
@@ -5796,6 +6001,10 @@
     quic_data2.AddWrite(ASYNC,
                         client_maker_.MakePingPacket(packet_number++, false));
   }
+  if (version_.UsesHttp3()) {
+    quic_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeRetireConnectionIdPacket(
+                                         packet_number++, false, 0u));
+  }
   server_maker_.Reset();
   quic_data2.AddRead(
       ASYNC,
@@ -5832,6 +6041,7 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream.
   HttpResponseInfo response;
@@ -5957,6 +6167,11 @@
   // Set up the second socket data provider that is used after migration.
   // The response to the earlier request is read on the new socket.
   MockQuicData quic_data2(version_);
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+  }
   // Connectivity probe to be sent on the new path.
   quic_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(
                                        packet_num++, true));
@@ -5969,6 +6184,8 @@
     // already sent on the new address, ping will no longer be sent.
     quic_data2.AddWrite(ASYNC, client_maker_.MakeAckAndRetransmissionPacket(
                                    packet_num++, 1, 1, 1, {1, 2}));
+    quic_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeRetireConnectionIdPacket(
+                                         packet_num++, false, 0u));
   } else {
     // Ping packet to send after migration is completed.
     quic_data2.AddWrite(
@@ -6024,6 +6241,7 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream.
   HttpResponseInfo response;
@@ -6392,6 +6610,13 @@
 
   // Set up the second socket data provider that is used for probing.
   MockQuicData quic_data1(version_);
+  quic::QuicConnectionId cid_on_old_path =
+      quic::QuicUtils::CreateRandomConnectionId(context_.random_generator());
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+  }
   // Connectivity probe to be sent on the new path.
   quic_data1.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(
                                        packet_num++, true));
@@ -6422,8 +6647,15 @@
     // Ping packet to send after migration is completed.
     quic_data1.AddWrite(SYNCHRONOUS,
                         client_maker_.MakePingPacket(packet_num++, false));
-
+    if (VersionUsesHttp3(version_.transport_version)) {
+      quic_data1.AddWrite(
+          SYNCHRONOUS,
+          client_maker_.MakeRetireConnectionIdPacket(packet_num++, false, 0u));
+    }
   } else {
+    if (VersionUsesHttp3(version_.transport_version)) {
+      client_maker_.set_connection_id(cid_on_old_path);
+    }
     if (version_.UsesTls()) {
       if (VersionUsesHttp3(version_.transport_version)) {
         socket_data.AddWrite(
@@ -6497,6 +6729,7 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Trigger connection migration. Since there is a non-migratable stream,
   // this should cause session to migrate.
@@ -6630,12 +6863,15 @@
   // migration. The request is rewritten to this new socket, and the
   // response to the request is read on this new socket.
   MockQuicData socket_data1(version_);
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      ConstructGetRequestPacket(packet_num++,
-                                GetNthClientInitiatedBidirectionalStreamId(0),
-                                true, true));
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+  }
   if (version_.UsesHttp3()) {
+    ConstructGetRequestPacket(packet_num++,
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              true, true);
     spdy::Http2HeaderBlock headers =
         client_maker_.GetRequestHeaders("GET", "https", "/");
     spdy::SpdyPriority priority =
@@ -6648,9 +6884,21 @@
             true, true, priority, std::move(headers),
             GetNthClientInitiatedBidirectionalStreamId(0),
             &spdy_headers_frame_len));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/false));
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeRetireConnectionIdPacket(
+                              packet_num++, /*include_version=*/false,
+                              /*sequence_number=*/0u));
   } else {
     socket_data1.AddWrite(
         SYNCHRONOUS,
+        ConstructGetRequestPacket(packet_num++,
+                                  GetNthClientInitiatedBidirectionalStreamId(0),
+                                  true, true));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
         ConstructGetRequestPacket(
             packet_num++, GetNthClientInitiatedBidirectionalStreamId(1),
             GetNthClientInitiatedBidirectionalStreamId(0), true, true));
@@ -6743,6 +6991,7 @@
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
   EXPECT_EQ(2u, session->GetNumActiveStreams());
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream1. This should cause an async write error.
   HttpResponseInfo response;
@@ -6766,6 +7015,8 @@
 
   // Run the task runner so that migration on write error is finally executed.
   task_runner->RunUntilIdle();
+  // Fire the retire connection ID alarm.
+  base::RunLoop().RunUntilIdle();
 
   // Verify the session is still alive and not marked as going away.
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
@@ -6814,6 +7065,7 @@
   MockQuicData socket_data(version_);
   socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   int packet_num = 1;
+  int peer_packet_num = 1;
   if (VersionUsesHttp3(version_.transport_version)) {
     socket_data.AddWrite(SYNCHRONOUS,
                          ConstructInitialSettingsPacket(packet_num++));
@@ -6825,20 +7077,44 @@
   // migration. The request is rewritten to this new socket, and the
   // response to the request is read on this new socket.
   MockQuicData quic_data2(version_);
-  quic_data2.AddWrite(
-      SYNCHRONOUS,
-      ConstructGetRequestPacket(packet_num++,
-                                GetNthClientInitiatedBidirectionalStreamId(0),
-                                true, true));
-  if (version_.UsesHttp3()) {
+  quic::QuicConnectionId cid1 = quic::test::TestConnectionId(12345678);
+  quic::QuicConnectionId cid2 = quic::test::TestConnectionId(87654321);
+  if (!version_.UsesHttp3()) {
+    quic_data2.AddWrite(
+        SYNCHRONOUS,
+        ConstructGetRequestPacket(packet_num++,
+                                  GetNthClientInitiatedBidirectionalStreamId(0),
+                                  /*should_include_version=*/true,
+                                  /*fin=*/true));
+  } else {
+    client_maker_.set_connection_id(cid1);
+    // Increment packet number to account for packet write error on the old
+    // path. Also save the packet in client_maker_ for constructing the
+    // retransmission packet.
+    ConstructGetRequestPacket(packet_num++,
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              /*should_include_version=*/false,
+                              /*fin=*/true);
     quic_data2.AddWrite(SYNCHRONOUS,
                         client_maker_.MakeCombinedRetransmissionPacket(
-                            {1, 2}, packet_num++, true));
+                            /*original_packet_numbers=*/{1, 2}, packet_num++,
+                            /*should_include_version=*/false));
+    quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/false));
+    quic_data2.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/false, /*sequence_number=*/0u));
+    quic_data2.AddRead(ASYNC, server_maker_.MakeAckAndNewConnectionIdPacket(
+                                  peer_packet_num++, false, packet_num - 1, 1u,
+                                  cid2, /*sequence_number=*/2u,
+                                  /*retire_prior_to=*/1u));
   }
   quic_data2.AddRead(
-      ASYNC,
-      ConstructOkResponsePacket(
-          1, GetNthClientInitiatedBidirectionalStreamId(0), false, false));
+      ASYNC, ConstructOkResponsePacket(
+                 peer_packet_num++,
+                 GetNthClientInitiatedBidirectionalStreamId(0), false, false));
   quic_data2.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   quic_data2.AddSocketDataToFactory(socket_factory_.get());
 
@@ -6869,6 +7145,7 @@
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
   EXPECT_EQ(1u, session->GetNumActiveStreams());
+  MaybeMakeNewConnectionIdAvailableToSession(cid1, session);
 
   // Send GET request. This should cause an async write error.
   HttpResponseInfo response;
@@ -6885,6 +7162,8 @@
 
   // Run the task runner so that migration on write error is finally executed.
   task_runner->RunUntilIdle();
+  // Make sure the alarm that retires connection ID on the old path is fired.
+  base::RunLoop().RunUntilIdle();
 
   // Verify the session is still alive and not marked as going away.
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
@@ -6904,18 +7183,23 @@
 
   // Set up the third socket data provider for migrate back to default network.
   MockQuicData quic_data3(version_);
+  if (version_.UsesHttp3()) {
+    client_maker_.set_connection_id(cid2);
+  }
   // Connectivity probe to be sent on the new path.
   quic_data3.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(
                                        packet_num++, false));
   // Connectivity probe to receive from the server.
-  quic_data3.AddRead(ASYNC,
-                     server_maker_.MakeConnectivityProbingPacket(2, false));
+  quic_data3.AddRead(ASYNC, server_maker_.MakeConnectivityProbingPacket(
+                                peer_packet_num++, /*include_version=*/false));
   quic_data3.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   if (version_.UsesHttp3()) {
-    // Data sent to the old address was in-flight and thus will be
-    // retransmistted.
-    quic_data3.AddWrite(ASYNC, client_maker_.MakeAckAndRetransmissionPacket(
-                                   packet_num++, 1, 2, 1, {1, 2}));
+    // There is no other data to retransmit as they have been acknowledged by
+    // the packet containing NEW_CONNECTION_ID frame from the server.
+    quic_data3.AddWrite(ASYNC, client_maker_.MakeAckPacket(
+                                   packet_num++, /*first_received=*/1,
+                                   /*largest_received=*/peer_packet_num - 1,
+                                   /*smallest_received=*/1));
   } else {
     quic_data3.AddWrite(ASYNC,
                         client_maker_.MakeAckPacket(packet_num++, 1, 2, 1));
@@ -7147,8 +7431,9 @@
   socket_data2.AddRead(ASYNC, ERR_IO_PENDING);  // Pause.
   // Change the encryption level after handshake is confirmed.
   client_maker_.SetEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE);
-  if (VersionUsesHttp3(version_.transport_version))
+  if (VersionUsesHttp3(version_.transport_version)) {
     socket_data2.AddWrite(ASYNC, ConstructInitialSettingsPacket(packet_num++));
+  }
   socket_data2.AddWrite(
       ASYNC, ConstructGetRequestPacket(
                  packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
@@ -7158,14 +7443,19 @@
       ConstructOkResponsePacket(
           1, GetNthClientInitiatedBidirectionalStreamId(0), false, false));
   socket_data2.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
-
   int probing_packet_num = packet_num++;
-
   if (VersionUsesHttp3(version_.transport_version)) {
-    socket_data2.AddWrite(
-        SYNCHRONOUS, client_maker_.MakeAckAndDataPacket(
-                         packet_num++, false, GetQpackDecoderStreamId(), 1, 1,
-                         false, StreamCancellationQpackDecoderInstruction(0)));
+    socket_data2.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeAckAndRetireConnectionIdPacket(
+                              packet_num++, /*include_version=*/false,
+                              /*largest_received=*/1u,
+                              /*smallest_received=*/1u,
+                              /*sequence_number=*/1u));
+    socket_data2.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeDataPacket(
+                              packet_num++, GetQpackDecoderStreamId(),
+                              /*should_include_version=*/false, /*fin=*/false,
+                              StreamCancellationQpackDecoderInstruction(0)));
     socket_data2.AddWrite(
         SYNCHRONOUS,
         client_maker_.MakeRstPacket(
@@ -7182,6 +7472,10 @@
 
   // Socket data for probing on the default network.
   MockQuicData probing_data(version_);
+  quic::QuicConnectionId cid_on_path1 = quic::test::TestConnectionId(1234567);
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_path1);
+  }
   probing_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
   probing_data.AddWrite(
       SYNCHRONOUS,
@@ -7233,6 +7527,7 @@
       ->NotifySessionOneRttKeyAvailable();
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_path1, session2);
   // Resume the data now so that data can be sent and read.
   socket_data2.Resume();
 
@@ -7521,24 +7816,46 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Set up second socket data provider that is used after
   // migration. The request is rewritten to this new socket, and the
   // response to the request is read on this new socket.
   MockQuicData socket_data1(version_);
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      ConstructGetRequestPacket(packet_num++,
-                                GetNthClientInitiatedBidirectionalStreamId(0),
-                                true, true));
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+    // Increment packet number to account for packet write error on the old
+    // path. Also save the packet in client_maker_ for constructing the
+    // retransmission packet.
+    ConstructGetRequestPacket(packet_num++,
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              /*should_include_version=*/false,
+                              /*fin=*/true);
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeCombinedRetransmissionPacket(
+                              /*original_packet_numbers=*/{1, 2}, packet_num++,
+                              /*should_include_version=*/false));
+    socket_data1.AddWrite(ASYNC, client_maker_.MakePingPacket(
+                                     packet_num++, /*include_version=*/false));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/false, /*sequence_number=*/0u));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        ConstructGetRequestPacket(packet_num++,
+                                  GetNthClientInitiatedBidirectionalStreamId(0),
+                                  true, true));
+  }
   socket_data1.AddRead(
       ASYNC,
       ConstructOkResponsePacket(
           1, GetNthClientInitiatedBidirectionalStreamId(0), false, false));
   socket_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   if (VersionUsesHttp3(version_.transport_version)) {
-    socket_data1.AddWrite(ASYNC, client_maker_.MakeCombinedRetransmissionPacket(
-                                     {1, 2}, packet_num++, false));
     socket_data1.AddWrite(
         SYNCHRONOUS, client_maker_.MakeAckAndDataPacket(
                          packet_num++, false, GetQpackDecoderStreamId(), 1, 1,
@@ -7568,7 +7885,7 @@
   // data queued in the new socket is read by the packet reader.
   base::RunLoop().RunUntilIdle();
 
-  // Verify that session is alive and not marked as going awya.
+  // Verify that session is alive and not marked as going away.
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
   EXPECT_EQ(1u, session->GetNumActiveStreams());
@@ -7730,19 +8047,42 @@
   // migration. The request is rewritten to this new socket, and the
   // response to the request is read on this new socket.
   MockQuicData socket_data1(version_);
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      ConstructGetRequestPacket(packet_num++,
-                                GetNthClientInitiatedBidirectionalStreamId(0),
-                                true, true));
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+    // Increment packet number to account for packet write error on the old
+    // path. Also save the packet in client_maker_ for constructing the
+    // retransmission packet.
+    ConstructGetRequestPacket(packet_num++,
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              /*should_include_version=*/false,
+                              /*fin=*/true);
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeCombinedRetransmissionPacket(
+                              /*original_packet_numbers=*/{1, 2}, packet_num++,
+                              /*should_include_version=*/false));
+    socket_data1.AddWrite(
+        SYNCHRONOUS, client_maker_.MakePingPacket(packet_num++,
+                                                  /*include_version=*/false));
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeRetireConnectionIdPacket(
+                              packet_num++, /*include_version=*/false,
+                              /*sequence_number=*/0u));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        ConstructGetRequestPacket(packet_num++,
+                                  GetNthClientInitiatedBidirectionalStreamId(0),
+                                  /*should_include_version=*/true,
+                                  /*fin=*/true));
+  }
   socket_data1.AddRead(
       ASYNC,
       ConstructOkResponsePacket(
           1, GetNthClientInitiatedBidirectionalStreamId(0), false, false));
   socket_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   if (VersionUsesHttp3(version_.transport_version)) {
-    socket_data1.AddWrite(ASYNC, client_maker_.MakeCombinedRetransmissionPacket(
-                                     {1, 2}, packet_num++, false));
     socket_data1.AddWrite(
         SYNCHRONOUS, client_maker_.MakeAckAndDataPacket(
                          packet_num++, false, GetQpackDecoderStreamId(), 1, 1,
@@ -7823,6 +8163,7 @@
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
   EXPECT_EQ(2u, session->GetNumActiveStreams());
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream. This should cause a write error, which triggers
   // a connection migration attempt.
@@ -7892,19 +8233,38 @@
   // migration. The request is rewritten to this new socket, and the
   // response to the request is read on this new socket.
   MockQuicData socket_data1(version_);
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      ConstructGetRequestPacket(packet_number++,
-                                GetNthClientInitiatedBidirectionalStreamId(0),
-                                true, true));
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(1234567);
   if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+    // Increment packet number to account for packet write error on the old
+    // path. Also save the packet in client_maker_ for constructing the
+    // retransmission packet.
+    ConstructGetRequestPacket(packet_number++,
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              /*should_include_version=*/false,
+                              /*fin=*/true);
     socket_data1.AddWrite(
         SYNCHRONOUS, client_maker_.MakeRetransmissionRstAndDataPacket(
-                         {1, 2}, packet_number++, true,
+                         /*original_packet_numbers=*/{1, 2}, packet_number++,
+                         /*include_version=*/false,
                          GetNthClientInitiatedBidirectionalStreamId(1),
                          quic::QUIC_STREAM_CANCELLED, GetQpackDecoderStreamId(),
                          StreamCancellationQpackDecoderInstruction(1)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS, client_maker_.MakePingPacket(packet_number++,
+                                                  /*include_version=*/false));
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeRetireConnectionIdPacket(
+                              packet_number++, /*include_version=*/false,
+                              /*sequence_number=*/0u));
   } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        ConstructGetRequestPacket(packet_number++,
+                                  GetNthClientInitiatedBidirectionalStreamId(0),
+                                  /*should_include_version=*/true,
+                                  /*fin=*/true));
     socket_data1.AddWrite(SYNCHRONOUS,
                           client_maker_.MakeRstPacket(
                               packet_number++, true,
@@ -7987,6 +8347,7 @@
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
   EXPECT_EQ(2u, session->GetNumActiveStreams());
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream 1. This should cause a write error, which
   // triggers a connection migration attempt.
@@ -8053,25 +8414,44 @@
                        ERR_ADDRESS_UNREACHABLE);  // Write error.
   socket_data.AddSocketDataToFactory(socket_factory_.get());
 
-  // Set up second socket data provider that is used after
-  // migration. The request is rewritten to this new socket, and the
-  // response to the request is read on this new socket.
+  // Set up second socket data provider that is used after migration. The
+  // request is rewritten to this new socket, and the response to the request is
+  // read on this new socket.
   MockQuicData socket_data1(version_);
-  // The packet triggered writer error will be sent anyway even if the stream
-  // will be cancelled later.
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      ConstructGetRequestPacket(packet_number++,
-                                GetNthClientInitiatedBidirectionalStreamId(1),
-                                true, true));
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
   if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+    // Increment packet number to account for packet write error on the old
+    // path. Also save the packet in client_maker_ for constructing the
+    // retransmission packet.
+    ConstructGetRequestPacket(packet_number++,
+                              GetNthClientInitiatedBidirectionalStreamId(1),
+                              /*should_include_version=*/false,
+                              /*fin=*/true);
     socket_data1.AddWrite(
         SYNCHRONOUS, client_maker_.MakeRetransmissionRstAndDataPacket(
-                         {1}, packet_number++, true,
+                         /*original_packet_numbers=*/{1}, packet_number++,
+                         /*include_version=*/false,
                          GetNthClientInitiatedBidirectionalStreamId(1),
                          quic::QUIC_STREAM_CANCELLED, GetQpackDecoderStreamId(),
                          StreamCancellationQpackDecoderInstruction(1)));
+    socket_data1.AddWrite(
+        SYNCHRONOUS, client_maker_.MakePingPacket(packet_number++,
+                                                  /*include_version=*/false));
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeRetireConnectionIdPacket(
+                              packet_number++, /*include_version=*/false,
+                              /*sequence_number=*/0u));
   } else {
+    // The packet triggered writer error will be sent anyway even if the stream
+    // will be cancelled later.
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        ConstructGetRequestPacket(packet_number++,
+                                  GetNthClientInitiatedBidirectionalStreamId(1),
+                                  /*should_include_version=*/true,
+                                  /*fin=*/true));
     socket_data1.AddWrite(SYNCHRONOUS,
                           client_maker_.MakeRstPacket(
                               packet_number++, true,
@@ -8159,6 +8539,7 @@
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
   EXPECT_EQ(2u, session->GetNumActiveStreams());
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream 2 which is non-migratable. This should cause a
   // write error, which triggers a connection migration attempt.
@@ -8214,6 +8595,8 @@
 
   MockQuicData failed_socket_data(version_);
   MockQuicData socket_data(version_);
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
   int packet_num = 1;
   if (migrate_idle_sessions) {
     // The socket data provider for the original socket before migration.
@@ -8227,24 +8610,42 @@
 
     // Set up second socket data provider that is used after migration.
     socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
-    // Although the write error occurs when writing a packet for the
-    // non-migratable stream and the stream will be cancelled during migration,
-    // the packet will still be retransimitted at the connection level.
-    socket_data.AddWrite(
-        SYNCHRONOUS,
-        ConstructGetRequestPacket(packet_num++,
-                                  GetNthClientInitiatedBidirectionalStreamId(0),
-                                  true, true));
-    // A RESET will be sent to the peer to cancel the non-migratable stream.
     if (VersionUsesHttp3(version_.transport_version)) {
+      client_maker_.set_connection_id(cid_on_new_path);
+      // Increment packet number to account for packet write error on the old
+      // path. Also save the packet in client_maker_ for constructing the
+      // retransmission packet.
+      ConstructGetRequestPacket(packet_num++,
+                                GetNthClientInitiatedBidirectionalStreamId(0),
+                                /*should_include_version=*/false,
+                                /*fin=*/true);
       socket_data.AddWrite(
           SYNCHRONOUS,
           client_maker_.MakeRetransmissionRstAndDataPacket(
-              {1}, packet_num++, true,
+              /*original_packet_numbers=*/{1}, packet_num++,
+              /*include_version=*/false,
               GetNthClientInitiatedBidirectionalStreamId(0),
               quic::QUIC_STREAM_CANCELLED, GetQpackDecoderStreamId(),
               StreamCancellationQpackDecoderInstruction(0)));
+      socket_data.AddWrite(
+          SYNCHRONOUS, client_maker_.MakePingPacket(packet_num++,
+                                                    /*include_version=*/false));
+      socket_data.AddWrite(SYNCHRONOUS,
+                           client_maker_.MakeRetireConnectionIdPacket(
+                               packet_num++, /*include_version=*/false,
+                               /*sequence_number=*/0u));
     } else {
+      // Although the write error occurs when writing a packet for the
+      // non-migratable stream and the stream will be cancelled during
+      // migration, the packet will still be retransimitted at the connection
+      // level.
+      socket_data.AddWrite(
+          SYNCHRONOUS,
+          ConstructGetRequestPacket(
+              packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+              /*should_include_version=*/true,
+              /*fin=*/true));
+      // A RESET will be sent to the peer to cancel the non-migratable stream.
       socket_data.AddWrite(
           SYNCHRONOUS,
           client_maker_.MakeRstPacket(
@@ -8287,6 +8688,7 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream. This should cause a write error, which triggers
   // a connection migration attempt.
@@ -8406,9 +8808,21 @@
   TestMigrationOnWriteErrorMigrationDisabled(ASYNC);
 }
 
-// Sets up a test which verifies that connection migration on write error can
-// eventually succeed and rewrite the packet on the new network with singals
-// delivered in the following order (alternate network is always availabe):
+// For IETF QUIC, this test the following scenario:
+// - original network encounters a SYNC/ASYNC write error based on
+//   |write_error_mode_on_old_network|, the packet failed to be written is
+//   cached, session migrates immediately to the alternate network.
+// - an immediate SYNC/ASYNC write error based on
+//   |write_error_mode_on_new_network| is encountered after migration to the
+//   alternate network, session migrates immediately to the original network.
+// - After a new socket for the original network is created and starts to read,
+//   connection migration fails due to lack of unused connection ID and
+//   connection is closed.
+//
+// For gQUIC, sets up a test which verifies that connection migration on write
+// error can eventually succeed and rewrite the packet on the new network with
+// signals delivered in the following order (alternate network is always
+// available):
 // - original network encounters a SYNC/ASYNC write error based on
 //   |write_error_mode_on_old_network|, the packet failed to be written is
 //   cached, session migrates immediately to the alternate network.
@@ -8446,54 +8860,47 @@
                         ERR_ADDRESS_UNREACHABLE);  // Write Error
   socket_data1.AddSocketDataToFactory(socket_factory_.get());
 
-  // Set up the socket data used by the alternate network, which also
-  // encounters a write error.
+  // Set up the socket data used by the alternate network, which
+  // - is not used to write as migration fails due to lack of connection ID.
+  // - encounters a write error in gQUIC.
   MockQuicData failed_quic_data2(version_);
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
   failed_quic_data2.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   failed_quic_data2.AddWrite(write_error_mode_on_new_network, ERR_FAILED);
   failed_quic_data2.AddSocketDataToFactory(socket_factory_.get());
 
-  // Set up the third socket data used by original network, which encounters a
-  // write error again.
+  // Set up the third socket data used by original network, which
+  // - encounters a write error again.
   MockQuicData failed_quic_data1(version_);
   failed_quic_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
-  failed_quic_data1.AddWrite(write_error_mode_on_old_network, ERR_FAILED);
+  if (!VersionUsesHttp3(version_.transport_version)) {
+    failed_quic_data1.AddWrite(write_error_mode_on_old_network, ERR_FAILED);
+  }
   failed_quic_data1.AddSocketDataToFactory(socket_factory_.get());
 
-  // Set up the last socket data used by the alternate network, which will
-  // finish migration successfully. The request is rewritten to this new socket,
-  // and the response to the request is read on this socket.
   MockQuicData socket_data2(version_);
-  socket_data2.AddWrite(
-      SYNCHRONOUS,
-      ConstructGetRequestPacket(packet_num++,
-                                GetNthClientInitiatedBidirectionalStreamId(0),
-                                true, true));
-  socket_data2.AddRead(
-      ASYNC,
-      ConstructOkResponsePacket(
-          1, GetNthClientInitiatedBidirectionalStreamId(0), false, false));
-  socket_data2.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
-  if (VersionUsesHttp3(version_.transport_version)) {
-    socket_data2.AddWrite(ASYNC, client_maker_.MakeCombinedRetransmissionPacket(
-                                     {1, 2}, packet_num++, false));
-    socket_data2.AddWrite(
-        SYNCHRONOUS, client_maker_.MakeAckAndDataPacket(
-                         packet_num++, false, GetQpackDecoderStreamId(), 1, 1,
-                         false, StreamCancellationQpackDecoderInstruction(0)));
+  if (!VersionUsesHttp3(version_.transport_version)) {
+    // Set up the last socket data used by the alternate network, which will
+    // finish migration successfully. The request is rewritten to this new
+    // socket, and the response to the request is read on this socket.
     socket_data2.AddWrite(
         SYNCHRONOUS,
-        client_maker_.MakeRstPacket(
-            packet_num++, false, GetNthClientInitiatedBidirectionalStreamId(0),
-            quic::QUIC_STREAM_CANCELLED));
-  } else {
+        ConstructGetRequestPacket(packet_num++,
+                                  GetNthClientInitiatedBidirectionalStreamId(0),
+                                  true, true));
+    socket_data2.AddRead(
+        ASYNC,
+        ConstructOkResponsePacket(
+            1, GetNthClientInitiatedBidirectionalStreamId(0), false, false));
+    socket_data2.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
     socket_data2.AddWrite(
         SYNCHRONOUS,
         client_maker_.MakeAckAndRstPacket(
             packet_num++, false, GetNthClientInitiatedBidirectionalStreamId(0),
             quic::QUIC_STREAM_CANCELLED, 1, 1));
+    socket_data2.AddSocketDataToFactory(socket_factory_.get());
   }
-  socket_data2.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
   QuicStreamRequest request(factory_.get());
@@ -8521,35 +8928,42 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream.
   // This should encounter a write error on network 1,
   // then migrate to network 2, which encounters another write error,
   // and migrate again to network 1, which encoutners one more write error.
-  // Finally the session migrates to network 2 successfully.
   HttpResponseInfo response;
   HttpRequestHeaders request_headers;
   EXPECT_EQ(OK, stream->SendRequest(request_headers, &response,
                                     callback_.callback()));
 
   base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
-  EXPECT_EQ(1u, session->GetNumActiveStreams());
+  if (VersionUsesHttp3(version_.transport_version)) {
+    // Connection is closed as there is no connection ID available yet for the
+    // second migration.
+    EXPECT_FALSE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+    stream.reset();
+  } else {
+    EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+    EXPECT_EQ(1u, session->GetNumActiveStreams());
 
-  // Verify that response headers on the migrated socket were delivered to the
-  // stream.
-  EXPECT_EQ(OK, stream->ReadResponseHeaders(callback_.callback()));
-  EXPECT_EQ(200, response.headers->response_code());
+    // Verify that response headers on the migrated socket were delivered to the
+    // stream.
+    EXPECT_EQ(OK, stream->ReadResponseHeaders(callback_.callback()));
+    EXPECT_EQ(200, response.headers->response_code());
 
-  stream.reset();
+    stream.reset();
+    EXPECT_TRUE(socket_data2.AllReadDataConsumed());
+    EXPECT_TRUE(socket_data2.AllWriteDataConsumed());
+  }
   EXPECT_TRUE(socket_data1.AllReadDataConsumed());
   EXPECT_TRUE(socket_data1.AllWriteDataConsumed());
   EXPECT_TRUE(failed_quic_data2.AllReadDataConsumed());
   EXPECT_TRUE(failed_quic_data2.AllWriteDataConsumed());
   EXPECT_TRUE(failed_quic_data1.AllReadDataConsumed());
   EXPECT_TRUE(failed_quic_data1.AllWriteDataConsumed());
-  EXPECT_TRUE(socket_data2.AllReadDataConsumed());
-  EXPECT_TRUE(socket_data2.AllWriteDataConsumed());
 }
 
 TEST_P(QuicStreamFactoryTest, MigrateSessionOnMultipleWriteErrorsSyncSync) {
@@ -8660,20 +9074,39 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Set up second socket data provider that is used after
   // migration. The request is rewritten to this new socket, and the
   // response to the request is read on this new socket.
   MockQuicData socket_data1(version_);
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      ConstructGetRequestPacket(packet_num++,
-                                GetNthClientInitiatedBidirectionalStreamId(0),
-                                true, true));
   if (version_.UsesHttp3()) {
+    client_maker_.set_connection_id(cid_on_new_path);
+    // Increment packet number to account for packet write error on the old
+    // path. Also save the packet in client_maker_ for constructing the
+    // retransmission packet.
+    ConstructGetRequestPacket(packet_num++,
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              /*should_include_version=*/false, /*fin=*/true);
     socket_data1.AddWrite(SYNCHRONOUS,
                           client_maker_.MakeCombinedRetransmissionPacket(
-                              {1, 2}, packet_num++, true));
+                              /*original_packet_numbers=*/{1, 2}, packet_num++,
+                              /*should_include_version=*/false));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/false));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/false, /*sequence_number=*/0u));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        ConstructGetRequestPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            /*should_include_version=*/true, /*fin=*/true));
   }
   socket_data1.AddRead(
       ASYNC,
@@ -8817,20 +9250,39 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Set up second socket data provider that is used after
   // migration. The request is rewritten to this new socket, and the
   // response to the request is read on this new socket.
   MockQuicData socket_data1(version_);
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      ConstructGetRequestPacket(packet_num++,
-                                GetNthClientInitiatedBidirectionalStreamId(0),
-                                true, true));
-  if (version_.UsesHttp3()) {
+  if (!version_.UsesHttp3()) {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        ConstructGetRequestPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            /*should_include_version=*/true, /*fin=*/true));
+  } else {
+    client_maker_.set_connection_id(cid_on_new_path);
+    // Increment packet number to account for packet write error on the old
+    // path. Also save the packet in client_maker_ for constructing the
+    // retransmission packet.
+    ConstructGetRequestPacket(packet_num++,
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              /*should_include_version=*/false, /*fin=*/true);
     socket_data1.AddWrite(SYNCHRONOUS,
                           client_maker_.MakeCombinedRetransmissionPacket(
-                              {1, 2}, packet_num++, true));
+                              /*original_packet_numbers=*/{1, 2}, packet_num++,
+                              /*should_include_version=*/false));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/false));
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeRetireConnectionIdPacket(
+                              packet_num++, /*include_version=*/false,
+                              /*sequence_number=*/0u));
   }
   socket_data1.AddRead(
       ASYNC,
@@ -8980,6 +9432,9 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream.
   HttpResponseInfo response;
@@ -8996,23 +9451,43 @@
   // Set up second socket data provider that is used after migration.
   // The response to the earlier request is read on this new socket.
   MockQuicData socket_data1(version_);
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      ConstructGetRequestPacket(packet_num++,
-                                GetNthClientInitiatedBidirectionalStreamId(0),
-                                true, true));
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+    // Increment packet number to account for packet write error on the old
+    // path. Also save the packet in client_maker_ for constructing the
+    // retransmission packet.
+    ConstructGetRequestPacket(packet_num++,
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              /*should_include_version=*/false, /*fin=*/true);
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        ConstructGetRequestPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            /*should_include_version=*/true, /*fin=*/true));
+  }
   socket_data1.AddRead(
       ASYNC,
       ConstructOkResponsePacket(
           1, GetNthClientInitiatedBidirectionalStreamId(0), false, false));
   socket_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   if (VersionUsesHttp3(version_.transport_version)) {
-    socket_data1.AddWrite(ASYNC, client_maker_.MakeCombinedRetransmissionPacket(
-                                     {1, 2}, packet_num++, false));
     socket_data1.AddWrite(
-        SYNCHRONOUS, client_maker_.MakeAckAndDataPacket(
-                         packet_num++, false, GetQpackDecoderStreamId(), 1, 1,
-                         false, StreamCancellationQpackDecoderInstruction(0)));
+        SYNCHRONOUS,
+        client_maker_.MakeAckRetransmissionAndRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/false,
+            /*largest_received=*/1,
+            /*smallest_received=*/1, /*original_packet_numbers=*/{1, 2},
+            /*sequence_number=*/0u));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/false));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_num++, GetQpackDecoderStreamId(),
+            /*should_include_version=*/false,
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
     socket_data1.AddWrite(
         SYNCHRONOUS,
         client_maker_.MakeRstPacket(
@@ -9135,18 +9610,26 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Set up second socket data provider that is used after
   // migration. The response to the request is read on this new socket.
   MockQuicData socket_data1(version_);
   if (version_.UsesHttp3()) {
+    client_maker_.set_connection_id(cid_on_new_path);
     socket_data1.AddWrite(SYNCHRONOUS,
                           client_maker_.MakeCombinedRetransmissionPacket(
                               {1, 2}, packet_num++, true));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/false));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
   }
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
   socket_data1.AddRead(
       ASYNC,
       ConstructOkResponsePacket(
@@ -9262,23 +9745,37 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Set up second socket data provider that is used after
   // migration. The request is written to this new socket, and the
   // response to the request is read on this new socket.
   MockQuicData socket_data1(version_);
   if (version_.UsesHttp3()) {
+    client_maker_.set_connection_id(cid_on_new_path);
     socket_data1.AddWrite(SYNCHRONOUS, client_maker_.MakeRetransmissionPacket(
                                            1, packet_num++, true));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/false));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
   }
   socket_data1.AddWrite(
       SYNCHRONOUS,
-      client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
       ConstructGetRequestPacket(packet_num++,
                                 GetNthClientInitiatedBidirectionalStreamId(0),
                                 true, true));
+  if (VersionUsesHttp3(version_.transport_version)) {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/true, /*sequence_number=*/0u));
+  }
   socket_data1.AddRead(
       ASYNC,
       ConstructOkResponsePacket(
@@ -9399,23 +9896,37 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Set up second socket data provider that is used after
   // migration. The request is written to this new socket, and the
   // response to the request is read on this new socket.
   MockQuicData socket_data1(version_);
   if (version_.UsesHttp3()) {
+    client_maker_.set_connection_id(cid_on_new_path);
     socket_data1.AddWrite(SYNCHRONOUS, client_maker_.MakeRetransmissionPacket(
                                            1, packet_num++, true));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/false));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
   }
   socket_data1.AddWrite(
       SYNCHRONOUS,
-      client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
       ConstructGetRequestPacket(packet_num++,
                                 GetNthClientInitiatedBidirectionalStreamId(0),
                                 true, true));
+  if (version_.UsesHttp3()) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeRetireConnectionIdPacket(
+                              packet_num++, /*include_version=*/false,
+                              /*sequence_number=*/0u));
+  }
   socket_data1.AddRead(
       ASYNC,
       ConstructOkResponsePacket(
@@ -9516,14 +10027,22 @@
   // migration. The request is written to this new socket, and the
   // response to the request is read on this new socket.
   MockQuicData socket_data1(version_);
-
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
   if (version_.UsesHttp3()) {
+    client_maker_.set_connection_id(cid_on_new_path);
     socket_data1.AddWrite(SYNCHRONOUS, client_maker_.MakeRetransmissionPacket(
                                            1, packet_num++, true));
   }
   // The PING packet sent post migration.
   socket_data1.AddWrite(SYNCHRONOUS,
                         client_maker_.MakePingPacket(packet_num++, true));
+  if (version_.UsesHttp3()) {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/false, /*sequence_number=*/0u));
+  }
   socket_data1.AddWrite(
       SYNCHRONOUS,
       ConstructGetRequestPacket(packet_num++,
@@ -9592,6 +10111,7 @@
   // Ensure that session is alive and active.
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Now notify network is disconnected, cause the migration to complete
   // immediately.
@@ -9684,13 +10204,22 @@
   // migration. The request is written to this new socket, and the
   // response to the request is read on this new socket.
   MockQuicData socket_data1(version_);
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
   if (version_.UsesHttp3()) {
+    client_maker_.set_connection_id(cid_on_new_path);
     socket_data1.AddWrite(SYNCHRONOUS, client_maker_.MakeRetransmissionPacket(
                                            1, packet_num++, true));
   }
   // The PING packet sent post migration.
   socket_data1.AddWrite(SYNCHRONOUS,
                         client_maker_.MakePingPacket(packet_num++, true));
+  if (version_.UsesHttp3()) {
+    socket_data1.AddWrite(SYNCHRONOUS,
+                          client_maker_.MakeRetireConnectionIdPacket(
+                              packet_num++, /*include_version=*/false,
+                              /*sequence_number=*/0u));
+  }
   socket_data1.AddWrite(
       SYNCHRONOUS,
       ConstructGetRequestPacket(packet_num++,
@@ -9758,6 +10287,7 @@
   // Ensure that session is alive and active.
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Now notify network is disconnected, cause the migration to complete
   // immediately.
@@ -10433,19 +10963,29 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Set up second socket data provider that is used after
   // migration. The request is written to this new socket, and the
   // response to the request is read on this new socket.
   MockQuicData socket_data1(version_);
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      ConstructGetRequestPacket(packet_num++,
-                                GetNthClientInitiatedBidirectionalStreamId(0),
-                                true, true));
   if (version_.UsesHttp3()) {
-    socket_data1.AddWrite(ASYNC, client_maker_.MakeCombinedRetransmissionPacket(
-                                     {1, 2}, packet_num++, true));
+    client_maker_.set_connection_id(cid_on_new_path);
+    ConstructGetRequestPacket(packet_num++,
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              /*should_include_version=*/true, /*fin=*/true);
+    socket_data1.AddWrite(ASYNC,
+                          client_maker_.MakeCombinedRetransmissionPacket(
+                              /*original_packet_numbers=*/{1, 2}, packet_num++,
+                              /*should_include_version=*/false));
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        ConstructGetRequestPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            /*should_include_version=*/true, /*fin=*/true));
   }
   socket_data1.AddRead(
       ASYNC,
@@ -10527,7 +11067,7 @@
       SYNCHRONOUS, /*disconnect_before_connect*/ true);
 }
 
-// Setps up test which verifies that session successfully migrate to alternate
+// Sets up test which verifies that session successfully migrate to alternate
 // network with signals delivered in the following order:
 // *NOTE* Signal (A) and (B) can reverse order based on
 // |disconnect_before_connect|.
@@ -10587,6 +11127,9 @@
   QuicChromiumClientSession* session = GetActiveSession(host_port_pair_);
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
   EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  quic::QuicConnectionId cid_on_new_path =
+      quic::test::TestConnectionId(12345678);
+  MaybeMakeNewConnectionIdAvailableToSession(cid_on_new_path, session);
 
   // Send GET request on stream. This should cause a write error, which triggers
   // a connection migration attempt.
@@ -10609,23 +11152,42 @@
   // migration. The request is rewritten to this new socket, and the
   // response to the request is read on this new socket.
   MockQuicData socket_data1(version_);
-  socket_data1.AddWrite(
-      SYNCHRONOUS,
-      ConstructGetRequestPacket(packet_num++,
-                                GetNthClientInitiatedBidirectionalStreamId(0),
-                                true, true));
+  if (VersionUsesHttp3(version_.transport_version)) {
+    client_maker_.set_connection_id(cid_on_new_path);
+    // Increment packet number to account for packet write error on the old
+    // path. Also save the packet in client_maker_ for constructing the
+    // retransmission packet.
+    ConstructGetRequestPacket(packet_num++,
+                              GetNthClientInitiatedBidirectionalStreamId(0),
+                              /*should_include_version=*/false, /*fin=*/true);
+  } else {
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        ConstructGetRequestPacket(
+            packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
+            /*should_include_version=*/true, /*fin=*/true));
+  }
   socket_data1.AddRead(
       ASYNC,
       ConstructOkResponsePacket(
           1, GetNthClientInitiatedBidirectionalStreamId(0), false, false));
   socket_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   if (VersionUsesHttp3(version_.transport_version)) {
-    socket_data1.AddWrite(ASYNC, client_maker_.MakeCombinedRetransmissionPacket(
-                                     {1, 2}, packet_num++, false));
+    socket_data1.AddWrite(ASYNC, client_maker_.MakeAckAndRetransmissionPacket(
+                                     packet_num++, /*first_received=*/1,
+                                     /*largest_received=*/1,
+                                     /*smallest_received=*/1,
+                                     /*original_packet_numbers=*/{1, 2}));
     socket_data1.AddWrite(
-        SYNCHRONOUS, client_maker_.MakeAckAndDataPacket(
-                         packet_num++, false, GetQpackDecoderStreamId(), 1, 1,
-                         false, StreamCancellationQpackDecoderInstruction(0)));
+        SYNCHRONOUS,
+        client_maker_.MakeRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/false, /*sequence_number=*/0u));
+    socket_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeDataPacket(
+            packet_num++, GetQpackDecoderStreamId(),
+            /*should_include_version=*/false,
+            /*fin=*/false, StreamCancellationQpackDecoderInstruction(0)));
     socket_data1.AddWrite(
         SYNCHRONOUS,
         client_maker_.MakeRstPacket(
@@ -10715,7 +11277,23 @@
   QuicStreamFactoryPeer::SetTickClock(factory_.get(),
                                       task_runner->GetMockTickClock());
 
+  quic::QuicConnectionId cid1 = quic::test::TestConnectionId(1234567);
+  quic::QuicConnectionId cid2 = quic::test::TestConnectionId(2345671);
+  quic::QuicConnectionId cid3 = quic::test::TestConnectionId(3456712);
+  quic::QuicConnectionId cid4 = quic::test::TestConnectionId(4567123);
+  quic::QuicConnectionId cid5 = quic::test::TestConnectionId(5671234);
+  quic::QuicConnectionId cid6 = quic::test::TestConnectionId(6712345);
+  quic::QuicConnectionId cid7 = quic::test::TestConnectionId(7123456);
+
+  int peer_packet_num = 1;
   MockQuicData default_socket_data(version_);
+  if (version_.UsesHttp3()) {
+    default_socket_data.AddRead(
+        SYNCHRONOUS, server_maker_.MakeNewConnectionIdPacket(
+                         peer_packet_num++, /*include_version=*/false, cid1,
+                         /*sequence_number=*/1u,
+                         /*retire_prior_to=*/0u));
+  }
   default_socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   int packet_num = 1;
   if (VersionUsesHttp3(version_.transport_version)) {
@@ -10726,16 +11304,88 @@
 
   // Set up second socket data provider that is used after migration.
   MockQuicData alternate_socket_data(version_);
-  alternate_socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
   if (version_.UsesHttp3()) {
+    client_maker_.set_connection_id(cid1);
+    alternate_socket_data.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeRetransmissionPacket(
+                         /*original_packet_number=*/1, packet_num++,
+                         /*should_include_version=*/false));
     alternate_socket_data.AddWrite(
         SYNCHRONOUS,
-        client_maker_.MakeRetransmissionPacket(1, packet_num++, true));
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/false));
+    alternate_socket_data.AddWrite(
+        ASYNC,
+        client_maker_.MakeRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/false, /*sequence_number=*/0u));
+    alternate_socket_data.AddRead(
+        ASYNC, server_maker_.MakeNewConnectionIdPacket(
+                   peer_packet_num++, /*include_version=*/false, cid2,
+                   /*sequence_number=*/2u,
+                   /*retire_prior_to=*/1u));
+    ++packet_num;  // Probing packet on default network encounters write error.
+    alternate_socket_data.AddWrite(
+        ASYNC,
+        client_maker_.MakeRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/false, /*sequence_number=*/2u));
+    alternate_socket_data.AddRead(ASYNC, ERR_IO_PENDING);  // Pause.
+    alternate_socket_data.AddRead(
+        ASYNC, server_maker_.MakeNewConnectionIdPacket(
+                   peer_packet_num++, /*include_version=*/false, cid3,
+                   /*sequence_number=*/3u,
+                   /*retire_prior_to=*/1u));
+    ++packet_num;  // Probing packet on default network encounters write error.
+    alternate_socket_data.AddWrite(
+        ASYNC,
+        client_maker_.MakeRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/false, /*sequence_number=*/3u));
+    alternate_socket_data.AddRead(ASYNC, ERR_IO_PENDING);  // Pause.
+    alternate_socket_data.AddRead(
+        ASYNC, server_maker_.MakeNewConnectionIdPacket(
+                   peer_packet_num++, /*include_version=*/false, cid4,
+                   /*sequence_number=*/4u,
+                   /*retire_prior_to=*/1u));
+    ++packet_num;  // Probing packet on default network encounters write error.
+    alternate_socket_data.AddWrite(
+        ASYNC,
+        client_maker_.MakeRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/false, /*sequence_number=*/4u));
+    alternate_socket_data.AddRead(ASYNC, ERR_IO_PENDING);  // Pause.
+    alternate_socket_data.AddRead(
+        ASYNC, server_maker_.MakeNewConnectionIdPacket(
+                   peer_packet_num++, /*include_version=*/false, cid5,
+                   /*sequence_number=*/5u,
+                   /*retire_prior_to=*/1u));
+    ++packet_num;  // Probing packet on default network encounters write error.
+    alternate_socket_data.AddWrite(
+        ASYNC,
+        client_maker_.MakeRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/false, /*sequence_number=*/5u));
+    alternate_socket_data.AddRead(ASYNC, ERR_IO_PENDING);  // Pause.
+    alternate_socket_data.AddRead(
+        ASYNC, server_maker_.MakeNewConnectionIdPacket(
+                   peer_packet_num++, /*include_version=*/false, cid6,
+                   /*sequence_number=*/6u,
+                   /*retire_prior_to=*/1u));
+    ++packet_num;  // Probing packet on default network encounters write error.
+    alternate_socket_data.AddWrite(
+        ASYNC, client_maker_.MakeRetireConnectionIdPacket(
+                   packet_num++, /*include_version=*/false, 6u));
+    alternate_socket_data.AddRead(ASYNC, ERR_IO_PENDING);  // Pause.
+    alternate_socket_data.AddRead(
+        ASYNC, server_maker_.MakeNewConnectionIdPacket(
+                   peer_packet_num++, /*include_version=*/false, cid7,
+                   /*sequence_number=*/7u,
+                   /*retire_prior_to=*/1u));
+    alternate_socket_data.AddRead(SYNCHRONOUS,
+                                  ERR_IO_PENDING);  // Hanging read.
+  } else {
+    alternate_socket_data.AddRead(SYNCHRONOUS,
+                                  ERR_IO_PENDING);  // Hanging read.
+    // Ping packet to send after migration.
+    alternate_socket_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
   }
-  // Ping packet to send after migration.
-  alternate_socket_data.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
   alternate_socket_data.AddSocketDataToFactory(socket_factory_.get());
 
   // Set up probing socket for migrating back to the default network.
@@ -10815,6 +11465,12 @@
   // Retry migrate back in 1, 2, 4, 8, 16s.
   // Session will be closed due to idle migration timeout.
   for (int i = 0; i < 5; i++) {
+    if (version_.UsesHttp3()) {
+      // Fire retire connection ID alarm.
+      base::RunLoop().RunUntilIdle();
+      // Make new connection ID available.
+      alternate_socket_data.Resume();
+    }
     EXPECT_TRUE(HasActiveSession(host_port_pair_));
     // A task is posted to migrate back to the default network in 2^i seconds.
     EXPECT_EQ(1u, task_runner->GetPendingTaskCount());
@@ -10846,7 +11502,22 @@
   QuicStreamFactoryPeer::SetTickClock(factory_.get(),
                                       task_runner->GetMockTickClock());
 
+  quic::QuicConnectionId cid1 = quic::test::TestConnectionId(1234567);
+  quic::QuicConnectionId cid2 = quic::test::TestConnectionId(2345671);
+  quic::QuicConnectionId cid3 = quic::test::TestConnectionId(3456712);
+  quic::QuicConnectionId cid4 = quic::test::TestConnectionId(4567123);
+  quic::QuicConnectionId cid5 = quic::test::TestConnectionId(5671234);
+  quic::QuicConnectionId cid6 = quic::test::TestConnectionId(6712345);
+
+  int peer_packet_num = 1;
   MockQuicData default_socket_data(version_);
+  if (version_.UsesHttp3()) {
+    default_socket_data.AddRead(
+        SYNCHRONOUS,
+        server_maker_.MakeNewConnectionIdPacket(peer_packet_num++, true, cid1,
+                                                /*sequence_number=*/1u,
+                                                /*retire_prior_to=*/0u));
+  }
   default_socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   int packet_num = 1;
   if (VersionUsesHttp3(version_.transport_version)) {
@@ -10857,16 +11528,67 @@
 
   // Set up second socket data provider that is used after migration.
   MockQuicData alternate_socket_data(version_);
-  alternate_socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
   if (version_.UsesHttp3()) {
+    client_maker_.set_connection_id(cid1);
+    alternate_socket_data.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeRetransmissionPacket(
+                         /*original_packet_number=*/1u, packet_num++,
+                         /*should_include_version=*/false));
     alternate_socket_data.AddWrite(
         SYNCHRONOUS,
-        client_maker_.MakeRetransmissionPacket(1, packet_num++, true));
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/false));
+    alternate_socket_data.AddWrite(
+        ASYNC,
+        client_maker_.MakeRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/false, /*sequence_number=*/0u));
+    alternate_socket_data.AddRead(ASYNC, ERR_IO_PENDING);  // Pause.
+    alternate_socket_data.AddRead(
+        ASYNC, server_maker_.MakeNewConnectionIdPacket(
+                   peer_packet_num++, /*include_version=*/false, cid2,
+                   /*sequence_number=*/2u,
+                   /*retire_prior_to=*/1u));
+    ++packet_num;  // Probing packet on default network encounters write error.
+    alternate_socket_data.AddWrite(
+        ASYNC,
+        client_maker_.MakeRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/false, /*sequence_number=*/2u));
+    alternate_socket_data.AddRead(ASYNC, ERR_IO_PENDING);  // Pause.
+    alternate_socket_data.AddRead(
+        ASYNC, server_maker_.MakeNewConnectionIdPacket(
+                   peer_packet_num++, /*include_version=*/false, cid3,
+                   /*sequence_number=*/3u,
+                   /*retire_prior_to=*/1u));
+    ++packet_num;  // Probing packet on default network encounters write error.
+    alternate_socket_data.AddWrite(
+        ASYNC,
+        client_maker_.MakeRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/false, /*sequence_number=*/3u));
+    alternate_socket_data.AddRead(ASYNC, ERR_IO_PENDING);  // Pause.
+    alternate_socket_data.AddRead(
+        ASYNC, server_maker_.MakeNewConnectionIdPacket(
+                   peer_packet_num++, /*include_version=*/false, cid4,
+                   /*sequence_number=*/4u,
+                   /*retire_prior_to=*/1u));
+    ++packet_num;  // Probing packet on default network encounters write error.
+    alternate_socket_data.AddWrite(
+        ASYNC,
+        client_maker_.MakeRetireConnectionIdPacket(
+            packet_num++, /*include_version=*/false, /*sequence_number=*/4u));
+    alternate_socket_data.AddRead(ASYNC, ERR_IO_PENDING);  // Pause.
+    alternate_socket_data.AddRead(
+        ASYNC, server_maker_.MakeNewConnectionIdPacket(
+                   peer_packet_num++, /*include_version=*/false, cid5,
+                   /*sequence_number=*/5u,
+                   /*retire_prior_to=*/1u));
+    alternate_socket_data.AddRead(SYNCHRONOUS,
+                                  ERR_IO_PENDING);  // Hanging read.
+  } else {
+    alternate_socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read
+    // Ping packet to send after migration.
+    alternate_socket_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
   }
-  // Ping packet to send after migration.
-  alternate_socket_data.AddWrite(
-      SYNCHRONOUS,
-      client_maker_.MakePingPacket(packet_num++, /*include_version=*/true));
   alternate_socket_data.AddSocketDataToFactory(socket_factory_.get());
 
   // Set up probing socket for migrating back to the default network.
@@ -10941,6 +11663,12 @@
   // Retry migrate back in 1, 2, 4, 8s.
   // Session will be closed due to idle migration timeout.
   for (int i = 0; i < 4; i++) {
+    if (version_.UsesHttp3()) {
+      // Fire retire connection ID alarm.
+      base::RunLoop().RunUntilIdle();
+      // Make new connection ID available.
+      alternate_socket_data.Resume();
+    }
     EXPECT_TRUE(HasActiveSession(host_port_pair_));
     // A task is posted to migrate back to the default network in 2^i seconds.
     EXPECT_EQ(1u, task_runner->GetPendingTaskCount());
diff --git a/net/quic/quic_test_packet_maker.cc b/net/quic/quic_test_packet_maker.cc
index f2e7b4a17..7d1c223 100644
--- a/net/quic/quic_test_packet_maker.cc
+++ b/net/quic/quic_test_packet_maker.cc
@@ -171,6 +171,46 @@
 }
 
 std::unique_ptr<quic::QuicReceivedPacket>
+QuicTestPacketMaker::MakeRetireConnectionIdPacket(uint64_t num,
+                                                  bool include_version,
+                                                  uint64_t sequence_number) {
+  InitializeHeader(num, include_version);
+  AddQuicRetireConnectionIdFrame(sequence_number);
+  return BuildPacket();
+}
+
+std::unique_ptr<quic::QuicReceivedPacket>
+QuicTestPacketMaker::MakeNewConnectionIdPacket(
+    uint64_t num,
+    bool include_version,
+    const quic::QuicConnectionId& cid,
+    uint64_t sequence_number,
+    uint64_t retire_prior_to) {
+  InitializeHeader(num, include_version);
+  AddQuicNewConnectionIdFrame(
+      cid, sequence_number, retire_prior_to,
+      quic::QuicUtils::GenerateStatelessResetToken(cid));
+  return BuildPacket();
+}
+
+std::unique_ptr<quic::QuicReceivedPacket>
+QuicTestPacketMaker::MakeAckAndNewConnectionIdPacket(
+    uint64_t num,
+    bool include_version,
+    uint64_t largest_received,
+    uint64_t smallest_received,
+    const quic::QuicConnectionId& cid,
+    uint64_t sequence_number,
+    uint64_t retire_prior_to) {
+  InitializeHeader(num, include_version);
+  AddQuicAckFrame(largest_received, smallest_received);
+  AddQuicNewConnectionIdFrame(
+      cid, sequence_number, retire_prior_to,
+      quic::QuicUtils::GenerateStatelessResetToken(cid));
+  return BuildPacket();
+}
+
+std::unique_ptr<quic::QuicReceivedPacket>
 QuicTestPacketMaker::MakeDummyCHLOPacket(uint64_t packet_num) {
   SetEncryptionLevel(quic::ENCRYPTION_INITIAL);
   InitializeHeader(packet_num, /*include_version=*/true);
@@ -207,6 +247,38 @@
 }
 
 std::unique_ptr<quic::QuicReceivedPacket>
+QuicTestPacketMaker::MakeAckAndRetireConnectionIdPacket(
+    uint64_t num,
+    bool include_version,
+    uint64_t largest_received,
+    uint64_t smallest_received,
+    uint64_t sequence_number) {
+  InitializeHeader(num, include_version);
+  AddQuicAckFrame(largest_received, smallest_received);
+  AddQuicRetireConnectionIdFrame(sequence_number);
+  return BuildPacket();
+}
+
+std::unique_ptr<quic::QuicReceivedPacket>
+QuicTestPacketMaker::MakeAckRetransmissionAndRetireConnectionIdPacket(
+    uint64_t num,
+    bool include_version,
+    uint64_t largest_received,
+    uint64_t smallest_received,
+    const std::vector<uint64_t>& original_packet_numbers,
+    uint64_t sequence_number) {
+  InitializeHeader(num, include_version);
+  AddQuicAckFrame(largest_received, smallest_received);
+  for (auto it : original_packet_numbers) {
+    for (auto frame : saved_frames_[quic::QuicPacketNumber(it)]) {
+      frames_.push_back(frame);
+    }
+  }
+  AddQuicRetireConnectionIdFrame(sequence_number);
+  return BuildPacket();
+}
+
+std::unique_ptr<quic::QuicReceivedPacket>
 QuicTestPacketMaker::MakeStreamsBlockedPacket(
     uint64_t num,
     bool include_version,
@@ -698,6 +770,29 @@
 }
 
 std::unique_ptr<quic::QuicReceivedPacket>
+QuicTestPacketMaker::MakeAckRetransmissionAndDataPacket(
+    uint64_t packet_number,
+    bool include_version,
+    const std::vector<uint64_t>& original_packet_numbers,
+    quic::QuicStreamId stream_id,
+    uint64_t largest_received,
+    uint64_t smallest_received,
+    bool fin,
+    absl::string_view data) {
+  InitializeHeader(packet_number, include_version);
+
+  AddQuicAckFrame(largest_received, smallest_received);
+  for (auto it : original_packet_numbers) {
+    for (auto frame : saved_frames_[quic::QuicPacketNumber(it)]) {
+      frames_.push_back(frame);
+    }
+  }
+  AddQuicStreamFrame(stream_id, fin, data);
+
+  return BuildPacket();
+}
+
+std::unique_ptr<quic::QuicReceivedPacket>
 QuicTestPacketMaker::MakeRequestHeadersAndMultipleDataFramesPacket(
     uint64_t packet_number,
     quic::QuicStreamId stream_id,
@@ -1263,6 +1358,28 @@
   DVLOG(1) << "Adding frame: " << frames_.back();
 }
 
+void QuicTestPacketMaker::AddQuicRetireConnectionIdFrame(
+    uint64_t sequence_number) {
+  auto* retire_cid_frame = new quic::QuicRetireConnectionIdFrame();
+  retire_cid_frame->sequence_number = sequence_number;
+  frames_.push_back(quic::QuicFrame(retire_cid_frame));
+  DVLOG(1) << "Adding frame: " << frames_.back();
+}
+
+void QuicTestPacketMaker::AddQuicNewConnectionIdFrame(
+    const quic::QuicConnectionId& cid,
+    uint64_t sequence_number,
+    uint64_t retire_prior_to,
+    quic::StatelessResetToken reset_token) {
+  auto* new_cid_frame = new quic::QuicNewConnectionIdFrame();
+  new_cid_frame->connection_id = cid;
+  new_cid_frame->sequence_number = sequence_number;
+  new_cid_frame->retire_prior_to = retire_prior_to;
+  new_cid_frame->stateless_reset_token = reset_token;
+  frames_.push_back(quic::QuicFrame(new_cid_frame));
+  DVLOG(1) << "Adding frame: " << frames_.back();
+}
+
 void QuicTestPacketMaker::AddQuicMaxStreamsFrame(
     quic::QuicControlFrameId control_frame_id,
     quic::QuicStreamCount stream_count,
diff --git a/net/quic/quic_test_packet_maker.h b/net/quic/quic_test_packet_maker.h
index 89b28837..1aad75e 100644
--- a/net/quic/quic_test_packet_maker.h
+++ b/net/quic/quic_test_packet_maker.h
@@ -8,6 +8,7 @@
 #define NET_QUIC_QUIC_TEST_PACKET_MAKER_H_
 
 #include <stddef.h>
+#include <sys/types.h>
 
 #include <memory>
 #include <string>
@@ -55,6 +56,10 @@
 
   void set_hostname(const std::string& host);
 
+  void set_connection_id(const quic::QuicConnectionId& connection_id) {
+    connection_id_ = connection_id;
+  }
+
   std::unique_ptr<quic::QuicReceivedPacket> MakeConnectivityProbingPacket(
       uint64_t num,
       bool include_version);
@@ -63,6 +68,27 @@
       uint64_t num,
       bool include_version);
 
+  std::unique_ptr<quic::QuicReceivedPacket> MakeRetireConnectionIdPacket(
+      uint64_t num,
+      bool include_version,
+      uint64_t sequence_number);
+
+  std::unique_ptr<quic::QuicReceivedPacket> MakeNewConnectionIdPacket(
+      uint64_t num,
+      bool include_version,
+      const quic::QuicConnectionId& cid,
+      uint64_t sequence_number,
+      uint64_t retire_prior_to);
+
+  std::unique_ptr<quic::QuicReceivedPacket> MakeAckAndNewConnectionIdPacket(
+      uint64_t num,
+      bool include_version,
+      uint64_t largest_received,
+      uint64_t smallest_received,
+      const quic::QuicConnectionId& cid,
+      uint64_t sequence_number,
+      uint64_t retire_prior_to);
+
   std::unique_ptr<quic::QuicReceivedPacket> MakeDummyCHLOPacket(
       uint64_t packet_num);
 
@@ -72,6 +98,22 @@
       uint64_t largest_received,
       uint64_t smallest_received);
 
+  std::unique_ptr<quic::QuicReceivedPacket> MakeAckAndRetireConnectionIdPacket(
+      uint64_t num,
+      bool include_version,
+      uint64_t largest_received,
+      uint64_t smallest_received,
+      uint64_t sequence_number);
+
+  std::unique_ptr<quic::QuicReceivedPacket>
+  MakeAckRetransmissionAndRetireConnectionIdPacket(
+      uint64_t num,
+      bool include_version,
+      uint64_t largest_received,
+      uint64_t smallest_received,
+      const std::vector<uint64_t>& original_packet_numbers,
+      uint64_t sequence_number);
+
   std::unique_ptr<quic::QuicReceivedPacket> MakeStreamsBlockedPacket(
       uint64_t num,
       bool include_version,
@@ -290,6 +332,16 @@
       bool fin,
       absl::string_view data);
 
+  std::unique_ptr<quic::QuicReceivedPacket> MakeAckRetransmissionAndDataPacket(
+      uint64_t packet_number,
+      bool include_version,
+      const std::vector<uint64_t>& original_packet_numbers,
+      quic::QuicStreamId stream_id,
+      uint64_t largest_received,
+      uint64_t smallest_received,
+      bool fin,
+      absl::string_view data);
+
   std::unique_ptr<quic::QuicReceivedPacket> MakeAckAndRetransmissionPacket(
       uint64_t packet_number,
       uint64_t first_received,
@@ -460,6 +512,11 @@
   // Add frames to current packet.
   void AddQuicPaddingFrame();
   void AddQuicPingFrame();
+  void AddQuicRetireConnectionIdFrame(uint64_t sequence_number);
+  void AddQuicNewConnectionIdFrame(const quic::QuicConnectionId& cid,
+                                   uint64_t sequence_number,
+                                   uint64_t retire_prior_to,
+                                   quic::StatelessResetToken reset_token);
   void AddQuicMaxStreamsFrame(quic::QuicControlFrameId control_frame_id,
                               quic::QuicStreamCount stream_count,
                               bool unidirectional);
diff --git a/net/socket/socket_test_util.cc b/net/socket/socket_test_util.cc
index 7f60bae..245e123 100644
--- a/net/socket/socket_test_util.cc
+++ b/net/socket/socket_test_util.cc
@@ -304,7 +304,8 @@
     return MockWriteResult(SYNCHRONOUS, data.length());
   }
   EXPECT_FALSE(helper_.AllWriteDataConsumed())
-      << "No more mock data to match write:\n"
+      << "No more mock data to match write:\nFormatted write data:\n"
+      << printer_->PrintWrite(data) << "Raw write data:\n"
       << HexDump(data);
   if (helper_.AllWriteDataConsumed()) {
     return MockWriteResult(SYNCHRONOUS, ERR_UNEXPECTED);
@@ -496,7 +497,8 @@
 MockWriteResult SequencedSocketData::OnWrite(const std::string& data) {
   CHECK_EQ(IDLE, write_state_);
   CHECK(!helper_.AllWriteDataConsumed())
-      << "\nNo more mock data to match write:\n"
+      << "\nNo more mock data to match write:\nFormatted write data:\n"
+      << printer_->PrintWrite(data) << "Raw write data:\n"
       << HexDump(data);
 
   NET_TRACE(1, " *** ") << "sequence_number: " << sequence_number_;
diff --git a/printing/backend/mojom/print_backend.mojom b/printing/backend/mojom/print_backend.mojom
index 508e8778..c035efe 100644
--- a/printing/backend/mojom/print_backend.mojom
+++ b/printing/backend/mojom/print_backend.mojom
@@ -66,7 +66,7 @@
   array<Paper> papers;
   array<Paper> user_defined_papers;
   Paper default_paper;
-  array<gfx.mojom.Size> dpis;
+  array<gfx.mojom.Size> dpis;  // Duplicates allowed.
   gfx.mojom.Size default_dpi;
 
   [EnableIf=is_chromeos]
diff --git a/printing/backend/mojom/print_backend_mojom_traits.cc b/printing/backend/mojom/print_backend_mojom_traits.cc
index 52ca698..8ef4e9a 100644
--- a/printing/backend/mojom/print_backend_mojom_traits.cc
+++ b/printing/backend/mojom/print_backend_mojom_traits.cc
@@ -242,12 +242,6 @@
     return false;
   }
 
-  DuplicateChecker<gfx::Size> dpis_dup_checker;
-  if (dpis_dup_checker.HasDuplicates(out->dpis)) {
-    DLOG(ERROR) << "Duplicate dpis detected.";
-    return false;
-  }
-
 #if defined(OS_CHROMEOS)
   DuplicateChecker<printing::AdvancedCapability>
       advanced_capabilities_dup_checker;
diff --git a/printing/backend/mojom/print_backend_mojom_traits_unittest.cc b/printing/backend/mojom/print_backend_mojom_traits_unittest.cc
index ad48784..b4f2fe8 100644
--- a/printing/backend/mojom/print_backend_mojom_traits_unittest.cc
+++ b/printing/backend/mojom/print_backend_mojom_traits_unittest.cc
@@ -312,12 +312,6 @@
   EXPECT_FALSE(mojo::test::SerializeAndDeserialize<
                mojom::PrinterSemanticCapsAndDefaults>(input, output));
 
-  input = GenerateSamplePrinterSemanticCapsAndDefaults();
-  input.dpis = {kDpi600, kDpi600, kDpi1200};
-
-  EXPECT_FALSE(mojo::test::SerializeAndDeserialize<
-               mojom::PrinterSemanticCapsAndDefaults>(input, output));
-
 #if defined(OS_CHROMEOS)
   // Use an advanced capability with same name but different other fields.
   AdvancedCapability advanced_capability1_prime = kAdvancedCapability1;
@@ -332,4 +326,22 @@
 #endif
 }
 
+TEST(
+    PrintBackendMojomTraitsTest,
+    TestSerializeAndDeserializePrinterSemanticCapsAndDefaultsAllowedDuplicatesInArrays) {
+  PrinterSemanticCapsAndDefaults input =
+      GenerateSamplePrinterSemanticCapsAndDefaults();
+  PrinterSemanticCapsAndDefaults output;
+
+  // Override sample with arrays containing duplicates where it is allowed.
+  // Duplicate DPIs are known to be possible, seen with the Kyocera KX driver.
+  const std::vector<gfx::Size> kDuplicateDpis{kDpi600, kDpi600, kDpi1200};
+  input.dpis = kDuplicateDpis;
+
+  EXPECT_TRUE(mojo::test::SerializeAndDeserialize<
+              mojom::PrinterSemanticCapsAndDefaults>(input, output));
+
+  EXPECT_EQ(kDuplicateDpis, output.dpis);
+}
+
 }  // namespace printing
diff --git a/remoting/host/chromeos/BUILD.gn b/remoting/host/chromeos/BUILD.gn
index f12d3ac..74d2ae5 100644
--- a/remoting/host/chromeos/BUILD.gn
+++ b/remoting/host/chromeos/BUILD.gn
@@ -44,6 +44,28 @@
   ]
 }
 
+source_set("remoting_service") {
+  sources = [
+    "remote_support_host_ash.cc",
+    "remote_support_host_ash.h",
+    "remoting_service.cc",
+    "remoting_service.h",
+  ]
+
+  public_deps = [ "//remoting/host/it2me:chrome_os_host" ]
+
+  configs += [ "//remoting/build/config:version" ]
+
+  deps = [
+    "//chrome/browser:browser_process",
+    "//content/public/browser:browser",
+    "//mojo/public/cpp/bindings",
+    "//remoting/base:base",
+    "//remoting/host",
+    "//remoting/host/mojom:mojom",
+  ]
+}
+
 # The host portions of the remoting unit tests.
 source_set("unit_tests") {
   testonly = true
diff --git a/remoting/host/chromeos/DEPS b/remoting/host/chromeos/DEPS
new file mode 100644
index 0000000..ca7508a
--- /dev/null
+++ b/remoting/host/chromeos/DEPS
@@ -0,0 +1,8 @@
+specific_include_rules = {
+  "remoting_service.cc": [
+    "+chrome/browser/browser_process.h",
+    "+content/public/browser/browser_task_traits.h",
+    "+content/public/browser/browser_thread.h",
+    "+content/public/browser/storage_partition.h",
+  ],
+}
\ No newline at end of file
diff --git a/remoting/host/chromeos/remote_support_host_ash.cc b/remoting/host/chromeos/remote_support_host_ash.cc
new file mode 100644
index 0000000..4dc86c7
--- /dev/null
+++ b/remoting/host/chromeos/remote_support_host_ash.cc
@@ -0,0 +1,71 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/host/chromeos/remote_support_host_ash.h"
+
+#include <utility>
+
+#include <stddef.h>
+
+#include "base/notreached.h"
+#include "base/strings/stringize_macros.h"
+#include "remoting/host/chromeos/remoting_service.h"
+#include "remoting/host/chromoting_host_context.h"
+#include "remoting/host/it2me/it2me_constants.h"
+#include "remoting/host/it2me/it2me_native_messaging_host_ash.h"
+#include "remoting/host/policy_watcher.h"
+
+namespace remoting {
+
+RemoteSupportHostAsh::RemoteSupportHostAsh(base::OnceClosure cleanup_callback)
+    : cleanup_callback_(std::move(cleanup_callback)) {}
+
+RemoteSupportHostAsh::~RemoteSupportHostAsh() = default;
+
+void RemoteSupportHostAsh::StartSession(mojom::SupportSessionParamsPtr params,
+                                        StartSessionCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Ensure there is at most one active remote support connection.
+  // Since we are initiating the disconnect, don't run the cleanup callback.
+  if (it2me_native_message_host_ash_) {
+    auto temp = std::move(it2me_native_message_host_ash_);
+    temp->Disconnect();
+  }
+
+  it2me_native_message_host_ash_ =
+      std::make_unique<It2MeNativeMessageHostAsh>();
+
+  mojo::PendingReceiver<mojom::SupportHostObserver> pending_receiver =
+      it2me_native_message_host_ash_->Start(
+          RemotingService::Get().CreateHostContext(),
+          RemotingService::Get().CreatePolicyWatcher());
+
+  mojom::StartSupportSessionResponsePtr response =
+      mojom::StartSupportSessionResponse::New();
+  response->set_observer(std::move(pending_receiver));
+
+  it2me_native_message_host_ash_->Connect(
+      std::move(params),
+      base::BindOnce(std::move(callback), std::move(response)),
+      base::BindOnce(&RemoteSupportHostAsh::OnSessionDisconnected,
+                     base::Unretained(this)));
+}
+
+// static
+mojom::SupportHostDetailsPtr RemoteSupportHostAsh::GetHostDetails() {
+  return mojom::SupportHostDetails::New(
+      STRINGIZE(VERSION), std::vector<std::string>({kFeatureAccessTokenAuth}));
+}
+
+void RemoteSupportHostAsh::OnSessionDisconnected() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (it2me_native_message_host_ash_) {
+    // Do not access any instance members after |cleanup_callback_| is run as
+    // this instance will be destroyed by running this.
+    std::move(cleanup_callback_).Run();
+  }
+}
+
+}  // namespace remoting
diff --git a/remoting/host/chromeos/remote_support_host_ash.h b/remoting/host/chromeos/remote_support_host_ash.h
new file mode 100644
index 0000000..952400e
--- /dev/null
+++ b/remoting/host/chromeos/remote_support_host_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 REMOTING_HOST_CHROMEOS_REMOTE_SUPPORT_HOST_ASH_H_
+#define REMOTING_HOST_CHROMEOS_REMOTE_SUPPORT_HOST_ASH_H_
+
+#include "base/callback.h"
+#include "base/sequence_checker.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"
+#include "remoting/host/it2me/it2me_native_messaging_host_ash.h"
+#include "remoting/host/mojom/remote_support.mojom.h"
+
+namespace remoting {
+
+class It2MeNativeMessageHostAsh;
+
+// This class represents a remote support host instance which can be connected
+// to and controlled over an IPC channel. It wraps a remote support host
+// implementation and enforces the single connection requirement. Method calls
+// and destruction must occur on the same sequence as creation. This object's
+// lifetime is tied to that of the wrapped host, meaning this instance will be
+// destroyed when the IPC channel is disconnected.
+class RemoteSupportHostAsh {
+ public:
+  explicit RemoteSupportHostAsh(base::OnceClosure cleanup_callback);
+  RemoteSupportHostAsh(const RemoteSupportHostAsh&) = delete;
+  RemoteSupportHostAsh& operator=(const RemoteSupportHostAsh&) = delete;
+  ~RemoteSupportHostAsh();
+
+  using StartSessionCallback =
+      base::OnceCallback<void(mojom::StartSupportSessionResponsePtr response)>;
+
+  // Returns a structure which includes the host version and supported features.
+  static mojom::SupportHostDetailsPtr GetHostDetails();
+
+  // Allows the caller to start a new remote support session.  |callback| is
+  // called with the result.
+  void StartSession(mojom::SupportSessionParamsPtr params,
+                    StartSessionCallback callback);
+
+ private:
+  void OnSessionDisconnected();
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  std::unique_ptr<It2MeNativeMessageHostAsh> it2me_native_message_host_ash_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
+  base::OnceClosure cleanup_callback_;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_CHROMEOS_REMOTE_SUPPORT_HOST_ASH_H_
diff --git a/remoting/host/chromeos/remoting_service.cc b/remoting/host/chromeos/remoting_service.cc
new file mode 100644
index 0000000..53dcea3
--- /dev/null
+++ b/remoting/host/chromeos/remoting_service.cc
@@ -0,0 +1,87 @@
+// 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 "remoting/host/chromeos/remoting_service.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/no_destructor.h"
+#include "base/sequence_checker.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
+#include "chrome/browser/browser_process.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/storage_partition.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+#include "remoting/base/auto_thread_task_runner.h"
+#include "remoting/host/chromeos/remote_support_host_ash.h"
+#include "remoting/host/chromoting_host_context.h"
+#include "remoting/host/policy_watcher.h"
+
+namespace remoting {
+
+namespace {
+
+class RemotingServiceImpl : public RemotingService {
+ public:
+  RemotingServiceImpl();
+  RemotingServiceImpl(const RemotingServiceImpl&) = delete;
+  RemotingServiceImpl& operator=(const RemotingServiceImpl&) = delete;
+  ~RemotingServiceImpl() override;
+
+  // RemotingService implementation.
+  RemoteSupportHostAsh& GetSupportHost() override;
+  std::unique_ptr<ChromotingHostContext> CreateHostContext() override;
+  std::unique_ptr<PolicyWatcher> CreatePolicyWatcher() override;
+
+ private:
+  void ReleaseSupportHost();
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  std::unique_ptr<RemoteSupportHostAsh> remote_support_host_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+};
+
+RemotingServiceImpl::RemotingServiceImpl() = default;
+RemotingServiceImpl::~RemotingServiceImpl() = default;
+
+RemoteSupportHostAsh& RemotingServiceImpl::GetSupportHost() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!remote_support_host_) {
+    remote_support_host_ =
+        std::make_unique<RemoteSupportHostAsh>(base::BindOnce(
+            &RemotingServiceImpl::ReleaseSupportHost, base::Unretained(this)));
+  }
+  return *remote_support_host_;
+}
+
+std::unique_ptr<ChromotingHostContext>
+RemotingServiceImpl::CreateHostContext() {
+  return ChromotingHostContext::CreateForChromeOS(
+      content::GetIOThreadTaskRunner({}), content::GetUIThreadTaskRunner({}),
+      base::ThreadPool::CreateSingleThreadTaskRunner(
+          {base::MayBlock(), base::TaskPriority::BEST_EFFORT}));
+}
+
+std::unique_ptr<PolicyWatcher> RemotingServiceImpl::CreatePolicyWatcher() {
+  return PolicyWatcher::CreateWithPolicyService(
+      g_browser_process->policy_service());
+}
+
+void RemotingServiceImpl::ReleaseSupportHost() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  remote_support_host_.reset();
+}
+
+}  // namespace
+
+RemotingService& RemotingService::Get() {
+  static base::NoDestructor<RemotingServiceImpl> instance;
+  return *instance;
+}
+
+}  // namespace remoting
diff --git a/remoting/host/chromeos/remoting_service.h b/remoting/host/chromeos/remoting_service.h
new file mode 100644
index 0000000..269d649
--- /dev/null
+++ b/remoting/host/chromeos/remoting_service.h
@@ -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.
+
+#ifndef REMOTING_HOST_CHROMEOS_REMOTING_SERVICE_H_
+#define REMOTING_HOST_CHROMEOS_REMOTING_SERVICE_H_
+
+#include <memory>
+
+namespace remoting {
+
+class ChromotingHostContext;
+class PolicyWatcher;
+class RemoteSupportHostAsh;
+
+// The RemotingService is a singleton which provides access to remoting
+// functionality to external callers in Chrome OS. This service also manages
+// state and lifetime of the instances which implement that functionality.
+// This service expects to be called on the sequence it was first called on
+// which is bound to the Main/UI sequence in production code.
+class RemotingService {
+ public:
+  static RemotingService& Get();
+  virtual ~RemotingService() = default;
+
+  // Must be called on the sequence the service was created on.
+  virtual RemoteSupportHostAsh& GetSupportHost() = 0;
+
+  // Can be called on any sequence.
+  virtual std::unique_ptr<ChromotingHostContext> CreateHostContext() = 0;
+
+  // Can be called on any sequence.
+  virtual std::unique_ptr<PolicyWatcher> CreatePolicyWatcher() = 0;
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_CHROMEOS_REMOTING_SERVICE_H_
diff --git a/remoting/host/it2me/BUILD.gn b/remoting/host/it2me/BUILD.gn
index b320b88..1d2be79c 100644
--- a/remoting/host/it2me/BUILD.gn
+++ b/remoting/host/it2me/BUILD.gn
@@ -24,6 +24,18 @@
   ]
 }
 
+source_set("helpers") {
+  sources = [
+    "it2me_helpers.cc",
+    "it2me_helpers.h",
+  ]
+  deps = [
+    ":constants",
+    "//base",
+    "//remoting/base",
+  ]
+}
+
 source_set("common") {
   sources = [
     "it2me_confirmation_dialog.h",
@@ -56,7 +68,10 @@
     "//remoting/build/config:version",
   ]
 
-  public_deps = [ ":constants" ]
+  public_deps = [
+    ":constants",
+    ":helpers",
+  ]
 
   deps = [
     "//base:i18n",
@@ -110,11 +125,18 @@
         "it2me_native_messaging_host_lacros.h",
       ]
       deps = [
+        ":constants",
+        ":helpers",
         "//base",
+        "//chromeos/crosapi/mojom",
+        "//chromeos/lacros",
         "//extensions/browser/api/messaging:native_messaging",
+        "//remoting/protocol:errors",
       ]
     } else {
       sources += [
+        "it2me_native_messaging_host_ash.cc",
+        "it2me_native_messaging_host_ash.h",
         "it2me_native_messaging_host_chromeos.cc",
         "it2me_native_messaging_host_chromeos.h",
       ]
@@ -124,6 +146,7 @@
         "//extensions/browser/api/messaging:native_messaging",
         "//remoting/base:base",
         "//remoting/host:common",
+        "//remoting/host/mojom:mojom",
         "//skia",
       ]
 
diff --git a/remoting/host/it2me/DEPS b/remoting/host/it2me/DEPS
index 52537fb8..f311d93 100644
--- a/remoting/host/it2me/DEPS
+++ b/remoting/host/it2me/DEPS
@@ -4,5 +4,9 @@
     # targets. It should be removed when support for classic ash is removed;
     # which means that remoting never touches ozone.
     "+ui/ozone/public/ozone_platform.h",
-  ]
+  ],
+  "it2me_native_messaging_host_lacros.cc": [
+    "+chromeos/crosapi/mojom/remoting.mojom.h",
+    "+chromeos/lacros/lacros_chrome_service_impl.h",
+  ],
 }
diff --git a/remoting/host/it2me/it2me_constants.cc b/remoting/host/it2me/it2me_constants.cc
index dafba874..33a86fd 100644
--- a/remoting/host/it2me/it2me_constants.cc
+++ b/remoting/host/it2me/it2me_constants.cc
@@ -6,6 +6,8 @@
 
 namespace remoting {
 
+const char kFeatureAccessTokenAuth[] = "accessTokenAuth";
+
 const char kMessageId[] = "id";
 const char kMessageType[] = "type";
 
diff --git a/remoting/host/it2me/it2me_constants.h b/remoting/host/it2me/it2me_constants.h
index 170ce67..e1bb9933 100644
--- a/remoting/host/it2me/it2me_constants.h
+++ b/remoting/host/it2me/it2me_constants.h
@@ -7,6 +7,23 @@
 
 namespace remoting {
 
+// These state values are defined in the website client as well.  Remember to
+// update both enums when making changes.
+enum class It2MeHostState {
+  kDisconnected,
+  kStarting,
+  kRequestedAccessCode,
+  kReceivedAccessCode,
+  kConnecting,
+  kConnected,
+  kError,
+  kInvalidDomainError,
+};
+
+// Indicates that an OAuth access token can be provided to the host which will
+// use it for service requests (e.g. ICE config, signaling, host registration).
+extern const char kFeatureAccessTokenAuth[];
+
 // ID used to identify the current message. Must be included in the response if
 // the sender includes it.
 extern const char kMessageId[];
diff --git a/remoting/host/it2me/it2me_helpers.cc b/remoting/host/it2me/it2me_helpers.cc
new file mode 100644
index 0000000..51cfcec
--- /dev/null
+++ b/remoting/host/it2me/it2me_helpers.cc
@@ -0,0 +1,61 @@
+// 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 "remoting/host/it2me/it2me_helpers.h"
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/notreached.h"
+#include "base/values.h"
+#include "remoting/base/name_value_map.h"
+#include "remoting/host/it2me/it2me_constants.h"
+
+namespace remoting {
+
+namespace {
+
+const NameMapElement<It2MeHostState> kIt2MeHostStates[] = {
+    {It2MeHostState::kDisconnected, kHostStateDisconnected},
+    {It2MeHostState::kStarting, kHostStateStarting},
+    {It2MeHostState::kRequestedAccessCode, kHostStateRequestedAccessCode},
+    {It2MeHostState::kReceivedAccessCode, kHostStateReceivedAccessCode},
+    {It2MeHostState::kConnecting, kHostStateConnecting},
+    {It2MeHostState::kConnected, kHostStateConnected},
+    {It2MeHostState::kError, kHostStateError},
+    {It2MeHostState::kInvalidDomainError, kHostStateDomainError},
+};
+
+}
+
+bool ParseIt2MeNativeMessageJson(const std::string& message,
+                                 std::string& message_type,
+                                 base::Value& dictionary_value) {
+  auto opt_message = base::JSONReader::Read(message);
+  if (!opt_message.has_value()) {
+    LOG(ERROR) << "Received a message that's not valid JSON.";
+    return false;
+  }
+
+  auto message_value = std::move(opt_message.value());
+  if (!message_value.is_dict()) {
+    LOG(ERROR) << "Received a message that's not a dictionary.";
+    return false;
+  }
+
+  const std::string* message_type_value =
+      message_value.FindStringPath(kMessageType);
+  if (message_type_value) {
+    message_type = *message_type_value;
+  }
+
+  dictionary_value = std::move(message_value);
+
+  return true;
+}
+
+std::string It2MeHostStateToString(It2MeHostState host_state) {
+  return ValueToName(kIt2MeHostStates, host_state);
+}
+
+}  // namespace remoting
diff --git a/remoting/host/it2me/it2me_helpers.h b/remoting/host/it2me/it2me_helpers.h
new file mode 100644
index 0000000..1bf2e3c
--- /dev/null
+++ b/remoting/host/it2me/it2me_helpers.h
@@ -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.
+
+#ifndef REMOTING_HOST_IT2ME_IT2ME_HELPERS_H_
+#define REMOTING_HOST_IT2ME_IT2ME_HELPERS_H_
+
+#include <string>
+
+namespace base {
+class Value;
+}
+
+namespace remoting {
+
+enum class It2MeHostState;
+
+// Parses a serialized JSON message from either the CRD website client or an
+// It2MeNativeMessageHost instance.  Returns true if the message was
+// successfully parsed. If parsing fails, the out params will not be valid.
+bool ParseIt2MeNativeMessageJson(const std::string& message,
+                                 std::string& message_type,
+                                 base::Value& parsed_message);
+
+// Provides a human readable name for a given It2MeHostState. This is used both
+// for logging and in host state changed JSON messages.
+std::string It2MeHostStateToString(It2MeHostState host_state);
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_IT2ME_IT2ME_HELPERS_H_
diff --git a/remoting/host/it2me/it2me_host.cc b/remoting/host/it2me/it2me_host.cc
index c6216de..cc642ab 100644
--- a/remoting/host/it2me/it2me_host.cc
+++ b/remoting/host/it2me/it2me_host.cc
@@ -27,6 +27,7 @@
 #include "remoting/host/host_secret.h"
 #include "remoting/host/host_status_logger.h"
 #include "remoting/host/it2me/it2me_confirmation_dialog.h"
+#include "remoting/host/it2me/it2me_helpers.h"
 #include "remoting/host/it2me_desktop_environment.h"
 #include "remoting/protocol/auth_util.h"
 #include "remoting/protocol/chromium_port_allocator_factory.h"
@@ -134,9 +135,9 @@
     const protocol::IceConfig& ice_config,
     CreateDeferredConnectContext create_context) {
   DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
-  DCHECK_EQ(kDisconnected, state_);
+  DCHECK_EQ(It2MeHostState::kDisconnected, state_);
 
-  SetState(kStarting, ErrorCode::OK);
+  SetState(It2MeHostState::kStarting, ErrorCode::OK);
 
   auto connection_context = std::move(create_context).Run(host_context_.get());
   log_to_server_ = std::move(connection_context->log_to_server);
@@ -155,7 +156,7 @@
       }
     }
     if (!matched) {
-      SetState(kInvalidDomainError, ErrorCode::OK);
+      SetState(It2MeHostState::kInvalidDomainError, ErrorCode::OK);
       return;
     }
   }
@@ -237,7 +238,7 @@
   signal_strategy_->Connect();
   host_->Start(username);
 
-  SetState(kRequestedAccessCode, ErrorCode::OK);
+  SetState(It2MeHostState::kRequestedAccessCode, ErrorCode::OK);
   return;
 }
 
@@ -248,10 +249,10 @@
   if (failed_login_attempts_ == kMaxLoginAttempts) {
     DisconnectOnNetworkThread();
   } else if (connecting_jid_ == NormalizeSignalingId(jid)) {
-    DCHECK_EQ(state_, kConnecting);
+    DCHECK_EQ(state_, It2MeHostState::kConnecting);
     connecting_jid_.clear();
     confirmation_dialog_proxy_.reset();
-    SetState(kReceivedAccessCode, ErrorCode::OK);
+    SetState(It2MeHostState::kReceivedAccessCode, ErrorCode::OK);
   }
 }
 
@@ -260,7 +261,7 @@
 
   // ChromotingHost doesn't allow concurrent connections and the host is
   // destroyed in OnClientDisconnected() after the first connection.
-  CHECK_NE(state_, kConnected);
+  CHECK_NE(state_, It2MeHostState::kConnected);
 
   std::string client_username;
   if (!SplitSignalingIdResource(jid, &client_username, /*resource=*/nullptr)) {
@@ -275,7 +276,7 @@
       FROM_HERE, base::BindOnce(&It2MeHost::Observer::OnClientAuthenticated,
                                 observer_, client_username));
 
-  SetState(kConnected, ErrorCode::OK);
+  SetState(It2MeHostState::kConnected, ErrorCode::OK);
 }
 
 void It2MeHost::OnClientDisconnected(const std::string& jid) {
@@ -417,40 +418,48 @@
   DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
 
   switch (state_) {
-    case kDisconnected:
-      DCHECK(state == kStarting ||
-             state == kError) << state;
+    case It2MeHostState::kDisconnected:
+      DCHECK(state == It2MeHostState::kStarting ||
+             state == It2MeHostState::kError)
+          << It2MeHostStateToString(state);
       break;
-    case kStarting:
-      DCHECK(state == kRequestedAccessCode ||
-             state == kDisconnected ||
-             state == kError ||
-             state == kInvalidDomainError) << state;
+    case It2MeHostState::kStarting:
+      DCHECK(state == It2MeHostState::kRequestedAccessCode ||
+             state == It2MeHostState::kDisconnected ||
+             state == It2MeHostState::kError ||
+             state == It2MeHostState::kInvalidDomainError)
+          << It2MeHostStateToString(state);
       break;
-    case kRequestedAccessCode:
-      DCHECK(state == kReceivedAccessCode ||
-             state == kDisconnected ||
-             state == kError) << state;
+    case It2MeHostState::kRequestedAccessCode:
+      DCHECK(state == It2MeHostState::kReceivedAccessCode ||
+             state == It2MeHostState::kDisconnected ||
+             state == It2MeHostState::kError)
+          << It2MeHostStateToString(state);
       break;
-    case kReceivedAccessCode:
-      DCHECK(state == kConnecting ||
-             state == kDisconnected ||
-             state == kError) << state;
+    case It2MeHostState::kReceivedAccessCode:
+      DCHECK(state == It2MeHostState::kConnecting ||
+             state == It2MeHostState::kDisconnected ||
+             state == It2MeHostState::kError)
+          << It2MeHostStateToString(state);
       break;
-    case kConnecting:
-      DCHECK(state == kConnected ||
-             state == kDisconnected ||
-             state == kError) << state;
+    case It2MeHostState::kConnecting:
+      DCHECK(state == It2MeHostState::kConnected ||
+             state == It2MeHostState::kDisconnected ||
+             state == It2MeHostState::kError)
+          << It2MeHostStateToString(state);
       break;
-    case kConnected:
-      DCHECK(state == kDisconnected ||
-             state == kError) << state;
+    case It2MeHostState::kConnected:
+      DCHECK(state == It2MeHostState::kDisconnected ||
+             state == It2MeHostState::kError)
+          << It2MeHostStateToString(state);
       break;
-    case kError:
-      DCHECK(state == kDisconnected) << state;
+    case It2MeHostState::kError:
+      DCHECK(state == It2MeHostState::kDisconnected)
+          << It2MeHostStateToString(state);
       break;
-    case kInvalidDomainError:
-      DCHECK(state == kDisconnected) << state;
+    case It2MeHostState::kInvalidDomainError:
+      DCHECK(state == It2MeHostState::kDisconnected)
+          << It2MeHostStateToString(state);
       break;
   };
 
@@ -463,8 +472,10 @@
 }
 
 bool It2MeHost::IsRunning() const {
-  return state_ == kRequestedAccessCode || state_ == kReceivedAccessCode ||
-         state_ == kConnected || state_ == kConnecting;
+  return state_ == It2MeHostState::kRequestedAccessCode ||
+         state_ == It2MeHostState::kReceivedAccessCode ||
+         state_ == It2MeHostState::kConnected ||
+         state_ == It2MeHostState::kConnecting;
 }
 
 void It2MeHost::OnReceivedSupportID(const std::string& support_id,
@@ -473,7 +484,7 @@
   DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
 
   if (error_code != ErrorCode::OK) {
-    SetState(kError, error_code);
+    SetState(It2MeHostState::kError, error_code);
     DisconnectOnNetworkThread();
     return;
   }
@@ -486,7 +497,7 @@
   std::string local_certificate = host_key_pair_->GenerateCertificate();
   if (local_certificate.empty()) {
     LOG(ERROR) << "Failed to generate host certificate.";
-    SetState(kError, ErrorCode::HOST_CERTIFICATE_ERROR);
+    SetState(It2MeHostState::kError, ErrorCode::HOST_CERTIFICATE_ERROR);
     DisconnectOnNetworkThread();
     return;
   }
@@ -503,7 +514,7 @@
       FROM_HERE, base::BindOnce(&It2MeHost::Observer::OnStoreAccessCode,
                                 observer_, access_code, lifetime));
 
-  SetState(kReceivedAccessCode, ErrorCode::OK);
+  SetState(It2MeHostState::kReceivedAccessCode, ErrorCode::OK);
 }
 
 void It2MeHost::DisconnectOnNetworkThread() {
@@ -511,7 +522,7 @@
 
   // Disconnect() may be called even after the host has already been stopped.
   // Ignore repeated calls.
-  if (state_ == kDisconnected) {
+  if (state_ == It2MeHostState::kDisconnected) {
     return;
   }
 
@@ -532,7 +543,7 @@
   host_context_->ui_task_runner()->DeleteSoon(
       FROM_HERE, desktop_environment_factory_.release());
 
-  SetState(kDisconnected, ErrorCode::OK);
+  SetState(It2MeHostState::kDisconnected, ErrorCode::OK);
 }
 
 void It2MeHost::ValidateConnectionDetails(
@@ -582,8 +593,8 @@
 
   // If we receive valid connection details multiple times, then we don't know
   // which remote user (if either) is valid so disconnect everyone.
-  if (state_ != kReceivedAccessCode) {
-    DCHECK_EQ(kConnecting, state_);
+  if (state_ != It2MeHostState::kReceivedAccessCode) {
+    DCHECK_EQ(It2MeHostState::kConnecting, state_);
     LOG(ERROR) << "Received too many connection requests.";
     std::move(result_callback)
         .Run(ValidationResult::ERROR_TOO_MANY_CONNECTIONS);
@@ -593,7 +604,7 @@
 
   HOST_LOG << "Client " << client_username << " connecting.";
   connecting_jid_ = remote_jid;
-  SetState(kConnecting, ErrorCode::OK);
+  SetState(It2MeHostState::kConnecting, ErrorCode::OK);
 
   // Show a confirmation dialog to the user to allow them to confirm/reject it.
   // If dialogs are suppressed, just call the callback directly.
diff --git a/remoting/host/it2me/it2me_host.h b/remoting/host/it2me/it2me_host.h
index 7ec16f9..ae8d3fb9 100644
--- a/remoting/host/it2me/it2me_host.h
+++ b/remoting/host/it2me/it2me_host.h
@@ -16,6 +16,7 @@
 #include "remoting/host/host_status_observer.h"
 #include "remoting/host/it2me/it2me_confirmation_dialog.h"
 #include "remoting/host/it2me/it2me_confirmation_dialog_proxy.h"
+#include "remoting/host/it2me/it2me_constants.h"
 #include "remoting/host/register_support_host_request.h"
 #include "remoting/protocol/errors.h"
 #include "remoting/protocol/port_range.h"
@@ -41,19 +42,6 @@
 struct IceConfig;
 }  // namespace protocol
 
-// These state values are duplicated in host_session.js.  Remember to update
-// both copies when making changes.
-enum It2MeHostState {
-  kDisconnected,
-  kStarting,
-  kRequestedAccessCode,
-  kReceivedAccessCode,
-  kConnecting,
-  kConnected,
-  kError,
-  kInvalidDomainError,
-};
-
 // Internal implementation of the plugin's It2Me host function.
 class It2MeHost : public base::RefCountedThreadSafe<It2MeHost>,
                   public HostStatusObserver {
@@ -188,7 +176,7 @@
   std::unique_ptr<SignalStrategy> signal_strategy_;
   std::unique_ptr<LogToServer> log_to_server_;
 
-  It2MeHostState state_ = kDisconnected;
+  It2MeHostState state_ = It2MeHostState::kDisconnected;
 
   scoped_refptr<RsaKeyPair> host_key_pair_;
   std::unique_ptr<RegisterSupportHostRequest> register_request_;
diff --git a/remoting/host/it2me/it2me_native_messaging_host.cc b/remoting/host/it2me/it2me_native_messaging_host.cc
index 4b1e1ac7..804df5b7 100644
--- a/remoting/host/it2me/it2me_native_messaging_host.cc
+++ b/remoting/host/it2me/it2me_native_messaging_host.cc
@@ -26,12 +26,12 @@
 #include "net/socket/client_socket_factory.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "remoting/base/auto_thread_task_runner.h"
-#include "remoting/base/name_value_map.h"
 #include "remoting/base/passthrough_oauth_token_getter.h"
 #include "remoting/host/chromoting_host_context.h"
 #include "remoting/host/host_exit_codes.h"
 #include "remoting/host/it2me/it2me_confirmation_dialog.h"
 #include "remoting/host/it2me/it2me_constants.h"
+#include "remoting/host/it2me/it2me_helpers.h"
 #include "remoting/host/policy_watcher.h"
 #include "remoting/host/remoting_register_support_host_request.h"
 #include "remoting/host/xmpp_register_support_host_request.h"
@@ -57,17 +57,6 @@
 
 namespace {
 
-const NameMapElement<It2MeHostState> kIt2MeHostStates[] = {
-    {kDisconnected, kHostStateDisconnected},
-    {kStarting, kHostStateStarting},
-    {kRequestedAccessCode, kHostStateRequestedAccessCode},
-    {kReceivedAccessCode, kHostStateReceivedAccessCode},
-    {kConnecting, kHostStateConnecting},
-    {kConnected, kHostStateConnected},
-    {kError, kHostStateError},
-    {kInvalidDomainError, kHostStateDomainError},
-};
-
 #if defined(OS_WIN)
 const base::FilePath::CharType kBaseHostBinaryName[] =
     FILE_PATH_LITERAL("remote_assistance_host.exe");
@@ -421,7 +410,8 @@
     incoming_message_callback_.Run(iq);
   } else {
     LOG(WARNING) << "Dropping message because signaling is not connected. "
-                 << "Current It2MeHost state: " << state_;
+                 << "Current It2MeHost state: "
+                 << It2MeHostStateToString(state_);
   }
   SendMessageToClient(std::move(response));
 }
@@ -466,24 +456,24 @@
   std::unique_ptr<base::DictionaryValue> message(new base::DictionaryValue());
 
   message->SetString(kMessageType, kHostStateChangedMessage);
-  message->SetString(kState, HostStateToString(state));
+  message->SetString(kState, It2MeHostStateToString(state));
 
   switch (state_) {
-    case kReceivedAccessCode:
+    case It2MeHostState::kReceivedAccessCode:
       message->SetString(kAccessCode, access_code_);
       message->SetInteger(kAccessCodeLifetime,
                           access_code_lifetime_.InSeconds());
       break;
 
-    case kConnected:
+    case It2MeHostState::kConnected:
       message->SetString(kClient, client_username_);
       break;
 
-    case kDisconnected:
+    case It2MeHostState::kDisconnected:
       client_username_.clear();
       break;
 
-    case kError:
+    case It2MeHostState::kError:
       // kError is an internal-only state, sent to the web-app by a separate
       // "error" message so that errors that occur before the "connect" message
       // is sent can be communicated.
@@ -543,13 +533,17 @@
 }
 
 /* static */
-std::string It2MeNativeMessagingHost::HostStateToString(
-    It2MeHostState host_state) {
-  return ValueToName(kIt2MeHostStates, host_state);
-}
 
 void It2MeNativeMessagingHost::OnPolicyUpdate(
     std::unique_ptr<base::DictionaryValue> policies) {
+  // If an It2MeHost exists, provide it with the updated policies first.
+  // That way it won't appear that the policies have changed if the pending
+  // connect callback is run. If done the other way around, there is a race
+  // condition which could cause the connection to be canceled before it starts.
+  if (it2me_host_) {
+    it2me_host_->OnPolicyUpdate(std::move(policies));
+  }
+
   if (!policy_received_) {
     policy_received_ = true;
 
@@ -557,10 +551,6 @@
       std::move(pending_connect_).Run();
     }
   }
-
-  if (it2me_host_) {
-    it2me_host_->OnPolicyUpdate(std::move(policies));
-  }
 }
 
 absl::optional<bool>
diff --git a/remoting/host/it2me/it2me_native_messaging_host.h b/remoting/host/it2me/it2me_native_messaging_host.h
index a077014..93b2b54 100644
--- a/remoting/host/it2me/it2me_native_messaging_host.h
+++ b/remoting/host/it2me/it2me_native_messaging_host.h
@@ -64,8 +64,6 @@
   // processed.
   void SetPolicyErrorClosureForTesting(base::OnceClosure closure);
 
-  static std::string HostStateToString(It2MeHostState host_state);
-
  private:
   // These "Process.." methods handle specific request types. The |response|
   // dictionary is pre-filled by ProcessMessage() with the parts of the
diff --git a/remoting/host/it2me/it2me_native_messaging_host_ash.cc b/remoting/host/it2me/it2me_native_messaging_host_ash.cc
new file mode 100644
index 0000000..8282f28
--- /dev/null
+++ b/remoting/host/it2me/it2me_native_messaging_host_ash.cc
@@ -0,0 +1,247 @@
+// 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 <utility>
+
+#include "remoting/host/it2me/it2me_native_messaging_host_ash.h"
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/lazy_instance.h"
+#include "base/stl_util.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "extensions/browser/api/messaging/native_message_host.h"
+#include "remoting/host/chromoting_host_context.h"
+#include "remoting/host/it2me/it2me_helpers.h"
+#include "remoting/host/it2me/it2me_native_messaging_host.h"
+#include "remoting/host/policy_watcher.h"
+
+namespace remoting {
+
+It2MeNativeMessageHostAsh::It2MeNativeMessageHostAsh() = default;
+It2MeNativeMessageHostAsh::~It2MeNativeMessageHostAsh() = default;
+
+mojo::PendingReceiver<mojom::SupportHostObserver>
+It2MeNativeMessageHostAsh::Start(
+    std::unique_ptr<ChromotingHostContext> context,
+    std::unique_ptr<PolicyWatcher> policy_watcher) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!native_message_host_);
+
+  // Create the remote IPC channel before starting the NMH so any errors are
+  // queued for sending once the receiver end of the channel is bound.
+  mojo::PendingReceiver<mojom::SupportHostObserver> observer =
+      remote_.BindNewPipeAndPassReceiver();
+
+  std::unique_ptr<It2MeHostFactory> host_factory(new It2MeHostFactory());
+  native_message_host_ = std::make_unique<It2MeNativeMessagingHost>(
+      /*needs_elevation=*/false, std::move(policy_watcher), std::move(context),
+      std::move(host_factory));
+  native_message_host_->Start(this);
+
+  return observer;
+}
+
+void It2MeNativeMessageHostAsh::Connect(
+    mojom::SupportSessionParamsPtr params,
+    base::OnceClosure connected_callback,
+    base::OnceClosure disconnected_callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(native_message_host_);
+  DCHECK(!connected_callback_);
+  DCHECK(!disconnected_callback_);
+
+  connected_callback_ = std::move(connected_callback);
+  disconnected_callback_ = std::move(disconnected_callback);
+
+  base::Value message(base::Value::Type::DICTIONARY);
+  message.SetStringKey(kMessageType, kConnectMessage);
+
+  message.SetStringKey(kUserName, params->user_name);
+  message.SetStringKey(kAuthServiceWithToken, params->oauth_access_token);
+
+  std::string message_json;
+  base::JSONWriter::Write(message, &message_json);
+  native_message_host_->OnMessage(message_json);
+}
+
+void It2MeNativeMessageHostAsh::Disconnect() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  base::Value message(base::Value::Type::DICTIONARY);
+  message.SetStringKey(kMessageType, kDisconnectMessage);
+
+  std::string message_json;
+  base::JSONWriter::Write(message, &message_json);
+  native_message_host_->OnMessage(message_json);
+
+  // Notify the owner that the host has been disconnected.  This will result in
+  // the destruction of this object so do not access member variables after this
+  // callback is run.
+  std::move(disconnected_callback_).Run();
+}
+
+void It2MeNativeMessageHostAsh::PostMessageFromNativeHost(
+    const std::string& message) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  std::string type;
+  base::Value contents;
+  if (!ParseIt2MeNativeMessageJson(message, type, contents)) {
+    CloseChannel(std::string());
+    return;
+  }
+
+  if (type.empty()) {
+    LOG(ERROR) << "'type' not found in request.";
+    CloseChannel(ErrorCodeToString(protocol::ErrorCode::INCOMPATIBLE_PROTOCOL));
+    return;
+  }
+
+  if (type == kConnectResponse) {
+    HandleConnectResponse();
+  } else if (type == kDisconnectResponse) {
+    HandleDisconnectResponse();
+  } else if (type == kIncomingIqResponse) {
+    // These responses do not need to be handled as the Lacros NMH sends a
+    // response when the request message is first received.
+  } else if (type == kHostStateChangedMessage) {
+    HandleHostStateChangeMessage(std::move(contents));
+  } else if (type == kNatPolicyChangedMessage) {
+    HandleNatPolicyChangedMessage(std::move(contents));
+  } else if (type == kPolicyErrorMessage) {
+    HandlePolicyErrorMessage(std::move(contents));
+  } else if (type == kErrorMessage) {
+    HandleErrorMessage(std::move(contents));
+  } else {
+    LOG(ERROR) << "Unsupported message type: " << type;
+    CloseChannel(ErrorCodeToString(protocol::ErrorCode::INCOMPATIBLE_PROTOCOL));
+  }
+}
+
+void It2MeNativeMessageHostAsh::CloseChannel(const std::string& error_message) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  LOG_IF(ERROR, !error_message.empty())
+      << "CloseChannel called with error: " << error_message;
+  Disconnect();
+}
+
+void It2MeNativeMessageHostAsh::HandleConnectResponse() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  std::move(connected_callback_).Run();
+}
+
+void It2MeNativeMessageHostAsh::HandleDisconnectResponse() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  remote_->OnHostStateDisconnected();
+}
+
+void It2MeNativeMessageHostAsh::HandleHostStateChangeMessage(
+    base::Value message) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  std::string* new_state = message.FindStringKey(kState);
+  if (!new_state) {
+    LOG(ERROR) << "Missing |" << kState << "| value in message.";
+    CloseChannel(ErrorCodeToString(protocol::ErrorCode::INCOMPATIBLE_PROTOCOL));
+    return;
+  }
+
+  if (*new_state == kHostStateStarting) {
+    remote_->OnHostStateStarting();
+  } else if (*new_state == kHostStateDisconnected) {
+    remote_->OnHostStateDisconnected();
+  } else if (*new_state == kHostStateRequestedAccessCode) {
+    remote_->OnHostStateRequestedAccessCode();
+  } else if (*new_state == kHostStateReceivedAccessCode) {
+    std::string* access_code = message.FindStringKey(kAccessCode);
+    if (!access_code) {
+      LOG(ERROR) << "Missing |" << kAccessCode << "| value in message.";
+      CloseChannel(
+          ErrorCodeToString(protocol::ErrorCode::INCOMPATIBLE_PROTOCOL));
+      return;
+    }
+    absl::optional<int> access_code_lifetime =
+        message.FindIntKey(kAccessCodeLifetime);
+    if (!access_code_lifetime) {
+      LOG(ERROR) << "Missing |" << kAccessCodeLifetime << "| value in message.";
+      CloseChannel(
+          ErrorCodeToString(protocol::ErrorCode::INCOMPATIBLE_PROTOCOL));
+      return;
+    }
+    remote_->OnHostStateReceivedAccessCode(
+        *access_code,
+        base::TimeDelta::FromSeconds(access_code_lifetime.value()));
+  } else if (*new_state == kHostStateConnecting) {
+    remote_->OnHostStateConnecting();
+  } else if (*new_state == kHostStateConnected) {
+    std::string* remote_username = message.FindStringKey(kClient);
+    if (!remote_username) {
+      LOG(ERROR) << "Missing |" << kClient << "| value in message.";
+      CloseChannel(
+          ErrorCodeToString(protocol::ErrorCode::INCOMPATIBLE_PROTOCOL));
+      return;
+    }
+    remote_->OnHostStateConnected(*remote_username);
+  } else if (*new_state == kHostStateError) {
+    absl::optional<int> error_code = message.FindIntKey(kErrorMessageCode);
+    if (!error_code) {
+      LOG(ERROR) << "Missing |" << kErrorMessageCode << "| value in message.";
+      CloseChannel(
+          ErrorCodeToString(protocol::ErrorCode::INCOMPATIBLE_PROTOCOL));
+      return;
+    }
+    remote_->OnHostStateError(error_code.value());
+  } else if (*new_state == kHostStateDomainError) {
+    remote_->OnInvalidDomainError();
+  } else {
+    NOTREACHED() << "Unknown state: " << *new_state;
+    CloseChannel(ErrorCodeToString(protocol::ErrorCode::INCOMPATIBLE_PROTOCOL));
+    return;
+  }
+}
+
+void It2MeNativeMessageHostAsh::HandleNatPolicyChangedMessage(
+    base::Value message) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  absl::optional<bool> nat_enabled =
+      message.FindBoolKey(kNatPolicyChangedMessageNatEnabled);
+  if (!nat_enabled.has_value()) {
+    LOG(ERROR) << "Missing |" << kNatPolicyChangedMessageNatEnabled
+               << "| value in message.";
+    CloseChannel(ErrorCodeToString(protocol::ErrorCode::INCOMPATIBLE_PROTOCOL));
+    return;
+  }
+
+  absl::optional<bool> relay_enabled =
+      message.FindBoolKey(kNatPolicyChangedMessageRelayEnabled);
+  if (!nat_enabled.has_value()) {
+    LOG(ERROR) << "Missing |" << kNatPolicyChangedMessageRelayEnabled
+               << "| value in message.";
+    CloseChannel(ErrorCodeToString(protocol::ErrorCode::INCOMPATIBLE_PROTOCOL));
+    return;
+  }
+
+  mojom::NatPolicyStatePtr nat_policy = mojom::NatPolicyState::New();
+  nat_policy->nat_enabled = nat_enabled.value();
+  nat_policy->relay_enabled = relay_enabled.value();
+  remote_->OnNatPolicyChanged(std::move(nat_policy));
+}
+
+void It2MeNativeMessageHostAsh::HandlePolicyErrorMessage(base::Value message) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  remote_->OnPolicyError();
+}
+
+void It2MeNativeMessageHostAsh::HandleErrorMessage(base::Value message) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  absl::optional<int> error_code = message.FindIntKey(kErrorMessageCode);
+  if (!error_code.has_value()) {
+    LOG(ERROR) << "Missing |" << kErrorMessageCode << "| value in message.";
+    CloseChannel(ErrorCodeToString(protocol::ErrorCode::INCOMPATIBLE_PROTOCOL));
+    return;
+  }
+
+  remote_->OnHostStateError(error_code.value());
+}
+
+}  // namespace remoting
diff --git a/remoting/host/it2me/it2me_native_messaging_host_ash.h b/remoting/host/it2me/it2me_native_messaging_host_ash.h
new file mode 100644
index 0000000..3c3d8df
--- /dev/null
+++ b/remoting/host/it2me/it2me_native_messaging_host_ash.h
@@ -0,0 +1,89 @@
+// 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 REMOTING_HOST_IT2ME_IT2ME_NATIVE_MESSAGING_HOST_ASH_H_
+#define REMOTING_HOST_IT2ME_IT2ME_NATIVE_MESSAGING_HOST_ASH_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/sequence_checker.h"
+#include "extensions/browser/api/messaging/native_message_host.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "remoting/host/mojom/remote_support.mojom.h"
+
+namespace base {
+class Value;
+}
+
+namespace extensions {
+class NativeMessageHost;
+}
+
+namespace remoting {
+
+class ChromotingHostContext;
+class PolicyWatcher;
+
+// This class wraps the It2MeNativeMessageHost instance used on other platforms
+// and provides a way to interact with it using Mojo IPC.  This instance
+// receives messages from the wrapped NMH by observing it via the Client
+// interface.  The messages and events are then converted from JSON to Mojo and
+// forwarded to the It2MeNativeMessageHostLacros instance over IPC.
+// All interactions with it must occur on the sequence it was created on.
+class It2MeNativeMessageHostAsh : public extensions::NativeMessageHost::Client {
+ public:
+  It2MeNativeMessageHostAsh();
+  It2MeNativeMessageHostAsh(const It2MeNativeMessageHostAsh&) = delete;
+  It2MeNativeMessageHostAsh& operator=(const It2MeNativeMessageHostAsh&) =
+      delete;
+  ~It2MeNativeMessageHostAsh() override;
+
+  // Creates a new NMH instance, creates a new SupportHostObserver remote and
+  // returns the pending_remote.  Start() must be called before the first call
+  // to |Connect()|.
+  mojo::PendingReceiver<mojom::SupportHostObserver> Start(
+      std::unique_ptr<ChromotingHostContext> context,
+      std::unique_ptr<PolicyWatcher> policy_watcher);
+
+  // extensions::NativeMessageHost::Client.
+  void PostMessageFromNativeHost(const std::string& message) override;
+  void CloseChannel(const std::string& error_message) override;
+
+  // Begins the connection process using the wrapped native message host.
+  // |connected_callback| is run after the connection process has completed.
+  void Connect(mojom::SupportSessionParamsPtr params,
+               base::OnceClosure connected_callback,
+               base::OnceClosure disconnected_callback);
+  // Disconnects an active session if one exists.
+  void Disconnect();
+
+ private:
+  // Handlers for messages received from the wrapped native message host.
+  void HandleConnectResponse();
+  void HandleDisconnectResponse();
+  void HandleHostStateChangeMessage(base::Value message);
+  void HandleNatPolicyChangedMessage(base::Value message);
+  void HandlePolicyErrorMessage(base::Value message);
+  void HandleErrorMessage(base::Value message);
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::OnceClosure connected_callback_ GUARDED_BY_CONTEXT(sequence_checker_);
+
+  base::OnceClosure disconnected_callback_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
+  std::unique_ptr<extensions::NativeMessageHost> native_message_host_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
+  mojo::Remote<mojom::SupportHostObserver> remote_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_HOST_IT2ME_IT2ME_NATIVE_MESSAGING_HOST_ASH_H_
diff --git a/remoting/host/it2me/it2me_native_messaging_host_lacros.cc b/remoting/host/it2me/it2me_native_messaging_host_lacros.cc
index a242356..c00cf9c 100644
--- a/remoting/host/it2me/it2me_native_messaging_host_lacros.cc
+++ b/remoting/host/it2me/it2me_native_messaging_host_lacros.cc
@@ -4,19 +4,426 @@
 
 #include "remoting/host/it2me/it2me_native_messaging_host_lacros.h"
 
-#include <memory>
+#include <stddef.h>
 
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/memory/weak_ptr.h"
 #include "base/notreached.h"
+#include "base/sequence_checker.h"
+#include "base/single_thread_task_runner.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "chromeos/crosapi/mojom/remoting.mojom.h"
+#include "chromeos/lacros/lacros_chrome_service_impl.h"
+#include "extensions/browser/api/messaging/native_message_host.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "remoting/host/it2me/it2me_constants.h"
+#include "remoting/host/it2me/it2me_helpers.h"
+#include "remoting/host/mojom/remote_support.mojom.h"
+#include "remoting/protocol/errors.h"
 
 namespace remoting {
 
+namespace {
+
+constexpr int kInvalidMessageId = -1;
+
+int GetMessageId(const base::Value& message) {
+  const auto* message_id = message.FindPath(kMessageId);
+  return message_id ? message_id->GetInt() : kInvalidMessageId;
+}
+
+protocol::ErrorCode SupportSessionErrorToProtocolError(
+    mojom::StartSupportSessionError session_error) {
+  switch (session_error) {
+    case mojom::StartSupportSessionError::kExistingAdminSession:
+      return protocol::ErrorCode::EXISTING_ADMIN_SESSION;
+    default:
+      return protocol::ErrorCode::UNKNOWN_ERROR;
+  }
+}
+
+// This class is JSON <-> Mojo message converter which enables communication
+// between a Chrome Remote Desktop (CRD) client website instance running in
+// Lacros and the CRD components running in Ash. This class should not contain
+// any logic beyond message processing, validation, and conversion.
+// All interactions with it must occur on the sequence it was created on.
+class It2MeNativeMessagingHostLacros : public extensions::NativeMessageHost,
+                                       public mojom::SupportHostObserver {
+ public:
+  explicit It2MeNativeMessagingHostLacros(
+      scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+  ~It2MeNativeMessagingHostLacros() override;
+
+  // extensions::NativeMessageHost implementation.
+  void OnMessage(const std::string& message) override;
+  void Start(Client* client) override;
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner() const override;
+
+  // mojom::SupportHostObserver implementation.
+  void OnHostStateStarting() override;
+  void OnHostStateRequestedAccessCode() override;
+  void OnHostStateReceivedAccessCode(const std::string& access_code,
+                                     base::TimeDelta lifetime) override;
+  void OnHostStateConnecting() override;
+  void OnHostStateConnected(const std::string& remote_username) override;
+  void OnHostStateDisconnected() override;
+  void OnNatPolicyChanged(mojom::NatPolicyStatePtr policy_state) override;
+  void OnHostStateError(int64_t error_code) override;
+  void OnPolicyError() override;
+  void OnInvalidDomainError() override;
+
+  // Handlers for Mojo responses received from Ash.
+  void OnSupportHostDetailsReceived(mojom::SupportHostDetailsPtr host_details);
+  void OnSupportSessionStarted(mojom::StartSupportSessionResponsePtr response);
+
+ private:
+  void ProcessHello();
+  void ProcessConnect(base::Value message);
+  void ProcessDisconnect(base::Value message);
+  void SendMessageToClient(base::Value message) const;
+  void SendErrorAndExit(const protocol::ErrorCode error_code,
+                        int message_id = kInvalidMessageId) const;
+
+  void HandleHostStateChange(It2MeHostState state);
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  Client* client_ GUARDED_BY_CONTEXT(sequence_checker_) = nullptr;
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+  std::string access_code_;
+  base::TimeDelta access_code_lifetime_;
+  std::string remote_username_;
+  int connect_response_id_ = kInvalidMessageId;
+
+  bool hello_response_pending_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
+  mojom::SupportHostDetailsPtr host_details_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+  mojo::Receiver<mojom::SupportHostObserver> support_host_observer_
+      GUARDED_BY_CONTEXT(sequence_checker_){this};
+
+  base::WeakPtrFactory<It2MeNativeMessagingHostLacros> weak_factory_
+      GUARDED_BY_CONTEXT(sequence_checker_){this};
+};
+
+It2MeNativeMessagingHostLacros::It2MeNativeMessagingHostLacros(
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner)
+    : task_runner_(task_runner) {}
+
+It2MeNativeMessagingHostLacros::~It2MeNativeMessagingHostLacros() = default;
+
+void It2MeNativeMessagingHostLacros::OnMessage(const std::string& message) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  std::string type;
+  base::Value contents;
+  if (!ParseIt2MeNativeMessageJson(message, type, contents)) {
+    client_->CloseChannel(std::string());
+    return;
+  }
+
+  if (type.empty()) {
+    LOG(ERROR) << "'type' not found in request.";
+    SendErrorAndExit(protocol::ErrorCode::INCOMPATIBLE_PROTOCOL);
+    return;
+  }
+
+  if (type == kHelloMessage) {
+    ProcessHello();
+  } else if (type == kConnectMessage) {
+    ProcessConnect(std::move(contents));
+  } else if (type == kDisconnectMessage) {
+    ProcessDisconnect(std::move(contents));
+  } else {
+    LOG(ERROR) << "Unsupported request type: " << type;
+    SendErrorAndExit(protocol::ErrorCode::INCOMPATIBLE_PROTOCOL);
+  }
+}
+
+void It2MeNativeMessagingHostLacros::Start(Client* client) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  client_ = client;
+
+  auto* lacros_chrome_service = chromeos::LacrosChromeServiceImpl::Get();
+  if (!lacros_chrome_service->IsAvailable<crosapi::mojom::Remoting>()) {
+    LOG(ERROR) << "Remoting is not available in this version of the browser.";
+    client_->CloseChannel(std::string());
+    return;
+  }
+
+  lacros_chrome_service->GetRemote<crosapi::mojom::Remoting>()
+      ->GetSupportHostDetails(base::BindOnce(
+          &It2MeNativeMessagingHostLacros::OnSupportHostDetailsReceived,
+          weak_factory_.GetWeakPtr()));
+}
+
+scoped_refptr<base::SingleThreadTaskRunner>
+It2MeNativeMessagingHostLacros::task_runner() const {
+  return task_runner_;
+}
+
+void It2MeNativeMessagingHostLacros::OnHostStateStarting() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  HandleHostStateChange(It2MeHostState::kStarting);
+}
+
+void It2MeNativeMessagingHostLacros::OnHostStateRequestedAccessCode() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  HandleHostStateChange(It2MeHostState::kRequestedAccessCode);
+}
+
+void It2MeNativeMessagingHostLacros::OnHostStateReceivedAccessCode(
+    const std::string& access_code,
+    base::TimeDelta lifetime) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  access_code_ = access_code;
+  access_code_lifetime_ = lifetime;
+  HandleHostStateChange(It2MeHostState::kReceivedAccessCode);
+}
+
+void It2MeNativeMessagingHostLacros::OnHostStateConnecting() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  HandleHostStateChange(It2MeHostState::kConnecting);
+}
+
+void It2MeNativeMessagingHostLacros::OnHostStateConnected(
+    const std::string& remote_username) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  remote_username_ = remote_username;
+  HandleHostStateChange(It2MeHostState::kConnected);
+}
+
+void It2MeNativeMessagingHostLacros::OnHostStateDisconnected() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  HandleHostStateChange(It2MeHostState::kDisconnected);
+}
+
+void It2MeNativeMessagingHostLacros::OnNatPolicyChanged(
+    mojom::NatPolicyStatePtr policy_state) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  base::Value message(base::Value::Type::DICTIONARY);
+  message.SetStringKey(kMessageType, kNatPolicyChangedMessage);
+  message.SetBoolKey(kNatPolicyChangedMessageNatEnabled,
+                     policy_state->nat_enabled);
+  message.SetBoolKey(kNatPolicyChangedMessageRelayEnabled,
+                     policy_state->relay_enabled);
+  SendMessageToClient(std::move(message));
+}
+
+void It2MeNativeMessagingHostLacros::OnHostStateError(int64_t error_code) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_GE(error_code, 0);
+  LOG_IF(WARNING, error_code >= protocol::ErrorCode::ERROR_CODE_MAX)
+      << "|error_code| is greater than the max known error_code.";
+  SendErrorAndExit(static_cast<protocol::ErrorCode>(error_code));
+}
+
+void It2MeNativeMessagingHostLacros::OnPolicyError() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  base::Value response(base::Value::Type::DICTIONARY);
+  response.SetStringKey(kMessageType, kPolicyErrorMessage);
+  SendMessageToClient(std::move(response));
+  client_->CloseChannel(std::string());
+}
+
+void It2MeNativeMessagingHostLacros::OnInvalidDomainError() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  HandleHostStateChange(It2MeHostState::kInvalidDomainError);
+}
+
+void It2MeNativeMessagingHostLacros::HandleHostStateChange(
+    It2MeHostState state) {
+  base::Value message(base::Value::Type::DICTIONARY);
+  message.SetStringKey(kMessageType, kHostStateChangedMessage);
+
+  switch (state) {
+    case It2MeHostState::kStarting:
+      message.SetStringKey(kState, kHostStateStarting);
+      break;
+
+    case It2MeHostState::kRequestedAccessCode:
+      message.SetStringKey(kState, kHostStateRequestedAccessCode);
+      break;
+
+    case It2MeHostState::kReceivedAccessCode:
+      message.SetStringKey(kState, kHostStateReceivedAccessCode);
+      message.SetStringKey(kAccessCode, access_code_);
+      message.SetIntKey(kAccessCodeLifetime, access_code_lifetime_.InSeconds());
+      break;
+
+    case It2MeHostState::kConnecting:
+      message.SetStringKey(kState, kHostStateConnecting);
+      break;
+
+    case It2MeHostState::kConnected:
+      message.SetStringKey(kState, kHostStateConnected);
+      message.SetStringKey(kClient, remote_username_);
+      break;
+
+    case It2MeHostState::kDisconnected:
+      message.SetStringKey(kState, kHostStateDisconnected);
+      remote_username_.clear();
+      break;
+
+    case It2MeHostState::kInvalidDomainError:
+      message.SetStringKey(kState, kHostStateDomainError);
+      break;
+
+    default:
+      NOTREACHED();
+      break;
+  }
+
+  SendMessageToClient(std::move(message));
+}
+
+void It2MeNativeMessagingHostLacros::OnSupportHostDetailsReceived(
+    mojom::SupportHostDetailsPtr host_details) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  host_details_ = std::move(host_details);
+
+  if (hello_response_pending_) {
+    hello_response_pending_ = false;
+    ProcessHello();
+  }
+}
+
+void It2MeNativeMessagingHostLacros::OnSupportSessionStarted(
+    mojom::StartSupportSessionResponsePtr mojo_response) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (mojo_response->is_support_session_error()) {
+    SendErrorAndExit(SupportSessionErrorToProtocolError(
+                         mojo_response->get_support_session_error()),
+                     connect_response_id_);
+    return;
+  }
+
+  support_host_observer_.Bind(std::move(mojo_response->get_observer()));
+
+  base::Value response(base::Value::Type::DICTIONARY);
+  response.SetStringKey(kMessageType, kConnectResponse);
+
+  if (connect_response_id_ != kInvalidMessageId) {
+    response.SetIntKey(kMessageId, connect_response_id_);
+    connect_response_id_ = kInvalidMessageId;
+  }
+
+  SendMessageToClient(std::move(response));
+}
+
+void It2MeNativeMessagingHostLacros::ProcessHello() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (host_details_.is_null()) {
+    // We haven't received the host details from ash so wait before responding.
+    hello_response_pending_ = true;
+    return;
+  }
+
+  base::Value response(base::Value::Type::DICTIONARY);
+  response.SetStringKey(kMessageType, kHelloResponse);
+  response.SetStringKey(kHostVersion, host_details_.get()->host_version);
+
+  std::vector<base::Value> features;
+  for (const auto& feature : host_details_.get()->supported_features) {
+    features.emplace_back(base::Value(feature));
+  }
+  response.SetKey(kSupportedFeatures, base::Value(std::move(features)));
+  SendMessageToClient(std::move(response));
+}
+
+void It2MeNativeMessagingHostLacros::ProcessConnect(base::Value message) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  int message_id = GetMessageId(message);
+  if (message_id != kInvalidMessageId) {
+    connect_response_id_ = message_id;
+  }
+
+  mojom::SupportSessionParamsPtr session_params =
+      mojom::SupportSessionParams::New();
+
+  std::string* user_name = message.FindStringKey(kUserName);
+  if (!user_name) {
+    SendErrorAndExit(protocol::ErrorCode::INCOMPATIBLE_PROTOCOL, message_id);
+    return;
+  }
+  session_params->user_name = *user_name;
+
+  std::string* access_token = message.FindStringKey(kAuthServiceWithToken);
+  if (!access_token) {
+    SendErrorAndExit(protocol::ErrorCode::INCOMPATIBLE_PROTOCOL, message_id);
+    return;
+  }
+  session_params->oauth_access_token = *access_token;
+
+  // TODO(joedow): Add the ability to toggle the RemoteCommand settings for
+  // testing purposes. This should probably be encapsulated in a check that the
+  // machine is in developer-mode and/or !NDEBUG.
+
+  auto* lacros_chrome_service = chromeos::LacrosChromeServiceImpl::Get();
+  lacros_chrome_service->GetRemote<crosapi::mojom::Remoting>()
+      ->StartSupportSession(
+          std::move(session_params),
+          base::BindOnce(
+              &It2MeNativeMessagingHostLacros::OnSupportSessionStarted,
+              base::Unretained(this)));
+}
+
+void It2MeNativeMessagingHostLacros::ProcessDisconnect(base::Value message) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  support_host_observer_.reset();
+
+  base::Value response(base::Value::Type::DICTIONARY);
+  response.SetStringKey(kMessageType, kDisconnectResponse);
+  int message_id = GetMessageId(message);
+  if (message_id != kInvalidMessageId) {
+    response.SetIntKey(kMessageId, message_id);
+  }
+  SendMessageToClient(std::move(response));
+}
+
+void It2MeNativeMessagingHostLacros::SendMessageToClient(
+    base::Value message) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  std::string message_json;
+  base::JSONWriter::Write(message, &message_json);
+  client_->PostMessageFromNativeHost(message_json);
+}
+
+void It2MeNativeMessagingHostLacros::SendErrorAndExit(
+    const protocol::ErrorCode error_code,
+    int message_id) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  base::Value message(base::Value::Type::DICTIONARY);
+
+  message.SetStringKey(kMessageType, kErrorMessage);
+  if (message_id != kInvalidMessageId) {
+    message.SetIntKey(kMessageId, message_id);
+  }
+  message.SetStringKey(kErrorMessageCode, ErrorCodeToString(error_code));
+  message.SetStringKey(kErrorMessageDescription, ErrorCodeToString(error_code));
+
+  SendMessageToClient(std::move(message));
+
+  // Trigger a host shutdown by sending an empty message.
+  client_->CloseChannel(std::string());
+}
+
+}  // namespace
+
 std::unique_ptr<extensions::NativeMessageHost>
 CreateIt2MeNativeMessagingHostForLacros(
     scoped_refptr<base::SingleThreadTaskRunner> io_runnner,
     scoped_refptr<base::SingleThreadTaskRunner> ui_runnner) {
-  // TODO(joedow): Implement a remote support host for LaCrOS.
-  NOTIMPLEMENTED();
-  return nullptr;
+  // TODO(joedow): Verify |io_runner| is not required then remove it.
+  return std::make_unique<It2MeNativeMessagingHostLacros>(ui_runnner);
 }
 
 }  // namespace remoting
diff --git a/remoting/host/it2me/it2me_native_messaging_host_unittest.cc b/remoting/host/it2me/it2me_native_messaging_host_unittest.cc
index 42dc9cb..a45d457 100644
--- a/remoting/host/it2me/it2me_native_messaging_host_unittest.cc
+++ b/remoting/host/it2me/it2me_native_messaging_host_unittest.cc
@@ -31,6 +31,7 @@
 #include "remoting/base/auto_thread_task_runner.h"
 #include "remoting/host/chromoting_host_context.h"
 #include "remoting/host/it2me/it2me_constants.h"
+#include "remoting/host/it2me/it2me_helpers.h"
 #include "remoting/host/native_messaging/log_message_handler.h"
 #include "remoting/host/native_messaging/native_messaging_pipe.h"
 #include "remoting/host/native_messaging/pipe_messaging_channel.h"
@@ -159,22 +160,22 @@
 
   OnPolicyUpdate(std::move(policies));
 
-  RunSetState(kStarting);
-  RunSetState(kRequestedAccessCode);
+  RunSetState(It2MeHostState::kStarting);
+  RunSetState(It2MeHostState::kRequestedAccessCode);
 
   host_context()->ui_task_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(&It2MeHost::Observer::OnStoreAccessCode, observer_,
                      kTestAccessCode, kTestAccessCodeLifetime));
 
-  RunSetState(kReceivedAccessCode);
-  RunSetState(kConnecting);
+  RunSetState(It2MeHostState::kReceivedAccessCode);
+  RunSetState(It2MeHostState::kConnecting);
 
   host_context()->ui_task_runner()->PostTask(
       FROM_HERE, base::BindOnce(&It2MeHost::Observer::OnClientAuthenticated,
                                 observer_, kTestClientUsername));
 
-  RunSetState(kConnected);
+  RunSetState(It2MeHostState::kConnected);
 }
 
 void MockIt2MeHost::Disconnect() {
@@ -189,7 +190,7 @@
   register_request_.reset();
   signal_strategy_.reset();
 
-  RunSetState(kDisconnected);
+  RunSetState(It2MeHostState::kDisconnected);
 }
 
 void MockIt2MeHost::CreateConnectionContextOnNetworkThread(
@@ -442,15 +443,15 @@
       ASSERT_TRUE(response->GetString(kState, &state));
 
       std::string value;
-      if (state == It2MeNativeMessagingHost::HostStateToString(kStarting)) {
+      if (state == It2MeHostStateToString(It2MeHostState::kStarting)) {
         EXPECT_FALSE(starting_received);
         starting_received = true;
-      } else if (state == It2MeNativeMessagingHost::HostStateToString(
-                              kRequestedAccessCode)) {
+      } else if (state ==
+                 It2MeHostStateToString(It2MeHostState::kRequestedAccessCode)) {
         EXPECT_FALSE(requestedAccessCode_received);
         requestedAccessCode_received = true;
-      } else if (state == It2MeNativeMessagingHost::HostStateToString(
-                              kReceivedAccessCode)) {
+      } else if (state ==
+                 It2MeHostStateToString(It2MeHostState::kReceivedAccessCode)) {
         EXPECT_FALSE(receivedAccessCode_received);
         receivedAccessCode_received = true;
 
@@ -461,12 +462,10 @@
         EXPECT_TRUE(
             response->GetInteger(kAccessCodeLifetime, &access_code_lifetime));
         EXPECT_EQ(kTestAccessCodeLifetime.InSeconds(), access_code_lifetime);
-      } else if (state ==
-                 It2MeNativeMessagingHost::HostStateToString(kConnecting)) {
+      } else if (state == It2MeHostStateToString(It2MeHostState::kConnecting)) {
         EXPECT_FALSE(connecting_received);
         connecting_received = true;
-      } else if (state ==
-                 It2MeNativeMessagingHost::HostStateToString(kConnected)) {
+      } else if (state == It2MeHostStateToString(It2MeHostState::kConnected)) {
         EXPECT_FALSE(connected_received);
         connected_received = true;
 
@@ -501,7 +500,7 @@
     } else if (type == kHostStateChangedMessage) {
       std::string state;
       ASSERT_TRUE(response->GetString(kState, &state));
-      if (state == It2MeNativeMessagingHost::HostStateToString(kDisconnected)) {
+      if (state == It2MeHostStateToString(It2MeHostState::kDisconnected)) {
         EXPECT_FALSE(disconnected_received);
         disconnected_received = true;
       } else {
diff --git a/remoting/host/mojom/BUILD.gn b/remoting/host/mojom/BUILD.gn
index ca9d08b..c87bdc64 100644
--- a/remoting/host/mojom/BUILD.gn
+++ b/remoting/host/mojom/BUILD.gn
@@ -5,6 +5,13 @@
 import("//mojo/public/tools/bindings/mojom.gni")
 
 mojom("mojom") {
-  sources = [ "remote_url_opener.mojom" ]
-  public_deps = [ "//url/mojom:url_mojom_gurl" ]
+  sources = [
+    "remote_support.mojom",
+    "remote_url_opener.mojom",
+  ]
+
+  deps = [
+    "//mojo/public/mojom/base",
+    "//url/mojom:url_mojom_gurl",
+  ]
 }
diff --git a/remoting/host/mojom/remote_support.mojom b/remoting/host/mojom/remote_support.mojom
new file mode 100644
index 0000000..0ea80f8
--- /dev/null
+++ b/remoting/host/mojom/remote_support.mojom
@@ -0,0 +1,122 @@
+// 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.
+
+module remoting.mojom;
+
+import "mojo/public/mojom/base/time.mojom";
+
+[Stable]
+struct SupportHostDetails {
+  // Version of the remote support host in major.minor.build.revision format.
+  // This is an opaque string which is parsed by the website client.
+  string host_version;
+
+  // The set of features supported by the remote support host.  This is
+  // an opaque set of strings which is parsed by the website client.
+  array<string> supported_features;
+};
+
+// Response to a StartSupportSession request. This union includes a support host
+// observer if the session was started successfully, otherwise it includes an
+// error reason to indicate why it failed.
+[Stable]
+union StartSupportSessionResponse {
+  // Used to observe connection state change events when a session was started
+  // successfully.
+  pending_receiver<remoting.mojom.SupportHostObserver> observer;
+
+  // Provides the reason the session was not started.
+  remoting.mojom.StartSupportSessionError support_session_error;
+};
+
+// Provides the reason a remote support session could not be started.
+[Stable, Extensible]
+enum StartSupportSessionError {
+  [Default] kUnknown = 0,
+
+  // The managed device admin has an existing connection which cannot be
+  // disconnected.
+  kExistingAdminSession = 1,
+};
+
+// The parameters required to start a remote support session.
+[Stable]
+struct SupportSessionParams {
+  // Gaia username of the user requesting the connection.
+  string user_name;
+
+  // The access token used for connecting to signaling and CRD backend services.
+  string oauth_access_token;
+
+  // Do not prompt local user for approval when connecting. Used for Admin
+  // initiated connections using the RemoteCommand infrastructure.
+  [EnableIf=is_chromeos]
+  bool suppress_user_dialogs;
+
+  // Do not show connection notifications in the system tray. Used for Admin
+  // initiated connections using the RemoteCommand infrastructure.
+  [EnableIf=is_chromeos]
+  bool suppress_notifications;
+
+  // Terminate the session if local user input is received. Used for Admin
+  // initiated connections using the RemoteCommand infrastructure.
+  [EnableIf=is_chromeos]
+  bool terminate_upon_input;
+};
+
+// Represents the state of the remote support host NAT device policies.
+[Stable]
+struct NatPolicyState {
+  // Indicates whether NAT traversal is enabled.
+  bool nat_enabled;
+
+  // Indicates whether TURN (relay) connections are allowed. Note that this
+  // value is only considered for P2P connection establishment when
+  // |nat_enabled| is true.
+  bool relay_enabled;
+};
+
+// Used by the remote support host to provides connection state change and error
+// events to the owner of the remote support session.  This is typically the
+// native message host which is owned by the CRD client website.
+// This is currently only implemented on CrOS, where the remote for this
+// interface is owned in ash-chrome and the receiver is bound in lacros-chrome.
+[Stable, Uuid="3961f91c-dd8f-4b9b-a6ae-6bf8371d5a91"]
+interface SupportHostObserver {
+  // Indicates the remote support host is starting.
+  OnHostStateStarting@0();
+
+  // Indicates the remote support host has requested an access code.
+  OnHostStateRequestedAccessCode@1();
+
+  // Indicates the remote support host has received an access code.
+  // |access_code| is an opaque string (12 digit code) which is provided to the
+  // remote user who uses it to connect to this session.
+  // |lifetime| is the amount of time |access_code| is valid.
+  OnHostStateReceivedAccessCode@2(string access_code,
+                                  mojo_base.mojom.TimeDelta lifetime);
+
+  // Indicates the remote support host is beginning the connection process.
+  OnHostStateConnecting@3();
+
+  // Indicates the remote support host has completed the connection process.
+  // |remote_user_address| is the Gaia address of the remote user.
+  OnHostStateConnected@4(string remote_username);
+
+  // Indicates the remote support host is now disconnected.
+  OnHostStateDisconnected@6();
+
+  // Indicates there was a change to the NAT device policy.
+  OnNatPolicyChanged@7(NatPolicyState nat_policy_state);
+
+  // Indicates the host experienced an unexpected error.
+  OnHostStateError@8(int64 error_code);
+
+  // Indicates the host experienced an error loading the device policy.
+  OnPolicyError@9();
+
+  // Indicates the remote support host could not start as the user's domain is
+  // not included in the device policy allowlist.
+  OnInvalidDomainError@10();
+};
diff --git a/remoting/protocol/BUILD.gn b/remoting/protocol/BUILD.gn
index a02f48f3..d8d8e8d 100644
--- a/remoting/protocol/BUILD.gn
+++ b/remoting/protocol/BUILD.gn
@@ -6,6 +6,14 @@
 import("//media/media_options.gni")
 import("//remoting/remoting_options.gni")
 
+static_library("errors") {
+  sources = [
+    "errors.cc",
+    "errors.h",
+  ]
+  deps = [ "//remoting/base:base" ]
+}
+
 static_library("protocol") {
   sources = [
     "audio_decode_scheduler.cc",
@@ -58,8 +66,6 @@
     "datagram_channel_factory.h",
     "display_size.cc",
     "display_size.h",
-    "errors.cc",
-    "errors.h",
     "file_transfer_helpers.cc",
     "file_transfer_helpers.h",
     "frame_consumer.h",
@@ -216,6 +222,7 @@
   ]
 
   public_deps = [
+    ":errors",
     ":session_config",
     "//remoting/proto",
     "//third_party/webrtc_overrides:webrtc_component",
diff --git a/remoting/protocol/errors.h b/remoting/protocol/errors.h
index b21d9d6..55aae27 100644
--- a/remoting/protocol/errors.h
+++ b/remoting/protocol/errors.h
@@ -32,6 +32,7 @@
   ELEVATION_ERROR,
   HOST_CERTIFICATE_ERROR,
   HOST_REGISTRATION_ERROR,
+  EXISTING_ADMIN_SESSION,
   ERROR_CODE_MAX = UNKNOWN_ERROR,
 };
 
diff --git a/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc b/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc
index 05c39f0..086c56a2 100644
--- a/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc
+++ b/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc
@@ -178,6 +178,12 @@
     return RestrictCloneToThreadsAndEPERMFork();
   }
 
+  // clone3 takes a pointer argument which we cannot examine, so return ENOSYS
+  // to force the libc to use clone. See https://crbug.com/1213452.
+  if (sysno == __NR_clone3) {
+    return Error(ENOSYS);
+  }
+
   if (sysno == __NR_fcntl)
     return RestrictFcntlCommands();
 
diff --git a/sandbox/linux/system_headers/arm64_linux_syscalls.h b/sandbox/linux/system_headers/arm64_linux_syscalls.h
index a242c18c..ab86b36 100644
--- a/sandbox/linux/system_headers/arm64_linux_syscalls.h
+++ b/sandbox/linux/system_headers/arm64_linux_syscalls.h
@@ -1119,4 +1119,100 @@
 #define __NR_rseq 293
 #endif
 
+#if !defined(__NR_kexec_file_load)
+#define __NR_kexec_file_load 294
+#endif
+
+#if !defined(__NR_pidfd_send_signal)
+#define __NR_pidfd_send_signal 424
+#endif
+
+#if !defined(__NR_io_uring_setup)
+#define __NR_io_uring_setup 425
+#endif
+
+#if !defined(__NR_io_uring_enter)
+#define __NR_io_uring_enter 426
+#endif
+
+#if !defined(__NR_io_uring_register)
+#define __NR_io_uring_register 427
+#endif
+
+#if !defined(__NR_open_tree)
+#define __NR_open_tree 428
+#endif
+
+#if !defined(__NR_move_mount)
+#define __NR_move_mount 429
+#endif
+
+#if !defined(__NR_fsopen)
+#define __NR_fsopen 430
+#endif
+
+#if !defined(__NR_fsconfig)
+#define __NR_fsconfig 431
+#endif
+
+#if !defined(__NR_fsmount)
+#define __NR_fsmount 432
+#endif
+
+#if !defined(__NR_fspick)
+#define __NR_fspick 433
+#endif
+
+#if !defined(__NR_pidfd_open)
+#define __NR_pidfd_open 434
+#endif
+
+#if !defined(__NR_clone3)
+#define __NR_clone3 435
+#endif
+
+#if !defined(__NR_close_range)
+#define __NR_close_range 436
+#endif
+
+#if !defined(__NR_openat2)
+#define __NR_openat2 437
+#endif
+
+#if !defined(__NR_pidfd_getfd)
+#define __NR_pidfd_getfd 438
+#endif
+
+#if !defined(__NR_faccessat2)
+#define __NR_faccessat2 439
+#endif
+
+#if !defined(__NR_process_madvise)
+#define __NR_process_madvise 440
+#endif
+
+#if !defined(__NR_epoll_pwait2)
+#define __NR_epoll_pwait2 441
+#endif
+
+#if !defined(__NR_mount_setattr)
+#define __NR_mount_setattr 442
+#endif
+
+#if !defined(__NR_quotactl_path)
+#define __NR_quotactl_path 443
+#endif
+
+#if !defined(__NR_landlock_create_ruleset)
+#define __NR_landlock_create_ruleset 444
+#endif
+
+#if !defined(__NR_landlock_add_rule)
+#define __NR_landlock_add_rule 445
+#endif
+
+#if !defined(__NR_landlock_restrict_self)
+#define __NR_landlock_restrict_self 446
+#endif
+
 #endif  // SANDBOX_LINUX_SYSTEM_HEADERS_ARM64_LINUX_SYSCALLS_H_
diff --git a/sandbox/linux/system_headers/arm_linux_syscalls.h b/sandbox/linux/system_headers/arm_linux_syscalls.h
index 85da6f4..9c44368 100644
--- a/sandbox/linux/system_headers/arm_linux_syscalls.h
+++ b/sandbox/linux/system_headers/arm_linux_syscalls.h
@@ -1605,6 +1605,18 @@
 #define __NR_mount_setattr (__NR_SYSCALL_BASE + 442)
 #endif
 
+#if !defined(__NR_landlock_create_ruleset)
+#define __NR_landlock_create_ruleset (__NR_SYSCALL_BASE + 444)
+#endif
+
+#if !defined(__NR_landlock_add_rule)
+#define __NR_landlock_add_rule (__NR_SYSCALL_BASE + 445)
+#endif
+
+#if !defined(__NR_landlock_restrict_self)
+#define __NR_landlock_restrict_self (__NR_SYSCALL_BASE + 446)
+#endif
+
 // ARM private syscalls.
 #if !defined(__ARM_NR_BASE)
 #define __ARM_NR_BASE (__NR_SYSCALL_BASE + 0xF0000)
diff --git a/sandbox/linux/system_headers/mips64_linux_syscalls.h b/sandbox/linux/system_headers/mips64_linux_syscalls.h
index ec75815a..ae7cb48 100644
--- a/sandbox/linux/system_headers/mips64_linux_syscalls.h
+++ b/sandbox/linux/system_headers/mips64_linux_syscalls.h
@@ -1271,4 +1271,148 @@
 #define __NR_memfd_create (__NR_Linux + 314)
 #endif
 
+#if !defined(__NR_bpf)
+#define __NR_bpf (__NR_Linux + 315)
+#endif
+
+#if !defined(__NR_execveat)
+#define __NR_execveat (__NR_Linux + 316)
+#endif
+
+#if !defined(__NR_userfaultfd)
+#define __NR_userfaultfd (__NR_Linux + 317)
+#endif
+
+#if !defined(__NR_membarrier)
+#define __NR_membarrier (__NR_Linux + 318)
+#endif
+
+#if !defined(__NR_mlock2)
+#define __NR_mlock2 (__NR_Linux + 319)
+#endif
+
+#if !defined(__NR_copy_file_range)
+#define __NR_copy_file_range (__NR_Linux + 320)
+#endif
+
+#if !defined(__NR_preadv2)
+#define __NR_preadv2 (__NR_Linux + 321)
+#endif
+
+#if !defined(__NR_pwritev2)
+#define __NR_pwritev2 (__NR_Linux + 322)
+#endif
+
+#if !defined(__NR_pkey_mprotect)
+#define __NR_pkey_mprotect (__NR_Linux + 323)
+#endif
+
+#if !defined(__NR_pkey_alloc)
+#define __NR_pkey_alloc (__NR_Linux + 324)
+#endif
+
+#if !defined(__NR_pkey_free)
+#define __NR_pkey_free (__NR_Linux + 325)
+#endif
+
+#if !defined(__NR_statx)
+#define __NR_statx (__NR_Linux + 326)
+#endif
+
+#if !defined(__NR_rseq)
+#define __NR_rseq (__NR_Linux + 327)
+#endif
+
+#if !defined(__NR_io_pgetevents)
+#define __NR_io_pgetevents (__NR_Linux + 328)
+#endif
+
+#if !defined(__NR_pidfd_send_signal)
+#define __NR_pidfd_send_signal (__NR_Linux + 424)
+#endif
+
+#if !defined(__NR_io_uring_setup)
+#define __NR_io_uring_setup (__NR_Linux + 425)
+#endif
+
+#if !defined(__NR_io_uring_enter)
+#define __NR_io_uring_enter (__NR_Linux + 426)
+#endif
+
+#if !defined(__NR_io_uring_register)
+#define __NR_io_uring_register (__NR_Linux + 427)
+#endif
+
+#if !defined(__NR_open_tree)
+#define __NR_open_tree (__NR_Linux + 428)
+#endif
+
+#if !defined(__NR_move_mount)
+#define __NR_move_mount (__NR_Linux + 429)
+#endif
+
+#if !defined(__NR_fsopen)
+#define __NR_fsopen (__NR_Linux + 430)
+#endif
+
+#if !defined(__NR_fsconfig)
+#define __NR_fsconfig (__NR_Linux + 431)
+#endif
+
+#if !defined(__NR_fsmount)
+#define __NR_fsmount (__NR_Linux + 432)
+#endif
+
+#if !defined(__NR_fspick)
+#define __NR_fspick (__NR_Linux + 433)
+#endif
+
+#if !defined(__NR_pidfd_open)
+#define __NR_pidfd_open (__NR_Linux + 434)
+#endif
+
+#if !defined(__NR_clone3)
+#define __NR_clone3 (__NR_Linux + 435)
+#endif
+
+#if !defined(__NR_close_range)
+#define __NR_close_range (__NR_Linux + 436)
+#endif
+
+#if !defined(__NR_openat2)
+#define __NR_openat2 (__NR_Linux + 437)
+#endif
+
+#if !defined(__NR_pidfd_getfd)
+#define __NR_pidfd_getfd (__NR_Linux + 438)
+#endif
+
+#if !defined(__NR_faccessat2)
+#define __NR_faccessat2 (__NR_Linux + 439)
+#endif
+
+#if !defined(__NR_process_madvise)
+#define __NR_process_madvise (__NR_Linux + 440)
+#endif
+
+#if !defined(__NR_epoll_pwait2)
+#define __NR_epoll_pwait2 (__NR_Linux + 441)
+#endif
+
+#if !defined(__NR_mount_setattr)
+#define __NR_mount_setattr (__NR_Linux + 442)
+#endif
+
+#if !defined(__NR_landlock_create_ruleset)
+#define __NR_landlock_create_ruleset (__NR_Linux + 444)
+#endif
+
+#if !defined(__NR_landlock_add_rule)
+#define __NR_landlock_add_rule (__NR_Linux + 445)
+#endif
+
+#if !defined(__NR_landlock_restrict_self)
+#define __NR_landlock_restrict_self (__NR_Linux + 446)
+#endif
+
 #endif  // SANDBOX_LINUX_SYSTEM_HEADERS_MIPS64_LINUX_SYSCALLS_H_
diff --git a/sandbox/linux/system_headers/mips_linux_syscalls.h b/sandbox/linux/system_headers/mips_linux_syscalls.h
index 50d9ea1..0937782 100644
--- a/sandbox/linux/system_headers/mips_linux_syscalls.h
+++ b/sandbox/linux/system_headers/mips_linux_syscalls.h
@@ -1685,4 +1685,16 @@
 #define __NR_mount_setattr (__NR_Linux + 442)
 #endif
 
+#if !defined(__NR_landlock_create_ruleset)
+#define __NR_landlock_create_ruleset (__NR_Linux + 444)
+#endif
+
+#if !defined(__NR_landlock_add_rule)
+#define __NR_landlock_add_rule (__NR_Linux + 445)
+#endif
+
+#if !defined(__NR_landlock_restrict_self)
+#define __NR_landlock_restrict_self (__NR_Linux + 446)
+#endif
+
 #endif  // SANDBOX_LINUX_SYSTEM_HEADERS_MIPS_LINUX_SYSCALLS_H_
diff --git a/sandbox/linux/system_headers/x86_32_linux_syscalls.h b/sandbox/linux/system_headers/x86_32_linux_syscalls.h
index 1720edb..2c81a930 100644
--- a/sandbox/linux/system_headers/x86_32_linux_syscalls.h
+++ b/sandbox/linux/system_headers/x86_32_linux_syscalls.h
@@ -1738,5 +1738,17 @@
 #define __NR_mount_setattr 442
 #endif
 
+#if !defined(__NR_landlock_create_ruleset)
+#define __NR_landlock_create_ruleset 444
+#endif
+
+#if !defined(__NR_landlock_add_rule)
+#define __NR_landlock_add_rule 445
+#endif
+
+#if !defined(__NR_landlock_restrict_self)
+#define __NR_landlock_restrict_self 446
+#endif
+
 #endif  // SANDBOX_LINUX_SYSTEM_HEADERS_X86_32_LINUX_SYSCALLS_H_
 
diff --git a/sandbox/linux/system_headers/x86_64_linux_syscalls.h b/sandbox/linux/system_headers/x86_64_linux_syscalls.h
index b0ae0a2..e618c62 100644
--- a/sandbox/linux/system_headers/x86_64_linux_syscalls.h
+++ b/sandbox/linux/system_headers/x86_64_linux_syscalls.h
@@ -1350,5 +1350,93 @@
 #define __NR_rseq 334
 #endif
 
+#if !defined(__NR_pidfd_send_signal)
+#define __NR_pidfd_send_signal 424
+#endif
+
+#if !defined(__NR_io_uring_setup)
+#define __NR_io_uring_setup 425
+#endif
+
+#if !defined(__NR_io_uring_enter)
+#define __NR_io_uring_enter 426
+#endif
+
+#if !defined(__NR_io_uring_register)
+#define __NR_io_uring_register 427
+#endif
+
+#if !defined(__NR_open_tree)
+#define __NR_open_tree 428
+#endif
+
+#if !defined(__NR_move_mount)
+#define __NR_move_mount 429
+#endif
+
+#if !defined(__NR_fsopen)
+#define __NR_fsopen 430
+#endif
+
+#if !defined(__NR_fsconfig)
+#define __NR_fsconfig 431
+#endif
+
+#if !defined(__NR_fsmount)
+#define __NR_fsmount 432
+#endif
+
+#if !defined(__NR_fspick)
+#define __NR_fspick 433
+#endif
+
+#if !defined(__NR_pidfd_open)
+#define __NR_pidfd_open 434
+#endif
+
+#if !defined(__NR_clone3)
+#define __NR_clone3 435
+#endif
+
+#if !defined(__NR_close_range)
+#define __NR_close_range 436
+#endif
+
+#if !defined(__NR_openat2)
+#define __NR_openat2 437
+#endif
+
+#if !defined(__NR_pidfd_getfd)
+#define __NR_pidfd_getfd 438
+#endif
+
+#if !defined(__NR_faccessat2)
+#define __NR_faccessat2 439
+#endif
+
+#if !defined(__NR_process_madvise)
+#define __NR_process_madvise 440
+#endif
+
+#if !defined(__NR_epoll_pwait2)
+#define __NR_epoll_pwait2 441
+#endif
+
+#if !defined(__NR_mount_setattr)
+#define __NR_mount_setattr 442
+#endif
+
+#if !defined(__NR_landlock_create_ruleset)
+#define __NR_landlock_create_ruleset 444
+#endif
+
+#if !defined(__NR_landlock_add_rule)
+#define __NR_landlock_add_rule 445
+#endif
+
+#if !defined(__NR_landlock_restrict_self)
+#define __NR_landlock_restrict_self 446
+#endif
+
 #endif  // SANDBOX_LINUX_SYSTEM_HEADERS_X86_64_LINUX_SYSCALLS_H_
 
diff --git a/services/network/cookie_settings.cc b/services/network/cookie_settings.cc
index 3e0c5b6..fe81960 100644
--- a/services/network/cookie_settings.cc
+++ b/services/network/cookie_settings.cc
@@ -191,11 +191,9 @@
 }
 
 bool CookieSettings::HasSessionOnlyOrigins() const {
-  for (const auto& entry : content_settings_) {
-    if (entry.GetContentSetting() == CONTENT_SETTING_SESSION_ONLY)
-      return true;
-  }
-  return false;
+  return base::ranges::any_of(content_settings_, [](const auto& entry) {
+    return entry.GetContentSetting() == CONTENT_SETTING_SESSION_ONLY;
+  });
 }
 
 }  // namespace network
diff --git a/services/network/trust_tokens/BUILD.gn b/services/network/trust_tokens/BUILD.gn
index ce25b0a..8a765ca 100644
--- a/services/network/trust_tokens/BUILD.gn
+++ b/services/network/trust_tokens/BUILD.gn
@@ -20,6 +20,10 @@
     "boringssl_trust_token_issuance_cryptographer.h",
     "boringssl_trust_token_redemption_cryptographer.cc",
     "boringssl_trust_token_redemption_cryptographer.h",
+    "ecdsa_p256_key_pair_generator.cc",
+    "ecdsa_p256_key_pair_generator.h",
+    "ecdsa_sha256_trust_token_request_signer.cc",
+    "ecdsa_sha256_trust_token_request_signer.h",
     "ed25519_key_pair_generator.cc",
     "ed25519_key_pair_generator.h",
     "ed25519_trust_token_request_signer.cc",
@@ -132,8 +136,8 @@
 
   sources = [
     "boringssl_trust_token_issuance_cryptographer_unittest.cc",
+    "ecdsa_p256_key_pair_generator_unittest.cc",
     "ed25519_key_pair_generator_unittest.cc",
-    "ed25519_trust_token_request_signer_unittest.cc",
     "expiry_inspecting_record_expiry_delegate_unittest.cc",
     "has_trust_tokens_answerer_unittest.cc",
     "local_trust_token_operation_delegate_impl_unittest.cc",
@@ -153,6 +157,7 @@
     "trust_token_request_helper_factory_unittest.cc",
     "trust_token_request_issuance_helper_unittest.cc",
     "trust_token_request_redemption_helper_unittest.cc",
+    "trust_token_request_signer_unittest.cc",
     "trust_token_request_signing_helper_unittest.cc",
     "trust_token_store_unittest.cc",
     "types_unittest.cc",
diff --git a/services/network/trust_tokens/ecdsa_p256_key_pair_generator.cc b/services/network/trust_tokens/ecdsa_p256_key_pair_generator.cc
new file mode 100644
index 0000000..cf679d7
--- /dev/null
+++ b/services/network/trust_tokens/ecdsa_p256_key_pair_generator.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 "services/network/trust_tokens/ecdsa_p256_key_pair_generator.h"
+
+#include "crypto/ec_private_key.h"
+
+namespace network {
+
+bool EcdsaP256KeyPairGenerator::Generate(std::string* signing_key_out,
+                                         std::string* verification_key_out) {
+  std::unique_ptr<crypto::ECPrivateKey> key_pair =
+      crypto::ECPrivateKey::Create();
+  std::vector<uint8_t> private_key;
+  std::string public_key;
+  if (!key_pair->ExportPrivateKey(&private_key)) {
+    return false;
+  }
+  if (!key_pair->ExportRawPublicKey(verification_key_out)) {
+    return false;
+  }
+  signing_key_out->assign(private_key.begin(), private_key.end());
+
+  return true;
+}
+
+}  // namespace network
diff --git a/services/network/trust_tokens/ecdsa_p256_key_pair_generator.h b/services/network/trust_tokens/ecdsa_p256_key_pair_generator.h
new file mode 100644
index 0000000..7776c4d
--- /dev/null
+++ b/services/network/trust_tokens/ecdsa_p256_key_pair_generator.h
@@ -0,0 +1,29 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_NETWORK_TRUST_TOKENS_ECDSA_P256_KEY_PAIR_GENERATOR_H_
+#define SERVICES_NETWORK_TRUST_TOKENS_ECDSA_P256_KEY_PAIR_GENERATOR_H_
+
+#include "services/network/trust_tokens/trust_token_request_redemption_helper.h"
+
+namespace network {
+
+// EcdsaP256KeyPairGenerator generates an ECDSA key pair based on the NIST
+// P-256 curve. The |verification_key_out| is encoded as an EC point in the
+// uncompressed point format and the |signing_key_out| is encoded as a PKCS #8
+// PrivateKeyInfo block.
+class EcdsaP256KeyPairGenerator
+    : public TrustTokenRequestRedemptionHelper::KeyPairGenerator {
+ public:
+  EcdsaP256KeyPairGenerator() = default;
+  ~EcdsaP256KeyPairGenerator() override = default;
+
+  // TrustTokenRequestRedemptionHelper::KeyPairGenerator implementation:
+  bool Generate(std::string* signing_key_out,
+                std::string* verification_key_out) override;
+};
+
+}  // namespace network
+
+#endif  // SERVICES_NETWORK_TRUST_TOKENS_ECDSA_P256_KEY_PAIR_GENERATOR_H_
diff --git a/services/network/trust_tokens/ecdsa_p256_key_pair_generator_unittest.cc b/services/network/trust_tokens/ecdsa_p256_key_pair_generator_unittest.cc
new file mode 100644
index 0000000..aa6c0a9
--- /dev/null
+++ b/services/network/trust_tokens/ecdsa_p256_key_pair_generator_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/network/trust_tokens/ecdsa_p256_key_pair_generator.h"
+#include "base/containers/span.h"
+#include "services/network/trust_tokens/ecdsa_sha256_trust_token_request_signer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace network {
+
+TEST(EcdsaP256KeyPairGenerator, Roundtrip) {
+  auto message = base::as_bytes(base::make_span(
+      "Four score and seven years ago our fathers brought forth on this "
+      "continent, a new nation, conceived in Liberty, and dedicated to the "
+      "proposition that all men are created equal."));
+
+  std::string signing, verification;
+  ASSERT_TRUE(EcdsaP256KeyPairGenerator().Generate(&signing, &verification));
+
+  EcdsaSha256TrustTokenRequestSigner signer;
+
+  absl::optional<std::vector<uint8_t>> signature =
+      signer.Sign(base::as_bytes(base::make_span(signing)), message);
+  ASSERT_TRUE(signature);
+
+  EXPECT_TRUE(signer.Verify(message, *signature,
+                            base::as_bytes(base::make_span(verification))));
+}
+
+}  // namespace network
diff --git a/services/network/trust_tokens/ecdsa_sha256_trust_token_request_signer.cc b/services/network/trust_tokens/ecdsa_sha256_trust_token_request_signer.cc
new file mode 100644
index 0000000..aac3f682
--- /dev/null
+++ b/services/network/trust_tokens/ecdsa_sha256_trust_token_request_signer.cc
@@ -0,0 +1,90 @@
+// 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/trust_tokens/ecdsa_sha256_trust_token_request_signer.h"
+
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "crypto/signature_verifier.h"
+#include "third_party/boringssl/src/include/openssl/bytestring.h"
+#include "third_party/boringssl/src/include/openssl/digest.h"
+#include "third_party/boringssl/src/include/openssl/ecdsa.h"
+#include "third_party/boringssl/src/include/openssl/evp.h"
+#include "third_party/boringssl/src/include/openssl/mem.h"
+
+namespace network {
+
+EcdsaSha256TrustTokenRequestSigner::EcdsaSha256TrustTokenRequestSigner() =
+    default;
+EcdsaSha256TrustTokenRequestSigner::~EcdsaSha256TrustTokenRequestSigner() =
+    default;
+
+absl::optional<std::vector<uint8_t>> EcdsaSha256TrustTokenRequestSigner::Sign(
+    base::span<const uint8_t> key,
+    base::span<const uint8_t> data) {
+  std::unique_ptr<crypto::ECPrivateKey> private_key =
+      crypto::ECPrivateKey::CreateFromPrivateKeyInfo(key);
+  if (!private_key)
+    return absl::nullopt;
+
+  EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(private_key->key());
+  if (!ec_key)
+    return absl::nullopt;
+
+  if (EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key)) !=
+      NID_X9_62_prime256v1)
+    return absl::nullopt;
+
+  std::unique_ptr<crypto::ECSignatureCreator> sig_creator =
+      crypto::ECSignatureCreator::Create(private_key.get());
+
+  std::vector<uint8_t> signature;
+
+  if (!sig_creator->Sign(data.data(), data.size(), &signature))
+    return absl::nullopt;
+
+  return signature;
+}
+
+bool EcdsaSha256TrustTokenRequestSigner::Verify(
+    base::span<const uint8_t> data,
+    base::span<const uint8_t> signature,
+    base::span<const uint8_t> verification_key) {
+  // Require the public key be in uncompressed form. EC_POINT_oct2point
+  // also accepts compressed form.
+  if (verification_key.empty() ||
+      verification_key[0] != POINT_CONVERSION_UNCOMPRESSED) {
+    return false;
+  }
+
+  bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+  bssl::UniquePtr<EC_POINT> pub_key(EC_POINT_new(EC_KEY_get0_group(key.get())));
+  if (!EC_POINT_oct2point(EC_KEY_get0_group(key.get()), pub_key.get(),
+                          verification_key.data(), verification_key.size(),
+                          nullptr) ||
+      !EC_KEY_set_public_key(key.get(), pub_key.get())) {
+    return false;
+  }
+
+  bssl::UniquePtr<EVP_PKEY> public_key(EVP_PKEY_new());
+  if (!EVP_PKEY_set1_EC_KEY(public_key.get(), key.get())) {
+    return false;
+  }
+
+  bssl::ScopedEVP_MD_CTX ctx;
+  EVP_PKEY_CTX* pctx;
+  if (!EVP_DigestVerifyInit(ctx.get(), &pctx, EVP_sha256(), nullptr,
+                            public_key.get())) {
+    return false;
+  }
+
+  return EVP_DigestVerify(ctx.get(), signature.data(), signature.size(),
+                          data.data(), data.size());
+}
+
+std::string EcdsaSha256TrustTokenRequestSigner::GetAlgorithmIdentifier() {
+  return "EcdsaSha256TrustTokenRequestSigner";
+}
+
+}  // namespace network
diff --git a/services/network/trust_tokens/ecdsa_sha256_trust_token_request_signer.h b/services/network/trust_tokens/ecdsa_sha256_trust_token_request_signer.h
new file mode 100644
index 0000000..8c7486e
--- /dev/null
+++ b/services/network/trust_tokens/ecdsa_sha256_trust_token_request_signer.h
@@ -0,0 +1,36 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_NETWORK_TRUST_TOKENS_ECDSA_SHA256_TRUST_TOKEN_REQUEST_SIGNER_H_
+#define SERVICES_NETWORK_TRUST_TOKENS_ECDSA_SHA256_TRUST_TOKEN_REQUEST_SIGNER_H_
+
+#include "services/network/trust_tokens/trust_token_request_signing_helper.h"
+
+namespace network {
+
+// EcdsaSha256TrustTokenRequestSigner provides a wrapper around BoringSSL's
+// ECDSA signing and verification routines capable of satisfying the Trust
+// Tokens signing request helper's Signer delegate interface. The signature is a
+// DER encoded ECDSA-Sig-Value from RFC 3279.
+class EcdsaSha256TrustTokenRequestSigner
+    : public TrustTokenRequestSigningHelper::Signer {
+ public:
+  EcdsaSha256TrustTokenRequestSigner();
+  ~EcdsaSha256TrustTokenRequestSigner() override;
+
+  // TrustTokenRequestSigningHelper::Signer implementation:
+  absl::optional<std::vector<uint8_t>> Sign(
+      base::span<const uint8_t> key,
+      base::span<const uint8_t> data) override;
+
+  bool Verify(base::span<const uint8_t> data,
+              base::span<const uint8_t> signature,
+              base::span<const uint8_t> verification_key) override;
+
+  std::string GetAlgorithmIdentifier() override;
+};
+
+}  // namespace network
+
+#endif  // SERVICES_NETWORK_TRUST_TOKENS_ECDSA_SHA256_TRUST_TOKEN_REQUEST_SIGNER_H_
diff --git a/services/network/trust_tokens/ed25519_trust_token_request_signer_unittest.cc b/services/network/trust_tokens/ed25519_trust_token_request_signer_unittest.cc
deleted file mode 100644
index b55a005..0000000
--- a/services/network/trust_tokens/ed25519_trust_token_request_signer_unittest.cc
+++ /dev/null
@@ -1,216 +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 "services/network/trust_tokens/ed25519_trust_token_request_signer.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/boringssl/src/include/openssl/curve25519.h"
-
-namespace network {
-
-namespace {
-
-struct Keys {
-  std::array<uint8_t, ED25519_PRIVATE_KEY_LEN> signing;
-  std::array<uint8_t, ED25519_PUBLIC_KEY_LEN> verification;
-};
-
-// The fixed constant 32 comes from curve25519.h and is not defined in a macro.
-Keys KeysFromSeed(base::span<const uint8_t, 32> seed) {
-  Keys ret;
-
-  // Cannot fail.
-  ED25519_keypair_from_seed(ret.verification.data(), ret.signing.data(),
-                            seed.data());
-
-  return ret;
-}
-
-const char kLongishMessage[] =
-    "Four score and seven years ago our fathers brought forth on this "
-    "continent, a new nation, conceived in Liberty, and dedicated to the "
-    "proposition that all men are created equal.";
-
-}  // namespace
-
-TEST(Ed25519TrustTokenRequestSigner, Roundtrip) {
-  auto message = base::as_bytes(base::make_span(kLongishMessage));
-
-  std::array<uint8_t, 32> seed{1, 2, 3, 4, 5};
-  Keys keys = KeysFromSeed(seed);
-
-  Ed25519TrustTokenRequestSigner signer;
-
-  absl::optional<std::vector<uint8_t>> signature =
-      signer.Sign(keys.signing, message);
-  ASSERT_TRUE(signature);
-
-  EXPECT_TRUE(signer.Verify(message, *signature, keys.verification));
-}
-
-TEST(Ed25519TrustTokenRequestSigner, EmptyMessage) {
-  auto message = base::span<const uint8_t>();
-
-  std::array<uint8_t, 32> seed{1, 2, 3, 4, 5};
-  Keys keys = KeysFromSeed(seed);
-
-  Ed25519TrustTokenRequestSigner signer;
-
-  absl::optional<std::vector<uint8_t>> signature =
-      signer.Sign(keys.signing, message);
-  ASSERT_TRUE(signature);
-
-  EXPECT_TRUE(signer.Verify(message, *signature, keys.verification));
-}
-
-TEST(Ed25519TrustTokenRequestSigner, ShortMessage) {
-  auto message = base::as_bytes(base::make_span("Hello"));
-
-  std::array<uint8_t, 32> seed{1, 2, 3, 4, 5};
-  Keys keys = KeysFromSeed(seed);
-
-  Ed25519TrustTokenRequestSigner signer;
-
-  absl::optional<std::vector<uint8_t>> signature =
-      signer.Sign(keys.signing, message);
-  ASSERT_TRUE(signature);
-
-  EXPECT_TRUE(signer.Verify(message, *signature, keys.verification));
-}
-
-TEST(Ed25519TrustTokenRequestSigner, LongerMessage) {
-  std::vector<uint8_t> message(1000000);
-  std::array<uint8_t, 32> seed{1, 2, 3, 4, 5};
-  Keys keys = KeysFromSeed(seed);
-
-  Ed25519TrustTokenRequestSigner signer;
-
-  absl::optional<std::vector<uint8_t>> signature =
-      signer.Sign(keys.signing, message);
-  ASSERT_TRUE(signature);
-
-  EXPECT_TRUE(signer.Verify(message, *signature, keys.verification));
-}
-
-TEST(Ed25519TrustTokenRequestSigner, VerificationFromDifferentSigner) {
-  // Test that Verify works without prior initialization and signing, as its
-  // contract promises.
-  auto message = base::as_bytes(base::make_span(kLongishMessage));
-
-  std::array<uint8_t, 32> seed{1, 2, 3, 4, 5};
-  Keys keys = KeysFromSeed(seed);
-
-  Ed25519TrustTokenRequestSigner signer;
-
-  absl::optional<std::vector<uint8_t>> signature =
-      signer.Sign(keys.signing, message);
-
-  Ed25519TrustTokenRequestSigner verifier;
-  EXPECT_TRUE(verifier.Verify(message, *signature, keys.verification));
-}
-
-TEST(Ed25519TrustTokenRequestSigner, SigningKeyTooShort) {
-  auto message = base::as_bytes(base::make_span(kLongishMessage));
-
-  std::array<uint8_t, 32> seed{1, 2, 3, 4, 5};
-  Keys keys = KeysFromSeed(seed);
-
-  Ed25519TrustTokenRequestSigner signer;
-
-  absl::optional<std::vector<uint8_t>> signature =
-      signer.Sign(base::make_span(keys.signing).subspan(1), message);
-  EXPECT_FALSE(signature);
-}
-
-TEST(Ed25519TrustTokenRequestSigner, SigningKeyTooLong) {
-  auto message = base::as_bytes(base::make_span(kLongishMessage));
-
-  Ed25519TrustTokenRequestSigner signer;
-
-  std::vector<uint8_t> overlong_signing_key(ED25519_PRIVATE_KEY_LEN + 1);
-
-  absl::optional<std::vector<uint8_t>> signature =
-      signer.Sign(overlong_signing_key, message);
-  EXPECT_FALSE(signature);
-}
-
-TEST(Ed25519TrustTokenRequestSigner, VerificationKeyTooShort) {
-  auto message = base::as_bytes(base::make_span(kLongishMessage));
-
-  std::array<uint8_t, 32> seed{1, 2, 3, 4, 5};
-  Keys keys = KeysFromSeed(seed);
-
-  Ed25519TrustTokenRequestSigner signer;
-
-  absl::optional<std::vector<uint8_t>> signature =
-      signer.Sign(keys.signing, message);
-
-  EXPECT_FALSE(signer.Verify(message, *signature,
-                             base::make_span(keys.verification).subspan(1)));
-}
-
-TEST(Ed25519TrustTokenRequestSigner, VerificationKeyTooLong) {
-  auto message = base::as_bytes(base::make_span(kLongishMessage));
-
-  std::array<uint8_t, 32> seed{1, 2, 3, 4, 5};
-  Keys keys = KeysFromSeed(seed);
-
-  Ed25519TrustTokenRequestSigner signer;
-
-  absl::optional<std::vector<uint8_t>> signature =
-      signer.Sign(keys.signing, message);
-
-  std::vector<uint8_t> overlong_verification_key(ED25519_PUBLIC_KEY_LEN + 1);
-
-  EXPECT_FALSE(signer.Verify(message, *signature, overlong_verification_key));
-}
-
-TEST(Ed25519TrustTokenRequestSigner, SignatureTooShort) {
-  auto message = base::as_bytes(base::make_span(kLongishMessage));
-
-  std::array<uint8_t, 32> seed{1, 2, 3, 4, 5};
-  Keys keys = KeysFromSeed(seed);
-
-  Ed25519TrustTokenRequestSigner signer;
-
-  absl::optional<std::vector<uint8_t>> signature =
-      signer.Sign(keys.signing, message);
-  signature->pop_back();
-
-  EXPECT_FALSE(signer.Verify(message, *signature, keys.verification));
-}
-
-TEST(Ed25519TrustTokenRequestSigner, SignatureTooLong) {
-  auto message = base::as_bytes(base::make_span(kLongishMessage));
-
-  std::array<uint8_t, 32> seed{1, 2, 3, 4, 5};
-  Keys keys = KeysFromSeed(seed);
-
-  Ed25519TrustTokenRequestSigner signer;
-
-  absl::optional<std::vector<uint8_t>> signature =
-      signer.Sign(keys.signing, message);
-  signature->push_back(0);
-
-  EXPECT_FALSE(
-      signer.Verify(message, base::make_span(*signature), keys.verification));
-}
-
-TEST(Ed25519TrustTokenRequestSigner, SignatureWrong) {
-  auto message = base::as_bytes(base::make_span(kLongishMessage));
-
-  std::array<uint8_t, 32> seed{1, 2, 3, 4, 5};
-  Keys keys = KeysFromSeed(seed);
-
-  Ed25519TrustTokenRequestSigner signer;
-
-  absl::optional<std::vector<uint8_t>> signature =
-      signer.Sign(keys.signing, message);
-
-  // Corrupt the signature.
-  signature->front() += 1;
-
-  EXPECT_FALSE(signer.Verify(message, *signature, keys.verification));
-}
-
-}  // namespace network
diff --git a/services/network/trust_tokens/trust_token_request_signer_unittest.cc b/services/network/trust_tokens/trust_token_request_signer_unittest.cc
new file mode 100644
index 0000000..5505b94
--- /dev/null
+++ b/services/network/trust_tokens/trust_token_request_signer_unittest.cc
@@ -0,0 +1,282 @@
+// 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 "crypto/ec_private_key.h"
+#include "services/network/trust_tokens/ecdsa_sha256_trust_token_request_signer.h"
+#include "services/network/trust_tokens/ed25519_trust_token_request_signer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/boringssl/src/include/openssl/curve25519.h"
+
+namespace network {
+
+namespace {
+
+struct Keys {
+  std::vector<uint8_t> signing;
+  std::vector<uint8_t> verification;
+};
+
+const char kLongishMessage[] =
+    "Four score and seven years ago our fathers brought forth on this "
+    "continent, a new nation, conceived in Liberty, and dedicated to the "
+    "proposition that all men are created equal.";
+
+enum class RequestSigner {
+  kEd25519 = 0,
+  kEcdsaSha256 = 1,
+};
+
+}  // namespace
+
+class TrustTokenRequestSigner : public ::testing::TestWithParam<RequestSigner> {
+ protected:
+  std::unique_ptr<TrustTokenRequestSigningHelper::Signer> CreateSigner() {
+    switch (GetParam()) {
+      case RequestSigner::kEd25519:
+        return std::make_unique<Ed25519TrustTokenRequestSigner>();
+
+      case RequestSigner::kEcdsaSha256:
+        return std::make_unique<EcdsaSha256TrustTokenRequestSigner>();
+    }
+  }
+
+  // The fixed constant 32 comes from curve25519.h and is not defined in a
+  // macro.
+  Keys GetTestKeys() {
+    Keys ret;
+    switch (GetParam()) {
+      case RequestSigner::kEd25519: {
+        ret.signing.resize(ED25519_PRIVATE_KEY_LEN);
+        ret.verification.resize(ED25519_PUBLIC_KEY_LEN);
+        // Cannot fail.
+        std::array<uint8_t, 32> seed{1, 2, 3, 4, 5};
+        ED25519_keypair_from_seed(ret.verification.data(), ret.signing.data(),
+                                  seed.data());
+        break;
+      }
+      case RequestSigner::kEcdsaSha256:
+        const std::vector<uint8_t> private_key = {
+            0x30, 0x81, 0x87, 0x02, 0x01, 0x00, 0x30, 0x13, 0x06, 0x07, 0x2a,
+            0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48,
+            0xce, 0x3d, 0x03, 0x01, 0x07, 0x04, 0x6d, 0x30, 0x6b, 0x02, 0x01,
+            0x01, 0x04, 0x20, 0x7f, 0x4c, 0x85, 0x5d, 0xcb, 0xd5, 0x3e, 0x9e,
+            0xed, 0x0a, 0x34, 0xc9, 0xbf, 0xbc, 0xfb, 0x0e, 0xcd, 0xd8, 0xa0,
+            0x89, 0x7e, 0x1d, 0xaf, 0x1c, 0x1e, 0x9f, 0x8c, 0x9f, 0xac, 0x21,
+            0xee, 0xa5, 0xa1, 0x44, 0x03, 0x42, 0x00, 0x04, 0x62, 0x22, 0x44,
+            0x4b, 0x41, 0x0e, 0x16, 0xcc, 0x6e, 0xbb, 0x72, 0xb9, 0xe5, 0x70,
+            0xba, 0x13, 0xd0, 0xd2, 0x1f, 0x8f, 0x2a, 0x10, 0x57, 0x32, 0x77,
+            0xb8, 0xd0, 0x62, 0x7e, 0x4d, 0x18, 0x6d, 0xc2, 0x87, 0x25, 0x17,
+            0x45, 0x11, 0x82, 0xf2, 0x93, 0xed, 0xd5, 0x60, 0x7f, 0xae, 0x67,
+            0x87, 0x39, 0x15, 0x90, 0x16, 0x91, 0x3c, 0xf9, 0x11, 0x76, 0x09,
+            0xfa, 0x51, 0x90, 0xa4, 0x2f, 0x9a};
+        std::unique_ptr<crypto::ECPrivateKey> key =
+            crypto::ECPrivateKey::CreateFromPrivateKeyInfo(private_key);
+        std::vector<uint8_t> ec_private_key;
+        key->ExportPrivateKey(&ec_private_key);
+        ret.signing.swap(ec_private_key);
+
+        std::string public_key;
+        key->ExportRawPublicKey(&public_key);
+        ret.verification =
+            std::vector<uint8_t>(public_key.begin(), public_key.end());
+        std::string raw_public_key;
+        key->ExportRawPublicKey(&raw_public_key);
+        break;
+    }
+    return ret;
+  }
+};  // namespace network
+
+TEST_P(TrustTokenRequestSigner, Roundtrip) {
+  auto message = base::as_bytes(base::make_span(kLongishMessage));
+
+  Keys keys = GetTestKeys();
+
+  std::unique_ptr<TrustTokenRequestSigningHelper::Signer> signer =
+      CreateSigner();
+
+  absl::optional<std::vector<uint8_t>> signature =
+      signer->Sign(keys.signing, message);
+  ASSERT_TRUE(signature);
+
+  EXPECT_TRUE(signer->Verify(message, *signature, keys.verification));
+}
+
+TEST_P(TrustTokenRequestSigner, EmptyMessage) {
+  auto message = base::span<const uint8_t>();
+
+  Keys keys = GetTestKeys();
+
+  std::unique_ptr<TrustTokenRequestSigningHelper::Signer> signer =
+      CreateSigner();
+
+  absl::optional<std::vector<uint8_t>> signature =
+      signer->Sign(keys.signing, message);
+  ASSERT_TRUE(signature);
+
+  EXPECT_TRUE(signer->Verify(message, *signature, keys.verification));
+}
+
+TEST_P(TrustTokenRequestSigner, ShortMessage) {
+  auto message = base::as_bytes(base::make_span("Hello"));
+
+  Keys keys = GetTestKeys();
+
+  std::unique_ptr<TrustTokenRequestSigningHelper::Signer> signer =
+      CreateSigner();
+
+  absl::optional<std::vector<uint8_t>> signature =
+      signer->Sign(keys.signing, message);
+  ASSERT_TRUE(signature);
+
+  EXPECT_TRUE(signer->Verify(message, *signature, keys.verification));
+}
+
+TEST_P(TrustTokenRequestSigner, LongerMessage) {
+  std::vector<uint8_t> message(1000000);
+  Keys keys = GetTestKeys();
+
+  std::unique_ptr<TrustTokenRequestSigningHelper::Signer> signer =
+      CreateSigner();
+
+  absl::optional<std::vector<uint8_t>> signature =
+      signer->Sign(keys.signing, message);
+  ASSERT_TRUE(signature);
+
+  EXPECT_TRUE(signer->Verify(message, *signature, keys.verification));
+}
+
+TEST_P(TrustTokenRequestSigner, VerificationFromDifferentSigner) {
+  // Test that Verify works without prior initialization and signing, as its
+  // contract promises.
+  auto message = base::as_bytes(base::make_span(kLongishMessage));
+
+  Keys keys = GetTestKeys();
+
+  std::unique_ptr<TrustTokenRequestSigningHelper::Signer> signer =
+      CreateSigner();
+
+  absl::optional<std::vector<uint8_t>> signature =
+      signer->Sign(keys.signing, message);
+
+  std::unique_ptr<TrustTokenRequestSigningHelper::Signer> verifier =
+      CreateSigner();
+  EXPECT_TRUE(verifier->Verify(message, *signature, keys.verification));
+}
+
+TEST_P(TrustTokenRequestSigner, SigningKeyTooShort) {
+  auto message = base::as_bytes(base::make_span(kLongishMessage));
+
+  Keys keys = GetTestKeys();
+
+  std::unique_ptr<TrustTokenRequestSigningHelper::Signer> signer =
+      CreateSigner();
+
+  absl::optional<std::vector<uint8_t>> signature =
+      signer->Sign(base::make_span(keys.signing).subspan(1), message);
+  EXPECT_FALSE(signature);
+}
+
+TEST_P(TrustTokenRequestSigner, SigningKeyTooLong) {
+  auto message = base::as_bytes(base::make_span(kLongishMessage));
+
+  std::unique_ptr<TrustTokenRequestSigningHelper::Signer> signer =
+      CreateSigner();
+
+  Keys keys = GetTestKeys();
+  std::vector<uint8_t> overlong_signing_key(keys.signing.size() + 1);
+
+  absl::optional<std::vector<uint8_t>> signature =
+      signer->Sign(overlong_signing_key, message);
+  EXPECT_FALSE(signature);
+}
+
+TEST_P(TrustTokenRequestSigner, VerificationKeyTooShort) {
+  auto message = base::as_bytes(base::make_span(kLongishMessage));
+
+  Keys keys = GetTestKeys();
+
+  std::unique_ptr<TrustTokenRequestSigningHelper::Signer> signer =
+      CreateSigner();
+
+  absl::optional<std::vector<uint8_t>> signature =
+      signer->Sign(keys.signing, message);
+
+  EXPECT_FALSE(signer->Verify(message, *signature,
+                              base::make_span(keys.verification).subspan(1)));
+}
+
+TEST_P(TrustTokenRequestSigner, VerificationKeyTooLong) {
+  auto message = base::as_bytes(base::make_span(kLongishMessage));
+
+  Keys keys = GetTestKeys();
+
+  std::unique_ptr<TrustTokenRequestSigningHelper::Signer> signer =
+      CreateSigner();
+
+  absl::optional<std::vector<uint8_t>> signature =
+      signer->Sign(keys.signing, message);
+
+  std::vector<uint8_t> overlong_verification_key(keys.verification.size() + 1);
+
+  EXPECT_FALSE(signer->Verify(message, *signature, overlong_verification_key));
+}
+
+TEST_P(TrustTokenRequestSigner, SignatureTooShort) {
+  auto message = base::as_bytes(base::make_span(kLongishMessage));
+
+  Keys keys = GetTestKeys();
+
+  std::unique_ptr<TrustTokenRequestSigningHelper::Signer> signer =
+      CreateSigner();
+
+  absl::optional<std::vector<uint8_t>> signature =
+      signer->Sign(keys.signing, message);
+  signature->pop_back();
+
+  EXPECT_FALSE(signer->Verify(message, *signature, keys.verification));
+}
+
+TEST_P(TrustTokenRequestSigner, SignatureTooLong) {
+  auto message = base::as_bytes(base::make_span(kLongishMessage));
+
+  Keys keys = GetTestKeys();
+
+  std::unique_ptr<TrustTokenRequestSigningHelper::Signer> signer =
+      CreateSigner();
+
+  absl::optional<std::vector<uint8_t>> signature =
+      signer->Sign(keys.signing, message);
+  signature->push_back(0);
+
+  EXPECT_FALSE(
+      signer->Verify(message, base::make_span(*signature), keys.verification));
+}
+
+TEST_P(TrustTokenRequestSigner, SignatureWrong) {
+  auto message = base::as_bytes(base::make_span(kLongishMessage));
+
+  Keys keys = GetTestKeys();
+
+  std::unique_ptr<TrustTokenRequestSigningHelper::Signer> signer =
+      CreateSigner();
+
+  absl::optional<std::vector<uint8_t>> signature =
+      signer->Sign(keys.signing, message);
+
+  // Corrupt the signature.
+  signature->front() += 1;
+
+  EXPECT_FALSE(signer->Verify(message, *signature, keys.verification));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Algorithm,
+    TrustTokenRequestSigner,
+    ::testing::Values(RequestSigner::kEd25519, RequestSigner::kEcdsaSha256),
+    [](const testing::TestParamInfo<TrustTokenRequestSigner::ParamType>& info) {
+      return info.param == RequestSigner::kEd25519 ? "ED25519" : "ECDSA_SHA256";
+    });
+
+}  // namespace network
diff --git a/storage/browser/quota/quota_database.cc b/storage/browser/quota/quota_database.cc
index eb65caf..6bf5540 100644
--- a/storage/browser/quota/quota_database.cc
+++ b/storage/browser/quota/quota_database.cc
@@ -131,7 +131,7 @@
                                  int64_t* quota) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(quota);
-  if (!LazyOpen(false))
+  if (LazyOpen(LazyOpenMode::kFailIfNotFound) != QuotaError::kNone)
     return false;
 
   static constexpr char kSql[] =
@@ -152,8 +152,9 @@
                                  int64_t quota) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_GE(quota, 0);
-  if (!LazyOpen(true))
+  if (LazyOpen(LazyOpenMode::kCreateIfNotFound) != QuotaError::kNone)
     return false;
+
   if (quota == 0)
     return DeleteHostQuota(host, type);
   if (!InsertOrReplaceHostQuota(host, type, quota))
@@ -167,8 +168,9 @@
     const std::string& bucket_name) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // TODO(crbug/1210259): Add DCHECKs for input validation.
-  if (!LazyOpen(/*create_if_needed=*/true))
-    return QuotaError::kDatabaseError;
+  QuotaError open_error = LazyOpen(LazyOpenMode::kCreateIfNotFound);
+  if (open_error != QuotaError::kNone)
+    return open_error;
 
   // TODO(crbug/1210252): Update to not execute 2 sql statements on creation.
   QuotaErrorOr<BucketId> bucket_result = GetBucketId(origin, bucket_name);
@@ -216,8 +218,12 @@
     const url::Origin& origin,
     const std::string& bucket_name) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyOpen(/*create_if_needed=*/true))
-    return QuotaError::kDatabaseError;
+  QuotaError open_error = LazyOpen(LazyOpenMode::kFailIfNotFound);
+  if (open_error != QuotaError::kNone) {
+    if (open_error == QuotaError::kDatabaseNotFound)
+      return BucketId();
+    return open_error;
+  }
 
   static constexpr char kSql[] =
       "SELECT id FROM buckets WHERE origin = ? AND type = ? AND name = ?";
@@ -239,7 +245,7 @@
                                             StorageType type,
                                             base::Time last_accessed) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyOpen(true))
+  if (LazyOpen(LazyOpenMode::kCreateIfNotFound) != QuotaError::kNone)
     return false;
 
   sql::Statement statement;
@@ -292,7 +298,7 @@
                                             base::Time last_accessed) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!bucket_id.is_null());
-  if (!LazyOpen(true))
+  if (LazyOpen(LazyOpenMode::kCreateIfNotFound) != QuotaError::kNone)
     return false;
 
   BucketTableEntry entry;
@@ -318,7 +324,7 @@
                                               StorageType type,
                                               base::Time last_modified) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyOpen(true))
+  if (LazyOpen(LazyOpenMode::kCreateIfNotFound) != QuotaError::kNone)
     return false;
 
   sql::Statement statement;
@@ -367,7 +373,7 @@
                                               base::Time last_modified) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!bucket_id.is_null());
-  if (!LazyOpen(true))
+  if (LazyOpen(LazyOpenMode::kCreateIfNotFound) != QuotaError::kNone)
     return false;
 
   BucketTableEntry entry;
@@ -391,7 +397,7 @@
     const std::set<url::Origin>& origins,
     StorageType type) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyOpen(true))
+  if (LazyOpen(LazyOpenMode::kCreateIfNotFound) != QuotaError::kNone)
     return false;
 
   for (const auto& origin : origins) {
@@ -426,7 +432,7 @@
                                   StorageType type,
                                   QuotaDatabase::BucketTableEntry* entry) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyOpen(false))
+  if (LazyOpen(LazyOpenMode::kFailIfNotFound) != QuotaError::kNone)
     return false;
 
   static constexpr char kSql[] =
@@ -458,7 +464,7 @@
                                   QuotaDatabase::BucketTableEntry* entry) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!bucket_id.is_null());
-  if (!LazyOpen(false))
+  if (LazyOpen(LazyOpenMode::kFailIfNotFound) != QuotaError::kNone)
     return false;
 
   static constexpr char kSql[] =
@@ -491,7 +497,7 @@
 bool QuotaDatabase::DeleteHostQuota(
     const std::string& host, StorageType type) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyOpen(false))
+  if (LazyOpen(LazyOpenMode::kFailIfNotFound) != QuotaError::kNone)
     return false;
 
   static constexpr char kSql[] =
@@ -510,7 +516,7 @@
 bool QuotaDatabase::DeleteOriginInfo(const url::Origin& origin,
                                      StorageType type) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyOpen(false))
+  if (LazyOpen(LazyOpenMode::kFailIfNotFound) != QuotaError::kNone)
     return false;
 
   static constexpr char kSql[] =
@@ -530,7 +536,7 @@
 bool QuotaDatabase::DeleteBucketInfo(const BucketId bucket_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!bucket_id.is_null());
-  if (!LazyOpen(false))
+  if (LazyOpen(LazyOpenMode::kFailIfNotFound) != QuotaError::kNone)
     return false;
 
   static constexpr char kSql[] = "DELETE FROM buckets WHERE id = ?";
@@ -550,7 +556,7 @@
                                  absl::optional<url::Origin>* origin) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(origin);
-  if (!LazyOpen(false))
+  if (LazyOpen(LazyOpenMode::kFailIfNotFound) != QuotaError::kNone)
     return false;
 
   static constexpr char kSql[] =
@@ -591,7 +597,7 @@
                                  absl::optional<BucketId>* bucket_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(bucket_id);
-  if (!LazyOpen(false))
+  if (LazyOpen(LazyOpenMode::kFailIfNotFound) != QuotaError::kNone)
     return false;
 
   static constexpr char kSql[] =
@@ -634,7 +640,7 @@
                                               base::Time end) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(origins);
-  if (!LazyOpen(false))
+  if (LazyOpen(LazyOpenMode::kFailIfNotFound) != QuotaError::kNone)
     return false;
 
   DCHECK(!begin.is_max());
@@ -665,7 +671,7 @@
                                               base::Time end) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(bucket_ids);
-  if (!LazyOpen(false))
+  if (LazyOpen(LazyOpenMode::kFailIfNotFound) != QuotaError::kNone)
     return false;
 
   DCHECK(!begin.is_max());
@@ -690,7 +696,7 @@
 
 bool QuotaDatabase::IsOriginDatabaseBootstrapped() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyOpen(true))
+  if (LazyOpen(LazyOpenMode::kCreateIfNotFound) != QuotaError::kNone)
     return false;
 
   int flag = 0;
@@ -699,7 +705,7 @@
 
 bool QuotaDatabase::SetOriginDatabaseBootstrapped(bool bootstrap_flag) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyOpen(true))
+  if (LazyOpen(LazyOpenMode::kCreateIfNotFound) != QuotaError::kNone)
     return false;
 
   return meta_table_->SetValue(kIsOriginTableBootstrapped, bootstrap_flag);
@@ -728,20 +734,20 @@
                this, &QuotaDatabase::Commit);
 }
 
-bool QuotaDatabase::LazyOpen(bool create_if_needed) {
+QuotaError QuotaDatabase::LazyOpen(LazyOpenMode mode) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (db_)
-    return true;
+    return QuotaError::kNone;
 
   // If we tried and failed once, don't try again in the same session
   // to avoid creating an incoherent mess on disk.
   if (is_disabled_)
-    return false;
+    return QuotaError::kDatabaseError;
 
   bool in_memory_only = db_file_path_.empty();
-  if (!create_if_needed &&
+  if (mode == LazyOpenMode::kFailIfNotFound &&
       (in_memory_only || !base::PathExists(db_file_path_))) {
-    return false;
+    return QuotaError::kDatabaseNotFound;
   }
 
   db_ = std::make_unique<sql::Database>(sql::DatabaseOptions{
@@ -771,14 +777,14 @@
       is_disabled_ = true;
       db_.reset();
       meta_table_.reset();
-      return false;
+      return QuotaError::kDatabaseError;
     }
   }
 
   // Start a long-running transaction.
   db_->BeginTransaction();
 
-  return true;
+  return QuotaError::kNone;
 }
 
 bool QuotaDatabase::EnsureDatabaseVersion() {
@@ -880,7 +886,7 @@
     return false;
 
   base::AutoReset<bool> auto_reset(&is_recreating_, true);
-  return LazyOpen(true);
+  return LazyOpen(LazyOpenMode::kCreateIfNotFound) == QuotaError::kNone;
 }
 
 bool QuotaDatabase::InsertOrReplaceHostQuota(const std::string& host,
@@ -902,7 +908,7 @@
 
 bool QuotaDatabase::DumpQuotaTable(const QuotaTableCallback& callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!LazyOpen(true))
+  if (LazyOpen(LazyOpenMode::kCreateIfNotFound) != QuotaError::kNone)
     return false;
 
   static constexpr char kSql[] = "SELECT * FROM quota";
@@ -924,7 +930,7 @@
 bool QuotaDatabase::DumpBucketTable(const BucketTableCallback& callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (!LazyOpen(true))
+  if (LazyOpen(LazyOpenMode::kCreateIfNotFound) != QuotaError::kNone)
     return false;
 
   static constexpr char kSql[] =
diff --git a/storage/browser/quota/quota_database.h b/storage/browser/quota/quota_database.h
index 70a8aa21..5c1171d 100644
--- a/storage/browser/quota/quota_database.h
+++ b/storage/browser/quota/quota_database.h
@@ -67,6 +67,8 @@
     base::Time last_modified;
   };
 
+  enum class LazyOpenMode { kCreateIfNotFound, kFailIfNotFound };
+
   // If 'path' is empty, an in memory database will be used.
   explicit QuotaDatabase(const base::FilePath& path);
   ~QuotaDatabase();
@@ -219,7 +221,7 @@
   void Commit();
   void ScheduleCommit();
 
-  bool LazyOpen(bool create_if_needed);
+  QuotaError LazyOpen(LazyOpenMode mode);
   bool EnsureDatabaseVersion();
   bool ResetSchema();
   bool UpgradeSchema(int current_version);
diff --git a/storage/browser/quota/quota_database_migrations_unittest.cc b/storage/browser/quota/quota_database_migrations_unittest.cc
index f81ec6b..d5970f1 100644
--- a/storage/browser/quota/quota_database_migrations_unittest.cc
+++ b/storage/browser/quota/quota_database_migrations_unittest.cc
@@ -59,7 +59,8 @@
 
   void MigrateDatabase() {
     QuotaDatabase db(DbPath());
-    EXPECT_TRUE(db.LazyOpen(true));
+    EXPECT_EQ(db.LazyOpen(QuotaDatabase::LazyOpenMode::kCreateIfNotFound),
+              QuotaError::kNone);
     EXPECT_TRUE(db.db_.get());
   }
 
diff --git a/storage/browser/quota/quota_database_unittest.cc b/storage/browser/quota/quota_database_unittest.cc
index c49573d..2f671e1 100644
--- a/storage/browser/quota/quota_database_unittest.cc
+++ b/storage/browser/quota/quota_database_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/test/task_environment.h"
+#include "build/build_config.h"
 #include "sql/database.h"
 #include "sql/meta_table.h"
 #include "sql/statement.h"
@@ -50,6 +51,7 @@
  protected:
   using QuotaTableEntry = QuotaDatabase::QuotaTableEntry;
   using BucketTableEntry = QuotaDatabase::BucketTableEntry;
+  using LazyOpenMode = QuotaDatabase::LazyOpenMode;
 
   void SetUp() override { ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); }
 
@@ -61,8 +63,8 @@
     return temp_directory_.GetPath().AppendASCII("quota_manager.db");
   }
 
-  bool LazyOpen(QuotaDatabase* db, bool create_if_needed) {
-    return db->LazyOpen(create_if_needed);
+  bool LazyOpen(QuotaDatabase* db, LazyOpenMode mode) {
+    return db->LazyOpen(mode) == QuotaError::kNone;
   }
 
   template <typename EntryType>
@@ -153,8 +155,8 @@
 
 TEST_P(QuotaDatabaseTest, LazyOpen) {
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
-  EXPECT_FALSE(LazyOpen(&db, /*create_if_needed=*/false));
-  EXPECT_TRUE(LazyOpen(&db, /*create_if_needed=*/true));
+  EXPECT_FALSE(LazyOpen(&db, LazyOpenMode::kFailIfNotFound));
+  EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
 
   if (GetParam()) {
     // Path should not exist for incognito mode.
@@ -166,7 +168,7 @@
 
 TEST_P(QuotaDatabaseTest, HostQuota) {
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
-  EXPECT_TRUE(LazyOpen(&db, /*create_if_needed=*/true));
+  EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
 
   const char* kHost = "foo.com";
   const int kQuota1 = 13579;
@@ -200,7 +202,7 @@
 
 TEST_P(QuotaDatabaseTest, CreateBucket) {
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
-  EXPECT_TRUE(LazyOpen(&db, /*create_if_needed=*/true));
+  EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
   url::Origin origin = ToOrigin("http://google/");
   std::string bucket_name = "google_bucket";
 
@@ -216,7 +218,7 @@
 
 TEST_P(QuotaDatabaseTest, GetBucketId) {
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
-  EXPECT_TRUE(LazyOpen(&db, /*create_if_needed=*/true));
+  EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
 
   // Add a bucket entry into the bucket table.
   url::Origin origin = ToOrigin("http://google/");
@@ -242,9 +244,41 @@
   EXPECT_TRUE(result.value().is_null());
 }
 
+TEST_P(QuotaDatabaseTest, GetBucketIdWithNoDb) {
+  QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
+  EXPECT_FALSE(LazyOpen(&db, LazyOpenMode::kFailIfNotFound));
+
+  url::Origin origin = ToOrigin("http://google/");
+  std::string bucket_name = "google_bucket";
+  QuotaErrorOr<BucketId> result = db.GetBucketId(origin, bucket_name);
+  ASSERT_TRUE(result.ok());
+  EXPECT_TRUE(result.value().is_null());
+}
+
+// TODO(crbug.com/1216094): Update test to have its behavior on Fuchsia match
+// with other platforms, and enable test on all platforms.
+#if !defined(OS_FUCHSIA)
+TEST_F(QuotaDatabaseTest, GetBucketIdWithOpenDatabaseError) {
+  sql::test::ScopedErrorExpecter expecter;
+  expecter.ExpectError(SQLITE_CANTOPEN);
+
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  QuotaDatabase db(temp_dir.GetPath());
+
+  url::Origin origin = ToOrigin("http://google/");
+  std::string bucket_name = "google_bucket";
+  QuotaErrorOr<BucketId> result = db.GetBucketId(origin, bucket_name);
+  ASSERT_FALSE(result.ok());
+  EXPECT_EQ(result.error(), QuotaError::kDatabaseError);
+
+  EXPECT_TRUE(expecter.SawExpectedErrors());
+}
+#endif  // !defined(OS_FUCHSIA)
+
 TEST_P(QuotaDatabaseTest, OriginLastAccessTimeLRU) {
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
-  EXPECT_TRUE(LazyOpen(&db, /*create_if_needed=*/true));
+  EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
 
   std::set<url::Origin> exceptions;
   absl::optional<url::Origin> origin;
@@ -314,7 +348,7 @@
 
 TEST_P(QuotaDatabaseTest, BucketLastAccessTimeLRU) {
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
-  EXPECT_TRUE(LazyOpen(&db, /*create_if_needed=*/true));
+  EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
 
   std::set<url::Origin> exceptions;
   absl::optional<BucketId> bucket_id;
@@ -393,7 +427,7 @@
 
 TEST_P(QuotaDatabaseTest, OriginLastModifiedBetween) {
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
-  EXPECT_TRUE(LazyOpen(&db, /*create_if_needed=*/true));
+  EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
 
   std::set<url::Origin> origins;
   EXPECT_TRUE(db.GetOriginsModifiedBetween(kTemp, &origins, base::Time(),
@@ -486,7 +520,7 @@
 
 TEST_P(QuotaDatabaseTest, BucketLastModifiedBetween) {
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
-  EXPECT_TRUE(LazyOpen(&db, /*create_if_needed=*/true));
+  EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
 
   std::set<BucketId> bucket_ids;
   EXPECT_TRUE(db.GetBucketsModifiedBetween(kTemp, &bucket_ids, base::Time(),
@@ -607,7 +641,7 @@
       {.host = "http://gle/", .type = kPerm, .quota = 3}};
 
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
-  EXPECT_TRUE(LazyOpen(&db, /*create_if_needed=*/true));
+  EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
   AssignQuotaTable(&db, kTableEntries);
 
   using Verifier = EntryVerifier<QuotaTableEntry>;
@@ -630,7 +664,7 @@
   };
 
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
-  EXPECT_TRUE(LazyOpen(&db, /*create_if_needed=*/true));
+  EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
   AssignBucketTable(&db, kTableEntries);
 
   using Verifier = EntryVerifier<Entry>;
@@ -647,7 +681,7 @@
                                  100, base::Time(), base::Time())};
 
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
-  EXPECT_TRUE(LazyOpen(&db, /*create_if_needed=*/true));
+  EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
   AssignBucketTable(&db, kTableEntries);
 
   {
@@ -675,7 +709,7 @@
                                  base::Time())};
 
   QuotaDatabase db(use_in_memory_db() ? base::FilePath() : DbPath());
-  EXPECT_TRUE(LazyOpen(&db, /*create_if_needed=*/true));
+  EXPECT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
   AssignBucketTable(&db, kTableEntries);
 
   {
@@ -711,7 +745,7 @@
   // Create database, force corruption and close db by leaving scope.
   {
     QuotaDatabase db(DbPath());
-    ASSERT_TRUE(LazyOpen(&db, /*create_if_needed=*/true));
+    ASSERT_TRUE(LazyOpen(&db, LazyOpenMode::kCreateIfNotFound));
     ASSERT_TRUE(sql::test::CorruptSizeInHeader(DbPath()));
   }
   // Reopen database and verify schema reset on reopen.
@@ -719,7 +753,7 @@
     sql::test::ScopedErrorExpecter expecter;
     expecter.ExpectError(SQLITE_CORRUPT);
     QuotaDatabase db(DbPath());
-    ASSERT_TRUE(LazyOpen(&db, /*create_if_needed=*/false));
+    ASSERT_TRUE(LazyOpen(&db, LazyOpenMode::kFailIfNotFound));
     EXPECT_TRUE(expecter.SawExpectedErrors());
   }
 }
diff --git a/styleguide/java/java.md b/styleguide/java/java.md
index 2c6ca80..1cabacb 100644
--- a/styleguide/java/java.md
+++ b/styleguide/java/java.md
@@ -94,7 +94,8 @@
 the [same
 scenarios](https://chromium.googlesource.com/chromium/src/+/main/styleguide/c++/c++.md#CHECK_DCHECK_and-NOTREACHED)
 where C++ DCHECK()s make sense. For multi-statement asserts, use
-`org.chromium.base.BuildConfig.DCHECK_IS_ON` to guard your code.
+`org.chromium.build.BuildConfig.ENABLE_ASSERTS` to guard your code (similar to
+`#if DCHECK_IS_ON()` in C++).
 
 Example assert:
 
@@ -102,10 +103,14 @@
 assert someCallWithoutSideEffects() : "assert description";
 ```
 
-Example use of `DCHECK_IS_ON`:
+Example use of `BuildConfig.ENABLE_ASSERTS`:
 
 ```java
-if (org.chromium.base.BuildConfig.DCHECK_IS_ON) {
+import org.chromium.build.BuildConfig;
+
+...
+
+if (BuildConfig.ENABLE_ASSERTS) {
   // Any code here will be stripped in Release by ProGuard.
   ...
 }
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index b7a52c8..6abc696 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -4689,7 +4689,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.95"
+              "revision": "version:91.0.4472.96"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -4768,7 +4768,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.42"
+              "revision": "version:92.0.4515.43"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -4926,7 +4926,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.95"
+              "revision": "version:91.0.4472.96"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -5005,7 +5005,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.42"
+              "revision": "version:92.0.4515.43"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 617d594..3ac2ff6 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -30898,7 +30898,7 @@
           "--passthrough",
           "--retry-limit=2"
         ],
-        "isolate_name": "telemetry_perf_unittests",
+        "isolate_name": "telemetry_perf_unittests_android_chrome",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -30927,7 +30927,7 @@
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests_android_chrome/"
       }
     ],
     "junit_tests": [
@@ -35612,7 +35612,7 @@
           "--passthrough",
           "--retry-limit=2"
         ],
-        "isolate_name": "telemetry_perf_unittests",
+        "isolate_name": "telemetry_perf_unittests_android_chrome",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -35641,7 +35641,7 @@
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests_android_chrome/"
       },
       {
         "args": [
@@ -35651,7 +35651,7 @@
           "--passthrough",
           "--retry-limit=2"
         ],
-        "isolate_name": "telemetry_perf_unittests",
+        "isolate_name": "telemetry_perf_unittests_android_monochrome",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -35680,13 +35680,13 @@
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests_android_monochrome/"
       },
       {
         "args": [
           "--extra-browser-args=--enable-crashpad"
         ],
-        "isolate_name": "telemetry_perf_unittests",
+        "isolate_name": "telemetry_perf_unittests_android_chrome",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -35717,7 +35717,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 12
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests_android_chrome/"
       }
     ],
     "junit_tests": [
@@ -44301,7 +44301,7 @@
           "--passthrough",
           "--retry-limit=2"
         ],
-        "isolate_name": "telemetry_perf_unittests",
+        "isolate_name": "telemetry_perf_unittests_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -44329,7 +44329,7 @@
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests_android_chrome/"
       },
       {
         "args": [
@@ -44339,7 +44339,7 @@
           "--passthrough",
           "--retry-limit=2"
         ],
-        "isolate_name": "telemetry_perf_unittests",
+        "isolate_name": "telemetry_perf_unittests_android_monochrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -44367,7 +44367,7 @@
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_perf_unittests_android_monochrome/"
       }
     ]
   },
@@ -53852,7 +53852,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.95"
+              "revision": "version:91.0.4472.96"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -53932,7 +53932,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.42"
+              "revision": "version:92.0.4515.43"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54092,7 +54092,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.95"
+              "revision": "version:91.0.4472.96"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54172,7 +54172,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.42"
+              "revision": "version:92.0.4515.43"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54397,7 +54397,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.95"
+              "revision": "version:91.0.4472.96"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54476,7 +54476,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.42"
+              "revision": "version:92.0.4515.43"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54634,7 +54634,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.95"
+              "revision": "version:91.0.4472.96"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54713,7 +54713,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.42"
+              "revision": "version:92.0.4515.43"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -54938,7 +54938,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.95"
+              "revision": "version:91.0.4472.96"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -55017,7 +55017,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.42"
+              "revision": "version:92.0.4515.43"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -55175,7 +55175,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M91",
-              "revision": "version:91.0.4472.95"
+              "revision": "version:91.0.4472.96"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -55254,7 +55254,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M92",
-              "revision": "version:92.0.4515.42"
+              "revision": "version:92.0.4515.43"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.angle.json b/testing/buildbot/chromium.angle.json
index 597eae93..e543e52f 100644
--- a/testing/buildbot/chromium.angle.json
+++ b/testing/buildbot/chromium.angle.json
@@ -257,7 +257,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gles --use-cmd-decoder=passthrough --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -289,7 +289,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 6
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       }
     ]
   },
diff --git a/testing/buildbot/chromium.clang.json b/testing/buildbot/chromium.clang.json
index f1f7c970..90d5bf5 100644
--- a/testing/buildbot/chromium.clang.json
+++ b/testing/buildbot/chromium.clang.json
@@ -43503,7 +43503,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43553,7 +43553,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43603,7 +43603,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43653,7 +43653,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43703,7 +43703,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43753,7 +43753,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43803,7 +43803,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43853,7 +43853,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43903,7 +43903,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -43953,7 +43953,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44003,7 +44003,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44053,7 +44053,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44103,7 +44103,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44153,7 +44153,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44203,7 +44203,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44253,7 +44253,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44303,7 +44303,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44353,7 +44353,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -44424,7 +44424,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -44469,7 +44469,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -44514,7 +44514,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -44559,7 +44559,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -44604,7 +44604,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -44649,7 +44649,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -44694,7 +44694,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -44739,7 +44739,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -44784,7 +44784,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -44829,7 +44829,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -44874,7 +44874,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -44919,7 +44919,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -44964,7 +44964,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -45009,7 +45009,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -45054,7 +45054,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -45099,7 +45099,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -45144,7 +45144,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
@@ -45189,7 +45189,7 @@
             {
               "cpu": "x86-64",
               "device": "iPhone8,1",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "expiration": 21600,
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 685ffbd..10836a4 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -20959,6 +20959,428 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/"
+      },
+      {
+        "args": [
+          "--num-retries=3",
+          "--device=aemu"
+        ],
+        "isolate_name": "blink_web_tests",
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "blink_web_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 12
+        },
+        "test_id_prefix": "ninja://:blink_web_tests/"
+      },
+      {
+        "args": [
+          "context_lost",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating",
+          "--device=aemu"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "context_lost_validating_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "depth_capture",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
+          "--device=aemu"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "depth_capture_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "gpu_process",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
+          "--device=aemu"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "gpu_process_launch_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "hardware_accelerated_feature",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
+          "--device=aemu"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "hardware_accelerated_feature_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "info_collection",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
+          "--expected-vendor-id",
+          "0",
+          "--expected-device-id",
+          "0",
+          "--device=aemu"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "info_collection_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "maps",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
+          "--device=aemu",
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "maps_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "mediapipe",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_higher_performance_gpu --use-cmd-decoder=validating",
+          "--device=aemu"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "mediapipe_validating_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "pixel",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
+          "--device=aemu",
+          "--git-revision=${got_revision}"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "pixel_tests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "screenshot_sync",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
+          "--device=aemu"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "screenshot_sync_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "trace_test",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
+          "--device=aemu"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "trace_test",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl_conformance",
+          "--show-stdout",
+          "--browser=web-engine-shell",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc",
+          "--device=aemu"
+        ],
+        "isolate_name": "fuchsia_telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl_conformance_tests",
+        "resultdb": {
+          "enable": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "arm64",
+              "inside_docker": "1",
+              "os": "Ubuntu-20.04"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 18
+        },
+        "test_id_prefix": "ninja://content/test:fuchsia_telemetry_gpu_integration_test/"
       }
     ]
   },
@@ -22887,7 +23309,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -22937,7 +23359,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -22987,7 +23409,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23037,7 +23459,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23087,7 +23509,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23137,7 +23559,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23187,7 +23609,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23237,7 +23659,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23287,7 +23709,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23337,7 +23759,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23387,7 +23809,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23437,7 +23859,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23487,7 +23909,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23537,7 +23959,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23587,7 +24009,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23637,7 +24059,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23687,7 +24109,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23737,7 +24159,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23787,7 +24209,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23837,7 +24259,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23887,7 +24309,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23937,7 +24359,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -23987,7 +24409,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24037,7 +24459,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24087,7 +24509,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24137,7 +24559,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24187,7 +24609,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24237,7 +24659,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24287,7 +24709,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24337,7 +24759,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24387,7 +24809,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24437,7 +24859,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24487,7 +24909,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24537,7 +24959,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24587,7 +25009,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24637,7 +25059,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24687,7 +25109,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24737,7 +25159,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24787,7 +25209,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24837,7 +25259,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24887,7 +25309,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24937,7 +25359,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -24987,7 +25409,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25037,7 +25459,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25087,7 +25509,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25137,7 +25559,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25194,7 +25616,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25247,7 +25669,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25300,7 +25722,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25353,7 +25775,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25406,7 +25828,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25459,7 +25881,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25512,7 +25934,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25565,7 +25987,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25618,7 +26040,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25671,7 +26093,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25724,7 +26146,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25777,7 +26199,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25830,7 +26252,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25883,7 +26305,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25936,7 +26358,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -25989,7 +26411,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26042,7 +26464,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26095,7 +26517,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26148,7 +26570,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26201,7 +26623,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26254,7 +26676,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26307,7 +26729,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26360,7 +26782,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26413,7 +26835,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26466,7 +26888,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26519,7 +26941,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26572,7 +26994,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26625,7 +27047,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26678,7 +27100,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26731,7 +27153,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26784,7 +27206,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26837,7 +27259,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26890,7 +27312,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26943,7 +27365,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -26997,7 +27419,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27051,7 +27473,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27105,7 +27527,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27159,7 +27581,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27213,7 +27635,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27267,7 +27689,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27321,7 +27743,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27376,7 +27798,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27431,7 +27853,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27486,7 +27908,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27541,7 +27963,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27596,7 +28018,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27651,7 +28073,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27706,7 +28128,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27761,7 +28183,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27816,7 +28238,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27871,7 +28293,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27926,7 +28348,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -27981,7 +28403,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28035,7 +28457,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28089,7 +28511,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28143,7 +28565,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28197,7 +28619,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28251,7 +28673,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28305,7 +28727,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28359,7 +28781,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28413,7 +28835,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28467,7 +28889,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28521,7 +28943,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28575,7 +28997,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28629,7 +29051,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28684,7 +29106,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28739,7 +29161,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28794,7 +29216,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28849,7 +29271,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28904,7 +29326,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -28958,7 +29380,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29011,7 +29433,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29064,7 +29486,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29117,7 +29539,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29170,7 +29592,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29223,7 +29645,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29276,7 +29698,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29329,7 +29751,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29383,7 +29805,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29437,7 +29859,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29491,7 +29913,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29545,7 +29967,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29599,7 +30021,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29653,7 +30075,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29706,7 +30128,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29759,7 +30181,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29812,7 +30234,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29865,7 +30287,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29918,7 +30340,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -29971,7 +30393,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30025,7 +30447,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30079,7 +30501,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30133,7 +30555,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30187,7 +30609,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30241,7 +30663,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30295,7 +30717,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30348,7 +30770,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30401,7 +30823,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30454,7 +30876,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30507,7 +30929,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30560,7 +30982,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30613,7 +31035,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30666,7 +31088,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30719,7 +31141,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30772,7 +31194,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30825,7 +31247,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30879,7 +31301,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30933,7 +31355,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -30987,7 +31409,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31041,7 +31463,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31095,7 +31517,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31149,7 +31571,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31202,7 +31624,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31255,7 +31677,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31308,7 +31730,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31361,7 +31783,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31414,7 +31836,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31467,7 +31889,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31520,7 +31942,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31573,7 +31995,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31626,7 +32048,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31679,7 +32101,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31732,7 +32154,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31785,7 +32207,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31838,7 +32260,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31891,7 +32313,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31944,7 +32366,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -31997,7 +32419,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32050,7 +32472,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32103,7 +32525,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32156,7 +32578,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32209,7 +32631,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32262,7 +32684,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32315,7 +32737,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32368,7 +32790,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32421,7 +32843,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32474,7 +32896,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32527,7 +32949,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32580,7 +33002,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32633,7 +33055,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32686,7 +33108,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32739,7 +33161,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32792,7 +33214,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32845,7 +33267,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32898,7 +33320,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -32951,7 +33373,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33004,7 +33426,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33057,7 +33479,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33110,7 +33532,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33163,7 +33585,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33216,7 +33638,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33269,7 +33691,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33322,7 +33744,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33375,7 +33797,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33428,7 +33850,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33481,7 +33903,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33534,7 +33956,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33587,7 +34009,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33640,7 +34062,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33693,7 +34115,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33747,7 +34169,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33797,7 +34219,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33851,7 +34273,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33901,7 +34323,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -33951,7 +34373,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34001,7 +34423,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34051,7 +34473,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34101,7 +34523,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34151,7 +34573,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34201,7 +34623,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34252,7 +34674,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34303,7 +34725,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34355,7 +34777,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34407,7 +34829,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34458,7 +34880,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34509,7 +34931,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34560,7 +34982,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34611,7 +35033,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34661,7 +35083,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34711,7 +35133,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34761,7 +35183,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34812,7 +35234,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34862,7 +35284,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34912,7 +35334,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -34963,7 +35385,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35013,7 +35435,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35063,7 +35485,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35113,7 +35535,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35163,7 +35585,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35213,7 +35635,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35263,7 +35685,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35313,7 +35735,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35363,7 +35785,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35413,7 +35835,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35469,7 +35891,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35521,7 +35943,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35573,7 +35995,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35625,7 +36047,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35677,7 +36099,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35729,7 +36151,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35781,7 +36203,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35833,7 +36255,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35885,7 +36307,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35937,7 +36359,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -35989,7 +36411,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36041,7 +36463,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36093,7 +36515,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36145,7 +36567,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36197,7 +36619,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36249,7 +36671,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36302,7 +36724,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36355,7 +36777,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36408,7 +36830,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36462,7 +36884,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36516,7 +36938,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36570,7 +36992,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36624,7 +37046,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36677,7 +37099,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36730,7 +37152,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36783,7 +37205,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36836,7 +37258,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36890,7 +37312,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36943,7 +37365,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -36995,7 +37417,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37048,7 +37470,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37101,7 +37523,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37153,7 +37575,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37205,7 +37627,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37257,7 +37679,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37309,7 +37731,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37361,7 +37783,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37413,7 +37835,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37466,7 +37888,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37519,7 +37941,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37571,7 +37993,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37623,7 +38045,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37675,7 +38097,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37727,7 +38149,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37780,7 +38202,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37833,7 +38255,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37885,7 +38307,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37937,7 +38359,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -37989,7 +38411,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38041,7 +38463,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38093,7 +38515,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38145,7 +38567,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38197,7 +38619,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38249,7 +38671,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38301,7 +38723,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38353,7 +38775,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38405,7 +38827,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38457,7 +38879,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38509,7 +38931,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38561,7 +38983,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38613,7 +39035,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38665,7 +39087,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38717,7 +39139,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -38769,7 +39191,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 7686000..f6db2b6 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -134,7 +134,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gles --use-cmd-decoder=passthrough --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -166,7 +166,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 6
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       }
     ]
   },
@@ -2139,7 +2139,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2171,7 +2171,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -2182,7 +2182,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2214,7 +2214,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -2225,7 +2225,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2257,7 +2257,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -2268,7 +2268,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2300,7 +2300,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -2315,7 +2315,7 @@
           "--expected-device-id",
           "0"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2347,7 +2347,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -2362,7 +2362,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2399,7 +2399,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -2414,7 +2414,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2451,7 +2451,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -2463,7 +2463,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force-online-connection-state-for-indicator",
           "--dont-restore-color-profile-after-test"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2495,7 +2495,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -2506,7 +2506,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2538,7 +2538,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -2550,7 +2550,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2583,7 +2583,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 12
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       }
     ]
   },
@@ -2777,7 +2777,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2809,7 +2809,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -2820,7 +2820,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2852,7 +2852,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -2863,7 +2863,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2895,7 +2895,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -2906,7 +2906,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2938,7 +2938,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -2953,7 +2953,7 @@
           "--expected-device-id",
           "0"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -2985,7 +2985,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3000,7 +3000,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3037,7 +3037,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3052,7 +3052,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3089,7 +3089,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3101,7 +3101,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force-online-connection-state-for-indicator",
           "--dont-restore-color-profile-after-test"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3133,7 +3133,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3144,7 +3144,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3176,7 +3176,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3188,7 +3188,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3221,7 +3221,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 12
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       }
     ]
   },
@@ -3326,7 +3326,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_webview",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3362,7 +3362,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_webview/"
       },
       {
         "args": [
@@ -3417,7 +3417,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3448,7 +3448,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3459,7 +3459,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3490,7 +3490,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3501,7 +3501,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3532,7 +3532,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3543,7 +3543,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3574,7 +3574,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3589,7 +3589,7 @@
           "--expected-device-id",
           "0"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3620,7 +3620,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3635,7 +3635,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3671,7 +3671,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3682,7 +3682,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_higher_performance_gpu --use-cmd-decoder=passthrough --use-gl=angle"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3713,7 +3713,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3724,7 +3724,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_higher_performance_gpu --use-cmd-decoder=validating"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3755,7 +3755,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3770,7 +3770,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3806,7 +3806,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3814,7 +3814,7 @@
           "--benchmarks=rendering.mobile",
           "--browser=android-chromium"
         ],
-        "isolate_name": "rendering_representative_perf_tests",
+        "isolate_name": "rendering_representative_perf_tests_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3843,7 +3843,7 @@
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:rendering_representative_perf_tests/"
+        "test_id_prefix": "ninja://chrome/test:rendering_representative_perf_tests_android_chrome/"
       },
       {
         "args": [
@@ -3855,7 +3855,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force-online-connection-state-for-indicator",
           "--dont-restore-color-profile-after-test"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3886,7 +3886,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3897,7 +3897,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3928,7 +3928,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3939,7 +3939,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -3970,7 +3970,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -3982,7 +3982,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gles --use-cmd-decoder=passthrough --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4014,7 +4014,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 6
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -4026,7 +4026,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4058,7 +4058,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 6
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       }
     ]
   },
@@ -4252,7 +4252,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4284,7 +4284,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -4295,7 +4295,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4327,7 +4327,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -4338,7 +4338,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4370,7 +4370,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -4381,7 +4381,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4413,7 +4413,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -4428,7 +4428,7 @@
           "--expected-device-id",
           "0"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4460,7 +4460,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -4475,7 +4475,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4512,7 +4512,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -4527,7 +4527,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4564,7 +4564,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -4576,7 +4576,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force-online-connection-state-for-indicator",
           "--dont-restore-color-profile-after-test"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4608,7 +4608,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -4619,7 +4619,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4651,7 +4651,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -4663,7 +4663,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4696,7 +4696,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 12
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       }
     ]
   },
@@ -4935,7 +4935,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -4967,7 +4967,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -4978,7 +4978,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5010,7 +5010,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5021,7 +5021,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5053,7 +5053,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5064,7 +5064,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5096,7 +5096,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5111,7 +5111,7 @@
           "--expected-device-id",
           "0"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5143,7 +5143,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5158,7 +5158,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5195,7 +5195,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5210,7 +5210,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5247,7 +5247,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5259,7 +5259,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force-online-connection-state-for-indicator",
           "--dont-restore-color-profile-after-test"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5291,7 +5291,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5302,7 +5302,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5334,7 +5334,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5346,7 +5346,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5379,7 +5379,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 12
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       }
     ]
   },
@@ -5398,7 +5398,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5435,7 +5435,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5447,7 +5447,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force-online-connection-state-for-indicator",
           "--dont-restore-color-profile-after-test"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5479,7 +5479,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5492,7 +5492,7 @@
           "--webgl-conformance-version=2.0.1",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5525,7 +5525,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 20
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5538,7 +5538,7 @@
           "--webgl-conformance-version=2.0.1",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5571,7 +5571,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 20
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       }
     ]
   },
@@ -5590,7 +5590,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_webview",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5627,7 +5627,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_webview/"
       },
       {
         "args": [
@@ -5638,7 +5638,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5670,7 +5670,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5681,7 +5681,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5713,7 +5713,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5724,7 +5724,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5756,7 +5756,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5767,7 +5767,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5799,7 +5799,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5810,7 +5810,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5842,7 +5842,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5857,7 +5857,7 @@
           "--expected-device-id",
           "0"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5889,7 +5889,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5904,7 +5904,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5941,7 +5941,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -5956,7 +5956,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -5993,7 +5993,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -6004,7 +6004,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_higher_performance_gpu --use-cmd-decoder=passthrough --use-gl=angle"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -6036,7 +6036,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -6047,7 +6047,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_higher_performance_gpu --use-cmd-decoder=validating"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -6079,7 +6079,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -6094,7 +6094,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -6131,7 +6131,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -6146,7 +6146,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -6183,7 +6183,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -6195,7 +6195,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle --force-online-connection-state-for-indicator --disable-wcg-for-test",
           "--dont-restore-color-profile-after-test"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -6227,7 +6227,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -6239,7 +6239,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force-online-connection-state-for-indicator --disable-wcg-for-test",
           "--dont-restore-color-profile-after-test"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -6271,7 +6271,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -6282,7 +6282,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -6314,7 +6314,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -6325,7 +6325,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -6357,7 +6357,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -6370,7 +6370,7 @@
           "--webgl-conformance-version=2.0.1",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -6403,7 +6403,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 20
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -6416,7 +6416,7 @@
           "--webgl-conformance-version=2.0.1",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -6449,7 +6449,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 20
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -6461,7 +6461,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gles --use-cmd-decoder=passthrough --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -6494,7 +6494,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 6
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -6506,7 +6506,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -6539,7 +6539,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 6
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       }
     ]
   },
@@ -6558,7 +6558,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -6594,7 +6594,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       }
     ]
   },
@@ -6667,7 +6667,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -6704,7 +6704,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       }
     ]
   },
@@ -26032,7 +26032,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_webview",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26069,7 +26069,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_webview/"
       },
       {
         "args": [
@@ -26080,7 +26080,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26112,7 +26112,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26123,7 +26123,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26155,7 +26155,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26166,7 +26166,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26198,7 +26198,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26209,7 +26209,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26241,7 +26241,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26252,7 +26252,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26284,7 +26284,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26299,7 +26299,7 @@
           "--expected-device-id",
           "0"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26331,7 +26331,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26346,7 +26346,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26383,7 +26383,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26398,7 +26398,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26435,7 +26435,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26446,7 +26446,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_higher_performance_gpu --use-cmd-decoder=passthrough --use-gl=angle"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26478,7 +26478,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26489,7 +26489,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_higher_performance_gpu --use-cmd-decoder=validating"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26521,7 +26521,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26536,7 +26536,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26573,7 +26573,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26588,7 +26588,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26625,7 +26625,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26637,7 +26637,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=passthrough --use-gl=angle --force-online-connection-state-for-indicator --disable-wcg-for-test",
           "--dont-restore-color-profile-after-test"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26669,7 +26669,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26681,7 +26681,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force-online-connection-state-for-indicator --disable-wcg-for-test",
           "--dont-restore-color-profile-after-test"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26713,7 +26713,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26724,7 +26724,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26756,7 +26756,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26767,7 +26767,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26799,7 +26799,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26812,7 +26812,7 @@
           "--webgl-conformance-version=2.0.1",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26845,7 +26845,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 20
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26858,7 +26858,7 @@
           "--webgl-conformance-version=2.0.1",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26891,7 +26891,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 20
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26903,7 +26903,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gles --use-cmd-decoder=passthrough --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26936,7 +26936,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 6
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -26948,7 +26948,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -26981,7 +26981,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 6
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       }
     ]
   },
diff --git a/testing/buildbot/chromium.gpu.json b/testing/buildbot/chromium.gpu.json
index 3782138..eb4d85e 100644
--- a/testing/buildbot/chromium.gpu.json
+++ b/testing/buildbot/chromium.gpu.json
@@ -12,7 +12,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -43,7 +43,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -54,7 +54,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -85,7 +85,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -96,7 +96,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -127,7 +127,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -138,7 +138,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -169,7 +169,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -184,7 +184,7 @@
           "--expected-device-id",
           "0"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -215,7 +215,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -230,7 +230,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -266,7 +266,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -281,7 +281,7 @@
           "${buildername}",
           "--git-revision=${got_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -317,7 +317,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -326,7 +326,7 @@
           "--browser=android-chromium"
         ],
         "experiment_percentage": 10,
-        "isolate_name": "rendering_representative_perf_tests",
+        "isolate_name": "rendering_representative_perf_tests_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -352,7 +352,7 @@
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:rendering_representative_perf_tests/"
+        "test_id_prefix": "ninja://chrome/test:rendering_representative_perf_tests_android_chrome/"
       },
       {
         "args": [
@@ -364,7 +364,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force-online-connection-state-for-indicator",
           "--dont-restore-color-profile-after-test"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -395,7 +395,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -406,7 +406,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -437,7 +437,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -449,7 +449,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -481,7 +481,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 12
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       }
     ]
   },
diff --git a/testing/buildbot/chromium.mac.json b/testing/buildbot/chromium.mac.json
index cfe6974..6683003 100644
--- a/testing/buildbot/chromium.mac.json
+++ b/testing/buildbot/chromium.mac.json
@@ -7985,6 +7985,7 @@
         "test_id_prefix": "ninja://headless:headless_unittests/"
       },
       {
+        "ci_only": true,
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -8740,6 +8741,7 @@
         "args": [
           "--num-retries=3"
         ],
+        "ci_only": true,
         "isolate_name": "blink_web_tests",
         "isolate_profile_data": true,
         "merge": {
@@ -12726,7 +12728,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -12777,7 +12779,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -12828,7 +12830,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -12879,7 +12881,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -12930,7 +12932,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -12981,7 +12983,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13032,7 +13034,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13083,7 +13085,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13134,7 +13136,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13185,7 +13187,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13236,7 +13238,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13287,7 +13289,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13338,7 +13340,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13389,7 +13391,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13440,7 +13442,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13491,7 +13493,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13542,7 +13544,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13593,7 +13595,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13644,7 +13646,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13695,7 +13697,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13746,7 +13748,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13797,7 +13799,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13848,7 +13850,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13899,7 +13901,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -13950,7 +13952,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14001,7 +14003,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14052,7 +14054,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14103,7 +14105,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14154,7 +14156,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14205,7 +14207,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14256,7 +14258,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14307,7 +14309,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14358,7 +14360,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14409,7 +14411,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14461,7 +14463,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14514,7 +14516,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14567,7 +14569,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14619,7 +14621,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14671,7 +14673,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14724,7 +14726,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14776,7 +14778,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14827,7 +14829,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14878,7 +14880,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14929,7 +14931,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -14980,7 +14982,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15031,7 +15033,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15082,7 +15084,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15133,7 +15135,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15184,7 +15186,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15235,7 +15237,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15286,7 +15288,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15337,7 +15339,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15388,7 +15390,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15439,7 +15441,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15490,7 +15492,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15541,7 +15543,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15592,7 +15594,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15643,7 +15645,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15694,7 +15696,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15745,7 +15747,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15796,7 +15798,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15847,7 +15849,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15898,7 +15900,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -15949,7 +15951,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16001,7 +16003,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16053,7 +16055,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16104,7 +16106,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16155,7 +16157,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16206,7 +16208,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16257,7 +16259,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16308,7 +16310,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16359,7 +16361,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16410,7 +16412,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16461,7 +16463,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16512,7 +16514,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16563,7 +16565,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16614,7 +16616,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16665,7 +16667,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16716,7 +16718,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16767,7 +16769,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16818,7 +16820,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16869,7 +16871,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16920,7 +16922,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -16971,7 +16973,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17022,7 +17024,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17073,7 +17075,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17124,7 +17126,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17175,7 +17177,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17226,7 +17228,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17277,7 +17279,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17328,7 +17330,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17379,7 +17381,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17430,7 +17432,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17481,7 +17483,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17532,7 +17534,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17583,7 +17585,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17634,7 +17636,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17685,7 +17687,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17736,7 +17738,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17787,7 +17789,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17838,7 +17840,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17889,7 +17891,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17940,7 +17942,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -17991,7 +17993,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18042,7 +18044,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18093,7 +18095,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18144,7 +18146,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18195,7 +18197,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18246,7 +18248,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18297,7 +18299,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18348,7 +18350,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18399,7 +18401,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18450,7 +18452,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18501,7 +18503,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18560,7 +18562,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18612,7 +18614,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18664,7 +18666,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18716,7 +18718,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18768,7 +18770,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18820,7 +18822,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18872,7 +18874,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18925,7 +18927,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -18978,7 +18980,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19031,7 +19033,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19084,7 +19086,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19137,7 +19139,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19190,7 +19192,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19243,7 +19245,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19296,7 +19298,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19349,7 +19351,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19402,7 +19404,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19454,7 +19456,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19506,7 +19508,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19558,7 +19560,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19610,7 +19612,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19662,7 +19664,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19714,7 +19716,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19766,7 +19768,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19818,7 +19820,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19870,7 +19872,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19922,7 +19924,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -19975,7 +19977,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20028,7 +20030,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20081,7 +20083,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20134,7 +20136,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20186,7 +20188,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20238,7 +20240,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20290,7 +20292,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20342,7 +20344,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20394,7 +20396,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20446,7 +20448,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20498,7 +20500,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20550,7 +20552,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20602,7 +20604,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20654,7 +20656,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20706,7 +20708,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20758,7 +20760,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20810,7 +20812,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20862,7 +20864,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20914,7 +20916,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -20971,7 +20973,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21021,7 +21023,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21071,7 +21073,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21121,7 +21123,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21171,7 +21173,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21222,7 +21224,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21273,7 +21275,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21324,7 +21326,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21376,7 +21378,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21428,7 +21430,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21480,7 +21482,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21532,7 +21534,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21583,7 +21585,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21634,7 +21636,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21685,7 +21687,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21736,7 +21738,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21788,7 +21790,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21840,7 +21842,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21891,7 +21893,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21941,7 +21943,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -21991,7 +21993,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -22041,7 +22043,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -22092,7 +22094,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -22143,7 +22145,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -22193,7 +22195,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -22244,7 +22246,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -22295,7 +22297,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -22345,7 +22347,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -22395,7 +22397,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -22445,7 +22447,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
@@ -22495,7 +22497,7 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "os": "Mac-10.15|Mac-10.16|Mac-11"
+              "os": "Mac-11|Mac-10.16"
             }
           ],
           "named_caches": [
diff --git a/testing/buildbot/client.v8.fyi.json b/testing/buildbot/client.v8.fyi.json
index c7a85ad..6985b939 100644
--- a/testing/buildbot/client.v8.fyi.json
+++ b/testing/buildbot/client.v8.fyi.json
@@ -12,7 +12,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -42,7 +42,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -53,7 +53,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -83,7 +83,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -94,7 +94,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -124,7 +124,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -135,7 +135,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -165,7 +165,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -180,7 +180,7 @@
           "${buildername}",
           "--git-revision=${got_cr_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -215,7 +215,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -230,7 +230,7 @@
           "${buildername}",
           "--git-revision=${got_cr_revision}"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -265,7 +265,7 @@
           "idempotent": false,
           "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -277,7 +277,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force-online-connection-state-for-indicator",
           "--dont-restore-color-profile-after-test"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -307,7 +307,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -318,7 +318,7 @@
           "-v",
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -348,7 +348,7 @@
           "idempotent": false,
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -360,7 +360,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gles --use-cmd-decoder=passthrough --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -391,7 +391,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 6
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       },
       {
         "args": [
@@ -403,7 +403,7 @@
           "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-cmd-decoder=validating --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json"
         ],
-        "isolate_name": "telemetry_gpu_integration_test",
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
@@ -434,7 +434,7 @@
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
           "shards": 6
         },
-        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
       }
     ]
   },
diff --git a/testing/buildbot/generate_buildbot_json.py b/testing/buildbot/generate_buildbot_json.py
index 9abc2185..7d3d20d 100755
--- a/testing/buildbot/generate_buildbot_json.py
+++ b/testing/buildbot/generate_buildbot_json.py
@@ -26,6 +26,13 @@
 
 THIS_DIR = os.path.dirname(os.path.abspath(__file__))
 
+BROWSER_CONFIG_TO_TARGET_SUFFIX_MAP = {
+    'android-chromium': '_android_chrome',
+    'android-chromium-monochrome': '_android_monochrome',
+    'android-weblayer': '_android_weblayer',
+    'android-webview': '_android_webview',
+}
+
 
 class BBGenErr(Exception):
   def __init__(self, message):
@@ -921,12 +928,11 @@
     if not result:
       return None
     result['isolate_name'] = test_config.get(
-      'isolate_name', 'telemetry_gpu_integration_test')
+        'isolate_name',
+        self.get_default_isolate_name(tester_config, is_android_webview))
 
     # Populate test_id_prefix.
-    gn_entry = (
-        self.gn_isolate_map.get(result['isolate_name']) or
-        self.gn_isolate_map.get('telemetry_gpu_integration_test'))
+    gn_entry = self.gn_isolate_map[result['isolate_name']]
     result['test_id_prefix'] = 'ninja:%s/' % gn_entry['label']
 
     args = result.get('args', [])
@@ -969,6 +975,16 @@
       tester_config, result['swarming'], args))
     return result
 
+  def get_default_isolate_name(self, tester_config, is_android_webview):
+    if self.is_android(tester_config):
+      if is_android_webview:
+        return 'telemetry_gpu_integration_test_android_webview'
+      return (
+          'telemetry_gpu_integration_test' +
+          BROWSER_CONFIG_TO_TARGET_SUFFIX_MAP[tester_config['browser_config']])
+    else:
+      return 'telemetry_gpu_integration_test'
+
   def get_test_generator_map(self):
     return {
         'android_webview_gpu_telemetry_tests':
diff --git a/testing/buildbot/generate_buildbot_json_unittest.py b/testing/buildbot/generate_buildbot_json_unittest.py
index fc8c000..15bcdc1 100755
--- a/testing/buildbot/generate_buildbot_json_unittest.py
+++ b/testing/buildbot/generate_buildbot_json_unittest.py
@@ -355,6 +355,58 @@
 ]
 """
 
+FOO_GPU_TELEMETRY_TEST_WATERFALL_ANDROID = """\
+[
+  {
+    'project': 'chromium',
+    'bucket': 'ci',
+    'name': 'chromium.test',
+    'machines': {
+      'Fake Tester': {
+        'os_type': 'android',
+        'browser_config': 'android-chromium',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_type': 'bullhead',
+            },
+          ],
+        },
+        'test_suites': {
+          'gpu_telemetry_tests': 'composition_tests',
+        },
+      },
+    },
+  },
+]
+"""
+
+FOO_GPU_TELEMETRY_TEST_WATERFALL_ANDROID_WEBVIEW = """\
+[
+  {
+    'project': 'chromium',
+    'bucket': 'ci',
+    'name': 'chromium.test',
+    'machines': {
+      'Fake Tester': {
+        'os_type': 'android',
+        'browser_config': 'not-a-real-browser',
+        'swarming': {
+          'dimension_sets': [
+            {
+              'device_type': 'bullhead',
+            },
+          ],
+        },
+        'test_suites': {
+          'android_webview_gpu_telemetry_tests': 'composition_tests',
+        },
+      },
+    },
+  },
+]
+"""
+
 NVIDIA_GPU_TELEMETRY_TEST_WATERFALL = """\
 [
   {
@@ -1554,6 +1606,96 @@
 }
 """
 
+GPU_TELEMETRY_TEST_OUTPUT_ANDROID = """\
+{
+  "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
+  "AAAAA2 See generate_buildbot_json.py to make changes": {},
+  "Fake Tester": {
+    "isolated_scripts": [
+      {
+        "args": [
+          "foo",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test_android_chrome",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "foo_tests",
+        "should_retry_with_patch": false,
+        "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": [
+            {
+              "device_type": "bullhead"
+            }
+          ],
+          "idempotent": false
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_chrome/"
+      }
+    ]
+  }
+}
+"""
+
+GPU_TELEMETRY_TEST_OUTPUT_ANDROID_WEBVIEW = """\
+{
+  "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
+  "AAAAA2 See generate_buildbot_json.py to make changes": {},
+  "Fake Tester": {
+    "isolated_scripts": [
+      {
+        "args": [
+          "foo",
+          "--show-stdout",
+          "--browser=android-webview-instrumentation",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test_android_webview",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "foo_tests",
+        "should_retry_with_patch": false,
+        "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": [
+            {
+              "device_type": "bullhead"
+            }
+          ],
+          "idempotent": false
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test_android_webview/"
+      }
+    ]
+  }
+}
+"""
+
 NVIDIA_GPU_TELEMETRY_TEST_OUTPUT = """\
 {
   "AAAAA1 AUTOGENERATED FILE DO NOT EDIT": {},
@@ -2146,6 +2288,25 @@
       }
 }
 """
+
+GPU_TELEMETRY_GN_ISOLATE_MAP_ANDROID = """\
+{
+  'telemetry_gpu_integration_test_android_chrome': {
+    'label': '//chrome/test:telemetry_gpu_integration_test_android_chrome',
+    'type': 'script',
+      }
+}
+"""
+
+GPU_TELEMETRY_GN_ISOLATE_MAP_ANDROID_WEBVIEW = """\
+{
+  'telemetry_gpu_integration_test_android_webview': {
+    'label': '//chrome/test:telemetry_gpu_integration_test_android_webview',
+    'type': 'script',
+      }
+}
+"""
+
 GN_ISOLATE_MAP_KEY_LABEL_MISMATCH="""\
 {
   'foo_test': {
@@ -2476,6 +2637,34 @@
     fbb.check_output_file_consistency(verbose=True)
     self.assertFalse(fbb.printed_lines)
 
+  def test_gpu_telemetry_tests_android(self):
+    fbb = FakeBBGen(self.args,
+                    FOO_GPU_TELEMETRY_TEST_WATERFALL_ANDROID,
+                    COMPOSITION_SUITE_WITH_NAME_NOT_ENDING_IN_TEST,
+                    LUCI_MILO_CFG,
+                    exceptions=NO_BAR_TEST_EXCEPTIONS,
+                    gn_isolate_map=GPU_TELEMETRY_GN_ISOLATE_MAP_ANDROID)
+    self.create_testing_buildbot_json_file('chromium.test.json',
+                                           GPU_TELEMETRY_TEST_OUTPUT_ANDROID)
+    self.create_testing_buildbot_json_file('chromium.ci.json',
+                                           GPU_TELEMETRY_TEST_OUTPUT_ANDROID)
+    fbb.check_output_file_consistency(verbose=True)
+    self.assertFalse(fbb.printed_lines)
+
+  def test_gpu_telemetry_tests_android_webview(self):
+    fbb = FakeBBGen(self.args,
+                    FOO_GPU_TELEMETRY_TEST_WATERFALL_ANDROID_WEBVIEW,
+                    COMPOSITION_SUITE_WITH_NAME_NOT_ENDING_IN_TEST,
+                    LUCI_MILO_CFG,
+                    exceptions=NO_BAR_TEST_EXCEPTIONS,
+                    gn_isolate_map=GPU_TELEMETRY_GN_ISOLATE_MAP_ANDROID_WEBVIEW)
+    self.create_testing_buildbot_json_file(
+        'chromium.test.json', GPU_TELEMETRY_TEST_OUTPUT_ANDROID_WEBVIEW)
+    self.create_testing_buildbot_json_file(
+        'chromium.ci.json', GPU_TELEMETRY_TEST_OUTPUT_ANDROID_WEBVIEW)
+    fbb.check_output_file_consistency(verbose=True)
+    self.assertFalse(fbb.printed_lines)
+
   def test_nvidia_gpu_telemetry_tests(self):
     fbb = FakeBBGen(self.args,
                     NVIDIA_GPU_TELEMETRY_TEST_WATERFALL,
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index d7706c7..bd85c3cc 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -1541,6 +1541,16 @@
     "script": "//testing/scripts/run_rendering_benchmark_with_gated_performance.py",
     "type": "script",
   },
+  "rendering_representative_perf_tests_android_chrome": {
+    "args": [
+      "--output-format=csv",
+      "--browser=release",
+      "--upload-results",
+    ],
+    "label": "//chrome/test:rendering_representative_perf_tests_android_chrome",
+    "script": "//testing/scripts/run_rendering_benchmark_with_gated_performance.py",
+    "type": "script",
+  },
   "resource_sizes_chrome_modern_public_minimal_apks": {
     "label": "//chrome/android:resource_sizes_chrome_modern_public_minimal_apks",
     "type": "generated_script",
@@ -1678,6 +1688,22 @@
     "script": "//testing/scripts/run_gpu_integration_test_as_googletest.py",
     "type": "script",
   },
+  "telemetry_gpu_integration_test_android_chrome": {
+    "args": [
+      "../../content/test/gpu/run_gpu_integration_test.py",
+    ],
+    "label": "//chrome/test:telemetry_gpu_integration_test_android_chrome",
+    "script": "//testing/scripts/run_gpu_integration_test_as_googletest.py",
+    "type": "script",
+  },
+  "telemetry_gpu_integration_test_android_webview": {
+    "args": [
+      "../../content/test/gpu/run_gpu_integration_test.py",
+    ],
+    "label": "//chrome/test:telemetry_gpu_integration_test_android_webview",
+    "script": "//testing/scripts/run_gpu_integration_test_as_googletest.py",
+    "type": "script",
+  },
   "telemetry_gpu_integration_test_scripts_only": {
     "label": "//chrome/test:telemetry_gpu_integration_test_scripts_only",
     "type": "additional_compile_target",
@@ -1710,6 +1736,24 @@
     "script": "//testing/scripts/run_telemetry_as_googletest.py",
     "type": "script",
   },
+  "telemetry_perf_unittests_android_chrome": {
+    "args": [
+      "../../tools/perf/run_tests",
+      "-v",
+    ],
+    "label": "//chrome/test:telemetry_perf_unittests_android_chrome",
+    "script": "//testing/scripts/run_telemetry_as_googletest.py",
+    "type": "script",
+  },
+  "telemetry_perf_unittests_android_monochrome": {
+    "args": [
+      "../../tools/perf/run_tests",
+      "-v",
+    ],
+    "label": "//chrome/test:telemetry_perf_unittests_android_monochrome",
+    "script": "//testing/scripts/run_telemetry_as_googletest.py",
+    "type": "script",
+  },
   "telemetry_unittests": {
     "args": [
       "--xvfb",
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index 8b4854e..402e249 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -675,14 +675,6 @@
       },
     },
   },
-  'mac_10.15_or_mac_11': {
-    'swarming': {
-      'dimensions': {
-        'cpu': 'x86-64',
-        'os': 'Mac-10.15|Mac-10.16|Mac-11',
-      },
-    },
-  },
   'mac_11_beta_x64': {
     'swarming': {
       'dimensions': {
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 7da90af..bd4e971 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -273,6 +273,7 @@
         },
       },
       'Mac10.15 Tests': {
+        'ci_only': True,
         'swarming': {
           'dimension_sets': [
             {
@@ -1796,6 +1797,9 @@
           'shards': 32, # Adjusted for testing, see https://crbug.com/1179567
         },
       },
+      'Mac10.15 Tests': {
+        'ci_only': True,
+      },
       'Mac11 Tests': {
         # TODO(crbug.com/1206401): Restore to defaults when there is enough
         # capacity.
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 5864a6be0..1850858 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -4752,7 +4752,7 @@
 
     'rendering_mobile_representative_perf_tests_isolated_scripts': {
       'rendering_representative_perf_tests': {
-        'isolate_name': 'rendering_representative_perf_tests',
+        'isolate_name': 'rendering_representative_perf_tests_android_chrome',
         'args': [
           "../../tools/perf/run_benchmark",
           "--benchmarks=rendering.mobile",
@@ -4764,7 +4764,7 @@
 
     'rendering_mobile_representative_perf_tests_isolated_scripts_experimental': {
       'rendering_representative_perf_tests': {
-        'isolate_name': 'rendering_representative_perf_tests',
+        'isolate_name': 'rendering_representative_perf_tests_android_chrome',
         'args': [
           "../../tools/perf/run_benchmark",
           "--benchmarks=rendering.mobile",
@@ -5044,7 +5044,7 @@
           '--passthrough',
           '--retry-limit=2',
         ],
-        'isolate_name': 'telemetry_perf_unittests',
+        'isolate_name': 'telemetry_perf_unittests_android_chrome',
         'resultdb': {
           'enable': True,
         },
@@ -5057,7 +5057,7 @@
           '--passthrough',
           '--retry-limit=2',
         ],
-        'isolate_name': 'telemetry_perf_unittests',
+        'isolate_name': 'telemetry_perf_unittests_android_monochrome',
         'resultdb': {
           'enable': True,
         },
@@ -5096,6 +5096,23 @@
       },
     },
 
+    'telemetry_perf_unittests_isolated_scripts_android': {
+      'telemetry_perf_unittests': {
+        'isolate_name': 'telemetry_perf_unittests_android_chrome',
+        'args': [
+          # TODO(crbug.com/1077284): Remove this once Crashpad is the default.
+          '--extra-browser-args=--enable-crashpad',
+        ],
+        'swarming': {
+          'idempotent': False,  # https://crbug.com/549140
+          'shards': 12,
+        },
+        'resultdb': {
+          'enable': True,
+        },
+      },
+    },
+
     'test_buildbucket_api_gpu_use_cases': {
       'test_buildbucket_api_gpu_use_cases': {},
     },
@@ -5974,7 +5991,7 @@
       'web_engine_gtests',
     ],
 
-    'fuchsia_x64_isolated_scripts': [
+    'fuchsia_isolated_scripts': [
       'chromium_webkit_isolated_scripts',
       'gpu_angle_fuchsia_unittests_isolated_scripts'
     ],
@@ -6750,14 +6767,14 @@
       'components_perftests_isolated_scripts',
       'monochrome_public_apk_checker_isolated_script',
       'telemetry_android_minidump_unittests_isolated_scripts',
-      'telemetry_perf_unittests_isolated_scripts',
+      'telemetry_perf_unittests_isolated_scripts_android',
     ],
 
     'marshmallow_pie_isolated_scripts': [
       'android_isolated_scripts',
       'components_perftests_isolated_scripts',
       'telemetry_android_minidump_unittests_isolated_scripts',
-      'telemetry_perf_unittests_isolated_scripts',
+      'telemetry_perf_unittests_isolated_scripts_android',
     ],
 
     'mojo_android_gtests': [
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 9de8d2e..6138c29 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -400,7 +400,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M92',
-          'revision': 'version:92.0.4515.42',
+          'revision': 'version:92.0.4515.43',
         }
       ],
     },
@@ -424,7 +424,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M91',
-          'revision': 'version:91.0.4472.95',
+          'revision': 'version:91.0.4472.96',
         }
       ],
     },
@@ -472,7 +472,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M92',
-          'revision': 'version:92.0.4515.42',
+          'revision': 'version:92.0.4515.43',
         }
       ],
     },
@@ -496,7 +496,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M91',
-          'revision': 'version:91.0.4472.95',
+          'revision': 'version:91.0.4472.96',
         }
       ],
     },
@@ -544,7 +544,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M92',
-          'revision': 'version:92.0.4515.42',
+          'revision': 'version:92.0.4515.43',
         }
       ],
     },
@@ -568,7 +568,7 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M91',
-          'revision': 'version:91.0.4472.95',
+          'revision': 'version:91.0.4472.96',
         }
       ],
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index adf28df..a58fe91 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -1746,7 +1746,7 @@
         },
         'test_suites': {
           'gtest_tests': 'fuchsia_gtests',
-          'isolated_scripts': 'fuchsia_x64_isolated_scripts',
+          'isolated_scripts': 'fuchsia_isolated_scripts',
           'gpu_telemetry_tests': 'fuchsia_gpu_telemetry_tests',
         },
       },
@@ -2041,7 +2041,7 @@
         ],
         'mixins': [
           'enable_resultdb',
-          'mac_10.15_or_mac_11',
+          'mac_11_x64',
           'mac_toolchain',
           'out_dir_arg',
           'xcode_12d4e',
@@ -2075,7 +2075,7 @@
           'enable_resultdb',
           'ios_restart_device',
           'limited_capacity_bot',
-          'mac_10.15_or_mac_11',
+          'mac_11_x64',
           'mac_toolchain',
           'out_dir_arg',
           'xcode_12d4e',
@@ -2809,9 +2809,12 @@
         'additional_compile_targets': [
           'all',
         ],
+        'browser_config': 'web-engine-shell',
+        'os_type': 'linux',
         'test_suites': {
+          'gpu_telemetry_tests': 'fuchsia_gpu_telemetry_tests',
           'gtest_tests': 'fuchsia_gtests',
-          'isolated_scripts': 'gpu_angle_fuchsia_unittests_isolated_scripts',
+          'isolated_scripts': 'fuchsia_isolated_scripts',
         },
         'mixins': [
           'arm64',
@@ -2876,7 +2879,7 @@
       'ios-asan': {
         'mixins': [
           'enable_resultdb',
-          'mac_10.15_or_mac_11',
+          'mac_11_x64',
           'mac_toolchain',
           'out_dir_arg',
           'xcode_12d4e',
@@ -2891,7 +2894,7 @@
           'enable_resultdb',
           'ios_output_disabled_tests',
           'isolate_profile_data',
-          'mac_10.15_or_mac_11',
+          'mac_11_x64',
           'mac_toolchain',
           'out_dir_arg',
           'xcode_12d4e',
@@ -2904,7 +2907,7 @@
       'ios-simulator-cronet': {
         'mixins': [
           'enable_resultdb',
-          'mac_10.15_or_mac_11',
+          'mac_11_x64',
           'mac_toolchain',
           'out_dir_arg',
           'xcode_12d4e',
@@ -2917,7 +2920,7 @@
       'ios-simulator-multi-window': {
         'mixins': [
           'enable_resultdb',
-          'mac_10.15_or_mac_11',
+          'mac_11_x64',
           'mac_toolchain',
           'out_dir_arg',
           'xcode_12d4e',
@@ -2931,7 +2934,7 @@
         'mixins': [
           'enable_resultdb',
           'ios_custom_webkit',
-          'mac_10.15_or_mac_11',
+          'mac_11_x64',
           'mac_toolchain',
           'out_dir_arg',
           'xcode_12e262',
@@ -4832,7 +4835,7 @@
         },
         'test_suites': {
           'gtest_tests': 'fuchsia_gtests',
-          'isolated_scripts': 'fuchsia_x64_isolated_scripts',
+          'isolated_scripts': 'fuchsia_isolated_scripts',
           'gpu_telemetry_tests': 'fuchsia_gpu_telemetry_tests',
         },
       },
@@ -5119,7 +5122,7 @@
         'mixins': [
           'enable_resultdb',
           'isolate_profile_data',
-          'mac_10.15_or_mac_11',
+          'mac_11_x64',
           'mac_toolchain',
           'out_dir_arg',
           'xcode_12d4e',
@@ -5136,7 +5139,7 @@
         'mixins': [
           'enable_resultdb',
           'isolate_profile_data',
-          'mac_10.15_or_mac_11',
+          'mac_11_x64',
           'mac_toolchain',
           'out_dir_arg',
           'xcode_12d4e',
@@ -5152,7 +5155,7 @@
         ],
         'mixins': [
           'enable_resultdb',
-          'mac_10.15_or_mac_11',
+          'mac_11_x64',
           'mac_toolchain',
           'out_dir_arg',
           'xcode_12d4e',
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 781f3ba..45ed5fe 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -487,23 +487,23 @@
             ]
         }
     ],
-    "AndroidInProductHelpToolbarButtons_TabSwitcher": [
+    "AndroidInProductHelpToolbarButtons": [
         {
             "platforms": [
                 "android"
             ],
             "experiments": [
                 {
-                    "name": "TabSwitcherButtonIphConfig",
+                    "name": "Default_20210520",
                     "params": {
-                        "availability": ">=1",
-                        "cohortFeatureName": "ToolbarIphAndroidCohort1",
+                        "availability": ">=14",
                         "event_trigger": "name:tab_switcher_iph_triggered;comparator:==0;window:90;storage:90",
                         "event_used": "name:tab_switcher_button_clicked;comparator:==0;window:14;storage:90",
                         "session_rate": "<1"
                     },
                     "enable_features": [
-                        "IPH_TabSwitcherButton"
+                        "IPH_TabSwitcherButton",
+                        "ToolbarIphAndroid"
                     ]
                 }
             ]
@@ -4171,25 +4171,6 @@
             ]
         }
     ],
-    "IOSStartSurface": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "ShrinkLogo",
-                    "params": {
-                        "ReturnToStartSurfaceInactiveDurationInSeconds": "0",
-                        "shrink_logo": "true"
-                    },
-                    "enable_features": [
-                        "StartSurface"
-                    ]
-                }
-            ]
-        }
-    ],
     "IOSUMABackgroundSessions": [
         {
             "platforms": [
@@ -7428,6 +7409,25 @@
             ]
         }
     ],
+    "StartSurface": [
+        {
+            "platforms": [
+                "ios"
+            ],
+            "experiments": [
+                {
+                    "name": "ShrinkLogo",
+                    "params": {
+                        "ReturnToStartSurfaceInactiveDurationInSeconds": "0",
+                        "shrink_logo": "true"
+                    },
+                    "enable_features": [
+                        "StartSurface"
+                    ]
+                }
+            ]
+        }
+    ],
     "StorageServiceOutOfProcess": [
         {
             "platforms": [
diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD.gn
index 617fdf6..59c92ace 100644
--- a/third_party/android_deps/BUILD.gn
+++ b/third_party/android_deps/BUILD.gn
@@ -138,7 +138,7 @@
   # This target does not come with most of its dependencies and is
   # only meant to be used by the resources shrinker. If you wish to use
   # this for other purposes, change buildCompileNoDeps in build.gradle.
-  visibility = [ "//build/android/gyp/resources_shrinker:*" ]
+  visibility = [ "//build/android/unused_resources:*" ]
 }
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
@@ -150,7 +150,7 @@
   # This target does not come with most of its dependencies and is
   # only meant to be used by the resources shrinker. If you wish to use
   # this for other purposes, change buildCompileNoDeps in build.gradle.
-  visibility = [ "//build/android/gyp/resources_shrinker:*" ]
+  visibility = [ "//build/android/unused_resources:*" ]
 }
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
@@ -162,7 +162,7 @@
   # This target does not come with most of its dependencies and is
   # only meant to be used by the resources shrinker. If you wish to use
   # this for other purposes, change buildCompileNoDeps in build.gradle.
-  visibility = [ "//build/android/gyp/resources_shrinker:*" ]
+  visibility = [ "//build/android/unused_resources:*" ]
 }
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
diff --git a/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy b/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
index f7c0fb16..2ca87fa9 100644
--- a/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
+++ b/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
@@ -655,7 +655,7 @@
                 sb.append('  # This target does not come with most of its dependencies and is\n')
                 sb.append('  # only meant to be used by the resources shrinker. If you wish to use\n')
                 sb.append('  # this for other purposes, change buildCompileNoDeps in build.gradle.\n')
-                sb.append('  visibility = [ "//build/android/gyp/resources_shrinker:*" ]\n')
+                sb.append('  visibility = [ "//build/android/unused_resources:*" ]\n')
                 break
             case 'org_jetbrains_kotlinx_kotlinx_coroutines_android':
                sb.append('requires_android = true')
diff --git a/third_party/blink/public/platform/platform.h b/third_party/blink/public/platform/platform.h
index 6826455..ba0b377 100644
--- a/third_party/blink/public/platform/platform.h
+++ b/third_party/blink/public/platform/platform.h
@@ -143,6 +143,7 @@
 class WebSecurityOrigin;
 class WebThemeEngine;
 class WebVideoCaptureImplManager;
+struct WebContentSecurityPolicyHeader;
 
 namespace scheduler {
 class WebThreadScheduler;
@@ -867,6 +868,14 @@
     return nullptr;
   }
 
+  // Content Security Policy --------------------------------------
+
+  // Appends to `csp`, the default CSP which should be applied to the given
+  // `url`. This allows the embedder to customize the applied CSP.
+  virtual void AppendContentSecurityPolicy(
+      const WebURL& url,
+      blink::WebVector<blink::WebContentSecurityPolicyHeader>* csp) {}
+
  private:
   static void InitializeMainThreadCommon(Platform* platform,
                                          std::unique_ptr<Thread> main_thread);
diff --git a/third_party/blink/public/platform/web_vector.h b/third_party/blink/public/platform/web_vector.h
index c7d72275c..b8df4922 100644
--- a/third_party/blink/public/platform/web_vector.h
+++ b/third_party/blink/public/platform/web_vector.h
@@ -171,6 +171,9 @@
     data_.emplace_back(std::forward<Args>(args)...);
   }
 
+  void push_back(const T& value) { data_.push_back(value); }
+  void push_back(T&& value) { data_.push_back(std::move(value)); }
+
   void Swap(WebVector<T>& other) { data_.swap(other.data_); }
   void Clear() { data_.clear(); }
 
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py b/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
index 33c1963a..5fd2955 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
@@ -9,7 +9,6 @@
 from . import name_style
 from .blink_v8_bridge import blink_class_name
 from .blink_v8_bridge import blink_type_info
-from .blink_v8_bridge import make_default_value_expr
 from .blink_v8_bridge import make_v8_to_blink_value
 from .blink_v8_bridge import make_v8_to_blink_value_variadic
 from .blink_v8_bridge import native_value_tag
@@ -97,25 +96,9 @@
 
 def callback_function_name(cg_context,
                            overload_index=None,
-                           argument_count=None,
                            for_cross_origin=False):
-    """
-    Args:
-        cg_context: A CodeGenContext of the target IDL construct.
-        overload_index: An overload index if the target is an overloaded
-            IDL operation.
-        argument_count: When the target is an IDL operation that has optional
-            arguments and is annotated with [NoAllocDirectCall], the value is
-            the number of arguments that V8 passes in (excluding the fixed
-            arguments like the receiver object and the
-            v8::FastApiCallbackOptions.)
-        for_cross_origin: True if the target is the cross origin accessible
-            version.
-    """
-
     assert isinstance(cg_context, CodeGenContext)
     assert overload_index is None or isinstance(overload_index, int)
-    assert argument_count is None or isinstance(argument_count, int)
     assert isinstance(for_cross_origin, bool)
 
     def _cxx_name(name):
@@ -166,24 +149,14 @@
     elif cg_context.stringifier:
         kind = "Operation"
 
-    if cg_context.no_alloc_direct_call:
-        nadc = "NoAllocDirectCall"
-    elif cg_context.no_alloc_direct_call_for_testing:
-        nadc = "NoAllocDirectCallForTesting"
-    else:
-        nadc = ""
-
-    overload = ""
-    if overload_index is not None and (len(cg_context.constructor_group
-                                           or cg_context.operation_group) > 1):
-        overload += "Overload{}".format(overload_index + 1)
-    if argument_count is not None:
-        overload += "Arg{}".format(argument_count)
-
     if for_cross_origin:
         suffix = "CrossOrigin"
-    elif nadc or overload:
-        suffix = nadc + overload
+    elif overload_index is not None:
+        suffix = "Overload{}".format(overload_index + 1)
+    elif cg_context.no_alloc_direct_call:
+        suffix = "NoAllocDirectCallCallback"
+    elif cg_context.no_alloc_direct_call_for_testing:
+        suffix = "NoAllocDirectCallForTestingCallback"
     else:
         suffix = "Callback"
 
@@ -2210,74 +2183,14 @@
     return SequenceNode([named_ctor_def, EmptyNode(), func_def])
 
 
-def list_no_alloc_direct_call_callbacks(cg_context):
-    """
-    Returns a list of [NoAllocDirectCall] callback functions to be registered
-    at V8, including all overloaded operations annotated with
-    [NoAllocDirectCall] and their variants of optional arguments.
-
-    Example:
-      Given the following Web IDL fragments,
-        void f(DOMString);                                             // (a)
-        [NoAllocDirectCall] void f(Node node);                         // (b)
-        [NoAllocDirectCall] void f(optional long a, optional long b);  // (c)
-      the following callback functions should be generated,
-        void F(v8::Local<v8::Value> node);  // (b)
-        void F();                           // (c)
-        void F(int32_t a);                  // (c)
-        void F(int32_t a, int32_t b);       // (c)
-      thus the following entries are returned.
-        [
-          Entry(operation=(b), argument_count=1),  # overload_index=2
-          Entry(operation=(c), argument_count=2),  # overload_index=3
-          Entry(operation=(c), argument_count=1),  # overload_index=3
-          Entry(operation=(c), argument_count=0),  # overload_index=3
-        ]
-    """
-    assert isinstance(cg_context, CodeGenContext)
-
-    class Entry(object):
-        def __init__(self, operation, argument_count):
-            self.operation = operation
-            self.argument_count = argument_count
-            self.callback_name = callback_function_name(
-                cg_context,
-                overload_index=self.operation.overload_index,
-                argument_count=self.argument_count)
-
-    entries = []
-    for operation in cg_context.operation_group:
-        if "NoAllocDirectCall" not in operation.extended_attributes:
-            continue
-        for argument in reversed(operation.arguments):
-            entries.append(Entry(operation, argument.index + 1))
-            if not argument.is_optional:
-                break
-        else:
-            entries.append(Entry(operation, 0))
-    return entries
-
-
-def make_no_alloc_direct_call_callback_def(cg_context, function_name,
-                                           argument_count):
-    """
-    Args:
-        cg_context: A CodeGenContext of the target IDL construct.
-        function_name: The function name to be produced.
-        argument_count: The number of arguments that the produced function
-            takes, which may be different from the number of arguments of
-            the target cg_context.function_like due to optional arguments.
-    """
+def make_no_alloc_direct_call_callback_def(cg_context, function_name):
     assert isinstance(cg_context, CodeGenContext)
     assert isinstance(function_name, str)
-    assert isinstance(argument_count, int)
 
     S = SymbolNode
     T = TextNode
     F = lambda *args, **kwargs: T(_format(*args, **kwargs))
 
-    function_like = cg_context.function_like
-
     class ArgumentInfo(object):
         def __init__(self, v8_type, v8_arg_name, blink_arg_name, symbol_node):
             self.v8_type = v8_type
@@ -2299,9 +2212,7 @@
                       "auto&& {} = {};".format(blink_arg_name, v8_arg_name)))
 
     arg_list = []
-    for argument in function_like.arguments:
-        if not (argument.index < argument_count):
-            break
+    for argument in cg_context.operation.arguments:
         blink_arg_name = name_style.arg_f("arg{}_{}", argument.index + 1,
                                           argument.identifier)
         v8_arg_name = name_style.arg_f("v8_arg{}_{}", argument.index + 1,
@@ -2315,8 +2226,8 @@
         map(lambda arg: "{} {}".format(arg.v8_type, arg.v8_arg_name),
             arg_list)) +
                  ["v8::FastApiCallbackOptions& v8_arg_callback_options"])
-    return_type = ("void" if function_like.return_type.is_void else
-                   blink_type_info(function_like.return_type).value_t)
+    return_type = ("void" if cg_context.operation.return_type.is_void else
+                   blink_type_info(cg_context.operation.return_type).value_t)
 
     func_def = CxxFuncDefNode(name=function_name,
                               arg_decls=arg_decls,
@@ -2355,20 +2266,6 @@
 
     blink_arguments = list(
         map(lambda arg: "${{{}}}".format(arg.blink_arg_name), arg_list))
-    # If there are following optional arguments with default values, append
-    # them filled with the default values.
-    for argument in function_like.arguments[argument_count:]:
-        if not argument.default_value:
-            break
-        blink_arg_name = name_style.arg_f("arg{}_{}", argument.index + 1,
-                                          argument.identifier)
-        default_expr = make_default_value_expr(argument.idl_type,
-                                               argument.default_value)
-        body.register_code_symbol(
-            S((blink_arg_name),
-              "auto&& {}{{{}}};".format(blink_arg_name,
-                                        default_expr.initializer_expr)))
-        blink_arguments.append("${{{}}}".format(blink_arg_name))
     if cg_context.may_throw_exception:
         blink_arguments.append("${exception_state}")
     body.append(
@@ -2421,10 +2318,10 @@
             "v8::FastApiCallbackOptions ${v8_fast_api_callback_options}"
             " = v8::FastApiCallbackOptions::CreateForTesting(${isolate});"))
     scope.extend([
-        F(("{}(${info}, ${v8_fast_api_callback_options});"),
-          callback_function_name(
-              cg_context.make_copy(no_alloc_direct_call_for_testing=True),
-              overload_index=cg_context.operation.overload_index)),
+        F(
+            "{}(${info}, ${v8_fast_api_callback_options});",
+            callback_function_name(
+                cg_context.make_copy(no_alloc_direct_call_for_testing=True))),
         CxxUnlikelyIfNode(cond="${blink_receiver}->HasDeferredActions()",
                           body=[
                               T("${blink_receiver}->FlushDeferredActions();"),
@@ -2436,7 +2333,6 @@
 
     return ListNode([
         T("#if DCHECK_IS_ON()"),
-        T("// [NoAllocDirectCall]"),
         CxxUnlikelyIfNode(cond=("RuntimeEnabledFeatures::"
                                 "FakeNoAllocDirectCallForTestingEnabled()"),
                           body=scope),
@@ -2450,15 +2346,12 @@
     if "NoAllocDirectCall" not in cg_context.operation.extended_attributes:
         return None
 
-    return SequenceNode([
-        TextNode("// [NoAllocDirectCall]"),
-        CxxUnlikelyIfNode(
-            cond="UNLIKELY(${blink_receiver}->HasDeferredActions())",
-            body=[
-                TextNode("${blink_receiver}->FlushDeferredActions();"),
-                TextNode("return;"),
-            ]),
-    ])
+    return CxxUnlikelyIfNode(
+        cond="UNLIKELY(${blink_receiver}->HasDeferredActions())",
+        body=[
+            TextNode("${blink_receiver}->FlushDeferredActions();"),
+            TextNode("return;"),
+        ])
 
 
 def make_operation_entry(cg_context):
@@ -2489,6 +2382,8 @@
         make_report_measure_as(cg_context),
         make_log_activity(cg_context),
         EmptyNode(),
+        make_no_alloc_direct_call_flush_deferred_actions(cg_context),
+        EmptyNode(),
     ])
 
     if "Custom" in cg_context.property_.extended_attributes:
@@ -2498,8 +2393,6 @@
         return func_def
 
     body.extend([
-        make_no_alloc_direct_call_flush_deferred_actions(cg_context),
-        EmptyNode(),
         make_check_argument_length(cg_context),
         EmptyNode(),
         make_steps_of_ce_reactions(cg_context),
@@ -2514,7 +2407,9 @@
     return func_def
 
 
-def make_operation_callback_def(cg_context, function_name):
+def make_operation_callback_def(cg_context,
+                                function_name,
+                                no_alloc_direct_callback_name=None):
     assert isinstance(cg_context, CodeGenContext)
     assert isinstance(function_name, str)
 
@@ -2522,54 +2417,48 @@
 
     assert (not ("Custom" in operation_group.extended_attributes)
             or len(operation_group) == 1)
+    assert (not ("NoAllocDirectCall" in operation_group.extended_attributes)
+            or len(operation_group) == 1)
 
-    nodes = SequenceNode()
     if "NoAllocDirectCall" in operation_group.extended_attributes:
-        for entry in list_no_alloc_direct_call_callbacks(cg_context):
-            cgc = cg_context.make_copy(operation=entry.operation,
-                                       no_alloc_direct_call=True)
-            nodes.extend([
-                make_no_alloc_direct_call_callback_def(
-                    cgc,
-                    callback_function_name(
-                        cgc,
-                        overload_index=entry.operation.overload_index,
-                        argument_count=entry.argument_count),
-                    argument_count=entry.argument_count),
-                EmptyNode(),
-            ])
-        for operation in operation_group:
-            if "NoAllocDirectCall" not in operation.extended_attributes:
-                continue
-            cgc = cg_context.make_copy(operation=operation,
-                                       no_alloc_direct_call_for_testing=True)
-            nodes.extend([
-                make_no_alloc_direct_call_for_testing_callback_def(
-                    cgc,
-                    callback_function_name(
-                        cgc, overload_index=operation.overload_index)),
-                EmptyNode(),
-            ])
-
-    if len(operation_group) == 1:
-        nodes.append(
-            make_operation_function_def(
-                cg_context.make_copy(operation=operation_group[0]),
-                function_name))
+        nodes = ListNode()
+        cgc = cg_context.make_copy(operation=operation_group[0],
+                                   no_alloc_direct_call=True)
+        nodes.extend([
+            make_no_alloc_direct_call_callback_def(
+                cgc, no_alloc_direct_callback_name),
+            EmptyNode(),
+        ])
+        cgc = cg_context.make_copy(operation=operation_group[0],
+                                   no_alloc_direct_call_for_testing=True)
+        nodes.extend([
+            make_no_alloc_direct_call_for_testing_callback_def(
+                cgc, callback_function_name(cgc)),
+            EmptyNode(),
+        ])
+        cgc = cg_context.make_copy(operation=operation_group[0])
+        nodes.extend([
+            make_operation_function_def(cgc, function_name),
+        ])
         return nodes
 
+    if len(operation_group) == 1:
+        return make_operation_function_def(
+            cg_context.make_copy(operation=operation_group[0]), function_name)
+
+    node = SequenceNode()
     for operation in operation_group:
         cgc = cg_context.make_copy(operation=operation)
-        nodes.extend([
+        node.extend([
             make_operation_function_def(
                 cgc,
                 callback_function_name(
                     cgc, overload_index=operation.overload_index)),
             EmptyNode(),
         ])
-    nodes.append(
+    node.append(
         make_overload_dispatcher_function_def(cg_context, function_name))
-    return nodes
+    return node
 
 
 def make_stringifier_callback_def(cg_context, function_name):
@@ -4743,6 +4632,13 @@
         return "unsigned(IDLMemberInstaller::FlagReceiverCheck::kCheck)"
 
 
+def _make_property_entry_v8_c_function(entry):
+    if entry.no_alloc_direct_callback_name is None:
+        return None
+    return "v8::CFunction::MakeWithFallbackSupport({})".format(
+        entry.no_alloc_direct_callback_name)
+
+
 def _make_property_entry_v8_cached_accessor(property_):
     return "unsigned(V8PrivateProperty::CachedAccessor::{})".format(
         property_.extended_attributes.value_of("CachedAccessor") or "kNone")
@@ -4790,20 +4686,20 @@
     T = TextNode
 
     entry_nodes = []
-    pattern = ("{{"
-               "\"{property_name}\", "
-               "{attribute_get_callback}, "
-               "{attribute_set_callback}, "
-               "{v8_property_attribute}, "
-               "{location}, "
-               "{world}, "
-               "{receiver_check}, "
-               "{cross_origin_check_for_get}, "
-               "{cross_origin_check_for_set}, "
-               "{v8_side_effect}, "
-               "{v8_cached_accessor}"
-               "}},")
     for entry in attribute_entries:
+        pattern = ("{{"
+                   "\"{property_name}\", "
+                   "{attribute_get_callback}, "
+                   "{attribute_set_callback}, "
+                   "{v8_property_attribute}, "
+                   "{location}, "
+                   "{world}, "
+                   "{receiver_check}, "
+                   "{cross_origin_check_for_get}, "
+                   "{cross_origin_check_for_set}, "
+                   "{v8_side_effect}, "
+                   "{v8_cached_accessor}"
+                   "}},")
         text = _format(
             pattern,
             property_name=entry.property_.identifier,
@@ -4846,12 +4742,8 @@
     T = TextNode
 
     entry_nodes = []
-    pattern = (
-        "{{"  #
-        "\"{property_name}\", "
-        "{constant_callback}"
-        "}},")
     for entry in constant_entries:
+        pattern = ("{{" "\"{property_name}\", " "{constant_callback}" "}},")
         text = _format(
             pattern,
             property_name=entry.property_.identifier,
@@ -4876,12 +4768,11 @@
     T = TextNode
 
     entry_nodes = []
-    pattern = (
-        "{{"  #
-        "\"{property_name}\", "
-        "{constant_value}"
-        "}},")
     for entry in constant_entries:
+        pattern = ("{{"
+                   "\"{property_name}\", "
+                   "{constant_value}"
+                   "}},")
         text = _format(pattern,
                        property_name=entry.property_.identifier,
                        constant_value=entry.const_constant_name)
@@ -4932,51 +4823,30 @@
         for entry in operation_entries)
 
     T = TextNode
-    F = lambda *args, **kwargs: T(_format(*args, **kwargs))
 
-    no_alloc_direct_call_count = len(
-        list(
-            filter(lambda entry: entry.no_alloc_direct_call_callbacks,
-                   operation_entries)))
+    no_alloc_direct_call_count = 0
+    for entry in operation_entries:
+        if entry.no_alloc_direct_callback_name:
+            no_alloc_direct_call_count += 1
     assert (no_alloc_direct_call_count == 0
             or no_alloc_direct_call_count == len(operation_entries))
-    no_alloc_direct_call_enabled = bool(no_alloc_direct_call_count)
+    no_alloc_direct_call_enabled = no_alloc_direct_call_count > 0
 
     entry_nodes = []
-    nadc_overload_nodes = ListNode()
-    pattern = ("{{"
-               "\"{property_name}\", "
-               "{operation_callback}, "
-               "{function_length}, "
-               "{v8_property_attribute}, "
-               "{location}, "
-               "{world}, "
-               "{receiver_check}, "
-               "{cross_origin_check}, "
-               "{v8_side_effect}"
-               "}}, ")
-    if no_alloc_direct_call_enabled:
-        pattern = ("{{" + pattern + "{v8_cfunction_table}, "
-                   "base::size({v8_cfunction_table})}}, ")
     for entry in operation_entries:
+        pattern = ("{{"
+                   "\"{property_name}\", "
+                   "{operation_callback}, "
+                   "{function_length}, "
+                   "{v8_property_attribute}, "
+                   "{location}, "
+                   "{world}, "
+                   "{receiver_check}, "
+                   "{cross_origin_check}, "
+                   "{v8_side_effect}"
+                   "}}, ")
         if no_alloc_direct_call_enabled:
-            nadc_overload_table_name = name_style.constant(
-                "no_alloc_direct_call_overloads_of_",
-                entry.property_.identifier)
-            nadc_overload_nodes.append(
-                ListNode([
-                    T("static const v8::CFunction " +
-                      nadc_overload_table_name + "[] = {"),
-                    ListNode([
-                        F("v8::CFunctionBuilder().Fn({}).Build(),",
-                          nadc_entry.callback_name)
-                        for nadc_entry in entry.no_alloc_direct_call_callbacks
-                    ]),
-                    T("};"),
-                ]))
-        else:
-            nadc_overload_table_name = None
-
+            pattern = "{{" + pattern + "{v8_c_function}}}, "
         text = _format(
             pattern,
             property_name=entry.property_.identifier,
@@ -4992,7 +4862,7 @@
                 entry.property_),
             v8_side_effect=_make_property_entry_v8_side_effect(
                 entry.property_),
-            v8_cfunction_table=nadc_overload_table_name)
+            v8_c_function=_make_property_entry_v8_c_function(entry))
         entry_nodes.append(T(text))
 
     table_decl_before_name = (
@@ -5001,18 +4871,11 @@
         table_decl_before_name = (
             "static const "
             "IDLMemberInstaller::NoAllocDirectCallOperationConfig")
-    node = ListNode()
-    if nadc_overload_nodes:
-        node.extend([
-            nadc_overload_nodes,
-            EmptyNode(),
-        ])
-    node.extend([
+    return ListNode([
         T(table_decl_before_name + " " + table_name + "[] = {"),
         ListNode(entry_nodes),
         T("};"),
     ])
-    return node
 
 
 class _PropEntryBase(object):
@@ -5081,7 +4944,7 @@
                  operation_group,
                  op_callback_name,
                  op_func_length,
-                 no_alloc_direct_call_callbacks=None):
+                 no_alloc_direct_callback_name=None):
         assert isinstance(op_callback_name, str)
         assert isinstance(op_func_length, int)
 
@@ -5089,7 +4952,7 @@
                                 exposure_conditional, world, operation_group)
         self.op_callback_name = op_callback_name
         self.op_func_length = op_func_length
-        self.no_alloc_direct_call_callbacks = no_alloc_direct_call_callbacks
+        self.no_alloc_direct_callback_name = no_alloc_direct_callback_name
 
 
 def make_property_entries_and_callback_defs(cg_context, attribute_entries,
@@ -5290,12 +5153,14 @@
         cgc = cg_context.make_copy(
             operation_group=operation_group, for_world=world)
         op_callback_name = callback_function_name(cgc)
-        op_callback_node = make_operation_callback_def(cgc, op_callback_name)
-        no_alloc_direct_call_callbacks = (
-            list_no_alloc_direct_call_callbacks(
-                cgc.make_copy(no_alloc_direct_call=True))
+        no_alloc_direct_callback_name = (
+            callback_function_name(cgc.make_copy(no_alloc_direct_call=True))
             if "NoAllocDirectCall" in operation_group.extended_attributes else
             None)
+        op_callback_node = make_operation_callback_def(
+            cgc,
+            op_callback_name,
+            no_alloc_direct_callback_name=no_alloc_direct_callback_name)
 
         callback_def_nodes.extend([
             op_callback_node,
@@ -5310,7 +5175,7 @@
                 operation_group=operation_group,
                 op_callback_name=op_callback_name,
                 op_func_length=operation_group.min_num_of_required_arguments,
-                no_alloc_direct_call_callbacks=no_alloc_direct_call_callbacks))
+                no_alloc_direct_callback_name=no_alloc_direct_callback_name))
 
     def process_stringifier(_, is_context_dependent, exposure_conditional,
                             world):
@@ -6042,12 +5907,12 @@
         install_func="IDLMemberInstaller::InstallOperations",
         table_name=table_name)
     entries = list(
-        filter(lambda entry: not entry.no_alloc_direct_call_callbacks,
+        filter(lambda entry: not entry.no_alloc_direct_callback_name,
                operation_entries))
     install_properties(table_name, entries, _make_operation_registration_table,
                        installer_call_text)
     entries = list(
-        filter(lambda entry: entry.no_alloc_direct_call_callbacks,
+        filter(lambda entry: entry.no_alloc_direct_callback_name,
                operation_entries))
     install_properties(table_name, entries, _make_operation_registration_table,
                        installer_call_text)
diff --git a/third_party/blink/renderer/build/scripts/core/css/templates/css_value_keywords.h.tmpl b/third_party/blink/renderer/build/scripts/core/css/templates/css_value_keywords.h.tmpl
index 4cada5c..b254d4b3 100644
--- a/third_party/blink/renderer/build/scripts/core/css/templates/css_value_keywords.h.tmpl
+++ b/third_party/blink/renderer/build/scripts/core/css/templates/css_value_keywords.h.tmpl
@@ -8,6 +8,7 @@
 #include <string.h>
 #include <stdint.h>
 
+#include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_mode.h"
 
 namespace blink {
@@ -27,7 +28,7 @@
     return id != CSSValueID::kInvalid;
 }
 
-const char* getValueName(CSSValueID);
+CORE_EXPORT const char* getValueName(CSSValueID);
 bool isValueAllowedInMode(CSSValueID id, CSSParserMode mode);
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index 5c50c4d..b601c941 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1341,7 +1341,6 @@
     "layout/layout_box_test.cc",
     "layout/layout_embedded_content_test.cc",
     "layout/layout_flexible_box_test.cc",
-    "layout/layout_grid_test.cc",
     "layout/layout_image_test.cc",
     "layout/layout_inline_test.cc",
     "layout/layout_list_marker_test.cc",
diff --git a/third_party/blink/renderer/core/dom/pseudo_element_data.h b/third_party/blink/renderer/core/dom/pseudo_element_data.h
index 73a78bc..5196b24 100644
--- a/third_party/blink/renderer/core/dom/pseudo_element_data.h
+++ b/third_party/blink/renderer/core/dom/pseudo_element_data.h
@@ -55,35 +55,34 @@
 
 inline void PseudoElementData::SetPseudoElement(PseudoId pseudo_id,
                                                 PseudoElement* element) {
+  PseudoElement* previous_element = nullptr;
   switch (pseudo_id) {
     case kPseudoIdBefore:
-      if (generated_before_)
-        generated_before_->Dispose();
+      previous_element = generated_before_;
       generated_before_ = element;
       break;
     case kPseudoIdAfter:
-      if (generated_after_)
-        generated_after_->Dispose();
+      previous_element = generated_after_;
       generated_after_ = element;
       break;
     case kPseudoIdMarker:
-      if (generated_marker_)
-        generated_marker_->Dispose();
+      previous_element = generated_marker_;
       generated_marker_ = element;
       break;
     case kPseudoIdBackdrop:
-      if (backdrop_)
-        backdrop_->Dispose();
+      previous_element = backdrop_;
       backdrop_ = element;
       break;
     case kPseudoIdFirstLetter:
-      if (generated_first_letter_)
-        generated_first_letter_->Dispose();
+      previous_element = generated_first_letter_;
       generated_first_letter_ = element;
       break;
     default:
       NOTREACHED();
   }
+
+  if (previous_element)
+    previous_element->Dispose();
 }
 
 inline PseudoElement* PseudoElementData::GetPseudoElement(
diff --git a/third_party/blink/renderer/core/frame/use_counter_impl_test.cc b/third_party/blink/renderer/core/frame/use_counter_impl_test.cc
index 70b081d..12bdc4a 100644
--- a/third_party/blink/renderer/core/frame/use_counter_impl_test.cc
+++ b/third_party/blink/renderer/core/frame/use_counter_impl_test.cc
@@ -261,100 +261,6 @@
   EXPECT_FALSE(document.IsUseCounted(feature));
 }
 
-TEST_F(UseCounterImplTest, CSSGridLayoutPercentageRowIndefiniteHeight1) {
-  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
-  Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
-  Document& document = dummy_page_holder->GetDocument();
-  WebFeature feature = WebFeature::kGridRowTrackPercentIndefiniteHeight;
-  EXPECT_FALSE(document.IsUseCounted(feature));
-  document.documentElement()->setInnerHTML(
-      "<div style='display: inline-grid; grid-template-rows: 50%;'>"
-      "</div>");
-  UpdateAllLifecyclePhases(document);
-  EXPECT_TRUE(document.IsUseCounted(feature));
-}
-
-TEST_F(UseCounterImplTest, CSSGridLayoutPercentageRowIndefiniteHeight2) {
-  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
-  Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
-  Document& document = dummy_page_holder->GetDocument();
-  WebFeature feature = WebFeature::kGridRowTrackPercentIndefiniteHeight;
-  EXPECT_FALSE(document.IsUseCounted(feature));
-  document.documentElement()->setInnerHTML(
-      "<div style='display: inline-grid; grid-template-rows: 50% 50%;'>"
-      "</div>");
-  UpdateAllLifecyclePhases(document);
-  EXPECT_TRUE(document.IsUseCounted(feature));
-}
-
-TEST_F(UseCounterImplTest, CSSGridLayoutPercentageRowIndefiniteHeight3) {
-  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
-  Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
-  Document& document = dummy_page_holder->GetDocument();
-  WebFeature feature = WebFeature::kGridRowTrackPercentIndefiniteHeight;
-  EXPECT_FALSE(document.IsUseCounted(feature));
-  document.documentElement()->setInnerHTML(
-      "<div style='display: inline-grid; grid-template-rows: 100% 100%;'>"
-      "</div>");
-  UpdateAllLifecyclePhases(document);
-  EXPECT_TRUE(document.IsUseCounted(feature));
-}
-
-TEST_F(UseCounterImplTest, CSSGridLayoutPercentageRowIndefiniteHeight4) {
-  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
-  Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
-  Document& document = dummy_page_holder->GetDocument();
-  WebFeature feature = WebFeature::kGridRowTrackPercentIndefiniteHeight;
-  EXPECT_FALSE(document.IsUseCounted(feature));
-  document.documentElement()->setInnerHTML(
-      "<div style='display: inline-grid; grid-template-rows: minmax(50%, "
-      "100%);'>"
-      "</div>");
-  UpdateAllLifecyclePhases(document);
-  EXPECT_TRUE(document.IsUseCounted(feature));
-}
-
-TEST_F(UseCounterImplTest, CSSGridLayoutPercentageRowIndefiniteHeight5) {
-  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
-  Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
-  Document& document = dummy_page_holder->GetDocument();
-  WebFeature feature = WebFeature::kGridRowTrackPercentIndefiniteHeight;
-  EXPECT_FALSE(document.IsUseCounted(feature));
-  document.documentElement()->setInnerHTML(
-      "<div style='display: inline-grid; max-height: 0; grid-template-rows: "
-      "100%;'>"
-      "</div>");
-  UpdateAllLifecyclePhases(document);
-  EXPECT_TRUE(document.IsUseCounted(feature));
-}
-
-TEST_F(UseCounterImplTest, CSSGridLayoutPercentageRowIndefiniteHeight6) {
-  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
-  Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
-  Document& document = dummy_page_holder->GetDocument();
-  WebFeature feature = WebFeature::kGridRowTrackPercentIndefiniteHeight;
-  EXPECT_FALSE(document.IsUseCounted(feature));
-  document.documentElement()->setInnerHTML(
-      "<div style='display: inline-grid; grid-template-rows: 100%;'>"
-      "</div>");
-  UpdateAllLifecyclePhases(document);
-  EXPECT_FALSE(document.IsUseCounted(feature));
-}
-
-TEST_F(UseCounterImplTest, CSSGridLayoutPercentageRowIndefiniteHeight7) {
-  auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
-  Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
-  Document& document = dummy_page_holder->GetDocument();
-  WebFeature feature = WebFeature::kGridRowTrackPercentIndefiniteHeight;
-  EXPECT_FALSE(document.IsUseCounted(feature));
-  document.documentElement()->setInnerHTML(
-      "<div style='display: inline-grid; grid-template-rows: minmax(100%, "
-      "100%);'>"
-      "</div>");
-  UpdateAllLifecyclePhases(document);
-  EXPECT_FALSE(document.IsUseCounted(feature));
-}
-
 TEST_F(UseCounterImplTest, CSSFlexibleBox) {
   auto dummy_page_holder = std::make_unique<DummyPageHolder>(IntSize(800, 600));
   Page::InsertOrdinaryPageForTesting(&dummy_page_holder->GetPage());
diff --git a/third_party/blink/renderer/core/layout/layout_grid_test.cc b/third_party/blink/renderer/core/layout/layout_grid_test.cc
deleted file mode 100644
index 69120c92..0000000
--- a/third_party/blink/renderer/core/layout/layout_grid_test.cc
+++ /dev/null
@@ -1,38 +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 "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
-
-namespace blink {
-
-class LayoutGridTest : public RenderingTest {};
-
-TEST_F(LayoutGridTest, RowGapUseCounter) {
-  SetBodyInnerHTML(R"HTML(
-    <div style="display: grid; gap: 20%;">
-      <div></div>
-      <div></div>
-    </div>
-  )HTML");
-  RunDocumentLifecycle();
-
-  EXPECT_TRUE(GetDocument().IsUseCounted(WebFeature::kGridRowGapPercent));
-  EXPECT_TRUE(
-      GetDocument().IsUseCounted(WebFeature::kGridRowGapPercentIndefinite));
-}
-
-TEST_F(LayoutGridTest, RowGapUseCounterOneTrack) {
-  SetBodyInnerHTML(R"HTML(
-    <div style="display: grid; gap: 20%;">
-      <div></div>
-    </div>
-  )HTML");
-  RunDocumentLifecycle();
-
-  EXPECT_FALSE(GetDocument().IsUseCounted(WebFeature::kGridRowGapPercent));
-  EXPECT_FALSE(
-      GetDocument().IsUseCounted(WebFeature::kGridRowGapPercentIndefinite));
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index d6323b2..71ec9eb 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -644,6 +644,13 @@
     return ShouldApplyPaintContainment() && ShouldApplyLayoutContainment() &&
            ShouldApplySizeContainment();
   }
+  inline bool ShouldApplyAnyContainment() const {
+    NOT_DESTROYED();
+    return ShouldApplyPaintContainment() || ShouldApplyLayoutContainment() ||
+           ShouldApplyStyleContainment() || ShouldApplyBlockSizeContainment() ||
+           ShouldApplyInlineSizeContainment();
+  }
+
   inline bool IsContainerForContainerQueries() const {
     NOT_DESTROYED();
     return ShouldApplyLayoutContainment() && ShouldApplyStyleContainment() &&
diff --git a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
index 7af143b8..492a9d65 100644
--- a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
@@ -29,6 +29,26 @@
   grid_available_size_ = grid_min_available_size_ = grid_max_available_size_ =
       ChildAvailableSize();
 
+  // Firstly if block-size containment applies compute the block-size ignoring
+  // children (just based on the row definitions).
+  if (grid_available_size_.block_size == kIndefiniteSize &&
+      Node().ShouldApplyBlockSizeContainment()) {
+    // We always need a definite min block-size in order to run the track
+    // sizing algorithm.
+    grid_min_available_size_.block_size = BorderScrollbarPadding().BlockSum();
+    contain_intrinsic_block_size_ = ComputeIntrinsicBlockSizeIgnoringChildren();
+
+    // Resolve the block-size, and set the available sizes.
+    const LayoutUnit block_size = ComputeBlockSizeForFragment(
+        ConstraintSpace(), Style(), BorderPadding(),
+        *contain_intrinsic_block_size_, border_box_size_.inline_size);
+
+    grid_available_size_.block_size = grid_min_available_size_.block_size =
+        grid_max_available_size_.block_size =
+            (block_size - BorderScrollbarPadding().BlockSum())
+                .ClampNegativeToZero();
+  }
+
   // Next if our inline-size is indefinite, compute the min/max inline-sizes.
   if (grid_available_size_.inline_size == kIndefiniteSize) {
     const LayoutUnit border_scrollbar_padding =
@@ -183,28 +203,29 @@
 
   ComputeGrid();
 
-  // Intrinsic block size is based on the final row offset. Because gutters
-  // are included in row offsets, subtract out the final gutter (if present).
-  LayoutUnit intrinsic_block_size =
-      grid_geometry.row_geometry.sets.back().offset -
-      grid_geometry.row_geometry.FinalGutterSize() +
-      BorderScrollbarPadding().block_end;
+  LayoutUnit intrinsic_block_size;
+  if (contain_intrinsic_block_size_) {
+    intrinsic_block_size = *contain_intrinsic_block_size_;
+  } else {
+    // Intrinsic block size is based on the final row offset. Because gutters
+    // are included in row offsets, subtract out the final gutter (if present).
+    intrinsic_block_size = grid_geometry.row_geometry.sets.back().offset -
+                           grid_geometry.row_geometry.FinalGutterSize() +
+                           BorderScrollbarPadding().block_end;
 
-  // TODO(layout-dev): This isn't great but matches legacy. Ideally this would
-  // only apply when we have only flexible track(s).
-  if (grid_items.IsEmpty() && Node().HasLineIfEmpty()) {
+    // TODO(layout-dev): This isn't great but matches legacy. Ideally this
+    // would only apply when we have only flexible track(s).
+    if (grid_items.IsEmpty() && Node().HasLineIfEmpty()) {
+      intrinsic_block_size =
+          std::max(intrinsic_block_size, BorderScrollbarPadding().BlockSum() +
+                                             Node().EmptyLineBlockSize());
+    }
+
     intrinsic_block_size =
-        std::max(intrinsic_block_size, BorderScrollbarPadding().BlockSum() +
-                                           Node().EmptyLineBlockSize());
+        ClampIntrinsicBlockSize(ConstraintSpace(), Node(),
+                                BorderScrollbarPadding(), intrinsic_block_size);
   }
 
-  // TODO(layout-dev): ClampIntrinsicBlockSize might not be correct for grid.
-  // Specifically do we need to run the sizing algorithm assuming no children
-  // for size containment.
-  intrinsic_block_size =
-      ClampIntrinsicBlockSize(ConstraintSpace(), Node(),
-                              BorderScrollbarPadding(), intrinsic_block_size);
-
   const LayoutUnit block_size = ComputeBlockSizeForFragment(
       ConstraintSpace(), container_style, BorderPadding(), intrinsic_block_size,
       border_box_size_.inline_size);
@@ -318,15 +339,22 @@
 
 MinMaxSizesResult NGGridLayoutAlgorithm::ComputeMinMaxSizes(
     const MinMaxSizesFloatInput&) const {
-  // TODO(janewman): Handle the cases typically done via:
-  // CalculateMinMaxSizesIgnoringChildren.
+  const LayoutUnit override_intrinsic_inline_size =
+      Node().OverrideIntrinsicContentInlineSize();
+  if (override_intrinsic_inline_size != kIndefiniteSize) {
+    MinMaxSizes sizes;
+    sizes =
+        BorderScrollbarPadding().InlineSum() + override_intrinsic_inline_size;
+    return MinMaxSizesResult{sizes,
+                             /* depends_on_block_constraints */ false};
+  }
 
-  // Measure items.
+  // Measure items. If we have inline size containment, ignore all children.
   GridItems grid_items;
-  ConstructAndAppendGridItems(&grid_items);
+  if (!Node().ShouldApplyInlineSizeContainment())
+    ConstructAndAppendGridItems(&grid_items);
 
-  const auto& container_style = Style();
-  NGGridPlacement grid_placement(container_style,
+  NGGridPlacement grid_placement(Style(),
                                  ComputeAutomaticRepetitions(kForColumns),
                                  ComputeAutomaticRepetitions(kForRows));
 
@@ -860,6 +888,47 @@
   }
 }
 
+LayoutUnit NGGridLayoutAlgorithm::ComputeIntrinsicBlockSizeIgnoringChildren()
+    const {
+  DCHECK(Node().ShouldApplyBlockSizeContainment());
+
+  // First check 'contain-intrinsic-size'.
+  const LayoutUnit override_intrinsic_block_size =
+      Node().OverrideIntrinsicContentBlockSize();
+  if (override_intrinsic_block_size != kIndefiniteSize)
+    return BorderScrollbarPadding().BlockSum() + override_intrinsic_block_size;
+
+  // Don't append any children for this calculation.
+  GridItems grid_items;
+  NGGridPlacement grid_placement(Style(),
+                                 ComputeAutomaticRepetitions(kForColumns),
+                                 ComputeAutomaticRepetitions(kForRows));
+
+  // Build block track collections.
+  NGGridBlockTrackCollection column_block_track_collection(kForColumns);
+  NGGridBlockTrackCollection row_block_track_collection(kForRows);
+  BuildBlockTrackCollections(&grid_items, &column_block_track_collection,
+                             &row_block_track_collection, &grid_placement);
+
+  // Build algorithm row track collection from the block track collection.
+  NGGridLayoutAlgorithmTrackCollection row_track_collection(
+      row_block_track_collection,
+      grid_available_size_.block_size == kIndefiniteSize);
+
+  GridGeometry grid_geometry(SetGeometry(),
+                             InitializeTrackSizes(&row_track_collection));
+
+  // Resolve the rows.
+  bool unused_needs_additional_pass = false;
+  grid_geometry.row_geometry = ComputeUsedTrackSizes(
+      SizingConstraint::kLayout, grid_geometry, &row_track_collection,
+      &grid_items, &unused_needs_additional_pass);
+
+  return grid_geometry.row_geometry.sets.back().offset -
+         grid_geometry.row_geometry.FinalGutterSize() +
+         BorderScrollbarPadding().block_end;
+}
+
 LayoutUnit NGGridLayoutAlgorithm::ContributionSizeForGridItem(
     const GridGeometry& grid_geometry,
     const GridItemData& grid_item,
@@ -883,7 +952,25 @@
       ComputeMarginsFor(space, node.Style(), ConstraintSpace());
 
   auto MinMaxContentSizes = [&]() -> MinMaxSizes {
-    return ComputeMinAndMaxContentContributionForSelf(node, space).sizes;
+    const auto result = ComputeMinAndMaxContentContributionForSelf(node, space);
+
+    // The min/max contribution may depend on the block-size of the grid-area:
+    // <div style="display: inline-grid; grid-template-columns: auto auto;">
+    //   <div style="height: 100%">
+    //     <img style="height: 50%;" />
+    //   </div>
+    //   <div>
+    //     <div style="height: 100px;"></div>
+    //   </div>
+    // </div>
+    // Mark ourselves as requiring an additional pass to re-resolve the column
+    // tracks for this case.
+    if (is_parallel && result.depends_on_block_constraints &&
+        space.AvailableSize().block_size == kIndefiniteSize) {
+      *needs_additional_pass = true;
+    }
+
+    return result.sizes;
   };
 
   // This function will determine the correct block-size of a grid-item.
diff --git a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h
index 3872e0b..4c957227 100644
--- a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.h
@@ -315,6 +315,8 @@
 
   enum class SizingConstraint { kLayout, kMinContent, kMaxContent };
 
+  LayoutUnit ComputeIntrinsicBlockSizeIgnoringChildren() const;
+
   // Returns the size that a grid item will distribute across the tracks with an
   // intrinsic sizing function it spans in the relevant track direction.
   LayoutUnit ContributionSizeForGridItem(
@@ -497,6 +499,8 @@
   LogicalSize grid_available_size_;
   LogicalSize grid_min_available_size_;
   LogicalSize grid_max_available_size_;
+
+  absl::optional<LayoutUnit> contain_intrinsic_block_size_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/svg/layout_ng_svg_text.cc b/third_party/blink/renderer/core/layout/ng/svg/layout_ng_svg_text.cc
index f980874..8ddbfb7 100644
--- a/third_party/blink/renderer/core/layout/ng/svg/layout_ng_svg_text.cc
+++ b/third_party/blink/renderer/core/layout/ng/svg/layout_ng_svg_text.cc
@@ -8,6 +8,7 @@
 #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
 #include "third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h"
+#include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
 #include "third_party/blink/renderer/core/svg/svg_text_element.h"
 
 namespace blink {
@@ -19,6 +20,19 @@
   DCHECK(IsA<SVGTextElement>(element));
 }
 
+void LayoutNGSVGText::StyleDidChange(StyleDifference diff,
+                                     const ComputedStyle* old_style) {
+  NOT_DESTROYED();
+  LayoutNGBlockFlowMixin<LayoutSVGBlock>::StyleDidChange(diff, old_style);
+  SVGResources::UpdatePaints(*GetElement(), old_style, StyleRef());
+}
+
+void LayoutNGSVGText::WillBeDestroyed() {
+  NOT_DESTROYED();
+  SVGResources::ClearPaints(*GetElement(), Style());
+  LayoutNGBlockFlowMixin<LayoutSVGBlock>::WillBeDestroyed();
+}
+
 const char* LayoutNGSVGText::GetName() const {
   NOT_DESTROYED();
   return "LayoutNGSVGText";
diff --git a/third_party/blink/renderer/core/layout/ng/svg/layout_ng_svg_text.h b/third_party/blink/renderer/core/layout/ng/svg/layout_ng_svg_text.h
index abc5607..8a8875a 100644
--- a/third_party/blink/renderer/core/layout/ng/svg/layout_ng_svg_text.h
+++ b/third_party/blink/renderer/core/layout/ng/svg/layout_ng_svg_text.h
@@ -32,6 +32,8 @@
   FloatRect ObjectBoundingBox() const override;
   FloatRect StrokeBoundingBox() const override;
   FloatRect VisualRectInLocalSVGCoordinates() const override;
+  void StyleDidChange(StyleDifference, const ComputedStyle* old_style) override;
+  void WillBeDestroyed() override;
 
   // LayoutBox override:
   bool CreatesNewFormattingContext() const override;
diff --git a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.cc b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.cc
index 9994752..c2789b0d 100644
--- a/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.cc
+++ b/third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.cc
@@ -39,16 +39,23 @@
     *inline_size = LayoutUnit(length.Value());
     if (is_content_box)
       *inline_size = **inline_size + inline_border_padding;
+    else
+      *inline_size = std::max(**inline_size, inline_border_padding);
   }
   if (min_length.IsFixed()) {
     *min_inline_size = LayoutUnit(min_length.Value());
     if (is_content_box)
       *min_inline_size = **min_inline_size + inline_border_padding;
+    else
+      *min_inline_size = std::max(**min_inline_size, inline_border_padding);
   }
   if (max_length.IsFixed()) {
     *max_inline_size = LayoutUnit(max_length.Value());
     if (is_content_box)
       *max_inline_size = **max_inline_size + inline_border_padding;
+    else
+      *max_inline_size = std::max(**max_inline_size, inline_border_padding);
+
     if (*min_inline_size)
       *max_inline_size = std::max(**min_inline_size, **max_inline_size);
   }
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 275d61d6..2311e161 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -43,6 +43,8 @@
 #include "third_party/blink/public/mojom/commit_result/commit_result.mojom-blink.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h"
+#include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/public/platform/web_content_security_policy_struct.h"
 #include "third_party/blink/public/platform/web_url_request.h"
 #include "third_party/blink/renderer/core/app_history/app_history.h"
 #include "third_party/blink/renderer/core/dom/document.h"
@@ -2665,6 +2667,15 @@
   policy_container_->AddContentSecurityPolicies(mojo::Clone(parsed_policies));
   csp->AddPolicies(std::move(parsed_policies));
 
+  // Check if the embedder wants to add any default policies, and add them.
+  WebVector<WebContentSecurityPolicyHeader> embedder_default_csp;
+  Platform::Current()->AppendContentSecurityPolicy(WebURL(Url()),
+                                                   &embedder_default_csp);
+  for (const auto& header : embedder_default_csp) {
+    csp->AddPolicies(ParseContentSecurityPolicies(
+        header.header_value, header.type, header.source, Url()));
+  }
+
   // Retrieve CSP stored in the OriginPolicy and add them to the policy
   // container.
   if (origin_policy_) {
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
index 644524a1..f289caede 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
@@ -34,6 +34,7 @@
 #include <memory>
 
 #include "base/feature_list.h"
+#include "base/metrics/histogram_macros.h"
 #include "build/build_config.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "net/http/structured_headers.h"
@@ -801,7 +802,16 @@
     const absl::optional<ResourceRequest::RedirectInfo>& redirect_info,
     ReportingDisposition reporting_disposition,
     const String& devtools_request_id) const {
-  if (GetResourceFetcherProperties().IsDetached())
+  const char kWellKnownConversionRegistrationPath[] =
+      "/.well-known/attribution-reporting/trigger-attribution";
+  if (url.GetPath() != kWellKnownConversionRegistrationPath)
+    return false;
+
+  const bool detached = GetResourceFetcherProperties().IsDetached();
+  UMA_HISTOGRAM_BOOLEAN("Conversions.RedirectInterceptedFrameDetached",
+                        detached);
+
+  if (detached)
     return false;
 
   if (!RuntimeEnabledFeatures::ConversionMeasurementEnabled(
@@ -815,11 +825,6 @@
     return false;
   }
 
-  const char kWellKnownConversionRegsitrationPath[] =
-      "/.well-known/attribution-reporting/trigger-attribution";
-  if (url.GetPath() != kWellKnownConversionRegsitrationPath)
-    return false;
-
   if (!document_->domWindow()->IsFeatureEnabled(
           mojom::blink::PermissionsPolicyFeature::kAttributionReporting)) {
     AuditsIssue::ReportAttributionIssue(
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc b/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
index dd9ee9c..1d04c3d 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
@@ -1347,6 +1347,43 @@
       net::SiteForCookies::FromUrl(document_url)));
 }
 
+TEST_F(FrameFetchContextTest,
+       SendConversionRequestInsteadOfRedirecting_RecordsMetric) {
+  const bool kDetach = true;
+  const bool kNoDetach = false;
+
+  const char kTriggerURL[] =
+      "https://www.example.com/.well-known/attribution-reporting/"
+      "trigger-attribution";
+  const char kNoTriggerURL[] = "https://www.example.com/";
+
+  struct {
+    KURL url;
+    bool detach;
+    int want;
+  } test_cases[] = {
+      {KURL(kNoTriggerURL), kNoDetach, 0},
+      {KURL(kTriggerURL), kNoDetach, 1},
+      {KURL(kNoTriggerURL), kDetach, 0},
+      {KURL(kTriggerURL), kDetach, 1},
+  };
+
+  for (const auto& test_case : test_cases) {
+    if (test_case.detach)
+      dummy_page_holder = nullptr;
+
+    HistogramTester histograms;
+    GetFetchContext()->SendConversionRequestInsteadOfRedirecting(
+        test_case.url, /*redirect_info=*/absl::nullopt,
+        ReportingDisposition::kReport, /*devtools_request_id=*/"abc");
+    histograms.ExpectUniqueSample(
+        "Conversions.RedirectInterceptedFrameDetached", test_case.detach,
+        test_case.want);
+
+    RecreateFetchContext();
+  }
+}
+
 TEST_F(FrameFetchContextTest, TopFrameOrigin) {
   const KURL document_url("https://www2.example.com/foo/bar");
   RecreateFetchContext(document_url);
diff --git a/third_party/blink/renderer/core/messaging/message_port.cc b/third_party/blink/renderer/core/messaging/message_port.cc
index bacf27d..cc07bc2 100644
--- a/third_party/blink/renderer/core/messaging/message_port.cc
+++ b/third_party/blink/renderer/core/messaging/message_port.cc
@@ -145,7 +145,7 @@
     return;
 
   started_ = true;
-  connector_->ResumeIncomingMethodCallProcessing();
+  connector_->StartReceiving(task_runner_);
 }
 
 void MessagePort::close() {
@@ -168,8 +168,7 @@
   port_ = std::move(port);
   connector_ = std::make_unique<mojo::Connector>(
       port_.TakeHandleToEntangle(GetExecutionContext()),
-      mojo::Connector::SINGLE_THREADED_SEND, task_runner_);
-  connector_->PauseIncomingMethodCallProcessing();
+      mojo::Connector::SINGLE_THREADED_SEND);
   connector_->set_incoming_receiver(this);
   connector_->set_connection_error_handler(
       WTF::Bind(&MessagePort::close, WrapWeakPersistent(this)));
diff --git a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
index 38e28ad..72c31017 100644
--- a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
+++ b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
@@ -196,17 +196,6 @@
   if (target_state == DocumentLifecycle::kCompositingInputsClean)
     return;
 
-  {
-    // TODO(szager): Remove this after diagnosing crash.
-    DisableCompositingQueryAsserts query_assert_disabler;
-    CHECK_EQ(InCompositingMode(), (bool)RootGraphicsLayer());
-    if (auto* owner = layout_view_->GetFrame()->OwnerLayoutObject()) {
-      auto* parent_compositor = owner->View()->Compositor();
-      CHECK(parent_compositor->StaleInCompositingMode() ||
-            !RootGraphicsLayer());
-    }
-  }
-
   if (layout_view_->GetFrameView()->ShouldThrottleRendering())
     return;
 
diff --git a/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc
index 3a6ef1b..3ef4c701 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_text_fragment_painter.cc
@@ -144,10 +144,14 @@
   const LayoutObject* layout_object = text_item.GetLayoutObject();
   const Document& document = layout_object->GetDocument();
   const bool is_printing = document.Printing();
+  // Don't paint selections when rendering a mask, clip-path (as a mask),
+  // pattern or feImage (element reference.)
+  const bool is_rendering_resource = paint_info.IsRenderingResourceSubtree();
 
   // Determine whether or not we're selected.
   absl::optional<NGHighlightPainter::SelectionPaintState> selection;
-  if (UNLIKELY(!is_printing && paint_info.phase != PaintPhase::kTextClip &&
+  if (UNLIKELY(!is_printing && !is_rendering_resource &&
+               paint_info.phase != PaintPhase::kTextClip &&
                layout_object->IsSelected())) {
     const NGInlineCursor& root_inline_cursor =
         InlineCursorForBlockFlow(cursor_, &inline_cursor_for_block_flow_);
@@ -238,14 +242,8 @@
   // Determine text colors.
 
   Node* node = layout_object->GetNode();
-  DCHECK(!svg_inline_text ||
-         (!IsA<SVGElement>(node) && IsA<SVGElement>(node->parentNode())));
   TextPaintStyle text_style =
-      svg_inline_text
-          ? TextPainterBase::SvgTextPaintingStyle(
-                document, SVGLengthContext(To<SVGElement>(node->parentNode())),
-                style, paint_info)
-          : TextPainterBase::TextPaintingStyle(document, style, paint_info);
+      TextPainterBase::TextPaintingStyle(document, style, paint_info);
   // TODO(crbug.com/1179585): Support SVG Paint Servers (e.g. Gradient, Pattern)
   if (UNLIKELY(selection)) {
     selection->ComputeSelectionStyle(document, style, node, paint_info,
@@ -261,41 +259,55 @@
   const bool paint_marker_backgrounds =
       paint_info.phase != PaintPhase::kSelectionDragImage &&
       paint_info.phase != PaintPhase::kTextClip && !is_printing;
-  absl::optional<GraphicsContextStateSaver> state_saver;
+  GraphicsContextStateSaver state_saver(context, /*save_and_restore=*/false);
   absl::optional<AffineTransform> rotation;
   const WritingMode writing_mode = style.GetWritingMode();
   const bool is_horizontal = IsHorizontalWritingMode(writing_mode);
   int ascent = font_data ? font_data->GetFontMetrics().Ascent() : 0;
   PhysicalOffset text_origin(box_rect.offset.left,
                              box_rect.offset.top + ascent);
-  if (svg_inline_text && scaling_factor != 1.0f) {
-    state_saver.emplace(context);
-    context.Scale(1 / scaling_factor, 1 / scaling_factor);
-  }
-  if (text_item.HasSvgTransformForPaint()) {
-    if (!state_saver)
-      state_saver.emplace(context);
-    context.ConcatCTM(text_item.BuildSvgTransformForPaint());
-  }
+
   NGTextPainter text_painter(context, font, fragment_paint_info, visual_rect,
                              text_origin, box_rect, is_horizontal);
   NGHighlightPainter highlight_painter(
       text_painter, paint_info, cursor_, *cursor_.CurrentItem(),
       box_rect.offset, style, std::move(selection), is_printing);
 
+  if (svg_inline_text) {
+    NGTextPainter::SvgTextPaintState& svg_state = text_painter.SetSvgState(
+        *svg_inline_text, style, paint_info.IsRenderingClipPathAsMaskImage());
+
+    if (scaling_factor != 1.0f) {
+      state_saver.SaveIfNeeded();
+      context.Scale(1 / scaling_factor, 1 / scaling_factor);
+      svg_state.EnsureShaderTransform().Scale(scaling_factor);
+    }
+    if (text_item.HasSvgTransformForPaint()) {
+      state_saver.SaveIfNeeded();
+      const auto fragment_transform = text_item.BuildSvgTransformForPaint();
+      context.ConcatCTM(fragment_transform);
+      DCHECK(fragment_transform.IsInvertible());
+      svg_state.EnsureShaderTransform().PreMultiply(
+          fragment_transform.Inverse());
+    }
+  }
+
   // 1. Paint backgrounds for document markers that don’t participate in the CSS
   // highlight overlay system, such as composition highlights. They use physical
   // coordinates, so are painted before GraphicsContext rotation.
   highlight_painter.Paint(NGHighlightPainter::kBackground);
 
   if (!is_horizontal) {
-    if (!state_saver)
-      state_saver.emplace(context);
+    state_saver.SaveIfNeeded();
     // Because we rotate the GraphicsContext to match the logical direction,
     // transpose the |box_rect| to match to it.
     box_rect.size = PhysicalSize(box_rect.Height(), box_rect.Width());
     rotation.emplace(TextPainterBase::Rotation(box_rect, writing_mode));
     context.ConcatCTM(*rotation);
+    if (NGTextPainter::SvgTextPaintState* state = text_painter.GetSvgState()) {
+      DCHECK(rotation->IsInvertible());
+      state->EnsureShaderTransform().PreMultiply(rotation->Inverse());
+    }
   }
 
   if (UNLIKELY(highlight_painter.Selection())) {
diff --git a/third_party/blink/renderer/core/paint/ng/ng_text_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_text_painter.cc
index 318b6ffa..4b79acf 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_text_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_text_painter.cc
@@ -9,23 +9,108 @@
 #include "third_party/blink/renderer/core/layout/ng/inline/ng_fragment_item.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h"
+#include "third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.h"
+#include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h"
+#include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
 #include "third_party/blink/renderer/core/paint/applied_decoration_painter.h"
 #include "third_party/blink/renderer/core/paint/box_painter.h"
 #include "third_party/blink/renderer/core/paint/paint_info.h"
 #include "third_party/blink/renderer/core/paint/paint_timing_detector.h"
+#include "third_party/blink/renderer/core/paint/svg_object_painter.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/core/style/shadow_list.h"
+#include "third_party/blink/renderer/core/svg/svg_element.h"
 #include "third_party/blink/renderer/platform/fonts/font.h"
 #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
+#include "third_party/blink/renderer/platform/graphics/paint/paint_flags.h"
 #include "third_party/blink/renderer/platform/wtf/text/character_names.h"
 
 namespace blink {
 
 namespace {
 
+class SelectionStyleScope {
+  STACK_ALLOCATED();
+
+ public:
+  SelectionStyleScope(LayoutObject&,
+                      const ComputedStyle& style,
+                      const ComputedStyle& selection_style);
+  SelectionStyleScope(const SelectionStyleScope&) = delete;
+  SelectionStyleScope& operator=(const SelectionStyleScope) = delete;
+  ~SelectionStyleScope();
+
+ private:
+  LayoutObject& layout_object_;
+  const ComputedStyle& selection_style_;
+  const bool styles_are_equal_;
+};
+
+SelectionStyleScope::SelectionStyleScope(LayoutObject& layout_object,
+                                         const ComputedStyle& style,
+                                         const ComputedStyle& selection_style)
+    : layout_object_(layout_object),
+      selection_style_(selection_style),
+      styles_are_equal_(style == selection_style) {
+  if (styles_are_equal_)
+    return;
+  DCHECK(!layout_object.IsSVGInlineText());
+  auto& element = To<SVGElement>(*layout_object_.GetNode());
+  SVGResources::UpdatePaints(element, nullptr, selection_style_);
+}
+
+SelectionStyleScope::~SelectionStyleScope() {
+  if (styles_are_equal_)
+    return;
+  auto& element = To<SVGElement>(*layout_object_.GetNode());
+  SVGResources::ClearPaints(element, &selection_style_);
+}
+
+bool SetupPaintForSvgText(const LayoutSVGInlineText& svg_inline_text,
+                          const GraphicsContext& context,
+                          bool is_rendering_clip_path_as_mask_image,
+                          const ComputedStyle& style,
+                          const AffineTransform* shader_transform,
+                          LayoutSVGResourceMode resource_mode,
+                          PaintFlags& flags) {
+  const LayoutObject* layout_parent = svg_inline_text.Parent();
+  if (!SVGObjectPainter(*layout_parent)
+           .PreparePaint(context, is_rendering_clip_path_as_mask_image, style,
+                         resource_mode, flags, shader_transform)) {
+    return false;
+  }
+
+  flags.setAntiAlias(true);
+
+  if (style.TextShadow() &&
+      // Text shadows are disabled when printing. http://crbug.com/258321
+      !svg_inline_text.GetDocument().Printing()) {
+    flags.setLooper(TextPainterBase::CreateDrawLooper(
+        style.TextShadow(), DrawLooperBuilder::kShadowRespectsAlpha,
+        style.VisitedDependentColor(GetCSSPropertyColor()),
+        style.UsedColorScheme()));
+  }
+
+  if (resource_mode == kApplyToStrokeMode) {
+    // The stroke geometry needs be generated based on the scaled font.
+    float stroke_scale_factor =
+        style.VectorEffect() != EVectorEffect::kNonScalingStroke
+            ? svg_inline_text.ScalingFactor()
+            : 1;
+    StrokeData stroke_data;
+    SVGLayoutSupport::ApplyStrokeStyleToStrokeData(
+        stroke_data, style, *layout_parent, stroke_scale_factor);
+    if (stroke_scale_factor != 1)
+      stroke_data.SetThickness(stroke_data.Thickness() * stroke_scale_factor);
+    stroke_data.SetupPaint(&flags);
+  }
+
+  return true;
+}
+
 absl::optional<TextDecorationInfo> DecorationsForLayer(
     const NGFragmentItem& text_item,
     const PhysicalRect& decoration_rect,
@@ -87,6 +172,11 @@
   // painting in most small text.
   snapped_selection_rect.Inflate(1);
   if (snapped_selection_rect.Contains(visual_rect_)) {
+    absl::optional<base::AutoReset<bool>> is_painting_selection_reset;
+    if (svg_text_paint_state_.has_value()) {
+      is_painting_selection_reset.emplace(
+          &svg_text_paint_state_->is_painting_selection_, true);
+    }
     Paint(start_offset, end_offset, length, selection_style, node_id);
     return;
   }
@@ -109,6 +199,11 @@
   }
   // Then draw the glyphs inside the selection area, with the selection style.
   {
+    absl::optional<base::AutoReset<bool>> is_painting_selection_reset;
+    if (svg_text_paint_state_.has_value()) {
+      is_painting_selection_reset.emplace(
+          &svg_text_paint_state_->is_painting_selection_, true);
+    }
     GraphicsContextStateSaver state_saver(graphics_context_);
     graphics_context_.Clip(float_selection_rect);
     Paint(start_offset, end_offset, length, selection_style, node_id);
@@ -175,8 +270,12 @@
         FloatPoint(text_origin_) + IntSize(0, emphasis_mark_offset_));
   } else {
     DCHECK(step == kPaintText);
-    graphics_context_.DrawText(font_, fragment_paint_info_,
-                               FloatPoint(text_origin_), node_id);
+    if (svg_text_paint_state_.has_value()) {
+      PaintSvgTextFragment(node_id);
+    } else {
+      graphics_context_.DrawText(font_, fragment_paint_info_,
+                                 FloatPoint(text_origin_), node_id);
+    }
     // TODO(npm): Check that there are non-whitespace characters. See
     // crbug.com/788444.
     graphics_context_.GetPaintController().SetTextPainted();
@@ -223,4 +322,113 @@
   DecorationsStripeIntercepts(upper, stripe_width, dilation, text_intercepts);
 }
 
+void NGTextPainter::PaintSvgTextFragment(DOMNodeId node_id) {
+  const NGTextPainter::SvgTextPaintState& state = *svg_text_paint_state_;
+  absl::optional<SelectionStyleScope> selection_style_scope;
+  bool has_fill = state.Style().HasFill();
+  bool has_visible_stroke = state.Style().HasVisibleStroke();
+  const ComputedStyle* style_to_paint = &state.Style();
+  if (state.IsPaintingSelection()) {
+    LayoutObject* layout_parent = state.InlineText().Parent();
+    style_to_paint =
+        layout_parent->GetCachedPseudoElementStyle(kPseudoIdSelection);
+    if (style_to_paint) {
+      if (!has_fill)
+        has_fill = style_to_paint->HasFill();
+      if (!has_visible_stroke)
+        has_visible_stroke = style_to_paint->HasVisibleStroke();
+    } else {
+      style_to_paint = &state.Style();
+    }
+
+    selection_style_scope.emplace(*layout_parent, state.Style(),
+                                  *style_to_paint);
+  }
+
+  if (state.IsRenderingClipPathAsMaskImage()) {
+    has_fill = true;
+    has_visible_stroke = false;
+  }
+
+  for (int i = 0; i < 3; i++) {
+    absl::optional<LayoutSVGResourceMode> resource_mode;
+
+    switch (state.Style().PaintOrderType(i)) {
+      case PT_FILL:
+        if (has_fill)
+          resource_mode = kApplyToFillMode;
+        break;
+      case PT_STROKE:
+        if (has_visible_stroke)
+          resource_mode = kApplyToStrokeMode;
+        break;
+      case PT_MARKERS:
+        // Markers don't apply to text
+        break;
+      default:
+        NOTREACHED();
+        break;
+    }
+
+    if (resource_mode) {
+      PaintFlags flags;
+      if (SetupPaintForSvgText(state.InlineText(), graphics_context_,
+                               state.IsRenderingClipPathAsMaskImage(),
+                               *style_to_paint, state.GetShaderTransform(),
+                               *resource_mode, flags)) {
+        graphics_context_.DrawText(font_, fragment_paint_info_,
+                                   FloatPoint(text_origin_), flags, node_id);
+      }
+    }
+  }
+}
+
+NGTextPainter::SvgTextPaintState& NGTextPainter::SetSvgState(
+    const LayoutSVGInlineText& svg_inline_text,
+    const ComputedStyle& style,
+    bool is_rendering_clip_path_as_mask_image) {
+  return svg_text_paint_state_.emplace(svg_inline_text, style,
+                                       is_rendering_clip_path_as_mask_image);
+}
+
+NGTextPainter::SvgTextPaintState* NGTextPainter::GetSvgState() {
+  return base::OptionalOrNullptr(svg_text_paint_state_);
+}
+
+NGTextPainter::SvgTextPaintState::SvgTextPaintState(
+    const LayoutSVGInlineText& layout_svg_inline_text,
+    const ComputedStyle& style,
+    bool is_rendering_clip_path_as_mask_image)
+    : layout_svg_inline_text_(layout_svg_inline_text),
+      style_(style),
+      is_rendering_clip_path_as_mask_image_(
+          is_rendering_clip_path_as_mask_image) {}
+
+const LayoutSVGInlineText& NGTextPainter::SvgTextPaintState::InlineText()
+    const {
+  return layout_svg_inline_text_;
+}
+
+const ComputedStyle& NGTextPainter::SvgTextPaintState::Style() const {
+  return style_;
+}
+
+bool NGTextPainter::SvgTextPaintState::IsPaintingSelection() const {
+  return is_painting_selection_;
+}
+
+bool NGTextPainter::SvgTextPaintState::IsRenderingClipPathAsMaskImage() const {
+  return is_rendering_clip_path_as_mask_image_;
+}
+
+AffineTransform& NGTextPainter::SvgTextPaintState::EnsureShaderTransform() {
+  return shader_transform_ ? shader_transform_.value()
+                           : shader_transform_.emplace();
+}
+
+const AffineTransform* NGTextPainter::SvgTextPaintState::GetShaderTransform()
+    const {
+  return base::OptionalOrNullptr(shader_transform_);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/ng/ng_text_painter.h b/third_party/blink/renderer/core/paint/ng/ng_text_painter.h
index 6e0f965..26b4bb67 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_text_painter.h
+++ b/third_party/blink/renderer/core/paint/ng/ng_text_painter.h
@@ -12,6 +12,7 @@
 
 namespace blink {
 
+class LayoutSVGInlineText;
 class NGFragmentItem;
 struct NGTextFragmentPaintInfo;
 
@@ -24,6 +25,29 @@
   STACK_ALLOCATED();
 
  public:
+  class SvgTextPaintState final {
+   public:
+    SvgTextPaintState(const LayoutSVGInlineText&,
+                      const ComputedStyle&,
+                      bool is_rendering_clip_path_as_mask_image);
+
+    const LayoutSVGInlineText& InlineText() const;
+    const ComputedStyle& Style() const;
+    bool IsPaintingSelection() const;
+    bool IsRenderingClipPathAsMaskImage() const;
+
+    AffineTransform& EnsureShaderTransform();
+    const AffineTransform* GetShaderTransform() const;
+
+   private:
+    const LayoutSVGInlineText& layout_svg_inline_text_;
+    const ComputedStyle& style_;
+    absl::optional<AffineTransform> shader_transform_;
+    bool is_painting_selection_ = false;
+    bool is_rendering_clip_path_as_mask_image_ = false;
+    friend class NGTextPainter;
+  };
+
   NGTextPainter(GraphicsContext& context,
                 const Font& font,
                 const NGTextFragmentPaintInfo& fragment_paint_info,
@@ -75,6 +99,11 @@
       const PhysicalRect& decoration_rect,
       const absl::optional<AppliedTextDecoration>& selection_decoration);
 
+  SvgTextPaintState& SetSvgState(const LayoutSVGInlineText&,
+                                 const ComputedStyle&,
+                                 bool is_rendering_clip_path_as_mask_image);
+  SvgTextPaintState* GetSvgState();
+
  private:
   template <PaintInternalStep step>
   void PaintInternalFragment(unsigned from, unsigned to, DOMNodeId node_id);
@@ -85,8 +114,11 @@
                      unsigned truncation_point,
                      DOMNodeId node_id);
 
+  void PaintSvgTextFragment(DOMNodeId node_id);
+
   NGTextFragmentPaintInfo fragment_paint_info_;
   const IntRect& visual_rect_;
+  absl::optional<SvgTextPaintState> svg_text_paint_state_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index 5809969..b1263e3 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -3479,31 +3479,32 @@
   UpdateBackdropFilters(old_style, new_style);
   UpdateClipPath(old_style, new_style);
 
-  if (!SelfNeedsRepaint()) {
-    if (diff.ZIndexChanged()) {
-      // We don't need to invalidate paint of objects when paint order
-      // changes. However, we do need to repaint the containing stacking
-      // context, in order to generate new paint chunks in the correct order.
-      // Raster invalidation will be issued if needed during paint.
-      SetNeedsRepaint();
-    } else if (old_style) {
-      bool new_painted_output_invisible =
-          PaintLayerPainter::PaintedOutputInvisible(new_style);
-      if (PaintLayerPainter::PaintedOutputInvisible(*old_style) !=
-          new_painted_output_invisible) {
-        if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
-          // Though CompositeAfterPaint ignores PaintedOutputInvisible during
-          // paint, we still force repaint to ensure FCP/LCP will be reported.
-          // See crbug.com/1184903. Only SetNeedsRepaint() won't work because
-          // we won't repaint the display items which are already in the old
-          // painted result. TODO(crbug.com/1104218): Optimize this.
-          if (!new_painted_output_invisible)
-            GetLayoutObject().SetSubtreeShouldDoFullPaintInvalidation();
-        } else {
-          // Change of PaintedOutputInvisible() will affect existence of paint
-          // chunks, so needs repaint.
-          SetNeedsRepaint();
-        }
+  if (diff.ZIndexChanged()) {
+    // We don't need to invalidate paint of objects when paint order
+    // changes. However, we do need to repaint the containing stacking
+    // context, in order to generate new paint chunks in the correct order.
+    // Raster invalidation will be issued if needed during paint.
+    if (auto* stacking_context = AncestorStackingContext())
+      stacking_context->SetNeedsRepaint();
+  }
+
+  if (old_style) {
+    bool new_painted_output_invisible =
+        PaintLayerPainter::PaintedOutputInvisible(new_style);
+    if (PaintLayerPainter::PaintedOutputInvisible(*old_style) !=
+        new_painted_output_invisible) {
+      if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
+        // Though CompositeAfterPaint ignores PaintedOutputInvisible during
+        // paint, we still force repaint to ensure FCP/LCP will be reported.
+        // See crbug.com/1184903. Only SetNeedsRepaint() won't work because
+        // we won't repaint the display items which are already in the old
+        // painted result. TODO(crbug.com/1104218): Optimize this.
+        if (!new_painted_output_invisible)
+          GetLayoutObject().SetSubtreeShouldDoFullPaintInvalidation();
+      } else {
+        // Change of PaintedOutputInvisible() will affect existence of paint
+        // chunks, so needs repaint.
+        SetNeedsRepaint();
       }
     }
   }
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 007c36e..313eba8 100644
--- a/third_party/blink/renderer/core/paint/text_painter_base.cc
+++ b/third_party/blink/renderer/core/paint/text_painter_base.cc
@@ -13,7 +13,6 @@
 #include "third_party/blink/renderer/core/paint/text_decoration_info.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/core/style/shadow_list.h"
-#include "third_party/blink/renderer/core/svg/svg_length_context.h"
 #include "third_party/blink/renderer/platform/fonts/font.h"
 #include "third_party/blink/renderer/platform/fonts/text_run_paint_info.h"
 #include "third_party/blink/renderer/platform/geometry/length_functions.h"
@@ -72,34 +71,6 @@
 }
 
 // static
-void TextPainterBase::AdjustTextStyleForClip(TextPaintStyle& text_style) {
-  // When we use the text as a clip, we only care about the alpha, thus we
-  // make all the colors black.
-  text_style.current_color = Color::kBlack;
-  text_style.fill_color = Color::kBlack;
-  text_style.stroke_color = Color::kBlack;
-  text_style.emphasis_mark_color = Color::kBlack;
-  text_style.shadow = nullptr;
-}
-
-// static
-void TextPainterBase::AdjustTextStyleForPrint(const Document& document,
-                                              const ComputedStyle& style,
-                                              TextPaintStyle& text_style) {
-  // Adjust text color when printing with a white background.
-  bool force_background_to_white =
-      BoxPainterBase::ShouldForceWhiteBackgroundForPrintEconomy(document,
-                                                                style);
-  if (force_background_to_white) {
-    text_style.fill_color = TextColorForWhiteBackground(text_style.fill_color);
-    text_style.stroke_color =
-        TextColorForWhiteBackground(text_style.stroke_color);
-    text_style.emphasis_mark_color =
-        TextColorForWhiteBackground(text_style.emphasis_mark_color);
-  }
-}
-
-// static
 void TextPainterBase::UpdateGraphicsContext(
     GraphicsContext& context,
     const TextPaintStyle& text_style,
@@ -186,7 +157,13 @@
   text_style.color_scheme = style.UsedColorScheme();
 
   if (paint_info.phase == PaintPhase::kTextClip) {
-    AdjustTextStyleForClip(text_style);
+    // When we use the text as a clip, we only care about the alpha, thus we
+    // make all the colors black.
+    text_style.current_color = Color::kBlack;
+    text_style.fill_color = Color::kBlack;
+    text_style.stroke_color = Color::kBlack;
+    text_style.emphasis_mark_color = Color::kBlack;
+    text_style.shadow = nullptr;
   } else {
     text_style.current_color =
         style.VisitedDependentColor(GetCSSPropertyColor());
@@ -198,54 +175,18 @@
         style.VisitedDependentColor(GetCSSPropertyWebkitTextEmphasisColor());
     text_style.shadow = style.TextShadow();
 
-    AdjustTextStyleForPrint(document, style, text_style);
-  }
-
-  return text_style;
-}
-
-// static
-TextPaintStyle TextPainterBase::SvgTextPaintingStyle(
-    const Document& document,
-    const SVGLengthContext& length_context,
-    const ComputedStyle& style,
-    const PaintInfo& paint_info) {
-  TextPaintStyle text_style;
-  text_style.stroke_width =
-      style.HasStroke() ? length_context.ValueForLength(style.StrokeWidth())
-                        : 0;
-  text_style.color_scheme = style.UsedColorScheme();
-
-  if (paint_info.phase == PaintPhase::kTextClip) {
-    AdjustTextStyleForClip(text_style);
-  } else {
-    text_style.current_color =
-        style.VisitedDependentColor(GetCSSPropertyColor());
-
-    const SVGPaint fill_paint = style.FillPaint();
-    if (fill_paint.IsNone()) {
-      text_style.fill_color = Color::kTransparent;
-    } else if (fill_paint.HasColor()) {
-      const Color color = style.VisitedDependentColor(GetCSSPropertyFill());
-      const float alpha = style.FillOpacity();
-      text_style.fill_color = ScaleAlpha(color.Rgb(), alpha);
-    } else {
-      text_style.fill_color = Color::kBlack;
+    // Adjust text color when printing with a white background.
+    bool force_background_to_white =
+        BoxPainterBase::ShouldForceWhiteBackgroundForPrintEconomy(document,
+                                                                  style);
+    if (force_background_to_white) {
+      text_style.fill_color =
+          TextColorForWhiteBackground(text_style.fill_color);
+      text_style.stroke_color =
+          TextColorForWhiteBackground(text_style.stroke_color);
+      text_style.emphasis_mark_color =
+          TextColorForWhiteBackground(text_style.emphasis_mark_color);
     }
-
-    if (style.StrokePaint().HasColor()) {
-      const Color color = style.VisitedDependentColor(GetCSSPropertyStroke());
-      const float alpha = style.StrokeOpacity();
-      text_style.stroke_color = ScaleAlpha(color.Rgb(), alpha);
-    } else {
-      text_style.stroke_color = Color::kTransparent;
-    }
-
-    text_style.emphasis_mark_color =
-        style.VisitedDependentColor(GetCSSPropertyWebkitTextEmphasisColor());
-    text_style.shadow = style.TextShadow();
-
-    AdjustTextStyleForPrint(document, style, text_style);
   }
 
   return text_style;
diff --git a/third_party/blink/renderer/core/paint/text_painter_base.h b/third_party/blink/renderer/core/paint/text_painter_base.h
index 4cdf405..bba7e4bb 100644
--- a/third_party/blink/renderer/core/paint/text_painter_base.h
+++ b/third_party/blink/renderer/core/paint/text_painter_base.h
@@ -25,7 +25,6 @@
 class GraphicsContext;
 class GraphicsContextStateSaver;
 class Node;
-class SVGLengthContext;
 class TextDecorationOffsetBase;
 struct PaintInfo;
 
@@ -72,10 +71,6 @@
   static TextPaintStyle TextPaintingStyle(const Document&,
                                           const ComputedStyle&,
                                           const PaintInfo&);
-  static TextPaintStyle SvgTextPaintingStyle(const Document&,
-                                             const SVGLengthContext&,
-                                             const ComputedStyle&,
-                                             const PaintInfo&);
   static TextPaintStyle SelectionPaintingStyle(
       const Document&,
       const ComputedStyle&,
@@ -89,10 +84,6 @@
   static AffineTransform Rotation(const PhysicalRect& box_rect, WritingMode);
 
  protected:
-  static void AdjustTextStyleForClip(TextPaintStyle&);
-  static void AdjustTextStyleForPrint(const Document&,
-                                      const ComputedStyle&,
-                                      TextPaintStyle&);
   void UpdateGraphicsContext(const TextPaintStyle& style,
                              GraphicsContextStateSaver& saver) {
     UpdateGraphicsContext(graphics_context_, style, horizontal_, saver);
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index d682a10..cccc323 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -43,6 +43,8 @@
 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/core/html/html_body_element.h"
+#include "third_party/blink/renderer/core/html/html_html_element.h"
 #include "third_party/blink/renderer/core/html/html_progress_element.h"
 #include "third_party/blink/renderer/core/layout/layout_theme.h"
 #include "third_party/blink/renderer/core/layout/ng/custom/layout_worklet.h"
@@ -2542,6 +2544,27 @@
   return static_cast<EPaintOrderType>(pt);
 }
 
+bool ComputedStyle::ShouldApplyAnyContainment(const Element& element) const {
+  DCHECK(IsA<HTMLBodyElement>(element) || IsA<HTMLHtmlElement>(element))
+      << "Since elements can override the computed display for which box type "
+         "to create, this method is not generally correct. Use "
+         "LayoutObject::ShouldApplyAnyContainment if possible.";
+  if (ContainsStyle())
+    return true;
+  if (!element.LayoutObjectIsNeeded(*this))
+    return false;
+  if (Display() == EDisplay::kInline)
+    return false;
+  if ((ContainsInlineSize() || ContainsBlockSize()) &&
+      (!IsDisplayTableType() || Display() == EDisplay::kTableCaption)) {
+    return true;
+  }
+  return (ContainsLayout() || ContainsPaint()) &&
+         (!IsDisplayTableType() || IsDisplayTableBox() ||
+          Display() == EDisplay::kTableCell ||
+          Display() == EDisplay::kTableCaption);
+}
+
 STATIC_ASSERT_ENUM(cc::OverscrollBehavior::Type::kAuto,
                    EOverscrollBehavior::kAuto);
 STATIC_ASSERT_ENUM(cc::OverscrollBehavior::Type::kContain,
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index d9e8564a..4365e066 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -2068,6 +2068,7 @@
   bool ContainsBlockSize() const {
     return (Contain() & kContainsBlockSize) || IsBlockSizeContainer();
   }
+  CORE_EXPORT bool ShouldApplyAnyContainment(const Element& element) const;
 
   // Display utility functions.
   bool IsDisplayReplacedType() const {
diff --git a/third_party/blink/renderer/core/style/computed_style_test.cc b/third_party/blink/renderer/core/style/computed_style_test.cc
index b6503ce..f8a4ceb 100644
--- a/third_party/blink/renderer/core/style/computed_style_test.cc
+++ b/third_party/blink/renderer/core/style/computed_style_test.cc
@@ -22,7 +22,10 @@
 #include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h"
 #include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/dom/node_computed_style.h"
 #include "third_party/blink/renderer/core/frame/settings.h"
+#include "third_party/blink/renderer/core/html/html_body_element.h"
+#include "third_party/blink/renderer/core/layout/layout_object.h"
 #include "third_party/blink/renderer/core/style/clip_path_operation.h"
 #include "third_party/blink/renderer/core/style/shape_clip_path_operation.h"
 #include "third_party/blink/renderer/core/style/shape_value.h"
@@ -1214,6 +1217,81 @@
   TEST_STYLE_VALUE_NO_DIFF(BaselineShift);
 }
 
+TEST_F(ComputedStyleTest, ShouldApplyAnyContainment) {
+  std::unique_ptr<DummyPageHolder> dummy_page_holder =
+      std::make_unique<DummyPageHolder>(IntSize(0, 0), nullptr);
+  Document& document = dummy_page_holder->GetDocument();
+
+  auto* html = document.documentElement();
+  auto* body = document.body();
+  ASSERT_TRUE(html);
+  ASSERT_TRUE(body);
+
+  auto display_types = {CSSValueID::kInline,
+                        CSSValueID::kBlock,
+                        CSSValueID::kListItem,
+                        CSSValueID::kInlineBlock,
+                        CSSValueID::kTable,
+                        CSSValueID::kInlineTable,
+                        CSSValueID::kTableRowGroup,
+                        CSSValueID::kTableHeaderGroup,
+                        CSSValueID::kTableFooterGroup,
+                        CSSValueID::kTableRow,
+                        CSSValueID::kTableColumnGroup,
+                        CSSValueID::kTableColumn,
+                        CSSValueID::kTableCell,
+                        CSSValueID::kTableCaption,
+                        CSSValueID::kWebkitBox,
+                        CSSValueID::kWebkitInlineBox,
+                        CSSValueID::kFlex,
+                        CSSValueID::kInlineFlex,
+                        CSSValueID::kGrid,
+                        CSSValueID::kInlineGrid,
+                        CSSValueID::kContents,
+                        CSSValueID::kFlowRoot,
+                        CSSValueID::kNone,
+                        CSSValueID::kMath};
+
+  for (auto contain :
+       {CSSValueID::kNone, CSSValueID::kLayout, CSSValueID::kPaint,
+        CSSValueID::kSize, CSSValueID::kStyle}) {
+    html->SetInlineStyleProperty(CSSPropertyID::kContain,
+                                 getValueName(contain));
+    body->SetInlineStyleProperty(CSSPropertyID::kContain,
+                                 getValueName(contain));
+    for (auto html_display : display_types) {
+      html->SetInlineStyleProperty(CSSPropertyID::kDisplay, html_display);
+      for (auto body_display : display_types) {
+        body->SetInlineStyleProperty(CSSPropertyID::kDisplay, body_display);
+        document.View()->UpdateAllLifecyclePhasesForTest();
+
+        if (!html->GetLayoutObject()) {
+          EXPECT_TRUE(!html->GetComputedStyle());
+          continue;
+        }
+        EXPECT_EQ(html->GetLayoutObject()->ShouldApplyAnyContainment(),
+                  html->GetLayoutObject()->StyleRef().ShouldApplyAnyContainment(
+                      *html))
+            << "html contain:" << getValueName(contain)
+            << " display:" << getValueName(html_display);
+        if (!body->GetLayoutObject()) {
+          if (const auto* body_style = body->GetComputedStyle()) {
+            EXPECT_EQ(body_style->Display(), EDisplay::kContents);
+            EXPECT_EQ(body_style->ShouldApplyAnyContainment(*body),
+                      contain == CSSValueID::kStyle);
+          }
+          continue;
+        }
+        EXPECT_EQ(body->GetLayoutObject()->ShouldApplyAnyContainment(),
+                  body->GetLayoutObject()->StyleRef().ShouldApplyAnyContainment(
+                      *body))
+            << "body contain:" << getValueName(contain)
+            << " display:" << getValueName(body_display);
+      }
+    }
+  }
+}
+
 #if DCHECK_IS_ON()
 
 TEST_F(ComputedStyleTest, DebugDiffFields) {
diff --git a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc
index 8a6be8db..eecc49f1 100644
--- a/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc
+++ b/third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.cc
@@ -4,6 +4,8 @@
 
 #include "third_party/blink/renderer/core/workers/worker_or_worklet_global_scope.h"
 
+#include <utility>
+
 #include "base/threading/thread_checker.h"
 #include "services/network/public/mojom/web_sandbox_flags.mojom-blink.h"
 #include "third_party/blink/public/common/features.h"
@@ -11,7 +13,9 @@
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/mojom/loader/content_security_notifier.mojom-blink.h"
 #include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom-blink.h"
+#include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/task_type.h"
+#include "third_party/blink/public/platform/web_content_security_policy_struct.h"
 #include "third_party/blink/public/platform/web_worker_fetch_context.h"
 #include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
 #include "third_party/blink/renderer/core/dom/events/event_queue.h"
@@ -38,6 +42,7 @@
 #include "third_party/blink/renderer/platform/loader/fetch/null_resource_fetcher_properties.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_load_observer.h"
+#include "third_party/blink/renderer/platform/network/http_parsers.h"
 #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
@@ -448,6 +453,16 @@
     auto* csp = MakeGarbageCollected<ContentSecurityPolicy>();
     csp->SetSupportsWasmEval(SchemeRegistry::SchemeSupportsWasmEvalCSP(
         GetSecurityOrigin()->Protocol()));
+
+    // Check if the embedder wants to add any default policies, and add them.
+    WebVector<WebContentSecurityPolicyHeader> embedder_default_csp;
+    Platform::Current()->AppendContentSecurityPolicy(WebURL(Url()),
+                                                     &embedder_default_csp);
+    for (const auto& header : embedder_default_csp) {
+      csp->AddPolicies(ParseContentSecurityPolicies(
+          header.header_value, header.type, header.source, Url()));
+    }
+
     SetContentSecurityPolicy(csp);
   }
   GetContentSecurityPolicy()->AddPolicies(std::move(policies));
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
index b2d1e4a..117bde81 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -105,6 +105,7 @@
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/style/computed_style_constants.h"
 #include "third_party/blink/renderer/core/svg/svg_element.h"
+#include "third_party/blink/renderer/core/svg/svg_title_element.h"
 #include "third_party/blink/renderer/modules/accessibility/ax_image_map_link.h"
 #include "third_party/blink/renderer/modules/accessibility/ax_inline_text_box.h"
 #include "third_party/blink/renderer/modules/accessibility/ax_layout_object.h"
@@ -267,6 +268,32 @@
   return ax::mojom::blink::TextDecorationStyle::kNone;
 }
 
+String GetTitle(blink::Element* element) {
+  if (!element)
+    return String();
+
+  if (blink::SVGElement* svg_element =
+          blink::DynamicTo<blink::SVGElement>(element)) {
+    // Don't use title() in SVG, as it calls innerText() which updates layout.
+    // Unfortunately, this must duplicate some logic from SVGElement::title().
+    if (svg_element->InUseShadowTree()) {
+      String title = GetTitle(svg_element->OwnerShadowHost());
+      if (!title.IsEmpty())
+        return title;
+    }
+    // If we aren't an instance in a <use> or the <use> title was not found,
+    // then find the first <title> child of this element. If a title child was
+    // found, return the text contents.
+    if (auto* title_element =
+            blink::Traversal<blink::SVGTitleElement>::FirstChild(*element)) {
+      return title_element->GetInnerTextWithoutUpdate();
+    }
+    return String();
+  }
+
+  return element->title();
+}
+
 }  // namespace
 
 namespace blink {
@@ -831,24 +858,18 @@
   if (IsA<HTMLImageElement>(GetNode()))
     return ax::mojom::blink::Role::kImage;
 
-  // <a href> or <svg:a xlink:href>
-  if (GetNode()->IsLink()) {
-    // |HTMLAnchorElement| sets isLink only when it has kHrefAttr.
-    return ax::mojom::blink::Role::kLink;
-  }
-
-  if (IsA<HTMLPortalElement>(*GetNode())) {
-    return ax::mojom::blink::Role::kPortal;
-  }
-
-  if (IsA<HTMLAnchorElement>(*GetNode())) {
-    // We assume that an anchor element is LinkRole if it has event listeners
-    // even though it doesn't have kHrefAttr.
-    if (IsClickable())
+  // <a> or <svg:a>.
+  if (IsA<HTMLAnchorElement>(GetNode()) || IsA<SVGAElement>(GetNode())) {
+    // Assume that an anchor element is a Role::kLink if it has an href or a
+    // click event listener.
+    if (GetNode()->IsLink() || IsClickable())
       return ax::mojom::blink::Role::kLink;
-    return ax::mojom::blink::Role::kAnchor;
+    return ax::mojom::blink::Role::kGenericContainer;
   }
 
+  if (IsA<HTMLPortalElement>(*GetNode()))
+    return ax::mojom::blink::Role::kPortal;
+
   if (IsA<HTMLButtonElement>(*GetNode()))
     return ButtonRoleType();
 
@@ -5264,15 +5285,9 @@
 
 String AXNodeObject::Title(ax::mojom::blink::NameFrom name_from) const {
   if (name_from == ax::mojom::blink::NameFrom::kTitle)
-    return String();
+    return String();  // Already exposed the title in the name field.
 
-  if (const auto* element = GetElement()) {
-    String title = element->title();
-    if (!title.IsEmpty())
-      return title;
-  }
-
-  return String();
+  return GetTitle(GetElement());
 }
 
 String AXNodeObject::PlaceholderFromNativeAttribute() const {
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index a8e4445b..fd893bc 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -2027,20 +2027,6 @@
   bool is_ignored = ComputeAccessibilityIsIgnored();
   bool is_ignored_but_included_in_tree =
       is_ignored && ComputeAccessibilityIsIgnoredButIncludedInTree();
-#if DCHECK_IS_ON()
-  // Ensure that display-locked text is pruned from the tree. This means that
-  // they will be missed in the virtual buffer; therefore, it may be a rule
-  // subject to change. Note that changing the rule would potentially cause a
-  // lot of display-locked whitespace to be exposed.
-  if (is_ignored && RoleValue() == ax::mojom::blink::Role::kStaticText &&
-      GetNode() &&
-      DisplayLockUtilities::NearestLockedExclusiveAncestor(*GetNode())) {
-    DCHECK(!cached_is_ignored_but_included_in_tree_)
-        << "Display locked text should not be included in the tree (subject to "
-           "future rule change): "
-        << ToString(true, true);
-  }
-#endif
   bool included_in_tree_changed = false;
 
   // If the child's "included in tree" state changes, we will be notifying the
@@ -5202,7 +5188,6 @@
   switch (RoleValue()) {
     // ----- NameFrom: contents -------------------------
     // Get their own name from contents, or contribute to ancestors
-    case ax::mojom::blink::Role::kAnchor:
     case ax::mojom::blink::Role::kButton:
     case ax::mojom::blink::Role::kCell:
     case ax::mojom::blink::Role::kCheckBox:
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index fe301c0..616a4b67 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -1326,6 +1326,7 @@
     Remove(object->AXObjectID());
 }
 
+// This is safe to call even if there isn't a current mapping.
 void AXObjectCacheImpl::Remove(AXID ax_id) {
   if (!ax_id)
     return;
@@ -1354,48 +1355,77 @@
   DCHECK_GE(objects_.size(), ids_in_use_.size());
 }
 
+// This is safe to call even if there isn't a current mapping.
 void AXObjectCacheImpl::Remove(AccessibleNode* accessible_node) {
   if (!accessible_node)
     return;
 
-  AXID ax_id = accessible_node_mapping_.at(accessible_node);
-  accessible_node_mapping_.erase(accessible_node);
+  auto iter = accessible_node_mapping_.find(accessible_node);
+  if (iter == accessible_node_mapping_.end())
+    return;
+
+  AXID ax_id = iter->value;
+  accessible_node_mapping_.erase(iter);
 
   Remove(ax_id);
 }
 
+// This is safe to call even if there isn't a current mapping.
 bool AXObjectCacheImpl::Remove(LayoutObject* layout_object) {
   if (!layout_object)
     return false;
 
-  AXID ax_id = layout_object_mapping_.at(layout_object);
-  if (!ax_id)
+  auto iter = layout_object_mapping_.find(layout_object);
+  if (iter == layout_object_mapping_.end())
     return false;
 
-  layout_object_mapping_.erase(layout_object);
+  AXID ax_id = iter->value;
+  DCHECK(ax_id);
+
+  layout_object_mapping_.erase(iter);
   Remove(ax_id);
 
   return true;
 }
 
+// This is safe to call even if there isn't a current mapping.
 void AXObjectCacheImpl::Remove(Node* node) {
   if (!node)
     return;
 
-  // This is all safe even if we didn't have a mapping.
-  AXID ax_id = node_object_mapping_.at(node);
-  node_object_mapping_.erase(node);
+  LayoutObject* layout_object = node->GetLayoutObject();
 
-  if (!Remove(node->GetLayoutObject()))
+  // A layout object will be used whenever it is available and relevant. It's
+  // the preferred backing object, rather than the DOM node.
+  if (Remove(node->GetLayoutObject())) {
+    DCHECK_EQ(node_object_mapping_.find(node), node_object_mapping_.end())
+        << "AXObject cannot be backed by both a layout object and node.";
+    return;
+  }
+
+  auto iter = node_object_mapping_.find(node);
+  if (iter != node_object_mapping_.end()) {
+    DCHECK(!layout_object || layout_object_mapping_.find(layout_object) ==
+                                 layout_object_mapping_.end())
+        << "AXObject cannot be backed by both a layout object and node.";
+    AXID ax_id = iter->value;
+    DCHECK(ax_id);
+    node_object_mapping_.erase(iter);
     Remove(ax_id);
+  }
 }
 
+// This is safe to call even if there isn't a current mapping.
 void AXObjectCacheImpl::Remove(AbstractInlineTextBox* inline_text_box) {
   if (!inline_text_box)
     return;
 
-  AXID ax_id = inline_text_box_object_mapping_.at(inline_text_box);
-  inline_text_box_object_mapping_.erase(inline_text_box);
+  auto iter = inline_text_box_object_mapping_.find(inline_text_box);
+  if (iter == inline_text_box_object_mapping_.end())
+    return;
+
+  AXID ax_id = iter->value;
+  inline_text_box_object_mapping_.erase(iter);
 
   Remove(ax_id);
 }
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_playback_speed_list_element.cc b/third_party/blink/renderer/modules/media_controls/elements/media_control_playback_speed_list_element.cc
index d6afb38..1d17a93 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_playback_speed_list_element.cc
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_playback_speed_list_element.cc
@@ -209,6 +209,7 @@
   auto* arg =
       MakeGarbageCollected<V8UnionBooleanOrScrollIntoViewOptions>(options);
   checked_item_->scrollIntoView(arg);
+  checked_item_->focus();
 }
 
 void MediaControlPlaybackSpeedListElement::Trace(Visitor* visitor) const {
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_data_init.idl b/third_party/blink/renderer/modules/webcodecs/audio_data_init.idl
index 0ec1f52..165cf05 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_data_init.idl
+++ b/third_party/blink/renderer/modules/webcodecs/audio_data_init.idl
@@ -5,6 +5,6 @@
 // https://github.com/WICG/web-codecs
 
 dictionary AudioDataInit {
-  required long long timestamp;  // microseconds
+  required [EnforceRange] long long timestamp;  // microseconds
   required AudioBuffer buffer;
 };
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_decoder_config.idl b/third_party/blink/renderer/modules/webcodecs/audio_decoder_config.idl
index 9948360..fc3527f 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_decoder_config.idl
+++ b/third_party/blink/renderer/modules/webcodecs/audio_decoder_config.idl
@@ -12,10 +12,10 @@
   required DOMString codec;
 
   // Rate of samples per second. 44100, 48000, etc.
-  required unsigned long sampleRate;
+  required [EnforceRange] unsigned long sampleRate;
 
   // 1, 2, etc.
-  required unsigned long numberOfChannels;
+  required [EnforceRange] unsigned long numberOfChannels;
 
   // Optional byte data required to initialize audio decoders such as Vorbis
   // codebooks.
diff --git a/third_party/blink/renderer/modules/webcodecs/audio_encoder_config.idl b/third_party/blink/renderer/modules/webcodecs/audio_encoder_config.idl
index ab29c94..8d8f0499 100644
--- a/third_party/blink/renderer/modules/webcodecs/audio_encoder_config.idl
+++ b/third_party/blink/renderer/modules/webcodecs/audio_encoder_config.idl
@@ -11,10 +11,10 @@
   required DOMString codec;
 
   // Rate of samples per second. 44100, 48000, etc.
-  required unsigned long sampleRate;
+  required [EnforceRange] unsigned long sampleRate;
 
   // 1, 2, etc.
-  required unsigned short numberOfChannels;
+  required [EnforceRange] unsigned short numberOfChannels;
 
-  unsigned long long bitrate;
+  [EnforceRange] unsigned long long bitrate;
 };
diff --git a/third_party/blink/renderer/modules/webcodecs/encoded_audio_chunk_init.idl b/third_party/blink/renderer/modules/webcodecs/encoded_audio_chunk_init.idl
index 41ff977..b64b6afc 100644
--- a/third_party/blink/renderer/modules/webcodecs/encoded_audio_chunk_init.idl
+++ b/third_party/blink/renderer/modules/webcodecs/encoded_audio_chunk_init.idl
@@ -6,6 +6,6 @@
 
 dictionary EncodedAudioChunkInit {
   required EncodedAudioChunkType type;
-  required long long timestamp; // microseconds
+  required [EnforceRange] long long timestamp; // microseconds
   required BufferSource data;
 };
diff --git a/third_party/blink/renderer/modules/webcodecs/encoded_video_chunk_init.idl b/third_party/blink/renderer/modules/webcodecs/encoded_video_chunk_init.idl
index a499ecf8..9c86828 100644
--- a/third_party/blink/renderer/modules/webcodecs/encoded_video_chunk_init.idl
+++ b/third_party/blink/renderer/modules/webcodecs/encoded_video_chunk_init.idl
@@ -6,7 +6,7 @@
 
 dictionary EncodedVideoChunkInit {
   required EncodedVideoChunkType type;
-  required long long timestamp; // microseconds
-  unsigned long long duration;  // microseconds
+  required [EnforceRange] long long timestamp; // microseconds
+  [EnforceRange] unsigned long long duration;  // microseconds
   required BufferSource data;
 };
diff --git a/third_party/blink/renderer/modules/webcodecs/encoded_video_chunk_metadata.idl b/third_party/blink/renderer/modules/webcodecs/encoded_video_chunk_metadata.idl
index da51cd23..4ae2695 100644
--- a/third_party/blink/renderer/modules/webcodecs/encoded_video_chunk_metadata.idl
+++ b/third_party/blink/renderer/modules/webcodecs/encoded_video_chunk_metadata.idl
@@ -6,5 +6,5 @@
 
 dictionary EncodedVideoChunkMetadata {
   VideoDecoderConfig decoderConfig;
-  unsigned long temporalLayerId;
+  [EnforceRange] unsigned long temporalLayerId;
 };
diff --git a/third_party/blink/renderer/modules/webcodecs/image_decode_options.idl b/third_party/blink/renderer/modules/webcodecs/image_decode_options.idl
index a38fdff..8cc91b4 100644
--- a/third_party/blink/renderer/modules/webcodecs/image_decode_options.idl
+++ b/third_party/blink/renderer/modules/webcodecs/image_decode_options.idl
@@ -6,7 +6,7 @@
 
 dictionary ImageDecodeOptions {
   // The index of the frame to decode.
-  unsigned long frameIndex = 0;
+  [EnforceRange] unsigned long frameIndex = 0;
 
   // When |completeFramesOnly| is set to false, partial progressive frames will
   // be returned. When in this mode, decode() calls will resolve only once per
diff --git a/third_party/blink/renderer/modules/webcodecs/image_track_list.idl b/third_party/blink/renderer/modules/webcodecs/image_track_list.idl
index d380cc3..e96fc2c 100644
--- a/third_party/blink/renderer/modules/webcodecs/image_track_list.idl
+++ b/third_party/blink/renderer/modules/webcodecs/image_track_list.idl
@@ -8,7 +8,7 @@
     Exposed=(Window,DedicatedWorker),
     RuntimeEnabled=WebCodecs
 ] interface ImageTrackList {
-  getter ImageTrack(unsigned long index);
+  getter ImageTrack([EnforceRange] unsigned long index);
   readonly attribute unsigned long length;
 
   // Index of the currently selected track or -1 if no track is selected.
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder_config.idl b/third_party/blink/renderer/modules/webcodecs/video_encoder_config.idl
index 80d801c..211ab00 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_encoder_config.idl
+++ b/third_party/blink/renderer/modules/webcodecs/video_encoder_config.idl
@@ -11,7 +11,7 @@
   HardwarePreference hardwareAcceleration = "allow";
   AlphaOption alpha = "discard";
 
-  unsigned long long bitrate;
+  [EnforceRange] unsigned long long bitrate;
 
   double framerate;
 
diff --git a/third_party/blink/renderer/modules/webcodecs/video_frame_init.idl b/third_party/blink/renderer/modules/webcodecs/video_frame_init.idl
index f9d8a2c..4903ddc3 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_frame_init.idl
+++ b/third_party/blink/renderer/modules/webcodecs/video_frame_init.idl
@@ -6,7 +6,7 @@
 
 enum AlphaOption { "discard", "keep" };
 dictionary VideoFrameInit {
-  long long timestamp;  // microseconds
-  unsigned long long duration;   // microseconds
+  [EnforceRange] long long timestamp;  // microseconds
+  [EnforceRange] unsigned long long duration;   // microseconds
   AlphaOption alpha = "keep";
 };
diff --git a/third_party/blink/renderer/modules/webcodecs/video_frame_plane_init.idl b/third_party/blink/renderer/modules/webcodecs/video_frame_plane_init.idl
index 68bb198..b7c9aec 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_frame_plane_init.idl
+++ b/third_party/blink/renderer/modules/webcodecs/video_frame_plane_init.idl
@@ -7,8 +7,8 @@
 dictionary VideoFramePlaneInit {
   required VideoPixelFormat format;
 
-  required long long timestamp;  // microseconds
-  unsigned long long duration;   // microseconds
+  required [EnforceRange] long long timestamp;  // microseconds
+  [EnforceRange] unsigned long long duration;  // microseconds
 
   required [EnforceRange] unsigned long codedWidth;
   required [EnforceRange] unsigned long codedHeight;
diff --git a/third_party/blink/renderer/platform/bindings/idl_member_installer.cc b/third_party/blink/renderer/platform/bindings/idl_member_installer.cc
index 90a743b..1f6f792d 100644
--- a/third_party/blink/renderer/platform/bindings/idl_member_installer.cc
+++ b/third_party/blink/renderer/platform/bindings/idl_member_installer.cc
@@ -137,8 +137,7 @@
     v8::Local<v8::Signature> signature,
     v8::Local<v8::String> name,
     const Config& config,
-    const v8::CFunction* v8_cfunction_table_data = nullptr,
-    uint32_t v8_cfunction_table_size = 0) {
+    const v8::CFunction* v8_c_function = nullptr) {
   v8::FunctionCallback callback = GetConfigCallback<kind>(config);
   if (!callback)
     return v8::Local<v8::FunctionTemplate>();
@@ -153,10 +152,9 @@
       (v8_cached_accessor ==
            V8PrivateProperty::CachedAccessor::kWindowDocument &&
        !world.IsMainWorld())) {
-    function_template = v8::FunctionTemplate::NewWithCFunctionOverloads(
+    function_template = v8::FunctionTemplate::New(
         isolate, callback, v8::Local<v8::Value>(), signature, length,
-        v8::ConstructorBehavior::kThrow, v8_side_effect,
-        {v8_cfunction_table_data, v8_cfunction_table_size});
+        v8::ConstructorBehavior::kThrow, v8_side_effect, v8_c_function);
   } else {
     function_template = v8::FunctionTemplate::NewWithCache(
         isolate, callback,
@@ -182,14 +180,12 @@
     v8::Local<v8::Signature> signature,
     v8::Local<v8::String> name,
     const Config& config,
-    const v8::CFunction* v8_cfunction_table_data = nullptr,
-    uint32_t v8_cfunction_table_size = 0) {
+    const v8::CFunction* v8_c_function = nullptr) {
   if (!GetConfigCallback<kind>(config))
     return v8::Local<v8::Function>();
 
   return CreateFunctionTemplate<kind>(isolate, world, signature, name, config,
-                                      v8_cfunction_table_data,
-                                      v8_cfunction_table_size)
+                                      v8_c_function)
       ->GetFunction(context)
       .ToLocalChecked();
 }
@@ -302,8 +298,7 @@
                       v8::Local<v8::Template> interface_template,
                       v8::Local<v8::Signature> signature,
                       const IDLMemberInstaller::OperationConfig& config,
-                      const v8::CFunction* v8_cfunction_table_data = nullptr,
-                      uint32_t v8_cfunction_table_size = 0) {
+                      const v8::CFunction* v8_c_function = nullptr) {
   if (!DoesWorldMatch(config, world))
     return;
 
@@ -318,8 +313,7 @@
   v8::Local<v8::String> name = V8AtomicString(isolate, config.name);
   v8::Local<v8::FunctionTemplate> func =
       CreateFunctionTemplate<FunctionKind::kOperation>(
-          isolate, world, signature, name, config, v8_cfunction_table_data,
-          v8_cfunction_table_size);
+          isolate, world, signature, name, config, v8_c_function);
 
   v8::Local<v8::Template> target_template;
   switch (location) {
@@ -348,8 +342,7 @@
                       v8::Local<v8::Object> interface_object,
                       v8::Local<v8::Signature> signature,
                       const IDLMemberInstaller::OperationConfig& config,
-                      const v8::CFunction* v8_cfunction_table_data = nullptr,
-                      uint32_t v8_cfunction_table_size = 0) {
+                      const v8::CFunction* v8_c_function = nullptr) {
   if (!DoesWorldMatch(config, world))
     return;
 
@@ -363,8 +356,7 @@
 
   v8::Local<v8::String> name = V8AtomicString(isolate, config.name);
   v8::Local<v8::Function> func = CreateFunction<FunctionKind::kOperation>(
-      isolate, context, world, signature, name, config, v8_cfunction_table_data,
-      v8_cfunction_table_size);
+      isolate, context, world, signature, name, config, v8_c_function);
 
   v8::Local<v8::Object> target_object;
   switch (location) {
@@ -519,8 +511,7 @@
   for (const auto& config : configs) {
     InstallOperation(isolate, world, instance_template, prototype_template,
                      interface_template, signature, config.operation_config,
-                     config.v8_cfunction_table_data,
-                     config.v8_cfunction_table_size);
+                     &config.v8_c_function);
   }
 }
 
@@ -537,8 +528,7 @@
   for (const auto& config : configs) {
     InstallOperation(isolate, context, world, instance_object, prototype_object,
                      interface_object, signature, config.operation_config,
-                     config.v8_cfunction_table_data,
-                     config.v8_cfunction_table_size);
+                     &config.v8_c_function);
   }
 }
 
diff --git a/third_party/blink/renderer/platform/bindings/idl_member_installer.h b/third_party/blink/renderer/platform/bindings/idl_member_installer.h
index 2d09b2f3..434d246 100644
--- a/third_party/blink/renderer/platform/bindings/idl_member_installer.h
+++ b/third_party/blink/renderer/platform/bindings/idl_member_installer.h
@@ -138,8 +138,7 @@
 
   struct NoAllocDirectCallOperationConfig {
     OperationConfig operation_config;
-    const v8::CFunction* v8_cfunction_table_data;
-    uint32_t v8_cfunction_table_size;
+    v8::CFunction v8_c_function;
   };
   static void InstallOperations(
       v8::Isolate* isolate,
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index 1a4f05d..de01599 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -351,12 +351,21 @@
     const PaintArtifact& previous_artifact,
     const PaintChunk& repainted,
     const PaintArtifact& repainted_artifact) {
-  if (repainted.is_moved_from_cached_subsequence)
-    return false;
-
   if (!repainted.Matches(previous))
     return true;
 
+  if (repainted.is_moved_from_cached_subsequence) {
+    DCHECK_EQ(previous.bounds, repainted.bounds);
+    DCHECK_EQ(previous.known_to_be_opaque, repainted.known_to_be_opaque);
+    DCHECK_EQ(previous.text_known_to_be_on_opaque_background,
+              repainted.text_known_to_be_on_opaque_background);
+    // Not checking ForeignLayer() here because the old ForeignDisplayItem
+    // was set to 0 when we moved the cached subsequence. This is also the
+    // reason why we check is_moved_from_cached_subsequence before checking
+    // ForeignLayer().
+    return false;
+  }
+
   // Bounds are used in overlap testing.
   // TODO(pdr): If the bounds shrink, that does affect overlap testing but we
   // could return false to continue using less-than-optimal overlap testing in
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.cc b/third_party/blink/renderer/platform/graphics/graphics_context.cc
index a1fd8d2..98c186d 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.cc
@@ -637,6 +637,17 @@
                           : Font::DrawType::kGlyphsOnly);
 }
 
+void GraphicsContext::DrawText(const Font& font,
+                               const NGTextFragmentPaintInfo& text_info,
+                               const FloatPoint& point,
+                               const PaintFlags& flags,
+                               DOMNodeId node_id) {
+  font.DrawText(canvas_, text_info, point, device_scale_factor_, node_id,
+                DarkModeFlags(this, flags, DarkModeFilter::ElementRole::kText),
+                printing_ ? Font::DrawType::kGlyphsAndClusters
+                          : Font::DrawType::kGlyphsOnly);
+}
+
 template <typename DrawTextFunc>
 void GraphicsContext::DrawTextPasses(const DrawTextFunc& draw_text) {
   TextDrawingModeFlags mode_flags = TextDrawingMode();
diff --git a/third_party/blink/renderer/platform/graphics/graphics_context.h b/third_party/blink/renderer/platform/graphics/graphics_context.h
index afeee01..8d71d83 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_context.h
+++ b/third_party/blink/renderer/platform/graphics/graphics_context.h
@@ -311,6 +311,14 @@
                 const PaintFlags&,
                 DOMNodeId);
 
+  // TODO(layout-dev): This method is only used by NGTextPainter, see if the
+  // four parameter overload can be removed or if it can wrap this method.
+  void DrawText(const Font&,
+                const NGTextFragmentPaintInfo&,
+                const FloatPoint&,
+                const PaintFlags&,
+                DOMNodeId);
+
   void DrawEmphasisMarks(const Font&,
                          const TextRunPaintInfo&,
                          const AtomicString& mark,
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc
index 33aece5..65dd419 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc
@@ -180,6 +180,10 @@
     }
   }
 
+  // To mirror what RTCVideoDecoderStreamAdapter does a little more closely,
+  // record an init failure here.  Otherwise, we only ever record successes.
+  base::UmaHistogramBoolean("Media.RTCVideoDecoderInitDecodeSuccess", false);
+
   return nullptr;
 }
 
@@ -253,7 +257,8 @@
   current_resolution_ =
       static_cast<int32_t>(codec_settings->width) * codec_settings->height;
 
-  UMA_HISTOGRAM_BOOLEAN("Media.RTCVideoDecoderInitDecodeSuccess", !has_error_);
+  base::UmaHistogramBoolean("Media.RTCVideoDecoderInitDecodeSuccess",
+                            !has_error_);
   if (!has_error_) {
     UMA_HISTOGRAM_ENUMERATION(
         "Media.RTCVideoDecoderProfile",
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.cc
index bf466d9..e7bae62 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.cc
@@ -8,6 +8,7 @@
 #include <functional>
 #include <utility>
 
+#include "base/atomic_ref_count.h"
 #include "base/callback_helpers.h"
 #include "base/containers/circular_deque.h"
 #include "base/feature_list.h"
@@ -74,6 +75,17 @@
 // never completed, or (c) we're hopelessly behind.
 constexpr int32_t kAbsoluteMaxPendingBuffers = 32;
 
+// Name we'll report for hardware decoders.
+constexpr const char* kExternalDecoderName = "ExternalDecoder";
+
+// Number of RTCVideoDecoder instances right now that have started decoding.
+std::atomic_int* GetDecoderCounter() {
+  static base::NoDestructor<std::atomic_int> s_counter(0);
+  // Note that this will init only in the first call in the ctor, so it's still
+  // single threaded.
+  return s_counter.get();
+}
+
 void RecordInitializationLatency(base::TimeDelta latency) {
   base::UmaHistogramTimes("Media.RTCVideoDecoderInitializationLatencyMs",
                           latency);
@@ -81,6 +93,9 @@
 
 }  // namespace
 
+// static
+constexpr gfx::Size RTCVideoDecoderStreamAdapter::kMinResolution;
+
 // DemuxerStream implementation that forwards DecoderBuffer from some other
 // source (i.e., VideoDecoder::Decode).
 class RTCVideoDecoderStreamAdapter::InternalDemuxerStream
@@ -245,7 +260,10 @@
       config_(config),
       max_pending_buffer_count_(kAbsoluteMaxPendingBuffers) {
   DVLOG(1) << __func__;
-  decoder_info_.implementation_name = "unknown";
+  // Default to hw-accelerated decoder, in case something checks before decoding
+  // a frame.  It's unclear what we should report in the long run, but for now,
+  // it's better to report hardware since that's all we support anyway.
+  decoder_info_.implementation_name = kExternalDecoderName;
   decoder_info_.is_hardware_accelerated = false;
   DETACH_FROM_SEQUENCE(decoding_sequence_checker_);
   weak_this_ = weak_this_factory_.GetWeakPtr();
@@ -254,6 +272,9 @@
 RTCVideoDecoderStreamAdapter::~RTCVideoDecoderStreamAdapter() {
   DVLOG(1) << __func__;
   DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+
+  if (have_started_decoding_)
+    --(*GetDecoderCounter());
 }
 
 void RTCVideoDecoderStreamAdapter::InitializeSync(
@@ -289,6 +310,8 @@
 
   base::AutoLock auto_lock(lock_);
   init_decode_complete_ = true;
+  current_resolution_ =
+      gfx::Size(codec_settings->width, codec_settings->height);
   AttemptLogInitializationState_Locked();
   return has_error_ ? WEBRTC_VIDEO_CODEC_UNINITIALIZED : WEBRTC_VIDEO_CODEC_OK;
 }
@@ -308,7 +331,8 @@
 
   logged_init_status_ = true;
 
-  UMA_HISTOGRAM_BOOLEAN("Media.RTCVideoDecoderInitDecodeSuccess", !has_error_);
+  base::UmaHistogramBoolean("Media.RTCVideoDecoderInitDecodeSuccess",
+                            !has_error_);
   if (!has_error_) {
     UMA_HISTOGRAM_ENUMERATION(
         "Media.RTCVideoDecoderProfile",
@@ -324,6 +348,36 @@
   DVLOG(2) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(decoding_sequence_checker_);
 
+  // If this is the first decode, then increment the count of working decoders.
+  if (!have_started_decoding_) {
+    have_started_decoding_ = true;
+    ++(*GetDecoderCounter());
+  }
+
+#if defined(OS_ANDROID) && !BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS)
+  const bool has_software_fallback =
+      video_codec_type_ != webrtc::kVideoCodecH264;
+#else
+  const bool has_software_fallback = true;
+#endif
+
+  // Don't allow hardware decode for small videos if there are too many
+  // decoder instances.  This includes the case where our resolution drops while
+  // too many decoders exist.  When DecoderStream supports software decoders,
+  // this should be moved to DecoderSelector.
+  {
+    base::AutoLock auto_lock(lock_);
+    if (has_software_fallback &&
+        current_resolution_.GetArea() < kMinResolution.GetArea() &&
+        GetDecoderCounter()->load() > kMaxDecoderInstances) {
+      // Decrement the count and clear the flag, so that other decoders don't
+      // fall back also.
+      have_started_decoding_ = false;
+      --(*GetDecoderCounter());
+      return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
+    }
+  }
+
   // Fall back to software decoding if there's no support for VP9 spatial
   // layers. See https://crbug.com/webrtc/9304.
   // TODO(chromium:1187565): Update RTCVideoDecoderFactory::QueryCodecSupport()
@@ -413,12 +467,6 @@
       // drop any other non-key frame.
       key_frame_required_ = true;
 
-#if defined(OS_ANDROID) && !BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS)
-      const bool has_software_fallback =
-          video_codec_type_ != webrtc::kVideoCodecH264;
-#else
-      const bool has_software_fallback = true;
-#endif
       // If we hit the absolute limit, then give up.
       if (has_software_fallback &&
           pending_buffer_count_ >= kAbsoluteMaxPendingBuffers) {
@@ -645,6 +693,10 @@
     start_time_.reset();
   }
 
+  // Update `current_resolution_`, in case it's changed.  This lets us fall back
+  // to software, or avoid doing so, if we're over the decoder limit.
+  current_resolution_ = gfx::Size(rtc_frame.width(), rtc_frame.height());
+
   // Try to read the next output, if any, regardless if this succeeded.
   AttemptRead_Locked();
 
@@ -774,7 +826,7 @@
   decoder_info_.is_hardware_accelerated = decoder->IsPlatformDecoder();
   decoder_info_.implementation_name =
       decoder->IsPlatformDecoder()
-          ? "ExternalDecoder"
+          ? kExternalDecoderName
           : media::GetDecoderName(decoder->GetDecoderType()) +
                 " (DecoderStream)";
 }
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.h b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.h
index 6f1b575..22a4de1 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.h
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.h
@@ -62,6 +62,20 @@
 class PLATFORM_EXPORT RTCVideoDecoderStreamAdapter
     : public webrtc::VideoDecoder {
  public:
+  // Minimum resolution that we'll consider "not low resolution" for the purpose
+  // of falling back to software.
+#if defined(OS_CHROMEOS)
+  // Effectively opt-out CrOS, since it may cause tests to fail (b/179724180).
+  static constexpr gfx::Size kMinResolution{2, 2};
+#else
+  static constexpr gfx::Size kMinResolution{320, 240};
+#endif
+
+  // Maximum number of decoder instances we'll allow before fallback to software
+  // if the resolution is too low.  We'll allow more than this for high
+  // resolution streams, but they'll fall back if they adapt below the limit.
+  static constexpr int32_t kMaxDecoderInstances = 8;
+
   // Creates and initializes an RTCVideoDecoderStreamAdapter. Returns nullptr if
   // |format| cannot be supported. The gpu_factories may be null, in which case
   // only SW decoders will be used.
@@ -161,6 +175,8 @@
   // Decoding thread members.
   bool key_frame_required_ = true;
   webrtc::VideoCodecType video_codec_type_ = webrtc::kVideoCodecGeneric;
+  // Has anything been sent to Decode() yet?
+  bool have_started_decoding_ = false;
 
   // Shared members.
   mutable base::Lock lock_;
@@ -184,6 +200,10 @@
   // Time since construction.  Cleared when we record that a frame has been
   // successfully decoded.
   absl::optional<base::TimeTicks> start_time_ GUARDED_BY(lock_);
+  // Resolution of most recently decoded frame, or the initial resolution if we
+  // haven't decoded anything yet.  Since this is updated asynchronously, it's
+  // only an approximation of "most recently".
+  gfx::Size current_resolution_ GUARDED_BY(lock_);
 
   // Do we have an outstanding `DecoderStream::Read()`?
   // Media thread only.
diff --git a/third_party/blink/tools/blinkpy/w3c/test_importer.py b/third_party/blink/tools/blinkpy/w3c/test_importer.py
index 918c7a7..7a6020a 100644
--- a/third_party/blink/tools/blinkpy/w3c/test_importer.py
+++ b/third_party/blink/tools/blinkpy/w3c/test_importer.py
@@ -186,7 +186,7 @@
             _log.info('Only manifest was updated; skipping the import.')
             return 0
 
-        with self._expectations_updater.prepare_smoke_tests():
+        with self._expectations_updater.prepare_smoke_tests(self.chromium_git):
             self._commit_changes(commit_message)
             _log.info('Changes imported and committed.')
 
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
index 4fe8e77..415d2d9 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
@@ -781,7 +781,7 @@
         return line_dict
 
     @contextlib.contextmanager
-    def prepare_smoke_tests(self):
+    def prepare_smoke_tests(self, chromium_git):
         """List test cases that should be run by the smoke test builder
 
         Add new and modified test cases to WPT_SMOKE_TESTS_FILE,
@@ -819,6 +819,7 @@
         finally:
             _log.info('Restore file WPTSmokeTestCases.')
             shutil.copyfile(self._saved_test_cases_file, WPT_SMOKE_TESTS_FILE)
+            chromium_git.commit_locally_with_message('Restore WPTSmokeTestCases')
 
     def cleanup_test_expectations_files(self):
         """Removes deleted tests from expectations files.
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 79432b5..2ecac5e 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -219,6 +219,10 @@
 crbug.com/1126305 wpt_internal/prerender/* [ Skip ]
 crbug.com/1126305 virtual/prerender/wpt_internal/prerender/* [ Pass ]
 
+## prerender test: speculationrules script do not set referrers.
+crbug.com/1215932 virtual/prerender/wpt_internal/prerender/referrer-policy-origin.html [ Failure ]
+crbug.com/1215932 virtual/prerender/wpt_internal/prerender/referrer.html [ Failure ]
+
 ## prerender test: the File System Access API is not supported on Android ##
 crbug.com/1182032 [ Android ] virtual/prerender/wpt_internal/prerender/restriction-local-file-system-access.https.html [ Skip ]
 ## prerender test: Notification constructor is not supported on Android ##
@@ -3786,6 +3790,8 @@
 crbug.com/1045599 external/wpt/css/css-grid/grid-definition/grid-auto-repeat-dynamic-003.html [ Failure ]
 crbug.com/1045599 external/wpt/css/css-grid/grid-definition/grid-repeat-max-width-001.html [ Failure ]
 crbug.com/1045599 external/wpt/css/css-grid/grid-items/aspect-ratio-004.html [ Failure ]
+crbug.com/1045599 external/wpt/css/css-grid/grid-items/grid-item-inline-contribution-002.tentative.html [ Failure ]
+crbug.com/1045599 external/wpt/css/css-grid/grid-items/grid-item-inline-contribution-003.tentative.html [ Failure ]
 crbug.com/1053825 external/wpt/css/css-grid/grid-model/grid-overflow-padding-001.html [ Failure ]
 crbug.com/1053825 external/wpt/css/css-grid/grid-model/grid-overflow-padding-002.html [ Failure ]
 crbug.com/1053825 external/wpt/css/css-grid/grid-model/grid-areas-overflowing-grid-container-001.html [ Failure ]
@@ -3797,6 +3803,8 @@
 crbug.com/935102 external/wpt/css/css-grid/layout-algorithm/grid-flex-track-intrinsic-sizes-002.html [ Failure ]
 crbug.com/935102 external/wpt/css/css-grid/layout-algorithm/grid-flex-track-intrinsic-sizes-003.html [ Failure ]
 crbug.com/1045599 fast/css-grid-layout/maximize-tracks-definite-indefinite-height.html [ Failure ]
+crbug.com/1045599 external/wpt/css/css-sizing/contain-intrinsic-size/contain-intrinsic-size-025.html [ Failure ]
+crbug.com/1045599 external/wpt/css/css-sizing/contain-intrinsic-size/contain-intrinsic-size-026.html [ Failure ]
 
 # Bad stretching of svgs without aspect-ratio.
 crbug.com/1114013 external/wpt/css/css-grid/alignment/grid-item-no-aspect-ratio-stretch-5.html [ Failure ]
@@ -3901,6 +3909,8 @@
 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-definition/grid-auto-repeat-dynamic-003.html [ Pass ]
 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-items/aspect-ratio-004.html [ Pass ]
 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-items/grid-auto-margin-and-replaced-item-001.html [ Pass ]
+virtual/layout-ng-grid/external/wpt/css/css-grid/grid-items/grid-item-inline-contribution-002.tentative.html [ Pass ]
+virtual/layout-ng-grid/external/wpt/css/css-grid/grid-items/grid-item-inline-contribution-003.tentative.html [ Pass ]
 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-model/grid-areas-overflowing-grid-container-001.html [ Pass ]
 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-model/grid-areas-overflowing-grid-container-002.html [ Pass ]
 virtual/layout-ng-grid/external/wpt/css/css-grid/grid-model/grid-areas-overflowing-grid-container-003.html [ Pass ]
@@ -6333,7 +6343,7 @@
 
 # Sheriff 2021-03-08
 crbug.com/1092462 [ Linux ] http/tests/media/media-source/mediasource-duration.html [ Pass Failure ]
-crbug.com/1092462 [ Linux ] virtual/synchronous_html_parser/media/video-seek-past-end-paused.html [ Pass Failure ]
+crbug.com/1092462 [ Linux ] media/video-seek-past-end-paused.html [ Pass Failure ]
 crbug.com/1185675 [ Mac ] virtual/gpu-rasterization/images/color-profile-object.html [ Pass Failure ]
 crbug.com/1185676 [ Mac ] http/tests/devtools/tracing/timeline-js/timeline-js-line-level-profile-end-to-end.js [ Pass Failure ]
 
@@ -6487,6 +6497,7 @@
 
 # Sheriff 2021-04-14
 crbug.com/1198828 [ Win ] virtual/scroll-unification/fast/events/mouse-events-on-node-deletion.html [ Pass Failure ]
+crbug.com/1198828 [ Linux ] virtual/scroll-unification/fast/events/mouse-events-on-node-deletion.html [ Pass Failure ]
 
 # Sheriff 2021-04-15
 crbug.com/1199380 virtual/scroll-unification-prefer_compositing_to_lcd_text/fast/scroll-behavior/overflow-scroll-root-frame-animates.html [ Skip ]
@@ -6913,34 +6924,6 @@
 crbug.com/1048761 external/wpt/websockets/interfaces/WebSocket/events/018.html?wpt_flags=h2 [ Failure ]
 crbug.com/1048761 external/wpt/websockets/interfaces/WebSocket/send/006.html?wpt_flags=h2 [ Failure ]
 
-# Fix to unblock wpt-importer
-crbug.com/1209223 external/wpt/css/css-typed-om/idlharness.html [ Failure ]
-crbug.com/1209223 external/wpt/html/dom/idlharness.https.html?exclude=(Document|Window|HTML.\*) [ Failure ]
-crbug.com/1209223 [ Linux ] external/wpt/html/dom/idlharness.https.html?include=HTML.* [ Failure ]
-crbug.com/1209223 [ Win ] external/wpt/html/dom/idlharness.https.html?include=HTML.* [ Failure ]
-crbug.com/1209223 external/wpt/html/dom/idlharness.worker.html [ Failure ]
-crbug.com/1209223 external/wpt/html/semantics/forms/form-submission-0/newline-normalization.html [ Failure ]
-crbug.com/1209223 external/wpt/html/semantics/forms/the-textarea-element/wrapping-transformation.window.html [ Failure ]
-crbug.com/1209223 external/wpt/payment-handler/idlharness.https.any.serviceworker.html [ Failure ]
-crbug.com/1209223 external/wpt/payment-handler/idlharness.https.any.sharedworker.html [ Failure ]
-crbug.com/1209223 external/wpt/payment-handler/idlharness.https.any.worker.html [ Failure ]
-crbug.com/1209223 external/wpt/payment-request/idlharness.https.window.html [ Failure ]
-crbug.com/1209223 external/wpt/private-click-measurement/idlharness.window.html [ Failure ]
-crbug.com/1209223 external/wpt/url/url-setters-a-area.window.html [ Failure ]
-crbug.com/1209223 external/wpt/url/url-setters.any.html [ Failure ]
-crbug.com/1209223 external/wpt/url/url-setters.any.worker.html [ Failure ]
-crbug.com/1209223 external/wpt/webrtc/RTCPeerConnection-mandatory-getStats.https.html [ Failure ]
-crbug.com/1209223 external/wpt/webrtc/RTCPeerConnection-setLocalDescription-pranswer.html [ Failure ]
-crbug.com/1209223 external/wpt/webxr/idlharness.https.window.html [ Failure ]
-crbug.com/1209223 external/wpt/xhr/formdata/constructor-formelement.html [ Failure ]
-crbug.com/1209223 virtual/plz-dedicated-worker/external/wpt/xhr/formdata/constructor-formelement.html [ Failure ]
-crbug.com/1209223 virtual/shared_array_buffer_on_desktop/external/wpt/xhr/formdata/constructor-formelement.html [ Failure ]
-crbug.com/1209223 [ Mac10.15 ] virtual/shared_array_buffer_on_desktop/http/tests/devtools/a11y-axe-core/sources/scope-pane-a11y-test.js [ Timeout ]
-crbug.com/1209223 virtual/synchronous_html_parser/external/wpt/html/semantics/forms/form-submission-0/newline-normalization.html [ Failure ]
-crbug.com/1167095 virtual/synchronous_html_parser/external/wpt/html/semantics/forms/the-textarea-element/wrapping-transformation.window.html [ Failure ]
-crbug.com/1209223 virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-mandatory-getStats.https.html [ Failure ]
-crbug.com/1209223 virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-setLocalDescription-pranswer.html [ Failure ]
-
 # Sheriff on 2021-05-26
 crbug.com/1213322 [ Mac ] external/wpt/css/css-values/minmax-percentage-serialize.html [ Pass Failure ]
 crbug.com/1213322 [ Mac ] external/wpt/html/browsers/the-window-object/named-access-on-the-window-object/window-named-properties.html [ Pass Failure ]
@@ -6966,4 +6949,11 @@
 
 # Sheriff 2021-06-03
 crbug.com/1185121 fast/scroll-snap/animate-fling-to-snap-points-1.html [ Pass Failure ]
+crbug.com/1176162 [ Linux ] http/tests/devtools/screen-orientation-override.js [ Pass Failure ]
 crbug.com/1215949 external/wpt/pointerevents/pointerevent_iframe-touch-action-none_touch.html [ Pass Timeout ]
+crbug.com/1216139 virtual/bfcache/http/tests/devtools/bfcache/bfcache-elements-update.js [ Pass Failure ]
+
+# TODO(csmartalton): Tessellation improves these tests. Rebaseline after Skia roll.
+skbug.com/10419 [ Fuchsia ] tables/mozilla/bugs/bug2479-3.html [ Pass Failure ]
+skbug.com/10419 [ Fuchsia ] tables/mozilla/bugs/bug7342.html [ Pass Failure ]
+skbug.com/10419 [ Fuchsia ] tables/mozilla/bugs/bug59354.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 29eb132..ebe1ab3 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -349,7 +349,9 @@
     "prefix": "layout-ng-grid",
     "bases": ["fast/css-grid-layout",
               "external/wpt/css/css-sizing/aspect-ratio",
-              "external/wpt/css/css-grid"],
+              "external/wpt/css/css-grid",
+              "external/wpt/css/css-contain",
+              "external/wpt/css/css-sizing/contain-intrinsic-size"],
     "args": ["--enable-blink-features=LayoutNGGrid"]
   },
   {
@@ -1044,14 +1046,15 @@
   },
   {
     "prefix": "force-renderer-accessibility",
-    "bases": ["accessibility/slot-poison.html",
-              "accessibility/details-summary-crash.html",
+    "bases": ["accessibility/details-summary-crash.html",
+              "accessibility/slot-poison.html",
               "accessibility/virtual-node-child-removal.html",
               "accessibility/virtual-node-parent-removal.html",
               "accessibility/virtual-node-build-parent.html",
               "accessibility/virtual-node-build-parent-multiple.html",
               "accessibility/virtual-node-removed-from-document.html",
-              "accessibility/virtual-node-repair-document.html"],
+              "accessibility/virtual-node-repair-document.html",
+              "external/wpt/accessibility/crashtests/content-visibility-generated-content-removal.html"],
     "args": ["--force-renderer-accessibility"]
   },
   {
diff --git a/third_party/blink/web_tests/WebGPUExpectations b/third_party/blink/web_tests/WebGPUExpectations
index f31f7d2..530c91a 100644
--- a/third_party/blink/web_tests/WebGPUExpectations
+++ b/third_party/blink/web_tests/WebGPUExpectations
@@ -185,6 +185,10 @@
 
 crbug.com/1192234 wpt_internal/webgpu/cts.html?q=webgpu:api,validation,encoding,cmds,setBindGroup:u32array_start_and_length:* [ Failure ]
 
+# crbug.com/1215024
+wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createComputePipeline:enrty_point_name_must_match:stageEntryPoint="main%5Cu0000";* [ Failure ]
+wpt_internal/webgpu/cts.html?q=webgpu:api,validation,createComputePipeline:enrty_point_name_must_match:stageEntryPoint="main%5Cu0000a";* [ Failure ]
+
 # Crash with validation layer.
 wpt_internal/webgpu/cts.html?q=webgpu:api,operation,render_pipeline,primitive_topology:* [ Failure Crash ]
 
diff --git a/third_party/blink/web_tests/accessibility/clickable-expected.txt b/third_party/blink/web_tests/accessibility/clickable-expected.txt
index d91a2ab8..c79e8d1 100644
--- a/third_party/blink/web_tests/accessibility/clickable-expected.txt
+++ b/third_party/blink/web_tests/accessibility/clickable-expected.txt
@@ -31,7 +31,7 @@
 PASS isAXElementClickable('mousedown-listener') is true
 PASS isAXElementClickable('click-listener-on-ancestor') is false
 PASS axRole('ancestor-with-click-listener') is 'AXRole: AXGenericContainer'
-PASS axRole('empty-anchor') is 'AXRole: AXAnchor'
+PASS axRole('empty-anchor') is 'AXRole: AXGenericContainer'
 PASS axRole('href-anchor') is 'AXRole: AXLink'
 PASS axRole('onclick-anchor') is 'AXRole: AXLink'
 PASS axRole('click-listener-anchor') is 'AXRole: AXLink'
diff --git a/third_party/blink/web_tests/accessibility/clickable.html b/third_party/blink/web_tests/accessibility/clickable.html
index 4c9f50b..ea7ca74 100644
--- a/third_party/blink/web_tests/accessibility/clickable.html
+++ b/third_party/blink/web_tests/accessibility/clickable.html
@@ -94,7 +94,7 @@
     shouldBe("isAXElementClickable('click-listener-on-ancestor')", "false");
 
     shouldBe("axRole('ancestor-with-click-listener')", "'AXRole: AXGenericContainer'");
-    shouldBe("axRole('empty-anchor')", "'AXRole: AXAnchor'");
+    shouldBe("axRole('empty-anchor')", "'AXRole: AXGenericContainer'");
     shouldBe("axRole('href-anchor')", "'AXRole: AXLink'");
     shouldBe("axRole('onclick-anchor')", "'AXRole: AXLink'");
     shouldBe("axRole('click-listener-anchor')", "'AXRole: AXLink'");
diff --git a/third_party/blink/web_tests/accessibility/continuation3.html b/third_party/blink/web_tests/accessibility/continuation3.html
deleted file mode 100644
index a6e7ad9..0000000
--- a/third_party/blink/web_tests/accessibility/continuation3.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE HTML>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-
-<div>
-  <span>
-    <a name="a">
-      <div id="before">Before</div>
-      <div id="ever">
-        Ever
-        <a href="#" id="after">After</a>
-      </div>
-    </a>
-  </span>
-</div>
-
-<script>
-test(function(t) {
-    var axBefore = accessibilityController.accessibleElementById("before");
-    assert_equals(axBefore.childAtIndex(0).name, "Before");
-    var axEver = accessibilityController.accessibleElementById("ever");
-    assert_equals(axEver.childAtIndex(0).name, "Ever ");
-    var axAfter = accessibilityController.accessibleElementById("after");
-    assert_equals(axAfter.name, "After");
-}, "Ensure that continuations are included in the accessibility tree.");
-</script>
diff --git a/third_party/blink/web_tests/accessibility/in-page-link-target.html b/third_party/blink/web_tests/accessibility/in-page-link-target.html
index dadb2eed..6ae7e47 100644
--- a/third_party/blink/web_tests/accessibility/in-page-link-target.html
+++ b/third_party/blink/web_tests/accessibility/in-page-link-target.html
@@ -24,9 +24,9 @@
   assert_not_equals(anchor, undefined);
   var target = anchor.inPageLinkTarget;
   assert_not_equals(target, undefined);
-  // AXAncor because this is how the "a" tag is marked in the accessibility tree
+  // AXGenericContainer because this is how the "a" tag is marked in the AX tree
   // when it's a target and not a link.
-  assert_equals(target.role, 'AXRole: AXAnchor');
+  assert_equals(target.role, 'AXRole: AXGenericContainer');
   assert_equals(target.name, '');
 }, 'Test finding the target when it is an empty anchor.');
 
@@ -35,10 +35,9 @@
   assert_not_equals(anchor, undefined);
   var target = anchor.inPageLinkTarget;
   assert_not_equals(target, undefined);
-  // AXAncor because this is how the "a" tag is marked in the accessibility tree
+  // AXGenericContainer because this is how the "a" tag is marked in the AX tree
   // when it's a target and not a link.
-  assert_equals(target.role, 'AXRole: AXAnchor');
-  assert_equals(target.name, 'Anchor with content');
+  assert_equals(target.role, 'AXRole: AXGenericContainer');
 }, 'Test finding the target when it is an anchor with content.');
 
 test(function() {
@@ -46,10 +45,9 @@
   assert_not_equals(anchor, undefined);
   var target = anchor.inPageLinkTarget;
   assert_not_equals(target, undefined);
-  // AXAncor because this is how the "a" tag is marked in the accessibility tree
+  // AXGenericContainer because this is how the "a" tag is marked in the AX tree
   // when it's a target and not a link.
-  assert_equals(target.role, 'AXRole: AXAnchor');
-  assert_equals(target.name, 'Anchor with ID');
+  assert_equals(target.role, 'AXRole: AXGenericContainer');
 }, 'Test finding the target when it is an anchor with ID.');
 
 test(function() {
diff --git a/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations b/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations
index 59d02c5..d3e1699a3 100644
--- a/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations
+++ b/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations
@@ -1708,7 +1708,6 @@
 crbug.com/1050754 external/wpt/uievents/click/auxclick_event.html [ Timeout ]
 crbug.com/1050754 external/wpt/uievents/click/click_events_on_input.html [ Crash Failure Pass ]
 crbug.com/1050754 external/wpt/uievents/idlharness.window.html [ Failure Pass ]
-crbug.com/1050754 external/wpt/uievents/interface/keyboard-accesskey-click-event.html [ Pass ]
 crbug.com/1050754 external/wpt/uievents/keyboard/modifier-keys-combinations.html [ Pass ]
 crbug.com/1050754 external/wpt/uievents/keyboard/modifier-keys.html [ Pass ]
 crbug.com/1050754 external/wpt/uievents/order-of-events/focus-events/focus-contained.html [ Pass Timeout ]
@@ -2281,3 +2280,9 @@
 crbug.com/1050754 external/wpt/resource-timing/sizes-redirect.any.sharedworker.html [ Failure ]
 crbug.com/1050754 external/wpt/web-bundle/subresource-loading/csp-blocked.https.tentative.html [ Failure ]
 crbug.com/1050754 external/wpt/video-rvfc/request-video-frame-callback.html [ Timeout ]
+
+# update on 06/03/2021
+crbug.com/1050754 external/wpt/html/cross-origin-embedder-policy/credentialless/reporting-subresource-corp.tentative.https.html [ Failure ]
+crbug.com/1050754 external/wpt/html/cross-origin-embedder-policy/reporting-to-endpoint.https.html [ Failure ]
+crbug.com/1050754 external/wpt/uievents/interface/keyboard-accesskey-click-event.html [ Pass Failure ]
+crbug.com/1050754 external/wpt/webcodecs/image-decoder.any.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/wpt/accessibility/crashtests/content-visibility-generated-content-removal.html b/third_party/blink/web_tests/external/wpt/accessibility/crashtests/content-visibility-generated-content-removal.html
new file mode 100644
index 0000000..b880e24
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/accessibility/crashtests/content-visibility-generated-content-removal.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html class="test-wait">
+<style>
+  div { content-visibility: auto; }
+  div::after { content: "Bar" }
+</style>
+<div>Foo</div>
+<div style="height:4000px"></div>
+<script>
+  // Ensure no crash when removing element with generated content after
+  // `content-visibility: auto` content goes out of view.
+  requestAnimationFrame(() => {
+    requestAnimationFrame(() => {
+      // Let one layout run with the div content in view, then
+      // scroll it out of view.
+      document.scrollingElement.scrollTop = 3000;
+
+      // Run three frames to ensure a new layout happens with the
+      // 'auto' content hidden (i.e. layout structures are
+      // destroyed), then remove the div with a pseudo element which
+      // was previously problematic.
+      requestAnimationFrame(() => {
+        requestAnimationFrame(() => {
+          requestAnimationFrame(() => {
+              document.querySelector('div').remove();
+              document.documentElement.className = '';
+          })
+        })
+      })
+    })
+  });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-items/grid-item-inline-contribution-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-items/grid-item-inline-contribution-001.html
new file mode 100644
index 0000000..ebed26b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-items/grid-item-inline-contribution-001.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<link rel="help" href="https://drafts.csswg.org/css-grid-2/#algo-overview">
+<meta name="assert" content="Tests the min-content contribution is using the correct block-size.">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="display: inline-grid; background: green; height: 100px; grid-template-rows: 50px;">
+  <div style="height: 100%;"> <!-- This height should resolve against 50px - not 100px -->
+    <canvas width="20" height="10" style="height: 100%;">
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-items/grid-item-inline-contribution-002.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-items/grid-item-inline-contribution-002.tentative.html
new file mode 100644
index 0000000..abf175d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-items/grid-item-inline-contribution-002.tentative.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<link rel="help" href="https://drafts.csswg.org/css-grid-2/#algo-overview">
+<meta name="assert" content="Tests the min-content contribution is re-resolved during a 2nd pass.">
+<!--
+
+"
+ Then, if the min-content contribution of any grid item has changed based on
+ the row sizes and alignment calculated in step 2, re-resolve the sizes of the
+ grid columns with the new min-content and max-content contributions (once
+ only).
+"
+
+In this testcase initially the row size is indefinite, then resolves to 100px.
+Using this information we re-resolve the columns, resulting in 100px for the
+first column.
+
+-->
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="display: grid; width: 0; grid-template: auto / auto auto;">
+  <div style="background: green; height: 100%;">
+    <canvas width="10" height="10" style="height: 100%;">
+  </div>
+  <div>
+    <div style="height: 100px;"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-items/grid-item-inline-contribution-003.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-items/grid-item-inline-contribution-003.tentative.html
new file mode 100644
index 0000000..0488165a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-items/grid-item-inline-contribution-003.tentative.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<link rel="help" href="https://drafts.csswg.org/css-grid-2/#algo-overview">
+<meta name="assert" content="Tests the min-content contribution is re-resolved during a 2nd pass.">
+<!--
+
+"
+ Then, if the min-content contribution of any grid item has changed based on
+ the row sizes and alignment calculated in step 2, re-resolve the sizes of the
+ grid columns with the new min-content and max-content contributions (once
+ only).
+"
+
+In this testcase initially the row size is indefinite, then resolves to 100px.
+Using this information we re-resolve the columns, resulting in 100px for the
+first column.
+
+-->
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="display: grid; width: 0; grid-template: auto / auto auto;">
+  <div style="background: green;"> <!-- Has stretch alignment which the child should resolve against. -->
+    <canvas width="10" height="10" style="height: 100%;">
+  </div>
+  <div>
+    <div style="height: 100px;"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/contain-intrinsic-size/contain-intrinsic-size-025-ref.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/contain-intrinsic-size/contain-intrinsic-size-025-ref.html
index f760a49..fbb1ce8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/contain-intrinsic-size/contain-intrinsic-size-025-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/contain-intrinsic-size/contain-intrinsic-size-025-ref.html
@@ -15,10 +15,13 @@
   grid-gap: 5px;
 }
 .one {
-  grid-template: none / 3fr 4fr;
+  grid-template: repeat(8, 10px) / 3fr 4fr;
 }
 .two {
-  grid-template: 1fr 2fr / none;
+  grid-template: 1fr 2fr / repeat(3, 15px);
+}
+.three {
+  grid-template: repeat(8, 10px) / repeat(3, 15px);
 }
 
 .item {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/contain-intrinsic-size/contain-intrinsic-size-026-ref.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/contain-intrinsic-size/contain-intrinsic-size-026-ref.html
index 8389ef70..3bf3a288 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-sizing/contain-intrinsic-size/contain-intrinsic-size-026-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/contain-intrinsic-size/contain-intrinsic-size-026-ref.html
@@ -8,6 +8,7 @@
 .grid {
   display: inline-grid;
   border: 1px solid black;
+  grid-template-columns: repeat(auto-fit, 100px);
   height: 40px;
 }
 .one {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-tables/tentative/td-box-sizing-003.html b/third_party/blink/web_tests/external/wpt/css/css-tables/tentative/td-box-sizing-003.html
index 3eb78e7..40b0147 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-tables/tentative/td-box-sizing-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-tables/tentative/td-box-sizing-003.html
@@ -79,6 +79,19 @@
   </tbody>
 </table>
 
+<p>box-sizing: border-box;padding: px, width px</p>
+<li>td's intrinsic width must be >= border + padding</li>
+
+<table style="width:300px;table-layout:fixed">
+  <tbody>
+    <tr>
+      <td style="box-sizing:border-box;padding-left:50px;padding-right:50px;width:30px" data-expected-width=100></td>
+      <td>auto</td>
+      <td>auto</td>
+    </tr>
+  </tbody>
+</table>
+
 <p>box-sizing: border-box; border px; padding %.</p>
 <li>intrinsic size of % padding is 0.
 <li>final size of % padding is computed against table's width.
@@ -127,6 +140,8 @@
     </td>
   </tr>
 </table>
+
+
 </main>
 <script>
   checkLayout("table");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-typed-om/idlharness-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-typed-om/idlharness-expected.txt
index 4b3bdb9..cd96b17 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-typed-om/idlharness-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/css-typed-om/idlharness-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 510 tests; 433 PASS, 77 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 508 tests; 431 PASS, 77 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS idl_test validation
 PASS Partial interface Element: original interface defined
@@ -398,13 +398,8 @@
 FAIL CSSColorValue interface: existence and properties of interface prototype object assert_equals: prototype of CSSColorValue.prototype is not CSSStyleValue.prototype expected [stringifying object threw TypeError: Illegal invocation with type object] but got object "[object Object]"
 PASS CSSColorValue interface: existence and properties of interface prototype object's "constructor" property
 PASS CSSColorValue interface: existence and properties of interface prototype object's @@unscopables property
-PASS CSSColorValue interface: operation toRGB()
-PASS CSSColorValue interface: operation toHSL()
-FAIL CSSColorValue interface: operation toHWB() assert_own_property: interface prototype object missing non-static operation expected property "toHWB" missing
-FAIL CSSColorValue interface: operation toGray() assert_own_property: interface prototype object missing non-static operation expected property "toGray" missing
-FAIL CSSColorValue interface: operation toLCH() assert_own_property: interface prototype object missing non-static operation expected property "toLCH" missing
-FAIL CSSColorValue interface: operation toLab() assert_own_property: interface prototype object missing non-static operation expected property "toLab" missing
-FAIL CSSColorValue interface: operation toColor(CSSKeywordish) assert_own_property: interface prototype object missing non-static operation expected property "toColor" missing
+FAIL CSSColorValue interface: attribute colorSpace assert_true: The prototype object must have a property "colorSpace" expected true got false
+FAIL CSSColorValue interface: operation to(CSSKeywordish) assert_own_property: interface prototype object missing non-static operation expected property "to" missing
 FAIL CSSColorValue interface: operation parse(USVString) assert_own_property: interface object missing static operation expected property "parse" missing
 PASS CSSRGB interface: existence and properties of interface object
 PASS CSSRGB interface object length
@@ -436,14 +431,6 @@
 FAIL CSSHWB interface: attribute w assert_own_property: self does not have own property "CSSHWB" expected property "CSSHWB" missing
 FAIL CSSHWB interface: attribute b assert_own_property: self does not have own property "CSSHWB" expected property "CSSHWB" missing
 FAIL CSSHWB interface: attribute alpha assert_own_property: self does not have own property "CSSHWB" expected property "CSSHWB" missing
-FAIL CSSGray interface: existence and properties of interface object assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
-FAIL CSSGray interface object length assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
-FAIL CSSGray interface object name assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
-FAIL CSSGray interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
-FAIL CSSGray interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
-FAIL CSSGray interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
-FAIL CSSGray interface: attribute gray assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
-FAIL CSSGray interface: attribute alpha assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
 FAIL CSSLCH interface: existence and properties of interface object assert_own_property: self does not have own property "CSSLCH" expected property "CSSLCH" missing
 FAIL CSSLCH interface object length assert_own_property: self does not have own property "CSSLCH" expected property "CSSLCH" missing
 FAIL CSSLCH interface object name assert_own_property: self does not have own property "CSSLCH" expected property "CSSLCH" missing
@@ -470,6 +457,17 @@
 FAIL CSSColor interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CSSColor" expected property "CSSColor" missing
 FAIL CSSColor interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CSSColor" expected property "CSSColor" missing
 FAIL CSSColor interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CSSColor" expected property "CSSColor" missing
+FAIL CSSDeviceCMYK interface: existence and properties of interface object assert_own_property: self does not have own property "CSSDeviceCMYK" expected property "CSSDeviceCMYK" missing
+FAIL CSSDeviceCMYK interface object length assert_own_property: self does not have own property "CSSDeviceCMYK" expected property "CSSDeviceCMYK" missing
+FAIL CSSDeviceCMYK interface object name assert_own_property: self does not have own property "CSSDeviceCMYK" expected property "CSSDeviceCMYK" missing
+FAIL CSSDeviceCMYK interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CSSDeviceCMYK" expected property "CSSDeviceCMYK" missing
+FAIL CSSDeviceCMYK interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CSSDeviceCMYK" expected property "CSSDeviceCMYK" missing
+FAIL CSSDeviceCMYK interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CSSDeviceCMYK" expected property "CSSDeviceCMYK" missing
+FAIL CSSDeviceCMYK interface: attribute c assert_own_property: self does not have own property "CSSDeviceCMYK" expected property "CSSDeviceCMYK" missing
+FAIL CSSDeviceCMYK interface: attribute m assert_own_property: self does not have own property "CSSDeviceCMYK" expected property "CSSDeviceCMYK" missing
+FAIL CSSDeviceCMYK interface: attribute y assert_own_property: self does not have own property "CSSDeviceCMYK" expected property "CSSDeviceCMYK" missing
+FAIL CSSDeviceCMYK interface: attribute k assert_own_property: self does not have own property "CSSDeviceCMYK" expected property "CSSDeviceCMYK" missing
+FAIL CSSDeviceCMYK interface: attribute alpha assert_own_property: self does not have own property "CSSDeviceCMYK" expected property "CSSDeviceCMYK" missing
 PASS CSSStyleRule interface: attribute styleMap
 PASS CSS namespace: operation escape(CSSOMString)
 PASS CSS namespace: operation number(double)
diff --git "a/third_party/blink/web_tests/external/wpt/html/dom/idlharness.https_exclude=\050Document_Window_HTML._\051-expected.txt" "b/third_party/blink/web_tests/external/wpt/html/dom/idlharness.https_exclude=\050Document_Window_HTML._\051-expected.txt"
index f3fcb35c..8332c366 100644
--- "a/third_party/blink/web_tests/external/wpt/html/dom/idlharness.https_exclude=\050Document_Window_HTML._\051-expected.txt"
+++ "b/third_party/blink/web_tests/external/wpt/html/dom/idlharness.https_exclude=\050Document_Window_HTML._\051-expected.txt"
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 1441 tests; 1410 PASS, 31 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 1445 tests; 1414 PASS, 31 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS idl_test validation
 PASS Partial interface Document: original interface defined
@@ -448,6 +448,7 @@
 PASS CanvasRenderingContext2D interface: attribute fillStyle
 PASS CanvasRenderingContext2D interface: operation createLinearGradient(double, double, double, double)
 PASS CanvasRenderingContext2D interface: operation createRadialGradient(double, double, double, double, double, double)
+PASS CanvasRenderingContext2D interface: operation createConicGradient(double, double, double)
 PASS CanvasRenderingContext2D interface: operation createPattern(CanvasImageSource, DOMString)
 PASS CanvasRenderingContext2D interface: attribute shadowOffsetX
 PASS CanvasRenderingContext2D interface: attribute shadowOffsetY
@@ -540,6 +541,8 @@
 PASS CanvasRenderingContext2D interface: calling createLinearGradient(double, double, double, double) on document.createElement("canvas").getContext("2d") with too few arguments must throw TypeError
 PASS CanvasRenderingContext2D interface: document.createElement("canvas").getContext("2d") must inherit property "createRadialGradient(double, double, double, double, double, double)" with the proper type
 PASS CanvasRenderingContext2D interface: calling createRadialGradient(double, double, double, double, double, double) on document.createElement("canvas").getContext("2d") with too few arguments must throw TypeError
+PASS CanvasRenderingContext2D interface: document.createElement("canvas").getContext("2d") must inherit property "createConicGradient(double, double, double)" with the proper type
+PASS CanvasRenderingContext2D interface: calling createConicGradient(double, double, double) on document.createElement("canvas").getContext("2d") with too few arguments must throw TypeError
 PASS CanvasRenderingContext2D interface: document.createElement("canvas").getContext("2d") must inherit property "createPattern(CanvasImageSource, DOMString)" with the proper type
 PASS CanvasRenderingContext2D interface: calling createPattern(CanvasImageSource, DOMString) on document.createElement("canvas").getContext("2d") with too few arguments must throw TypeError
 PASS CanvasRenderingContext2D interface: document.createElement("canvas").getContext("2d") must inherit property "shadowOffsetX" with the proper type
@@ -747,6 +750,7 @@
 PASS OffscreenCanvasRenderingContext2D interface: attribute fillStyle
 PASS OffscreenCanvasRenderingContext2D interface: operation createLinearGradient(double, double, double, double)
 PASS OffscreenCanvasRenderingContext2D interface: operation createRadialGradient(double, double, double, double, double, double)
+PASS OffscreenCanvasRenderingContext2D interface: operation createConicGradient(double, double, double)
 PASS OffscreenCanvasRenderingContext2D interface: operation createPattern(CanvasImageSource, DOMString)
 PASS OffscreenCanvasRenderingContext2D interface: attribute shadowOffsetX
 PASS OffscreenCanvasRenderingContext2D interface: attribute shadowOffsetY
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/idlharness.worker-expected.txt b/third_party/blink/web_tests/external/wpt/html/dom/idlharness.worker-expected.txt
index 260003c..4900aac 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/idlharness.worker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/html/dom/idlharness.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 804 tests; 796 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 805 tests; 797 PASS, 8 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS idl_test validation
 PASS Partial interface Document: original interface defined
@@ -358,6 +358,7 @@
 PASS OffscreenCanvasRenderingContext2D interface: attribute fillStyle
 PASS OffscreenCanvasRenderingContext2D interface: operation createLinearGradient(double, double, double, double)
 PASS OffscreenCanvasRenderingContext2D interface: operation createRadialGradient(double, double, double, double, double, double)
+PASS OffscreenCanvasRenderingContext2D interface: operation createConicGradient(double, double, double)
 PASS OffscreenCanvasRenderingContext2D interface: operation createPattern(CanvasImageSource, DOMString)
 PASS OffscreenCanvasRenderingContext2D interface: attribute shadowOffsetX
 PASS OffscreenCanvasRenderingContext2D interface: attribute shadowOffsetY
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/form-submission-0/newline-normalization-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/forms/form-submission-0/newline-normalization-expected.txt
new file mode 100644
index 0000000..fa7284e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/form-submission-0/newline-normalization-expected.txt
@@ -0,0 +1,15 @@
+This is a testharness.js-based test.
+FAIL Constructing the entry list shouldn't perform newline normalization: \n in the value assert_equals: expected "b\nc" but got "b\r\nc"
+FAIL Constructing the entry list shouldn't perform newline normalization: \r in the value assert_equals: expected "b\rc" but got "b\r\nc"
+PASS Constructing the entry list shouldn't perform newline normalization: \r\n in the value
+FAIL Constructing the entry list shouldn't perform newline normalization: \n\r in the value assert_equals: expected "b\n\rc" but got "b\r\n\r\nc"
+FAIL Constructing the entry list shouldn't perform newline normalization: \n in the name assert_equals: expected "a\nb" but got "a\r\nb"
+FAIL Constructing the entry list shouldn't perform newline normalization: \r in the name assert_equals: expected "a\rb" but got "a\r\nb"
+PASS Constructing the entry list shouldn't perform newline normalization: \r\n in the name
+FAIL Constructing the entry list shouldn't perform newline normalization: \n\r in the name assert_equals: expected "a\n\rb" but got "a\r\n\r\nb"
+PASS Constructing the entry list shouldn't perform newline normalization: \n in the filename
+PASS Constructing the entry list shouldn't perform newline normalization: \r in the filename
+PASS Constructing the entry list shouldn't perform newline normalization: \r\n in the filename
+PASS Constructing the entry list shouldn't perform newline normalization: \n\r in the filename
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-textarea-element/wrapping-transformation.window-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-textarea-element/wrapping-transformation.window-expected.txt
new file mode 100644
index 0000000..0c63131a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-textarea-element/wrapping-transformation.window-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+FAIL Textarea wrapping transformation: Newlines should be normalized to LF. assert_equals: expected "a\nb\nc\nd\n\ne" but got "a\r\nb\r\nc\r\nd\r\n\r\ne"
+FAIL Textarea wrapping transformation: Wrapping happens with LF newlines. assert_true: The wrapping done on the value must be LF, not CRLF. expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/payment-handler/idlharness.https.any.serviceworker-expected.txt b/third_party/blink/web_tests/external/wpt/payment-handler/idlharness.https.any.serviceworker-expected.txt
index 0c26f729..3b829c2 100644
--- a/third_party/blink/web_tests/external/wpt/payment-handler/idlharness.https.any.serviceworker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/payment-handler/idlharness.https.any.serviceworker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 114 tests; 71 PASS, 43 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 99 tests; 66 PASS, 33 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS idl_test validation
 PASS Partial interface ServiceWorkerRegistration: original interface defined
@@ -17,13 +17,10 @@
 FAIL PaymentManager interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
 FAIL PaymentManager interface: attribute instruments assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
 FAIL PaymentManager interface: attribute userHint assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
-FAIL PaymentManager interface: operation enableDelegations(sequence<PaymentDelegation>) assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
 FAIL PaymentManager must be primary interface of paymentManager assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
 PASS Stringification of paymentManager
 PASS PaymentManager interface: paymentManager must inherit property "instruments" with the proper type
 PASS PaymentManager interface: paymentManager must inherit property "userHint" with the proper type
-PASS PaymentManager interface: paymentManager must inherit property "enableDelegations(sequence<PaymentDelegation>)" with the proper type
-PASS PaymentManager interface: calling enableDelegations(sequence<PaymentDelegation>) on paymentManager with too few arguments must throw TypeError
 PASS PaymentInstruments interface: existence and properties of interface object
 PASS PaymentInstruments interface object length
 PASS PaymentInstruments interface object name
@@ -78,13 +75,8 @@
 PASS PaymentRequestEvent interface: attribute total
 PASS PaymentRequestEvent interface: attribute modifiers
 PASS PaymentRequestEvent interface: attribute instrumentKey
-FAIL PaymentRequestEvent interface: attribute requestBillingAddress assert_true: The prototype object must have a property "requestBillingAddress" expected true got false
-PASS PaymentRequestEvent interface: attribute paymentOptions
-PASS PaymentRequestEvent interface: attribute shippingOptions
 PASS PaymentRequestEvent interface: operation openWindow(USVString)
 PASS PaymentRequestEvent interface: operation changePaymentMethod(DOMString, optional object?)
-FAIL PaymentRequestEvent interface: operation changeShippingAddress(optional AddressInit) assert_equals: property has wrong .length expected 0 but got 1
-PASS PaymentRequestEvent interface: operation changeShippingOption(DOMString)
 PASS PaymentRequestEvent interface: operation respondWith(Promise<PaymentHandlerResponse>)
 FAIL PaymentRequestEvent must be primary interface of new PaymentRequestEvent("type") assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
 FAIL Stringification of new PaymentRequestEvent("type") assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
@@ -95,17 +87,10 @@
 FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "total" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
 FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "modifiers" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
 FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "instrumentKey" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
-FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "requestBillingAddress" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
-FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "paymentOptions" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
-FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "shippingOptions" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
 FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "openWindow(USVString)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
 FAIL PaymentRequestEvent interface: calling openWindow(USVString) on new PaymentRequestEvent("type") with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
 FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "changePaymentMethod(DOMString, optional object?)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
 FAIL PaymentRequestEvent interface: calling changePaymentMethod(DOMString, optional object?) on new PaymentRequestEvent("type") with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
-FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "changeShippingAddress(optional AddressInit)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
-FAIL PaymentRequestEvent interface: calling changeShippingAddress(optional AddressInit) on new PaymentRequestEvent("type") with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
-FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "changeShippingOption(DOMString)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
-FAIL PaymentRequestEvent interface: calling changeShippingOption(DOMString) on new PaymentRequestEvent("type") with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
 FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "respondWith(Promise<PaymentHandlerResponse>)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
 FAIL PaymentRequestEvent interface: calling respondWith(Promise<PaymentHandlerResponse>) on new PaymentRequestEvent("type") with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
 PASS ServiceWorkerRegistration interface: attribute paymentManager
diff --git a/third_party/blink/web_tests/external/wpt/payment-handler/idlharness.https.any.sharedworker-expected.txt b/third_party/blink/web_tests/external/wpt/payment-handler/idlharness.https.any.sharedworker-expected.txt
index 2ee5817..b282125 100644
--- a/third_party/blink/web_tests/external/wpt/payment-handler/idlharness.https.any.sharedworker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/payment-handler/idlharness.https.any.sharedworker-expected.txt
@@ -16,7 +16,6 @@
 FAIL PaymentManager interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
 FAIL PaymentManager interface: attribute instruments assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
 FAIL PaymentManager interface: attribute userHint assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
-FAIL PaymentManager interface: operation enableDelegations(sequence<PaymentDelegation>) assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
 PASS PaymentInstruments interface: existence and properties of interface object
 PASS PaymentInstruments interface object length
 PASS PaymentInstruments interface object name
diff --git a/third_party/blink/web_tests/external/wpt/payment-handler/idlharness.https.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/payment-handler/idlharness.https.any.worker-expected.txt
index 2ee5817..b282125 100644
--- a/third_party/blink/web_tests/external/wpt/payment-handler/idlharness.https.any.worker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/payment-handler/idlharness.https.any.worker-expected.txt
@@ -16,7 +16,6 @@
 FAIL PaymentManager interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
 FAIL PaymentManager interface: attribute instruments assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
 FAIL PaymentManager interface: attribute userHint assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
-FAIL PaymentManager interface: operation enableDelegations(sequence<PaymentDelegation>) assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
 PASS PaymentInstruments interface: existence and properties of interface object
 PASS PaymentInstruments interface object length
 PASS PaymentInstruments interface object name
diff --git a/third_party/blink/web_tests/external/wpt/payment-request/idlharness.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/payment-request/idlharness.https.window-expected.txt
index dd488a1..df50505 100644
--- a/third_party/blink/web_tests/external/wpt/payment-request/idlharness.https.window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/payment-request/idlharness.https.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 91 tests; 90 PASS, 1 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 75 tests; 74 PASS, 1 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS idl_test validation
 PASS PaymentRequest interface: existence and properties of interface object
@@ -12,11 +12,6 @@
 PASS PaymentRequest interface: operation abort()
 PASS PaymentRequest interface: operation canMakePayment()
 PASS PaymentRequest interface: attribute id
-PASS PaymentRequest interface: attribute shippingAddress
-PASS PaymentRequest interface: attribute shippingOption
-PASS PaymentRequest interface: attribute shippingType
-PASS PaymentRequest interface: attribute onshippingaddresschange
-PASS PaymentRequest interface: attribute onshippingoptionchange
 PASS PaymentRequest interface: attribute onpaymentmethodchange
 PASS PaymentRequest must be primary interface of paymentRequest
 PASS Stringification of paymentRequest
@@ -25,29 +20,7 @@
 PASS PaymentRequest interface: paymentRequest must inherit property "abort()" with the proper type
 PASS PaymentRequest interface: paymentRequest must inherit property "canMakePayment()" with the proper type
 PASS PaymentRequest interface: paymentRequest must inherit property "id" with the proper type
-PASS PaymentRequest interface: paymentRequest must inherit property "shippingAddress" with the proper type
-PASS PaymentRequest interface: paymentRequest must inherit property "shippingOption" with the proper type
-PASS PaymentRequest interface: paymentRequest must inherit property "shippingType" with the proper type
-PASS PaymentRequest interface: paymentRequest must inherit property "onshippingaddresschange" with the proper type
-PASS PaymentRequest interface: paymentRequest must inherit property "onshippingoptionchange" with the proper type
 PASS PaymentRequest interface: paymentRequest must inherit property "onpaymentmethodchange" with the proper type
-PASS PaymentAddress interface: existence and properties of interface object
-PASS PaymentAddress interface object length
-PASS PaymentAddress interface object name
-PASS PaymentAddress interface: existence and properties of interface prototype object
-PASS PaymentAddress interface: existence and properties of interface prototype object's "constructor" property
-PASS PaymentAddress interface: existence and properties of interface prototype object's @@unscopables property
-PASS PaymentAddress interface: operation toJSON()
-PASS PaymentAddress interface: attribute city
-PASS PaymentAddress interface: attribute country
-PASS PaymentAddress interface: attribute dependentLocality
-PASS PaymentAddress interface: attribute organization
-PASS PaymentAddress interface: attribute phone
-PASS PaymentAddress interface: attribute postalCode
-PASS PaymentAddress interface: attribute recipient
-PASS PaymentAddress interface: attribute region
-PASS PaymentAddress interface: attribute sortingCode
-PASS PaymentAddress interface: attribute addressLine
 PASS PaymentResponse interface: existence and properties of interface object
 PASS PaymentResponse interface object length
 PASS PaymentResponse interface object name
@@ -58,14 +31,8 @@
 PASS PaymentResponse interface: attribute requestId
 PASS PaymentResponse interface: attribute methodName
 PASS PaymentResponse interface: attribute details
-PASS PaymentResponse interface: attribute shippingAddress
-PASS PaymentResponse interface: attribute shippingOption
-PASS PaymentResponse interface: attribute payerName
-PASS PaymentResponse interface: attribute payerEmail
-PASS PaymentResponse interface: attribute payerPhone
 PASS PaymentResponse interface: operation complete(optional PaymentComplete)
 PASS PaymentResponse interface: operation retry(optional PaymentValidationErrors)
-PASS PaymentResponse interface: attribute onpayerdetailchange
 PASS PaymentMethodChangeEvent interface: existence and properties of interface object
 PASS PaymentMethodChangeEvent interface object length
 PASS PaymentMethodChangeEvent interface object name
@@ -91,5 +58,22 @@
 PASS Stringification of new PaymentRequestUpdateEvent("paymentrequestupdate")
 PASS PaymentRequestUpdateEvent interface: new PaymentRequestUpdateEvent("paymentrequestupdate") must inherit property "updateWith(Promise<PaymentDetailsUpdate>)" with the proper type
 PASS PaymentRequestUpdateEvent interface: calling updateWith(Promise<PaymentDetailsUpdate>) on new PaymentRequestUpdateEvent("paymentrequestupdate") with too few arguments must throw TypeError
+PASS PaymentAddress interface: existence and properties of interface object
+PASS PaymentAddress interface object length
+PASS PaymentAddress interface object name
+PASS PaymentAddress interface: existence and properties of interface prototype object
+PASS PaymentAddress interface: existence and properties of interface prototype object's "constructor" property
+PASS PaymentAddress interface: existence and properties of interface prototype object's @@unscopables property
+PASS PaymentAddress interface: operation toJSON()
+PASS PaymentAddress interface: attribute city
+PASS PaymentAddress interface: attribute country
+PASS PaymentAddress interface: attribute dependentLocality
+PASS PaymentAddress interface: attribute organization
+PASS PaymentAddress interface: attribute phone
+PASS PaymentAddress interface: attribute postalCode
+PASS PaymentAddress interface: attribute recipient
+PASS PaymentAddress interface: attribute region
+PASS PaymentAddress interface: attribute sortingCode
+PASS PaymentAddress interface: attribute addressLine
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/private-click-measurement/idlharness.window-expected.txt b/third_party/blink/web_tests/external/wpt/private-click-measurement/idlharness.window-expected.txt
new file mode 100644
index 0000000..046f895
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/private-click-measurement/idlharness.window-expected.txt
@@ -0,0 +1,21 @@
+This is a testharness.js-based test.
+PASS idl_test setup
+PASS idl_test validation
+PASS Partial interface HTMLAnchorElement: original interface defined
+PASS Partial interface HTMLAnchorElement: member names are unique
+PASS Partial interface HTMLAnchorElement[2]: member names are unique
+PASS HTMLElement includes GlobalEventHandlers: member names are unique
+PASS HTMLElement includes DocumentAndElementEventHandlers: member names are unique
+PASS HTMLElement includes ElementContentEditable: member names are unique
+PASS HTMLElement includes HTMLOrSVGElement: member names are unique
+PASS HTMLAnchorElement includes HTMLHyperlinkElementUtils: member names are unique
+PASS Element includes ParentNode: member names are unique
+PASS Element includes NonDocumentTypeChildNode: member names are unique
+PASS Element includes ChildNode: member names are unique
+PASS Element includes Slottable: member names are unique
+FAIL HTMLAnchorElement interface: attribute attributionSourceId assert_true: The prototype object must have a property "attributionSourceId" expected true got false
+PASS HTMLAnchorElement interface: attribute attributionDestination
+FAIL HTMLAnchorElement interface: document.createElement("a") must inherit property "attributionSourceId" with the proper type assert_inherits: property "attributionSourceId" not found in prototype chain
+PASS HTMLAnchorElement interface: document.createElement("a") must inherit property "attributionDestination" with the proper type
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-mandatory-getStats.https-expected.txt b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-mandatory-getStats.https-expected.txt
index ce5ff0d0..010886b 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-mandatory-getStats.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-mandatory-getStats.https-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 77 tests; 70 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 75 tests; 70 PASS, 5 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS getStats succeeds
 PASS Validating stats
 PASS RTCRtpStreamStats's ssrc
@@ -49,7 +49,6 @@
 PASS RTCVideoSourceStats's framesPerSecond
 FAIL RTCMediaHandlerStats's trackIdentifier assert_true: Is trackIdentifier present expected true got false
 PASS RTCCodecStats's payloadType
-FAIL RTCCodecStats's codecType assert_true: Is codecType present expected true got false
 PASS RTCCodecStats's mimeType
 PASS RTCCodecStats's clockRate
 PASS RTCCodecStats's channels
@@ -76,6 +75,5 @@
 PASS RTCCertificateStats's fingerprint
 PASS RTCCertificateStats's fingerprintAlgorithm
 PASS RTCCertificateStats's base64Certificate
-FAIL RTCCertificateStats's issuerCertificateId assert_true: Is issuerCertificateId present expected true got false
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-setLocalDescription-pranswer-expected.txt b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-setLocalDescription-pranswer-expected.txt
index f514bf73..585d803 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-setLocalDescription-pranswer-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-setLocalDescription-pranswer-expected.txt
@@ -1,6 +1,6 @@
 This is a testharness.js-based test.
 FAIL setLocalDescription(pranswer) from stable state should reject with InvalidStateError promise_rejects_dom: function "function() { throw e }" threw object "OperationError: Failed to execute 'setLocalDescription' on 'RTCPeerConnection': Failed to set local pranswer sdp: Called in wrong state: stable" that is not a DOMException InvalidStateError: property "code" is equal to 0, expected 11
-FAIL setLocalDescription(pranswer) should succeed assert_equals: expected null but got object "[object RTCSessionDescription]"
+PASS setLocalDescription(pranswer) should succeed
 PASS setLocalDescription(pranswer) can be applied multiple times while still in have-local-pranswer
 PASS setLocalDescription(answer) from have-local-pranswer state should succeed
 Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/external/wpt/webxr/idlharness.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/webxr/idlharness.https.window-expected.txt
index bd78477..d7675f0 100644
--- a/third_party/blink/web_tests/external/wpt/webxr/idlharness.https.window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/webxr/idlharness.https.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 281 tests; 270 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 283 tests; 270 PASS, 13 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
 PASS idl_test validation
 PASS Partial interface Navigator: original interface defined
@@ -215,6 +215,7 @@
 PASS XRWebGLLayer interface: existence and properties of interface prototype object's @@unscopables property
 PASS XRWebGLLayer interface: attribute antialias
 PASS XRWebGLLayer interface: attribute ignoreDepthValues
+FAIL XRWebGLLayer interface: attribute fixedFoveation assert_true: The prototype object must have a property "fixedFoveation" expected true got false
 PASS XRWebGLLayer interface: attribute framebuffer
 PASS XRWebGLLayer interface: attribute framebufferWidth
 PASS XRWebGLLayer interface: attribute framebufferHeight
@@ -224,6 +225,7 @@
 PASS Stringification of xrWebGLLayer
 PASS XRWebGLLayer interface: xrWebGLLayer must inherit property "antialias" with the proper type
 PASS XRWebGLLayer interface: xrWebGLLayer must inherit property "ignoreDepthValues" with the proper type
+FAIL XRWebGLLayer interface: xrWebGLLayer must inherit property "fixedFoveation" with the proper type assert_inherits: property "fixedFoveation" not found in prototype chain
 PASS XRWebGLLayer interface: xrWebGLLayer must inherit property "framebuffer" with the proper type
 PASS XRWebGLLayer interface: xrWebGLLayer must inherit property "framebufferWidth" with the proper type
 PASS XRWebGLLayer interface: xrWebGLLayer must inherit property "framebufferHeight" with the proper type
diff --git a/third_party/blink/web_tests/external/wpt/xhr/formdata/constructor-formelement-expected.txt b/third_party/blink/web_tests/external/wpt/xhr/formdata/constructor-formelement-expected.txt
new file mode 100644
index 0000000..6d375ee
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/xhr/formdata/constructor-formelement-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL test that FormData is correctly constructed from the form data set assert_array_equals: submit-me-16 expected property 0 to be "textarea value\nwith linebreaks set to LF" but got "textarea value\r\nwith linebreaks set to LF" (expected array ["textarea value\nwith linebreaks set to LF"] got ["textarea value\r\nwith linebreaks set to LF"])
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-all-on-background-hw-expected.png b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-all-on-background-hw-expected.png
new file mode 100644
index 0000000..2dccb00
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-all-on-background-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-brightness-clamping-hw-expected.png b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-brightness-clamping-hw-expected.png
new file mode 100644
index 0000000..26e9b80
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-brightness-clamping-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-brightness-hw-expected.png b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-brightness-hw-expected.png
new file mode 100644
index 0000000..ce7e6a7
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-brightness-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-combined-hw-expected.png b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-combined-hw-expected.png
new file mode 100644
index 0000000..e8f25ee
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-combined-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-contrast-hw-expected.png b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-contrast-hw-expected.png
new file mode 100644
index 0000000..bb0f665
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-contrast-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-grayscale-hw-expected.png b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-grayscale-hw-expected.png
new file mode 100644
index 0000000..440bc23
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-grayscale-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-hue-rotate-hw-expected.png b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-hue-rotate-hw-expected.png
new file mode 100644
index 0000000..f2a8f3e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-hue-rotate-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-invert-hw-expected.png b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-invert-hw-expected.png
new file mode 100644
index 0000000..8933d61
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-invert-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-opacity-hw-expected.png b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-opacity-hw-expected.png
new file mode 100644
index 0000000..9b01b9e
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-opacity-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
index eb0f3a3..b8c6179 100644
--- a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-zoom-hw-expected.png b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-zoom-hw-expected.png
index 8c86a07..5b428dd 100644
--- a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-zoom-hw-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-zoom-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-saturate-hw-expected.png b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-saturate-hw-expected.png
new file mode 100644
index 0000000..7d5a881
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-saturate-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-sepia-hw-expected.png b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-sepia-hw-expected.png
new file mode 100644
index 0000000..e673696
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-use-zoom-for-dsf/platform/mac/virtual/scalefactor200/css3/filters/effect-sepia-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window-expected.txt
index 21d9d9d..9a71787 100644
--- a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters-a-area.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 413 tests; 253 PASS, 160 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 413 tests; 255 PASS, 158 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS <a>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
 PASS <area>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
@@ -388,9 +388,9 @@
 PASS <a>: Setting <https://example.net>.search = ''
 PASS <area>: Setting <https://example.net>.search = ''
 FAIL <a>: Setting <a:/>.search = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%09%0A%0D%1F !\"#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%1F !\"#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 FAIL <area>: Setting <a:/>.search = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%09%0A%0D%1F !\"#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%1F !\"#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 PASS <a>: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
 PASS <area>: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
 PASS <a>: Setting <https://example.net>.hash = 'main'
@@ -415,10 +415,10 @@
 PASS <area>: Setting <http://example.net>.hash = '#foo>bar'
 PASS <a>: Setting <http://example.net>.hash = '#foo`bar'
 PASS <area>: Setting <http://example.net>.hash = '#foo`bar'
-FAIL <a>: Setting <a:/>.hash = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
-FAIL <area>: Setting <a:/>.hash = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+PASS <a>: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed
+PASS <area>: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed
 PASS <a>: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS <area>: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS <a>: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any-expected.txt
index 326d569..92293f7 100644
--- a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 207 tests; 129 PASS, 78 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 207 tests; 130 PASS, 77 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
 PASS URL: Setting <a://example.net>.protocol = 'b'
@@ -195,7 +195,7 @@
 PASS URL: Setting <https://example.net?lang=en-US>.search = ''
 PASS URL: Setting <https://example.net>.search = ''
 FAIL URL: Setting <a:/>.search = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%09%0A%0D%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 PASS URL: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
 PASS URL: Setting <https://example.net>.hash = 'main'
 PASS URL: Setting <https://example.net#nav>.hash = 'main'
@@ -208,8 +208,8 @@
 PASS URL: Setting <http://example.net>.hash = '#foo<bar'
 PASS URL: Setting <http://example.net>.hash = '#foo>bar'
 PASS URL: Setting <http://example.net>.hash = '#foo`bar'
-FAIL URL: Setting <a:/>.hash = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+PASS URL: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed
 PASS URL: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS URL: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS URL: Setting <http://example.net>.hash = '%c3%89té' Bytes already percent-encoded are left as-is
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker-expected.txt
index 326d569..92293f7 100644
--- a/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/url/url-setters.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 207 tests; 129 PASS, 78 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 207 tests; 130 PASS, 77 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
 PASS URL: Setting <a://example.net>.protocol = 'b'
@@ -195,7 +195,7 @@
 PASS URL: Setting <https://example.net?lang=en-US>.search = ''
 PASS URL: Setting <https://example.net>.search = ''
 FAIL URL: Setting <a:/>.search = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%09%0A%0D%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 PASS URL: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
 PASS URL: Setting <https://example.net>.hash = 'main'
 PASS URL: Setting <https://example.net#nav>.hash = 'main'
@@ -208,8 +208,8 @@
 PASS URL: Setting <http://example.net>.hash = '#foo<bar'
 PASS URL: Setting <http://example.net>.hash = '#foo>bar'
 PASS URL: Setting <http://example.net>.hash = '#foo`bar'
-FAIL URL: Setting <a:/>.hash = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+PASS URL: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed
 PASS URL: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS URL: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS URL: Setting <http://example.net>.hash = '%c3%89té' Bytes already percent-encoded are left as-is
diff --git a/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/paint/invalidation/svg/animated-path-inside-transformed-html-expected.png b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/paint/invalidation/svg/animated-path-inside-transformed-html-expected.png
index 88b1a61..ca09d30e 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/paint/invalidation/svg/animated-path-inside-transformed-html-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/paint/invalidation/svg/animated-path-inside-transformed-html-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1-SE/text-tspan-02-b-expected.png b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1-SE/text-tspan-02-b-expected.png
index 3a75210..4c82445 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1-SE/text-tspan-02-b-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1-SE/text-tspan-02-b-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/animate-elem-40-t-expected.png b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/animate-elem-40-t-expected.png
index 18e4369..3ea3de64 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/animate-elem-40-t-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/animate-elem-40-t-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/metadata-example-01-b-expected.png b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/metadata-example-01-b-expected.png
index d8b4a1b..8f8dcef 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/metadata-example-01-b-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/metadata-example-01-b-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/text/text-gradient-positioning-expected.png b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/text/text-gradient-positioning-expected.png
index 20e2a7f2..c1f26b0 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/text/text-gradient-positioning-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/layout_ng_svg_text/svg/text/text-gradient-positioning-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/css/css-typed-om/idlharness-expected.txt b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/css/css-typed-om/idlharness-expected.txt
new file mode 100644
index 0000000..4b3bdb9
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/css/css-typed-om/idlharness-expected.txt
@@ -0,0 +1,514 @@
+This is a testharness.js-based test.
+Found 510 tests; 433 PASS, 77 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS idl_test setup
+PASS idl_test validation
+PASS Partial interface Element: original interface defined
+PASS Partial interface Element: member names are unique
+PASS Partial interface CSSStyleRule: original interface defined
+PASS Partial interface CSSStyleRule: member names are unique
+PASS Partial interface mixin ElementCSSInlineStyle: original interface mixin defined
+PASS Partial interface mixin ElementCSSInlineStyle: member names are unique
+PASS Partial namespace CSS: original namespace defined
+PASS Partial namespace CSS: member names are unique
+PASS HTMLElement includes ElementCSSInlineStyle: member names are unique
+PASS SVGElement includes ElementCSSInlineStyle: member names are unique
+PASS MathMLElement includes ElementCSSInlineStyle: member names are unique
+PASS SVGElement includes GlobalEventHandlers: member names are unique
+PASS SVGElement includes DocumentAndElementEventHandlers: member names are unique
+PASS SVGElement includes SVGElementInstance: member names are unique
+PASS SVGElement includes HTMLOrSVGElement: member names are unique
+PASS HTMLElement includes GlobalEventHandlers: member names are unique
+PASS HTMLElement includes DocumentAndElementEventHandlers: member names are unique
+PASS HTMLElement includes ElementContentEditable: member names are unique
+PASS HTMLElement includes HTMLOrSVGElement: member names are unique
+PASS Element includes ParentNode: member names are unique
+PASS Element includes NonDocumentTypeChildNode: member names are unique
+PASS Element includes ChildNode: member names are unique
+PASS Element includes Slottable: member names are unique
+PASS MathMLElement includes GlobalEventHandlers: member names are unique
+PASS MathMLElement includes DocumentAndElementEventHandlers: member names are unique
+PASS MathMLElement includes HTMLOrSVGElement: member names are unique
+PASS CSSStyleValue interface: existence and properties of interface object
+PASS CSSStyleValue interface object length
+PASS CSSStyleValue interface object name
+PASS CSSStyleValue interface: existence and properties of interface prototype object
+PASS CSSStyleValue interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSStyleValue interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSStyleValue interface: stringifier
+PASS CSSStyleValue interface: operation parse(USVString, USVString)
+PASS CSSStyleValue interface: operation parseAll(USVString, USVString)
+PASS StylePropertyMapReadOnly interface: existence and properties of interface object
+PASS StylePropertyMapReadOnly interface object length
+PASS StylePropertyMapReadOnly interface object name
+PASS StylePropertyMapReadOnly interface: existence and properties of interface prototype object
+PASS StylePropertyMapReadOnly interface: existence and properties of interface prototype object's "constructor" property
+PASS StylePropertyMapReadOnly interface: existence and properties of interface prototype object's @@unscopables property
+PASS StylePropertyMapReadOnly interface: iterable<USVString, [object Object]>
+PASS StylePropertyMapReadOnly interface: operation get(USVString)
+PASS StylePropertyMapReadOnly interface: operation getAll(USVString)
+PASS StylePropertyMapReadOnly interface: operation has(USVString)
+PASS StylePropertyMapReadOnly interface: attribute size
+PASS StylePropertyMap interface: existence and properties of interface object
+PASS StylePropertyMap interface object length
+PASS StylePropertyMap interface object name
+PASS StylePropertyMap interface: existence and properties of interface prototype object
+PASS StylePropertyMap interface: existence and properties of interface prototype object's "constructor" property
+PASS StylePropertyMap interface: existence and properties of interface prototype object's @@unscopables property
+PASS StylePropertyMap interface: operation set(USVString, (CSSStyleValue or USVString)...)
+PASS StylePropertyMap interface: operation append(USVString, (CSSStyleValue or USVString)...)
+PASS StylePropertyMap interface: operation delete(USVString)
+PASS StylePropertyMap interface: operation clear()
+PASS StylePropertyMap must be primary interface of styleMap
+PASS Stringification of styleMap
+PASS StylePropertyMap interface: styleMap must inherit property "set(USVString, (CSSStyleValue or USVString)...)" with the proper type
+PASS StylePropertyMap interface: calling set(USVString, (CSSStyleValue or USVString)...) on styleMap with too few arguments must throw TypeError
+PASS StylePropertyMap interface: styleMap must inherit property "append(USVString, (CSSStyleValue or USVString)...)" with the proper type
+PASS StylePropertyMap interface: calling append(USVString, (CSSStyleValue or USVString)...) on styleMap with too few arguments must throw TypeError
+PASS StylePropertyMap interface: styleMap must inherit property "delete(USVString)" with the proper type
+PASS StylePropertyMap interface: calling delete(USVString) on styleMap with too few arguments must throw TypeError
+PASS StylePropertyMap interface: styleMap must inherit property "clear()" with the proper type
+PASS StylePropertyMapReadOnly interface: styleMap must inherit property "get(USVString)" with the proper type
+PASS StylePropertyMapReadOnly interface: calling get(USVString) on styleMap with too few arguments must throw TypeError
+PASS StylePropertyMapReadOnly interface: styleMap must inherit property "getAll(USVString)" with the proper type
+PASS StylePropertyMapReadOnly interface: calling getAll(USVString) on styleMap with too few arguments must throw TypeError
+PASS StylePropertyMapReadOnly interface: styleMap must inherit property "has(USVString)" with the proper type
+PASS StylePropertyMapReadOnly interface: calling has(USVString) on styleMap with too few arguments must throw TypeError
+PASS StylePropertyMapReadOnly interface: styleMap must inherit property "size" with the proper type
+PASS CSSUnparsedValue interface: existence and properties of interface object
+PASS CSSUnparsedValue interface object length
+PASS CSSUnparsedValue interface object name
+PASS CSSUnparsedValue interface: existence and properties of interface prototype object
+PASS CSSUnparsedValue interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSUnparsedValue interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSUnparsedValue interface: iterable<CSSUnparsedSegment>
+PASS CSSUnparsedValue interface: attribute length
+PASS CSSVariableReferenceValue interface: existence and properties of interface object
+PASS CSSVariableReferenceValue interface object length
+PASS CSSVariableReferenceValue interface object name
+PASS CSSVariableReferenceValue interface: existence and properties of interface prototype object
+PASS CSSVariableReferenceValue interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSVariableReferenceValue interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSVariableReferenceValue interface: attribute variable
+PASS CSSVariableReferenceValue interface: attribute fallback
+PASS CSSKeywordValue interface: existence and properties of interface object
+PASS CSSKeywordValue interface object length
+PASS CSSKeywordValue interface object name
+PASS CSSKeywordValue interface: existence and properties of interface prototype object
+PASS CSSKeywordValue interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSKeywordValue interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSKeywordValue interface: attribute value
+PASS CSSNumericValue interface: existence and properties of interface object
+PASS CSSNumericValue interface object length
+PASS CSSNumericValue interface object name
+PASS CSSNumericValue interface: existence and properties of interface prototype object
+PASS CSSNumericValue interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSNumericValue interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSNumericValue interface: operation add(CSSNumberish...)
+PASS CSSNumericValue interface: operation sub(CSSNumberish...)
+PASS CSSNumericValue interface: operation mul(CSSNumberish...)
+PASS CSSNumericValue interface: operation div(CSSNumberish...)
+PASS CSSNumericValue interface: operation min(CSSNumberish...)
+PASS CSSNumericValue interface: operation max(CSSNumberish...)
+PASS CSSNumericValue interface: operation equals(CSSNumberish...)
+PASS CSSNumericValue interface: operation to(USVString)
+PASS CSSNumericValue interface: operation toSum(USVString...)
+PASS CSSNumericValue interface: operation type()
+PASS CSSNumericValue interface: operation parse(USVString)
+PASS CSSUnitValue interface: existence and properties of interface object
+PASS CSSUnitValue interface object length
+PASS CSSUnitValue interface object name
+PASS CSSUnitValue interface: existence and properties of interface prototype object
+PASS CSSUnitValue interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSUnitValue interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSUnitValue interface: attribute value
+PASS CSSUnitValue interface: attribute unit
+PASS CSSUnitValue must be primary interface of unitValue
+PASS Stringification of unitValue
+PASS CSSUnitValue interface: unitValue must inherit property "value" with the proper type
+PASS CSSUnitValue interface: unitValue must inherit property "unit" with the proper type
+PASS CSSNumericValue interface: unitValue must inherit property "add(CSSNumberish...)" with the proper type
+PASS CSSNumericValue interface: calling add(CSSNumberish...) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "sub(CSSNumberish...)" with the proper type
+PASS CSSNumericValue interface: calling sub(CSSNumberish...) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "mul(CSSNumberish...)" with the proper type
+PASS CSSNumericValue interface: calling mul(CSSNumberish...) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "div(CSSNumberish...)" with the proper type
+PASS CSSNumericValue interface: calling div(CSSNumberish...) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "min(CSSNumberish...)" with the proper type
+PASS CSSNumericValue interface: calling min(CSSNumberish...) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "max(CSSNumberish...)" with the proper type
+PASS CSSNumericValue interface: calling max(CSSNumberish...) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "equals(CSSNumberish...)" with the proper type
+PASS CSSNumericValue interface: calling equals(CSSNumberish...) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "to(USVString)" with the proper type
+PASS CSSNumericValue interface: calling to(USVString) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "toSum(USVString...)" with the proper type
+PASS CSSNumericValue interface: calling toSum(USVString...) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "type()" with the proper type
+PASS CSSNumericValue interface: unitValue must inherit property "parse(USVString)" with the proper type
+FAIL CSSNumericValue interface: calling parse(USVString) on unitValue with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parse" missing
+PASS CSSStyleValue interface: unitValue must inherit property "parse(USVString, USVString)" with the proper type
+FAIL CSSStyleValue interface: calling parse(USVString, USVString) on unitValue with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parse" missing
+PASS CSSStyleValue interface: unitValue must inherit property "parseAll(USVString, USVString)" with the proper type
+FAIL CSSStyleValue interface: calling parseAll(USVString, USVString) on unitValue with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parseAll" missing
+PASS CSSMathValue interface: existence and properties of interface object
+PASS CSSMathValue interface object length
+PASS CSSMathValue interface object name
+PASS CSSMathValue interface: existence and properties of interface prototype object
+PASS CSSMathValue interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSMathValue interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSMathValue interface: attribute operator
+PASS CSSMathSum interface: existence and properties of interface object
+PASS CSSMathSum interface object length
+PASS CSSMathSum interface object name
+PASS CSSMathSum interface: existence and properties of interface prototype object
+PASS CSSMathSum interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSMathSum interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSMathSum interface: attribute values
+PASS CSSMathSum must be primary interface of mathSum
+PASS Stringification of mathSum
+PASS CSSMathSum interface: mathSum must inherit property "values" with the proper type
+PASS CSSMathValue interface: mathSum must inherit property "operator" with the proper type
+PASS CSSNumericValue interface: mathSum must inherit property "add(CSSNumberish...)" with the proper type
+PASS CSSNumericValue interface: calling add(CSSNumberish...) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "sub(CSSNumberish...)" with the proper type
+PASS CSSNumericValue interface: calling sub(CSSNumberish...) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "mul(CSSNumberish...)" with the proper type
+PASS CSSNumericValue interface: calling mul(CSSNumberish...) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "div(CSSNumberish...)" with the proper type
+PASS CSSNumericValue interface: calling div(CSSNumberish...) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "min(CSSNumberish...)" with the proper type
+PASS CSSNumericValue interface: calling min(CSSNumberish...) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "max(CSSNumberish...)" with the proper type
+PASS CSSNumericValue interface: calling max(CSSNumberish...) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "equals(CSSNumberish...)" with the proper type
+PASS CSSNumericValue interface: calling equals(CSSNumberish...) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "to(USVString)" with the proper type
+PASS CSSNumericValue interface: calling to(USVString) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "toSum(USVString...)" with the proper type
+PASS CSSNumericValue interface: calling toSum(USVString...) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "type()" with the proper type
+PASS CSSNumericValue interface: mathSum must inherit property "parse(USVString)" with the proper type
+FAIL CSSNumericValue interface: calling parse(USVString) on mathSum with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parse" missing
+PASS CSSStyleValue interface: mathSum must inherit property "parse(USVString, USVString)" with the proper type
+FAIL CSSStyleValue interface: calling parse(USVString, USVString) on mathSum with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parse" missing
+PASS CSSStyleValue interface: mathSum must inherit property "parseAll(USVString, USVString)" with the proper type
+FAIL CSSStyleValue interface: calling parseAll(USVString, USVString) on mathSum with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parseAll" missing
+PASS CSSMathProduct interface: existence and properties of interface object
+PASS CSSMathProduct interface object length
+PASS CSSMathProduct interface object name
+PASS CSSMathProduct interface: existence and properties of interface prototype object
+PASS CSSMathProduct interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSMathProduct interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSMathProduct interface: attribute values
+PASS CSSMathNegate interface: existence and properties of interface object
+PASS CSSMathNegate interface object length
+PASS CSSMathNegate interface object name
+PASS CSSMathNegate interface: existence and properties of interface prototype object
+PASS CSSMathNegate interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSMathNegate interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSMathNegate interface: attribute value
+PASS CSSMathInvert interface: existence and properties of interface object
+PASS CSSMathInvert interface object length
+PASS CSSMathInvert interface object name
+PASS CSSMathInvert interface: existence and properties of interface prototype object
+PASS CSSMathInvert interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSMathInvert interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSMathInvert interface: attribute value
+PASS CSSMathMin interface: existence and properties of interface object
+PASS CSSMathMin interface object length
+PASS CSSMathMin interface object name
+PASS CSSMathMin interface: existence and properties of interface prototype object
+PASS CSSMathMin interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSMathMin interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSMathMin interface: attribute values
+PASS CSSMathMax interface: existence and properties of interface object
+PASS CSSMathMax interface object length
+PASS CSSMathMax interface object name
+PASS CSSMathMax interface: existence and properties of interface prototype object
+PASS CSSMathMax interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSMathMax interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSMathMax interface: attribute values
+FAIL CSSMathClamp interface: existence and properties of interface object assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface object length assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface object name assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface: attribute min assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface: attribute val assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface: attribute max assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+PASS CSSNumericArray interface: existence and properties of interface object
+PASS CSSNumericArray interface object length
+PASS CSSNumericArray interface object name
+PASS CSSNumericArray interface: existence and properties of interface prototype object
+PASS CSSNumericArray interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSNumericArray interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSNumericArray interface: iterable<CSSNumericValue>
+PASS CSSNumericArray interface: attribute length
+PASS CSSTransformValue interface: existence and properties of interface object
+PASS CSSTransformValue interface object length
+PASS CSSTransformValue interface object name
+PASS CSSTransformValue interface: existence and properties of interface prototype object
+PASS CSSTransformValue interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSTransformValue interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSTransformValue interface: iterable<CSSTransformComponent>
+PASS CSSTransformValue interface: attribute length
+PASS CSSTransformValue interface: attribute is2D
+PASS CSSTransformValue interface: operation toMatrix()
+PASS CSSTransformValue must be primary interface of transformValue
+PASS Stringification of transformValue
+PASS CSSTransformValue interface: transformValue must inherit property "length" with the proper type
+PASS CSSTransformValue interface: transformValue must inherit property "is2D" with the proper type
+PASS CSSTransformValue interface: transformValue must inherit property "toMatrix()" with the proper type
+PASS CSSStyleValue interface: transformValue must inherit property "parse(USVString, USVString)" with the proper type
+FAIL CSSStyleValue interface: calling parse(USVString, USVString) on transformValue with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parse" missing
+PASS CSSStyleValue interface: transformValue must inherit property "parseAll(USVString, USVString)" with the proper type
+FAIL CSSStyleValue interface: calling parseAll(USVString, USVString) on transformValue with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parseAll" missing
+PASS CSSTransformComponent interface: existence and properties of interface object
+PASS CSSTransformComponent interface object length
+PASS CSSTransformComponent interface object name
+PASS CSSTransformComponent interface: existence and properties of interface prototype object
+PASS CSSTransformComponent interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSTransformComponent interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSTransformComponent interface: stringifier
+PASS CSSTransformComponent interface: attribute is2D
+PASS CSSTransformComponent interface: operation toMatrix()
+PASS CSSTranslate interface: existence and properties of interface object
+PASS CSSTranslate interface object length
+PASS CSSTranslate interface object name
+PASS CSSTranslate interface: existence and properties of interface prototype object
+PASS CSSTranslate interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSTranslate interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSTranslate interface: attribute x
+PASS CSSTranslate interface: attribute y
+PASS CSSTranslate interface: attribute z
+PASS CSSTranslate must be primary interface of transformValue[0]
+PASS Stringification of transformValue[0]
+PASS CSSTranslate interface: transformValue[0] must inherit property "x" with the proper type
+PASS CSSTranslate interface: transformValue[0] must inherit property "y" with the proper type
+PASS CSSTranslate interface: transformValue[0] must inherit property "z" with the proper type
+PASS CSSTransformComponent interface: transformValue[0] must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: transformValue[0] must inherit property "toMatrix()" with the proper type
+PASS CSSRotate interface: existence and properties of interface object
+PASS CSSRotate interface object length
+PASS CSSRotate interface object name
+PASS CSSRotate interface: existence and properties of interface prototype object
+PASS CSSRotate interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSRotate interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSRotate interface: attribute x
+PASS CSSRotate interface: attribute y
+PASS CSSRotate interface: attribute z
+PASS CSSRotate interface: attribute angle
+PASS CSSRotate must be primary interface of rotate
+PASS Stringification of rotate
+PASS CSSRotate interface: rotate must inherit property "x" with the proper type
+PASS CSSRotate interface: rotate must inherit property "y" with the proper type
+PASS CSSRotate interface: rotate must inherit property "z" with the proper type
+PASS CSSRotate interface: rotate must inherit property "angle" with the proper type
+PASS CSSTransformComponent interface: rotate must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: rotate must inherit property "toMatrix()" with the proper type
+PASS CSSScale interface: existence and properties of interface object
+PASS CSSScale interface object length
+PASS CSSScale interface object name
+PASS CSSScale interface: existence and properties of interface prototype object
+PASS CSSScale interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSScale interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSScale interface: attribute x
+PASS CSSScale interface: attribute y
+PASS CSSScale interface: attribute z
+PASS CSSScale must be primary interface of scale
+PASS Stringification of scale
+PASS CSSScale interface: scale must inherit property "x" with the proper type
+PASS CSSScale interface: scale must inherit property "y" with the proper type
+PASS CSSScale interface: scale must inherit property "z" with the proper type
+PASS CSSTransformComponent interface: scale must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: scale must inherit property "toMatrix()" with the proper type
+PASS CSSSkew interface: existence and properties of interface object
+PASS CSSSkew interface object length
+PASS CSSSkew interface object name
+PASS CSSSkew interface: existence and properties of interface prototype object
+PASS CSSSkew interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSSkew interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSSkew interface: attribute ax
+PASS CSSSkew interface: attribute ay
+PASS CSSSkew must be primary interface of skew
+PASS Stringification of skew
+PASS CSSSkew interface: skew must inherit property "ax" with the proper type
+PASS CSSSkew interface: skew must inherit property "ay" with the proper type
+PASS CSSTransformComponent interface: skew must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: skew must inherit property "toMatrix()" with the proper type
+PASS CSSSkewX interface: existence and properties of interface object
+PASS CSSSkewX interface object length
+PASS CSSSkewX interface object name
+PASS CSSSkewX interface: existence and properties of interface prototype object
+PASS CSSSkewX interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSSkewX interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSSkewX interface: attribute ax
+PASS CSSSkewX must be primary interface of skewX
+PASS Stringification of skewX
+PASS CSSSkewX interface: skewX must inherit property "ax" with the proper type
+PASS CSSTransformComponent interface: skewX must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: skewX must inherit property "toMatrix()" with the proper type
+PASS CSSSkewY interface: existence and properties of interface object
+PASS CSSSkewY interface object length
+PASS CSSSkewY interface object name
+PASS CSSSkewY interface: existence and properties of interface prototype object
+PASS CSSSkewY interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSSkewY interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSSkewY interface: attribute ay
+PASS CSSSkewY must be primary interface of skewY
+PASS Stringification of skewY
+PASS CSSSkewY interface: skewY must inherit property "ay" with the proper type
+PASS CSSTransformComponent interface: skewY must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: skewY must inherit property "toMatrix()" with the proper type
+PASS CSSPerspective interface: existence and properties of interface object
+PASS CSSPerspective interface object length
+PASS CSSPerspective interface object name
+PASS CSSPerspective interface: existence and properties of interface prototype object
+PASS CSSPerspective interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSPerspective interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSPerspective interface: attribute length
+PASS CSSPerspective must be primary interface of perspective
+PASS Stringification of perspective
+PASS CSSPerspective interface: perspective must inherit property "length" with the proper type
+PASS CSSTransformComponent interface: perspective must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: perspective must inherit property "toMatrix()" with the proper type
+PASS CSSMatrixComponent interface: existence and properties of interface object
+PASS CSSMatrixComponent interface object length
+PASS CSSMatrixComponent interface object name
+PASS CSSMatrixComponent interface: existence and properties of interface prototype object
+PASS CSSMatrixComponent interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSMatrixComponent interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSMatrixComponent interface: attribute matrix
+PASS CSSMatrixComponent must be primary interface of matrix
+PASS Stringification of matrix
+PASS CSSMatrixComponent interface: matrix must inherit property "matrix" with the proper type
+PASS CSSTransformComponent interface: matrix must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: matrix must inherit property "toMatrix()" with the proper type
+PASS CSSImageValue interface: existence and properties of interface object
+PASS CSSImageValue interface object length
+PASS CSSImageValue interface object name
+PASS CSSImageValue interface: existence and properties of interface prototype object
+PASS CSSImageValue interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSImageValue interface: existence and properties of interface prototype object's @@unscopables property
+FAIL CSSColorValue interface: existence and properties of interface object assert_equals: prototype of CSSColorValue is not CSSStyleValue expected function "function CSSStyleValue() { [native code] }" but got function "function () { [native code] }"
+PASS CSSColorValue interface object length
+PASS CSSColorValue interface object name
+FAIL CSSColorValue interface: existence and properties of interface prototype object assert_equals: prototype of CSSColorValue.prototype is not CSSStyleValue.prototype expected [stringifying object threw TypeError: Illegal invocation with type object] but got object "[object Object]"
+PASS CSSColorValue interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSColorValue interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSColorValue interface: operation toRGB()
+PASS CSSColorValue interface: operation toHSL()
+FAIL CSSColorValue interface: operation toHWB() assert_own_property: interface prototype object missing non-static operation expected property "toHWB" missing
+FAIL CSSColorValue interface: operation toGray() assert_own_property: interface prototype object missing non-static operation expected property "toGray" missing
+FAIL CSSColorValue interface: operation toLCH() assert_own_property: interface prototype object missing non-static operation expected property "toLCH" missing
+FAIL CSSColorValue interface: operation toLab() assert_own_property: interface prototype object missing non-static operation expected property "toLab" missing
+FAIL CSSColorValue interface: operation toColor(CSSKeywordish) assert_own_property: interface prototype object missing non-static operation expected property "toColor" missing
+FAIL CSSColorValue interface: operation parse(USVString) assert_own_property: interface object missing static operation expected property "parse" missing
+PASS CSSRGB interface: existence and properties of interface object
+PASS CSSRGB interface object length
+PASS CSSRGB interface object name
+PASS CSSRGB interface: existence and properties of interface prototype object
+PASS CSSRGB interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSRGB interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSRGB interface: attribute r
+PASS CSSRGB interface: attribute g
+PASS CSSRGB interface: attribute b
+PASS CSSRGB interface: attribute alpha
+PASS CSSHSL interface: existence and properties of interface object
+PASS CSSHSL interface object length
+PASS CSSHSL interface object name
+PASS CSSHSL interface: existence and properties of interface prototype object
+PASS CSSHSL interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSHSL interface: existence and properties of interface prototype object's @@unscopables property
+PASS CSSHSL interface: attribute h
+PASS CSSHSL interface: attribute s
+PASS CSSHSL interface: attribute l
+PASS CSSHSL interface: attribute alpha
+FAIL CSSHWB interface: existence and properties of interface object assert_own_property: self does not have own property "CSSHWB" expected property "CSSHWB" missing
+FAIL CSSHWB interface object length assert_own_property: self does not have own property "CSSHWB" expected property "CSSHWB" missing
+FAIL CSSHWB interface object name assert_own_property: self does not have own property "CSSHWB" expected property "CSSHWB" missing
+FAIL CSSHWB interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CSSHWB" expected property "CSSHWB" missing
+FAIL CSSHWB interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CSSHWB" expected property "CSSHWB" missing
+FAIL CSSHWB interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CSSHWB" expected property "CSSHWB" missing
+FAIL CSSHWB interface: attribute h assert_own_property: self does not have own property "CSSHWB" expected property "CSSHWB" missing
+FAIL CSSHWB interface: attribute w assert_own_property: self does not have own property "CSSHWB" expected property "CSSHWB" missing
+FAIL CSSHWB interface: attribute b assert_own_property: self does not have own property "CSSHWB" expected property "CSSHWB" missing
+FAIL CSSHWB interface: attribute alpha assert_own_property: self does not have own property "CSSHWB" expected property "CSSHWB" missing
+FAIL CSSGray interface: existence and properties of interface object assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
+FAIL CSSGray interface object length assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
+FAIL CSSGray interface object name assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
+FAIL CSSGray interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
+FAIL CSSGray interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
+FAIL CSSGray interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
+FAIL CSSGray interface: attribute gray assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
+FAIL CSSGray interface: attribute alpha assert_own_property: self does not have own property "CSSGray" expected property "CSSGray" missing
+FAIL CSSLCH interface: existence and properties of interface object assert_own_property: self does not have own property "CSSLCH" expected property "CSSLCH" missing
+FAIL CSSLCH interface object length assert_own_property: self does not have own property "CSSLCH" expected property "CSSLCH" missing
+FAIL CSSLCH interface object name assert_own_property: self does not have own property "CSSLCH" expected property "CSSLCH" missing
+FAIL CSSLCH interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CSSLCH" expected property "CSSLCH" missing
+FAIL CSSLCH interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CSSLCH" expected property "CSSLCH" missing
+FAIL CSSLCH interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CSSLCH" expected property "CSSLCH" missing
+FAIL CSSLCH interface: attribute l assert_own_property: self does not have own property "CSSLCH" expected property "CSSLCH" missing
+FAIL CSSLCH interface: attribute c assert_own_property: self does not have own property "CSSLCH" expected property "CSSLCH" missing
+FAIL CSSLCH interface: attribute h assert_own_property: self does not have own property "CSSLCH" expected property "CSSLCH" missing
+FAIL CSSLCH interface: attribute alpha assert_own_property: self does not have own property "CSSLCH" expected property "CSSLCH" missing
+FAIL CSSLab interface: existence and properties of interface object assert_own_property: self does not have own property "CSSLab" expected property "CSSLab" missing
+FAIL CSSLab interface object length assert_own_property: self does not have own property "CSSLab" expected property "CSSLab" missing
+FAIL CSSLab interface object name assert_own_property: self does not have own property "CSSLab" expected property "CSSLab" missing
+FAIL CSSLab interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CSSLab" expected property "CSSLab" missing
+FAIL CSSLab interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CSSLab" expected property "CSSLab" missing
+FAIL CSSLab interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CSSLab" expected property "CSSLab" missing
+FAIL CSSLab interface: attribute l assert_own_property: self does not have own property "CSSLab" expected property "CSSLab" missing
+FAIL CSSLab interface: attribute a assert_own_property: self does not have own property "CSSLab" expected property "CSSLab" missing
+FAIL CSSLab interface: attribute b assert_own_property: self does not have own property "CSSLab" expected property "CSSLab" missing
+FAIL CSSLab interface: attribute alpha assert_own_property: self does not have own property "CSSLab" expected property "CSSLab" missing
+FAIL CSSColor interface: existence and properties of interface object assert_own_property: self does not have own property "CSSColor" expected property "CSSColor" missing
+FAIL CSSColor interface object length assert_own_property: self does not have own property "CSSColor" expected property "CSSColor" missing
+FAIL CSSColor interface object name assert_own_property: self does not have own property "CSSColor" expected property "CSSColor" missing
+FAIL CSSColor interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CSSColor" expected property "CSSColor" missing
+FAIL CSSColor interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CSSColor" expected property "CSSColor" missing
+FAIL CSSColor interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CSSColor" expected property "CSSColor" missing
+PASS CSSStyleRule interface: attribute styleMap
+PASS CSS namespace: operation escape(CSSOMString)
+PASS CSS namespace: operation number(double)
+PASS CSS namespace: operation percent(double)
+PASS CSS namespace: operation em(double)
+PASS CSS namespace: operation ex(double)
+PASS CSS namespace: operation ch(double)
+FAIL CSS namespace: operation ic(double) assert_own_property: namespace object missing operation "ic" expected property "ic" missing
+PASS CSS namespace: operation rem(double)
+FAIL CSS namespace: operation lh(double) assert_own_property: namespace object missing operation "lh" expected property "lh" missing
+FAIL CSS namespace: operation rlh(double) assert_own_property: namespace object missing operation "rlh" expected property "rlh" missing
+PASS CSS namespace: operation vw(double)
+PASS CSS namespace: operation vh(double)
+FAIL CSS namespace: operation vi(double) assert_own_property: namespace object missing operation "vi" expected property "vi" missing
+FAIL CSS namespace: operation vb(double) assert_own_property: namespace object missing operation "vb" expected property "vb" missing
+PASS CSS namespace: operation vmin(double)
+PASS CSS namespace: operation vmax(double)
+PASS CSS namespace: operation cm(double)
+PASS CSS namespace: operation mm(double)
+PASS CSS namespace: operation Q(double)
+PASS CSS namespace: operation in(double)
+PASS CSS namespace: operation pt(double)
+PASS CSS namespace: operation pc(double)
+PASS CSS namespace: operation px(double)
+PASS CSS namespace: operation deg(double)
+PASS CSS namespace: operation grad(double)
+PASS CSS namespace: operation rad(double)
+PASS CSS namespace: operation turn(double)
+PASS CSS namespace: operation s(double)
+PASS CSS namespace: operation ms(double)
+PASS CSS namespace: operation Hz(double)
+PASS CSS namespace: operation kHz(double)
+PASS CSS namespace: operation dpi(double)
+PASS CSS namespace: operation dpcm(double)
+PASS CSS namespace: operation dppx(double)
+PASS CSS namespace: operation fr(double)
+FAIL SVGElement interface: attribute attributeStyleMap assert_own_property: expected property "attributeStyleMap" missing
+FAIL HTMLElement interface: attribute attributeStyleMap assert_own_property: expected property "attributeStyleMap" missing
+PASS Element interface: operation computedStyleMap()
+FAIL MathMLElement interface: attribute attributeStyleMap assert_own_property: expected property "attributeStyleMap" missing
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/payment-handler/idlharness.https.any.serviceworker-expected.txt b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/payment-handler/idlharness.https.any.serviceworker-expected.txt
new file mode 100644
index 0000000..0c26f729
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/payment-handler/idlharness.https.any.serviceworker-expected.txt
@@ -0,0 +1,118 @@
+This is a testharness.js-based test.
+Found 114 tests; 71 PASS, 43 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS idl_test setup
+PASS idl_test validation
+PASS Partial interface ServiceWorkerRegistration: original interface defined
+PASS Partial interface ServiceWorkerRegistration: member names are unique
+PASS Partial interface ServiceWorkerGlobalScope: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope: member names are unique
+PASS Partial interface ServiceWorkerGlobalScope[2]: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope[2]: member names are unique
+PASS WorkerGlobalScope includes WindowOrWorkerGlobalScope: member names are unique
+FAIL PaymentManager interface: existence and properties of interface object assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface object length assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface object name assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: existence and properties of interface prototype object assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: attribute instruments assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: attribute userHint assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: operation enableDelegations(sequence<PaymentDelegation>) assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager must be primary interface of paymentManager assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+PASS Stringification of paymentManager
+PASS PaymentManager interface: paymentManager must inherit property "instruments" with the proper type
+PASS PaymentManager interface: paymentManager must inherit property "userHint" with the proper type
+PASS PaymentManager interface: paymentManager must inherit property "enableDelegations(sequence<PaymentDelegation>)" with the proper type
+PASS PaymentManager interface: calling enableDelegations(sequence<PaymentDelegation>) on paymentManager with too few arguments must throw TypeError
+PASS PaymentInstruments interface: existence and properties of interface object
+PASS PaymentInstruments interface object length
+PASS PaymentInstruments interface object name
+PASS PaymentInstruments interface: existence and properties of interface prototype object
+PASS PaymentInstruments interface: existence and properties of interface prototype object's "constructor" property
+PASS PaymentInstruments interface: existence and properties of interface prototype object's @@unscopables property
+PASS PaymentInstruments interface: operation delete(DOMString)
+PASS PaymentInstruments interface: operation get(DOMString)
+PASS PaymentInstruments interface: operation keys()
+PASS PaymentInstruments interface: operation has(DOMString)
+PASS PaymentInstruments interface: operation set(DOMString, PaymentInstrument)
+PASS PaymentInstruments interface: operation clear()
+PASS PaymentInstruments must be primary interface of instruments
+PASS Stringification of instruments
+PASS PaymentInstruments interface: instruments must inherit property "delete(DOMString)" with the proper type
+PASS PaymentInstruments interface: calling delete(DOMString) on instruments with too few arguments must throw TypeError
+PASS PaymentInstruments interface: instruments must inherit property "get(DOMString)" with the proper type
+PASS PaymentInstruments interface: calling get(DOMString) on instruments with too few arguments must throw TypeError
+PASS PaymentInstruments interface: instruments must inherit property "keys()" with the proper type
+PASS PaymentInstruments interface: instruments must inherit property "has(DOMString)" with the proper type
+PASS PaymentInstruments interface: calling has(DOMString) on instruments with too few arguments must throw TypeError
+PASS PaymentInstruments interface: instruments must inherit property "set(DOMString, PaymentInstrument)" with the proper type
+PASS PaymentInstruments interface: calling set(DOMString, PaymentInstrument) on instruments with too few arguments must throw TypeError
+PASS PaymentInstruments interface: instruments must inherit property "clear()" with the proper type
+PASS CanMakePaymentEvent interface: existence and properties of interface object
+FAIL CanMakePaymentEvent interface object length assert_equals: wrong value for CanMakePaymentEvent.length expected 1 but got 2
+PASS CanMakePaymentEvent interface object name
+PASS CanMakePaymentEvent interface: existence and properties of interface prototype object
+PASS CanMakePaymentEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS CanMakePaymentEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS CanMakePaymentEvent interface: attribute topOrigin
+PASS CanMakePaymentEvent interface: attribute paymentRequestOrigin
+PASS CanMakePaymentEvent interface: attribute methodData
+PASS CanMakePaymentEvent interface: operation respondWith(Promise<boolean>)
+FAIL CanMakePaymentEvent must be primary interface of new CanMakePaymentEvent("type") assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'CanMakePaymentEvent': 2 arguments required, but only 1 present."
+FAIL Stringification of new CanMakePaymentEvent("type") assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'CanMakePaymentEvent': 2 arguments required, but only 1 present."
+FAIL CanMakePaymentEvent interface: new CanMakePaymentEvent("type") must inherit property "topOrigin" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'CanMakePaymentEvent': 2 arguments required, but only 1 present."
+FAIL CanMakePaymentEvent interface: new CanMakePaymentEvent("type") must inherit property "paymentRequestOrigin" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'CanMakePaymentEvent': 2 arguments required, but only 1 present."
+FAIL CanMakePaymentEvent interface: new CanMakePaymentEvent("type") must inherit property "methodData" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'CanMakePaymentEvent': 2 arguments required, but only 1 present."
+FAIL CanMakePaymentEvent interface: new CanMakePaymentEvent("type") must inherit property "respondWith(Promise<boolean>)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'CanMakePaymentEvent': 2 arguments required, but only 1 present."
+FAIL CanMakePaymentEvent interface: calling respondWith(Promise<boolean>) on new CanMakePaymentEvent("type") with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'CanMakePaymentEvent': 2 arguments required, but only 1 present."
+PASS PaymentRequestEvent interface: existence and properties of interface object
+FAIL PaymentRequestEvent interface object length assert_equals: wrong value for PaymentRequestEvent.length expected 1 but got 2
+PASS PaymentRequestEvent interface object name
+PASS PaymentRequestEvent interface: existence and properties of interface prototype object
+PASS PaymentRequestEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS PaymentRequestEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS PaymentRequestEvent interface: attribute topOrigin
+PASS PaymentRequestEvent interface: attribute paymentRequestOrigin
+PASS PaymentRequestEvent interface: attribute paymentRequestId
+PASS PaymentRequestEvent interface: attribute methodData
+PASS PaymentRequestEvent interface: attribute total
+PASS PaymentRequestEvent interface: attribute modifiers
+PASS PaymentRequestEvent interface: attribute instrumentKey
+FAIL PaymentRequestEvent interface: attribute requestBillingAddress assert_true: The prototype object must have a property "requestBillingAddress" expected true got false
+PASS PaymentRequestEvent interface: attribute paymentOptions
+PASS PaymentRequestEvent interface: attribute shippingOptions
+PASS PaymentRequestEvent interface: operation openWindow(USVString)
+PASS PaymentRequestEvent interface: operation changePaymentMethod(DOMString, optional object?)
+FAIL PaymentRequestEvent interface: operation changeShippingAddress(optional AddressInit) assert_equals: property has wrong .length expected 0 but got 1
+PASS PaymentRequestEvent interface: operation changeShippingOption(DOMString)
+PASS PaymentRequestEvent interface: operation respondWith(Promise<PaymentHandlerResponse>)
+FAIL PaymentRequestEvent must be primary interface of new PaymentRequestEvent("type") assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL Stringification of new PaymentRequestEvent("type") assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "topOrigin" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "paymentRequestOrigin" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "paymentRequestId" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "methodData" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "total" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "modifiers" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "instrumentKey" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "requestBillingAddress" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "paymentOptions" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "shippingOptions" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "openWindow(USVString)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: calling openWindow(USVString) on new PaymentRequestEvent("type") with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "changePaymentMethod(DOMString, optional object?)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: calling changePaymentMethod(DOMString, optional object?) on new PaymentRequestEvent("type") with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "changeShippingAddress(optional AddressInit)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: calling changeShippingAddress(optional AddressInit) on new PaymentRequestEvent("type") with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "changeShippingOption(DOMString)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: calling changeShippingOption(DOMString) on new PaymentRequestEvent("type") with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: new PaymentRequestEvent("type") must inherit property "respondWith(Promise<PaymentHandlerResponse>)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+FAIL PaymentRequestEvent interface: calling respondWith(Promise<PaymentHandlerResponse>) on new PaymentRequestEvent("type") with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "TypeError: Failed to construct 'PaymentRequestEvent': 2 arguments required, but only 1 present."
+PASS ServiceWorkerRegistration interface: attribute paymentManager
+PASS ServiceWorkerRegistration interface: registration must inherit property "paymentManager" with the proper type
+PASS ServiceWorkerGlobalScope interface: attribute oncanmakepayment
+PASS ServiceWorkerGlobalScope interface: attribute onpaymentrequest
+PASS ServiceWorkerGlobalScope interface: self must inherit property "oncanmakepayment" with the proper type
+PASS ServiceWorkerGlobalScope interface: self must inherit property "onpaymentrequest" with the proper type
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/payment-handler/idlharness.https.any.sharedworker-expected.txt b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/payment-handler/idlharness.https.any.sharedworker-expected.txt
new file mode 100644
index 0000000..2ee5817
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/payment-handler/idlharness.https.any.sharedworker-expected.txt
@@ -0,0 +1,36 @@
+This is a testharness.js-based test.
+PASS idl_test setup
+PASS idl_test validation
+PASS Partial interface ServiceWorkerRegistration: original interface defined
+PASS Partial interface ServiceWorkerRegistration: member names are unique
+PASS Partial interface ServiceWorkerGlobalScope: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope: member names are unique
+PASS Partial interface ServiceWorkerGlobalScope[2]: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope[2]: member names are unique
+PASS WorkerGlobalScope includes WindowOrWorkerGlobalScope: member names are unique
+FAIL PaymentManager interface: existence and properties of interface object assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface object length assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface object name assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: existence and properties of interface prototype object assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: attribute instruments assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: attribute userHint assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: operation enableDelegations(sequence<PaymentDelegation>) assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+PASS PaymentInstruments interface: existence and properties of interface object
+PASS PaymentInstruments interface object length
+PASS PaymentInstruments interface object name
+PASS PaymentInstruments interface: existence and properties of interface prototype object
+PASS PaymentInstruments interface: existence and properties of interface prototype object's "constructor" property
+PASS PaymentInstruments interface: existence and properties of interface prototype object's @@unscopables property
+PASS PaymentInstruments interface: operation delete(DOMString)
+PASS PaymentInstruments interface: operation get(DOMString)
+PASS PaymentInstruments interface: operation keys()
+PASS PaymentInstruments interface: operation has(DOMString)
+PASS PaymentInstruments interface: operation set(DOMString, PaymentInstrument)
+PASS PaymentInstruments interface: operation clear()
+PASS CanMakePaymentEvent interface: existence and properties of interface object
+PASS PaymentRequestEvent interface: existence and properties of interface object
+PASS ServiceWorkerRegistration interface: attribute paymentManager
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/payment-handler/idlharness.https.any.worker-expected.txt b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/payment-handler/idlharness.https.any.worker-expected.txt
new file mode 100644
index 0000000..2ee5817
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/payment-handler/idlharness.https.any.worker-expected.txt
@@ -0,0 +1,36 @@
+This is a testharness.js-based test.
+PASS idl_test setup
+PASS idl_test validation
+PASS Partial interface ServiceWorkerRegistration: original interface defined
+PASS Partial interface ServiceWorkerRegistration: member names are unique
+PASS Partial interface ServiceWorkerGlobalScope: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope: member names are unique
+PASS Partial interface ServiceWorkerGlobalScope[2]: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope[2]: member names are unique
+PASS WorkerGlobalScope includes WindowOrWorkerGlobalScope: member names are unique
+FAIL PaymentManager interface: existence and properties of interface object assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface object length assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface object name assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: existence and properties of interface prototype object assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: attribute instruments assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: attribute userHint assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: operation enableDelegations(sequence<PaymentDelegation>) assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+PASS PaymentInstruments interface: existence and properties of interface object
+PASS PaymentInstruments interface object length
+PASS PaymentInstruments interface object name
+PASS PaymentInstruments interface: existence and properties of interface prototype object
+PASS PaymentInstruments interface: existence and properties of interface prototype object's "constructor" property
+PASS PaymentInstruments interface: existence and properties of interface prototype object's @@unscopables property
+PASS PaymentInstruments interface: operation delete(DOMString)
+PASS PaymentInstruments interface: operation get(DOMString)
+PASS PaymentInstruments interface: operation keys()
+PASS PaymentInstruments interface: operation has(DOMString)
+PASS PaymentInstruments interface: operation set(DOMString, PaymentInstrument)
+PASS PaymentInstruments interface: operation clear()
+PASS CanMakePaymentEvent interface: existence and properties of interface object
+PASS PaymentRequestEvent interface: existence and properties of interface object
+PASS ServiceWorkerRegistration interface: attribute paymentManager
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/payment-request/idlharness.https.window-expected.txt b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/payment-request/idlharness.https.window-expected.txt
new file mode 100644
index 0000000..dd488a1
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/payment-request/idlharness.https.window-expected.txt
@@ -0,0 +1,95 @@
+This is a testharness.js-based test.
+Found 91 tests; 90 PASS, 1 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS idl_test setup
+PASS idl_test validation
+PASS PaymentRequest interface: existence and properties of interface object
+FAIL PaymentRequest interface object length assert_equals: wrong value for PaymentRequest.length expected 2 but got 1
+PASS PaymentRequest interface object name
+PASS PaymentRequest interface: existence and properties of interface prototype object
+PASS PaymentRequest interface: existence and properties of interface prototype object's "constructor" property
+PASS PaymentRequest interface: existence and properties of interface prototype object's @@unscopables property
+PASS PaymentRequest interface: operation show(optional Promise<PaymentDetailsUpdate>)
+PASS PaymentRequest interface: operation abort()
+PASS PaymentRequest interface: operation canMakePayment()
+PASS PaymentRequest interface: attribute id
+PASS PaymentRequest interface: attribute shippingAddress
+PASS PaymentRequest interface: attribute shippingOption
+PASS PaymentRequest interface: attribute shippingType
+PASS PaymentRequest interface: attribute onshippingaddresschange
+PASS PaymentRequest interface: attribute onshippingoptionchange
+PASS PaymentRequest interface: attribute onpaymentmethodchange
+PASS PaymentRequest must be primary interface of paymentRequest
+PASS Stringification of paymentRequest
+PASS PaymentRequest interface: paymentRequest must inherit property "show(optional Promise<PaymentDetailsUpdate>)" with the proper type
+PASS PaymentRequest interface: calling show(optional Promise<PaymentDetailsUpdate>) on paymentRequest with too few arguments must throw TypeError
+PASS PaymentRequest interface: paymentRequest must inherit property "abort()" with the proper type
+PASS PaymentRequest interface: paymentRequest must inherit property "canMakePayment()" with the proper type
+PASS PaymentRequest interface: paymentRequest must inherit property "id" with the proper type
+PASS PaymentRequest interface: paymentRequest must inherit property "shippingAddress" with the proper type
+PASS PaymentRequest interface: paymentRequest must inherit property "shippingOption" with the proper type
+PASS PaymentRequest interface: paymentRequest must inherit property "shippingType" with the proper type
+PASS PaymentRequest interface: paymentRequest must inherit property "onshippingaddresschange" with the proper type
+PASS PaymentRequest interface: paymentRequest must inherit property "onshippingoptionchange" with the proper type
+PASS PaymentRequest interface: paymentRequest must inherit property "onpaymentmethodchange" with the proper type
+PASS PaymentAddress interface: existence and properties of interface object
+PASS PaymentAddress interface object length
+PASS PaymentAddress interface object name
+PASS PaymentAddress interface: existence and properties of interface prototype object
+PASS PaymentAddress interface: existence and properties of interface prototype object's "constructor" property
+PASS PaymentAddress interface: existence and properties of interface prototype object's @@unscopables property
+PASS PaymentAddress interface: operation toJSON()
+PASS PaymentAddress interface: attribute city
+PASS PaymentAddress interface: attribute country
+PASS PaymentAddress interface: attribute dependentLocality
+PASS PaymentAddress interface: attribute organization
+PASS PaymentAddress interface: attribute phone
+PASS PaymentAddress interface: attribute postalCode
+PASS PaymentAddress interface: attribute recipient
+PASS PaymentAddress interface: attribute region
+PASS PaymentAddress interface: attribute sortingCode
+PASS PaymentAddress interface: attribute addressLine
+PASS PaymentResponse interface: existence and properties of interface object
+PASS PaymentResponse interface object length
+PASS PaymentResponse interface object name
+PASS PaymentResponse interface: existence and properties of interface prototype object
+PASS PaymentResponse interface: existence and properties of interface prototype object's "constructor" property
+PASS PaymentResponse interface: existence and properties of interface prototype object's @@unscopables property
+PASS PaymentResponse interface: operation toJSON()
+PASS PaymentResponse interface: attribute requestId
+PASS PaymentResponse interface: attribute methodName
+PASS PaymentResponse interface: attribute details
+PASS PaymentResponse interface: attribute shippingAddress
+PASS PaymentResponse interface: attribute shippingOption
+PASS PaymentResponse interface: attribute payerName
+PASS PaymentResponse interface: attribute payerEmail
+PASS PaymentResponse interface: attribute payerPhone
+PASS PaymentResponse interface: operation complete(optional PaymentComplete)
+PASS PaymentResponse interface: operation retry(optional PaymentValidationErrors)
+PASS PaymentResponse interface: attribute onpayerdetailchange
+PASS PaymentMethodChangeEvent interface: existence and properties of interface object
+PASS PaymentMethodChangeEvent interface object length
+PASS PaymentMethodChangeEvent interface object name
+PASS PaymentMethodChangeEvent interface: existence and properties of interface prototype object
+PASS PaymentMethodChangeEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS PaymentMethodChangeEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS PaymentMethodChangeEvent interface: attribute methodName
+PASS PaymentMethodChangeEvent interface: attribute methodDetails
+PASS PaymentMethodChangeEvent must be primary interface of new PaymentMethodChangeEvent("paymentmethodchange")
+PASS Stringification of new PaymentMethodChangeEvent("paymentmethodchange")
+PASS PaymentMethodChangeEvent interface: new PaymentMethodChangeEvent("paymentmethodchange") must inherit property "methodName" with the proper type
+PASS PaymentMethodChangeEvent interface: new PaymentMethodChangeEvent("paymentmethodchange") must inherit property "methodDetails" with the proper type
+PASS PaymentRequestUpdateEvent interface: new PaymentMethodChangeEvent("paymentmethodchange") must inherit property "updateWith(Promise<PaymentDetailsUpdate>)" with the proper type
+PASS PaymentRequestUpdateEvent interface: calling updateWith(Promise<PaymentDetailsUpdate>) on new PaymentMethodChangeEvent("paymentmethodchange") with too few arguments must throw TypeError
+PASS PaymentRequestUpdateEvent interface: existence and properties of interface object
+PASS PaymentRequestUpdateEvent interface object length
+PASS PaymentRequestUpdateEvent interface object name
+PASS PaymentRequestUpdateEvent interface: existence and properties of interface prototype object
+PASS PaymentRequestUpdateEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS PaymentRequestUpdateEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS PaymentRequestUpdateEvent interface: operation updateWith(Promise<PaymentDetailsUpdate>)
+PASS PaymentRequestUpdateEvent must be primary interface of new PaymentRequestUpdateEvent("paymentrequestupdate")
+PASS Stringification of new PaymentRequestUpdateEvent("paymentrequestupdate")
+PASS PaymentRequestUpdateEvent interface: new PaymentRequestUpdateEvent("paymentrequestupdate") must inherit property "updateWith(Promise<PaymentDetailsUpdate>)" with the proper type
+PASS PaymentRequestUpdateEvent interface: calling updateWith(Promise<PaymentDetailsUpdate>) on new PaymentRequestUpdateEvent("paymentrequestupdate") with too few arguments must throw TypeError
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/url/url-setters-a-area.window-expected.txt b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/url/url-setters-a-area.window-expected.txt
new file mode 100644
index 0000000..21d9d9d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/url/url-setters-a-area.window-expected.txt
@@ -0,0 +1,431 @@
+This is a testharness.js-based test.
+Found 413 tests; 253 PASS, 160 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Loading data…
+PASS <a>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
+PASS <area>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
+PASS <a>: Setting <a://example.net>.protocol = 'b'
+PASS <area>: Setting <a://example.net>.protocol = 'b'
+PASS <a>: Setting <javascript:alert(1)>.protocol = 'defuse'
+PASS <area>: Setting <javascript:alert(1)>.protocol = 'defuse'
+PASS <a>: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased
+PASS <area>: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased
+PASS <a>: Setting <a://example.net>.protocol = 'é' Non-ASCII is rejected
+PASS <area>: Setting <a://example.net>.protocol = 'é' Non-ASCII is rejected
+PASS <a>: Setting <a://example.net>.protocol = '0b' No leading digit
+PASS <area>: Setting <a://example.net>.protocol = '0b' No leading digit
+PASS <a>: Setting <a://example.net>.protocol = '+b' No leading punctuation
+PASS <area>: Setting <a://example.net>.protocol = '+b' No leading punctuation
+PASS <a>: Setting <a://example.net>.protocol = 'bC0+-.'
+PASS <area>: Setting <a://example.net>.protocol = 'bC0+-.'
+PASS <a>: Setting <a://example.net>.protocol = 'b,c' Only some punctuation is acceptable
+PASS <area>: Setting <a://example.net>.protocol = 'b,c' Only some punctuation is acceptable
+PASS <a>: Setting <a://example.net>.protocol = 'bé' Non-ASCII is rejected
+PASS <area>: Setting <a://example.net>.protocol = 'bé' Non-ASCII is rejected
+PASS <a>: Setting <http://test@example.net>.protocol = 'file' Can’t switch from URL containing username/password/port to file
+PASS <area>: Setting <http://test@example.net>.protocol = 'file' Can’t switch from URL containing username/password/port to file
+PASS <a>: Setting <https://example.net:1234>.protocol = 'file'
+PASS <area>: Setting <https://example.net:1234>.protocol = 'file'
+PASS <a>: Setting <wss://x:x@example.net:1234>.protocol = 'file'
+PASS <area>: Setting <wss://x:x@example.net:1234>.protocol = 'file'
+FAIL <a>: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/"
+FAIL <area>: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/"
+PASS <a>: Setting <file:///test>.protocol = 'https'
+PASS <area>: Setting <file:///test>.protocol = 'https'
+PASS <a>: Setting <file:>.protocol = 'wss'
+PASS <area>: Setting <file:>.protocol = 'wss'
+FAIL <a>: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "b://example.net/"
+FAIL <area>: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "b://example.net/"
+FAIL <a>: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path"
+FAIL <area>: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path"
+FAIL <a>: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "s://example.net/"
+FAIL <area>: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "s://example.net/"
+FAIL <a>: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/"
+FAIL <area>: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/"
+FAIL <a>: Setting <mailto:me@example.net>.protocol = 'http' Cannot-be-a-base URL doesn’t have a host, but URL in a special scheme must. assert_equals: expected "mailto:me@example.net" but got "http://me@example.net/"
+FAIL <area>: Setting <mailto:me@example.net>.protocol = 'http' Cannot-be-a-base URL doesn’t have a host, but URL in a special scheme must. assert_equals: expected "mailto:me@example.net" but got "http://me@example.net/"
+FAIL <a>: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/"
+FAIL <area>: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/"
+FAIL <a>: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/"
+FAIL <area>: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/"
+FAIL <a>: Setting <ssh://me@example.net>.protocol = 'file' assert_equals: expected "ssh://me@example.net" but got "file://me%40example.net/"
+FAIL <area>: Setting <ssh://me@example.net>.protocol = 'file' assert_equals: expected "ssh://me@example.net" but got "file://me%40example.net/"
+FAIL <a>: Setting <ssh://example.net>.protocol = 'file' assert_equals: expected "ssh://example.net" but got "file://example.net/"
+FAIL <area>: Setting <ssh://example.net>.protocol = 'file' assert_equals: expected "ssh://example.net" but got "file://example.net/"
+FAIL <a>: Setting <nonsense:///test>.protocol = 'https' assert_equals: expected "nonsense:///test" but got "https://test/"
+FAIL <area>: Setting <nonsense:///test>.protocol = 'https' assert_equals: expected "nonsense:///test" but got "https://test/"
+PASS <a>: Setting <http://example.net>.protocol = 'https:foo : bar' Stuff after the first ':' is ignored
+PASS <area>: Setting <http://example.net>.protocol = 'https:foo : bar' Stuff after the first ':' is ignored
+PASS <a>: Setting <data:text/html,<p>Test>.protocol = 'view-source+data:foo : bar' Stuff after the first ':' is ignored
+PASS <area>: Setting <data:text/html,<p>Test>.protocol = 'view-source+data:foo : bar' Stuff after the first ':' is ignored
+PASS <a>: Setting <http://foo.com:443/>.protocol = 'https' Port is set to null if it is the default for new scheme.
+PASS <area>: Setting <http://foo.com:443/>.protocol = 'https' Port is set to null if it is the default for new scheme.
+PASS <a>: Setting <file:///home/you/index.html>.username = 'me' No host means no username
+PASS <area>: Setting <file:///home/you/index.html>.username = 'me' No host means no username
+PASS <a>: Setting <unix:/run/foo.socket>.username = 'me' No host means no username
+PASS <area>: Setting <unix:/run/foo.socket>.username = 'me' No host means no username
+PASS <a>: Setting <mailto:you@example.net>.username = 'me' Cannot-be-a-base means no username
+PASS <area>: Setting <mailto:you@example.net>.username = 'me' Cannot-be-a-base means no username
+PASS <a>: Setting <javascript:alert(1)>.username = 'wario'
+PASS <area>: Setting <javascript:alert(1)>.username = 'wario'
+PASS <a>: Setting <http://example.net>.username = 'me'
+PASS <area>: Setting <http://example.net>.username = 'me'
+PASS <a>: Setting <http://:secret@example.net>.username = 'me'
+PASS <area>: Setting <http://:secret@example.net>.username = 'me'
+PASS <a>: Setting <http://me@example.net>.username = ''
+PASS <area>: Setting <http://me@example.net>.username = ''
+PASS <a>: Setting <http://me:secret@example.net>.username = ''
+PASS <area>: Setting <http://me:secret@example.net>.username = ''
+FAIL <a>: Setting <http://example.net>.username = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+FAIL <area>: Setting <http://example.net>.username = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+PASS <a>: Setting <http://example.net>.username = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS <area>: Setting <http://example.net>.username = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS <a>: Setting <sc:///>.username = 'x'
+PASS <area>: Setting <sc:///>.username = 'x'
+FAIL <a>: Setting <javascript://x/>.username = 'wario' assert_equals: expected "javascript://wario@x/" but got "javascript://x/"
+FAIL <area>: Setting <javascript://x/>.username = 'wario' assert_equals: expected "javascript://wario@x/" but got "javascript://x/"
+PASS <a>: Setting <file://test/>.username = 'test'
+PASS <area>: Setting <file://test/>.username = 'test'
+PASS <a>: Setting <file:///home/me/index.html>.password = 'secret' No host means no password
+PASS <area>: Setting <file:///home/me/index.html>.password = 'secret' No host means no password
+PASS <a>: Setting <unix:/run/foo.socket>.password = 'secret' No host means no password
+PASS <area>: Setting <unix:/run/foo.socket>.password = 'secret' No host means no password
+PASS <a>: Setting <mailto:me@example.net>.password = 'secret' Cannot-be-a-base means no password
+PASS <area>: Setting <mailto:me@example.net>.password = 'secret' Cannot-be-a-base means no password
+PASS <a>: Setting <http://example.net>.password = 'secret'
+PASS <area>: Setting <http://example.net>.password = 'secret'
+PASS <a>: Setting <http://me@example.net>.password = 'secret'
+PASS <area>: Setting <http://me@example.net>.password = 'secret'
+PASS <a>: Setting <http://:secret@example.net>.password = ''
+PASS <area>: Setting <http://:secret@example.net>.password = ''
+PASS <a>: Setting <http://me:secret@example.net>.password = ''
+PASS <area>: Setting <http://me:secret@example.net>.password = ''
+FAIL <a>: Setting <http://example.net>.password = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://:%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://:%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+FAIL <area>: Setting <http://example.net>.password = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://:%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://:%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+PASS <a>: Setting <http://example.net>.password = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS <area>: Setting <http://example.net>.password = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS <a>: Setting <sc:///>.password = 'x'
+PASS <area>: Setting <sc:///>.password = 'x'
+FAIL <a>: Setting <javascript://x/>.password = 'bowser' assert_equals: expected "javascript://:bowser@x/" but got "javascript://x/"
+FAIL <area>: Setting <javascript://x/>.password = 'bowser' assert_equals: expected "javascript://:bowser@x/" but got "javascript://x/"
+PASS <a>: Setting <file://test/>.password = 'test'
+PASS <area>: Setting <file://test/>.password = 'test'
+FAIL <a>: Setting <sc://x/>.host = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL <area>: Setting <sc://x/>.host = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL <a>: Setting <sc://x/>.host = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.host = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.host = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.host = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.host = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.host = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.host = ' ' assert_equals: expected "x" but got ""
+FAIL <area>: Setting <sc://x/>.host = ' ' assert_equals: expected "x" but got ""
+FAIL <a>: Setting <sc://x/>.host = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.host = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.host = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.host = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
+FAIL <area>: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
+FAIL <a>: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
+FAIL <a>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
+FAIL <area>: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
+PASS <a>: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
+PASS <area>: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
+PASS <a>: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
+PASS <area>: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
+PASS <a>: Setting <http://example.net>.host = 'example.com:8080'
+PASS <area>: Setting <http://example.net>.host = 'example.com:8080'
+PASS <a>: Setting <http://example.net:8080>.host = 'example.com' Port number is unchanged if not specified in the new value
+PASS <area>: Setting <http://example.net:8080>.host = 'example.com' Port number is unchanged if not specified in the new value
+PASS <a>: Setting <http://example.net:8080>.host = 'example.com:' Port number is unchanged if not specified
+PASS <area>: Setting <http://example.net:8080>.host = 'example.com:' Port number is unchanged if not specified
+PASS <a>: Setting <http://example.net>.host = '' The empty host is not valid for special schemes
+PASS <area>: Setting <http://example.net>.host = '' The empty host is not valid for special schemes
+FAIL <a>: Setting <view-source+http://example.net/foo>.host = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL <area>: Setting <view-source+http://example.net/foo>.host = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL <a>: Setting <a:/foo>.host = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+FAIL <area>: Setting <a:/foo>.host = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+PASS <a>: Setting <http://example.net>.host = '0x7F000001:8080' IPv4 address syntax is normalized
+PASS <area>: Setting <http://example.net>.host = '0x7F000001:8080' IPv4 address syntax is normalized
+PASS <a>: Setting <http://example.net>.host = '[::0:01]:2' IPv6 address syntax is normalized
+PASS <area>: Setting <http://example.net>.host = '[::0:01]:2' IPv6 address syntax is normalized
+PASS <a>: Setting <http://example.net>.host = '[2001:db8::2]:4002' IPv6 literal address with port, crbug.com/1012416
+PASS <area>: Setting <http://example.net>.host = '[2001:db8::2]:4002' IPv6 literal address with port, crbug.com/1012416
+PASS <a>: Setting <http://example.net>.host = 'example.com:80' Default port number is removed
+PASS <area>: Setting <http://example.net>.host = 'example.com:80' Default port number is removed
+PASS <a>: Setting <https://example.net>.host = 'example.com:443' Default port number is removed
+PASS <area>: Setting <https://example.net>.host = 'example.com:443' Default port number is removed
+PASS <a>: Setting <https://example.net>.host = 'example.com:80' Default port number is only removed for the relevant scheme
+PASS <area>: Setting <https://example.net>.host = 'example.com:80' Default port number is only removed for the relevant scheme
+PASS <a>: Setting <http://example.net:8080>.host = 'example.com:80' Port number is removed if new port is scheme default and existing URL has a non-default port
+PASS <area>: Setting <http://example.net:8080>.host = 'example.com:80' Port number is removed if new port is scheme default and existing URL has a non-default port
+PASS <a>: Setting <http://example.net/path>.host = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.host = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:8080/stuff' Stuff after a / delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:8080/stuff' Stuff after a / delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.host = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.host = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:8080?stuff' Stuff after a ? delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:8080?stuff' Stuff after a ? delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.host = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.host = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:8080#stuff' Stuff after a # delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:8080#stuff' Stuff after a # delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.host = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+PASS <area>: Setting <http://example.net/path>.host = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL <a>: Setting <view-source+http://example.net/path>.host = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+FAIL <area>: Setting <view-source+http://example.net/path>.host = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+FAIL <a>: Setting <view-source+http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.com:8080/path" but got "view-source+http://example.net/path"
+FAIL <area>: Setting <view-source+http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.com:8080/path" but got "view-source+http://example.net/path"
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:65535' Port numbers are 16 bit integers
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:65535' Port numbers are 16 bit integers
+PASS <a>: Setting <http://example.net/path>.host = 'example.com:65536' Port numbers are 16 bit integers, overflowing is an error. Hostname is still set, though.
+PASS <area>: Setting <http://example.net/path>.host = 'example.com:65536' Port numbers are 16 bit integers, overflowing is an error. Hostname is still set, though.
+PASS <a>: Setting <http://example.net/>.host = '[google.com]' Broken IPv6
+PASS <area>: Setting <http://example.net/>.host = '[google.com]' Broken IPv6
+PASS <a>: Setting <http://example.net/>.host = '[::1.2.3.4x]'
+PASS <area>: Setting <http://example.net/>.host = '[::1.2.3.4x]'
+FAIL <a>: Setting <http://example.net/>.host = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL <area>: Setting <http://example.net/>.host = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL <a>: Setting <http://example.net/>.host = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL <area>: Setting <http://example.net/>.host = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL <a>: Setting <http://example.net/>.host = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+FAIL <area>: Setting <http://example.net/>.host = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+FAIL <a>: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/"
+FAIL <area>: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/"
+FAIL <a>: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL <area>: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL <a>: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL <area>: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL <a>: Setting <sc://test@test/>.host = '' assert_equals: expected "test" but got ""
+FAIL <area>: Setting <sc://test@test/>.host = '' assert_equals: expected "test" but got ""
+FAIL <a>: Setting <sc://test:12/>.host = '' assert_equals: expected "test:12" but got ""
+FAIL <area>: Setting <sc://test:12/>.host = '' assert_equals: expected "test:12" but got ""
+FAIL <a>: Setting <sc://x/>.hostname = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL <area>: Setting <sc://x/>.hostname = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL <a>: Setting <sc://x/>.hostname = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.hostname = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.hostname = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.hostname = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.hostname = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.hostname = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.hostname = ' ' assert_equals: expected "x" but got ""
+FAIL <area>: Setting <sc://x/>.hostname = ' ' assert_equals: expected "x" but got ""
+FAIL <a>: Setting <sc://x/>.hostname = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.hostname = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.hostname = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.hostname = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.hostname = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.hostname = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL <a>: Setting <sc://x/>.hostname = '@' assert_equals: expected "x" but got ""
+FAIL <area>: Setting <sc://x/>.hostname = '@' assert_equals: expected "x" but got ""
+PASS <a>: Setting <mailto:me@example.net>.hostname = 'example.com' Cannot-be-a-base means no host
+PASS <area>: Setting <mailto:me@example.net>.hostname = 'example.com' Cannot-be-a-base means no host
+PASS <a>: Setting <data:text/plain,Stuff>.hostname = 'example.net' Cannot-be-a-base means no host
+PASS <area>: Setting <data:text/plain,Stuff>.hostname = 'example.net' Cannot-be-a-base means no host
+PASS <a>: Setting <http://example.net:8080>.hostname = 'example.com'
+PASS <area>: Setting <http://example.net:8080>.hostname = 'example.com'
+PASS <a>: Setting <http://example.net>.hostname = '' The empty host is not valid for special schemes
+PASS <area>: Setting <http://example.net>.hostname = '' The empty host is not valid for special schemes
+FAIL <a>: Setting <view-source+http://example.net/foo>.hostname = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL <area>: Setting <view-source+http://example.net/foo>.hostname = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL <a>: Setting <a:/foo>.hostname = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+FAIL <area>: Setting <a:/foo>.hostname = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+PASS <a>: Setting <http://example.net:8080>.hostname = '0x7F000001' IPv4 address syntax is normalized
+PASS <area>: Setting <http://example.net:8080>.hostname = '0x7F000001' IPv4 address syntax is normalized
+PASS <a>: Setting <http://example.net>.hostname = '[::0:01]' IPv6 address syntax is normalized
+PASS <area>: Setting <http://example.net>.hostname = '[::0:01]' IPv6 address syntax is normalized
+PASS <a>: Setting <http://example.net/path>.hostname = 'example.com:8080' : delimiter invalidates entire value
+PASS <area>: Setting <http://example.net/path>.hostname = 'example.com:8080' : delimiter invalidates entire value
+PASS <a>: Setting <http://example.net:8080/path>.hostname = 'example.com:' : delimiter invalidates entire value
+PASS <area>: Setting <http://example.net:8080/path>.hostname = 'example.com:' : delimiter invalidates entire value
+PASS <a>: Setting <http://example.net/path>.hostname = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.hostname = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.hostname = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.hostname = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.hostname = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.hostname = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.hostname = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+PASS <area>: Setting <http://example.net/path>.hostname = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL <a>: Setting <view-source+http://example.net/path>.hostname = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+FAIL <area>: Setting <view-source+http://example.net/path>.hostname = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+PASS <a>: Setting <http://example.net/>.hostname = '[google.com]' Broken IPv6
+PASS <area>: Setting <http://example.net/>.hostname = '[google.com]' Broken IPv6
+PASS <a>: Setting <http://example.net/>.hostname = '[::1.2.3.4x]'
+PASS <area>: Setting <http://example.net/>.hostname = '[::1.2.3.4x]'
+FAIL <a>: Setting <http://example.net/>.hostname = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL <area>: Setting <http://example.net/>.hostname = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL <a>: Setting <http://example.net/>.hostname = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL <area>: Setting <http://example.net/>.hostname = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL <a>: Setting <http://example.net/>.hostname = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+FAIL <area>: Setting <http://example.net/>.hostname = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+PASS <a>: Setting <file://y/>.hostname = 'x:123'
+PASS <area>: Setting <file://y/>.hostname = 'x:123'
+FAIL <a>: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL <area>: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL <a>: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL <area>: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL <a>: Setting <sc://test@test/>.hostname = '' assert_equals: expected "test" but got ""
+FAIL <area>: Setting <sc://test@test/>.hostname = '' assert_equals: expected "test" but got ""
+FAIL <a>: Setting <sc://test:12/>.hostname = '' assert_equals: expected "test:12" but got ""
+FAIL <area>: Setting <sc://test:12/>.hostname = '' assert_equals: expected "test:12" but got ""
+FAIL <a>: Setting <non-spec:/.//p>.hostname = 'h' Drop /. from path assert_equals: expected "non-spec://h//p" but got "non-spec:/.//p"
+FAIL <area>: Setting <non-spec:/.//p>.hostname = 'h' Drop /. from path assert_equals: expected "non-spec://h//p" but got "non-spec:/.//p"
+FAIL <a>: Setting <non-spec:/.//p>.hostname = '' assert_equals: expected "non-spec:////p" but got "non-spec:/.//p"
+FAIL <area>: Setting <non-spec:/.//p>.hostname = '' assert_equals: expected "non-spec:////p" but got "non-spec:/.//p"
+PASS <a>: Setting <http://example.net>.port = '8080'
+PASS <area>: Setting <http://example.net>.port = '8080'
+PASS <a>: Setting <http://example.net:8080>.port = '' Port number is removed if empty is the new value
+PASS <area>: Setting <http://example.net:8080>.port = '' Port number is removed if empty is the new value
+PASS <a>: Setting <http://example.net:8080>.port = '80' Default port number is removed
+PASS <area>: Setting <http://example.net:8080>.port = '80' Default port number is removed
+PASS <a>: Setting <https://example.net:4433>.port = '443' Default port number is removed
+PASS <area>: Setting <https://example.net:4433>.port = '443' Default port number is removed
+PASS <a>: Setting <https://example.net>.port = '80' Default port number is only removed for the relevant scheme
+PASS <area>: Setting <https://example.net>.port = '80' Default port number is only removed for the relevant scheme
+PASS <a>: Setting <http://example.net/path>.port = '8080/stuff' Stuff after a / delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.port = '8080/stuff' Stuff after a / delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.port = '8080?stuff' Stuff after a ? delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.port = '8080?stuff' Stuff after a ? delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.port = '8080#stuff' Stuff after a # delimiter is ignored
+PASS <area>: Setting <http://example.net/path>.port = '8080#stuff' Stuff after a # delimiter is ignored
+PASS <a>: Setting <http://example.net/path>.port = '8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+PASS <area>: Setting <http://example.net/path>.port = '8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL <a>: Setting <view-source+http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.net:8080/path" but got "view-source+http://example.net/path"
+FAIL <area>: Setting <view-source+http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.net:8080/path" but got "view-source+http://example.net/path"
+PASS <a>: Setting <http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <area>: Setting <http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <a>: Setting <http://example.net/path>.port = '8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <area>: Setting <http://example.net/path>.port = '8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS <a>: Setting <http://example.net/path>.port = '65535' Port numbers are 16 bit integers
+PASS <area>: Setting <http://example.net/path>.port = '65535' Port numbers are 16 bit integers
+FAIL <a>: Setting <http://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "http://example.net:8080/path" but got "http://example.net:0/path"
+FAIL <area>: Setting <http://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "http://example.net:8080/path" but got "http://example.net:0/path"
+FAIL <a>: Setting <non-special://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "example.net:8080" but got ""
+FAIL <area>: Setting <non-special://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "example.net:8080" but got ""
+PASS <a>: Setting <file://test/>.port = '12'
+PASS <area>: Setting <file://test/>.port = '12'
+FAIL <a>: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL <area>: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/"
+PASS <a>: Setting <non-base:value>.port = '12'
+PASS <area>: Setting <non-base:value>.port = '12'
+PASS <a>: Setting <sc:///>.port = '12'
+PASS <area>: Setting <sc:///>.port = '12'
+FAIL <a>: Setting <sc://x/>.port = '12' assert_equals: expected "sc://x:12/" but got "sc://x/"
+FAIL <area>: Setting <sc://x/>.port = '12' assert_equals: expected "sc://x:12/" but got "sc://x/"
+FAIL <a>: Setting <javascript://x/>.port = '12' assert_equals: expected "javascript://x:12/" but got "javascript://x/"
+FAIL <area>: Setting <javascript://x/>.port = '12' assert_equals: expected "javascript://x:12/" but got "javascript://x/"
+PASS <a>: Setting <mailto:me@example.net>.pathname = '/foo' Cannot-be-a-base don’t have a path
+PASS <area>: Setting <mailto:me@example.net>.pathname = '/foo' Cannot-be-a-base don’t have a path
+FAIL <a>: Setting <unix:/run/foo.socket?timeout=10>.pathname = '/var/log/../run/bar.socket' assert_equals: expected "unix:/var/run/bar.socket?timeout=10" but got "unix:/run/foo.socket?timeout=10"
+FAIL <area>: Setting <unix:/run/foo.socket?timeout=10>.pathname = '/var/log/../run/bar.socket' assert_equals: expected "unix:/var/run/bar.socket?timeout=10" but got "unix:/run/foo.socket?timeout=10"
+PASS <a>: Setting <https://example.net#nav>.pathname = 'home'
+PASS <area>: Setting <https://example.net#nav>.pathname = 'home'
+PASS <a>: Setting <https://example.net#nav>.pathname = '../home'
+PASS <area>: Setting <https://example.net#nav>.pathname = '../home'
+PASS <a>: Setting <http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is a segment delimiter for 'special' URLs
+PASS <area>: Setting <http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is a segment delimiter for 'special' URLs
+FAIL <a>: Setting <view-source+http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is *not* a segment delimiter for non-'special' URLs assert_equals: expected "view-source+http://example.net/\\a\\%2E\\b\\%2e.\\c?lang=fr#nav" but got "view-source+http://example.net/home?lang=fr#nav"
+FAIL <area>: Setting <view-source+http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is *not* a segment delimiter for non-'special' URLs assert_equals: expected "view-source+http://example.net/\\a\\%2E\\b\\%2e.\\c?lang=fr#nav" but got "view-source+http://example.net/home?lang=fr#nav"
+FAIL <a>: Setting <a:/>.pathname = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the default encode set. Tabs and newlines are removed. assert_equals: expected "a:/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/"
+FAIL <area>: Setting <a:/>.pathname = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the default encode set. Tabs and newlines are removed. assert_equals: expected "a:/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/"
+FAIL <a>: Setting <http://example.net>.pathname = '%2e%2E%c3%89té' Bytes already percent-encoded are left as-is, including %2E outside dotted segments. assert_equals: expected "http://example.net/%2e%2E%c3%89t%C3%A9" but got "http://example.net/..%c3%89t%C3%A9"
+FAIL <area>: Setting <http://example.net>.pathname = '%2e%2E%c3%89té' Bytes already percent-encoded are left as-is, including %2E outside dotted segments. assert_equals: expected "http://example.net/%2e%2E%c3%89t%C3%A9" but got "http://example.net/..%c3%89t%C3%A9"
+PASS <a>: Setting <http://example.net>.pathname = '?' ? needs to be encoded
+PASS <area>: Setting <http://example.net>.pathname = '?' ? needs to be encoded
+PASS <a>: Setting <http://example.net>.pathname = '#' # needs to be encoded
+PASS <area>: Setting <http://example.net>.pathname = '#' # needs to be encoded
+FAIL <a>: Setting <sc://example.net>.pathname = '?' ? needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%3F" but got "sc://example.net"
+FAIL <area>: Setting <sc://example.net>.pathname = '?' ? needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%3F" but got "sc://example.net"
+FAIL <a>: Setting <sc://example.net>.pathname = '#' # needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%23" but got "sc://example.net"
+FAIL <area>: Setting <sc://example.net>.pathname = '#' # needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%23" but got "sc://example.net"
+PASS <a>: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes
+PASS <area>: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes
+FAIL <a>: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes assert_equals: expected "file://////" but got "file:///"
+FAIL <area>: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes assert_equals: expected "file://////" but got "file:///"
+FAIL <a>: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes assert_equals: expected "file://///" but got "file:///"
+FAIL <area>: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes assert_equals: expected "file://///" but got "file:///"
+FAIL <a>: Setting <non-spec:/>.pathname = '/.//p' Serialize /. in path assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL <area>: Setting <non-spec:/>.pathname = '/.//p' Serialize /. in path assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL <a>: Setting <non-spec:/>.pathname = '/..//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL <area>: Setting <non-spec:/>.pathname = '/..//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL <a>: Setting <non-spec:/>.pathname = '//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL <area>: Setting <non-spec:/>.pathname = '//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL <a>: Setting <non-spec:/.//>.pathname = 'p' Drop /. from path assert_equals: expected "non-spec:/p" but got "non-spec:/.//"
+FAIL <area>: Setting <non-spec:/.//>.pathname = 'p' Drop /. from path assert_equals: expected "non-spec:/p" but got "non-spec:/.//"
+PASS <a>: Setting <https://example.net#nav>.search = 'lang=fr'
+PASS <area>: Setting <https://example.net#nav>.search = 'lang=fr'
+PASS <a>: Setting <https://example.net?lang=en-US#nav>.search = 'lang=fr'
+PASS <area>: Setting <https://example.net?lang=en-US#nav>.search = 'lang=fr'
+PASS <a>: Setting <https://example.net?lang=en-US#nav>.search = '?lang=fr'
+PASS <area>: Setting <https://example.net?lang=en-US#nav>.search = '?lang=fr'
+PASS <a>: Setting <https://example.net?lang=en-US#nav>.search = '??lang=fr'
+PASS <area>: Setting <https://example.net?lang=en-US#nav>.search = '??lang=fr'
+FAIL <a>: Setting <https://example.net?lang=en-US#nav>.search = '?' assert_equals: expected "https://example.net/?#nav" but got "https://example.net/#nav"
+FAIL <area>: Setting <https://example.net?lang=en-US#nav>.search = '?' assert_equals: expected "https://example.net/?#nav" but got "https://example.net/#nav"
+PASS <a>: Setting <https://example.net?lang=en-US#nav>.search = ''
+PASS <area>: Setting <https://example.net?lang=en-US#nav>.search = ''
+PASS <a>: Setting <https://example.net?lang=en-US>.search = ''
+PASS <area>: Setting <https://example.net?lang=en-US>.search = ''
+PASS <a>: Setting <https://example.net>.search = ''
+PASS <area>: Setting <https://example.net>.search = ''
+FAIL <a>: Setting <a:/>.search = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%09%0A%0D%1F !\"#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+FAIL <area>: Setting <a:/>.search = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%09%0A%0D%1F !\"#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+PASS <a>: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS <area>: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS <a>: Setting <https://example.net>.hash = 'main'
+PASS <area>: Setting <https://example.net>.hash = 'main'
+PASS <a>: Setting <https://example.net#nav>.hash = 'main'
+PASS <area>: Setting <https://example.net#nav>.hash = 'main'
+PASS <a>: Setting <https://example.net?lang=en-US>.hash = '##nav'
+PASS <area>: Setting <https://example.net?lang=en-US>.hash = '##nav'
+PASS <a>: Setting <https://example.net?lang=en-US#nav>.hash = '#main'
+PASS <area>: Setting <https://example.net?lang=en-US#nav>.hash = '#main'
+PASS <a>: Setting <https://example.net?lang=en-US#nav>.hash = '#'
+PASS <area>: Setting <https://example.net?lang=en-US#nav>.hash = '#'
+PASS <a>: Setting <https://example.net?lang=en-US#nav>.hash = ''
+PASS <area>: Setting <https://example.net?lang=en-US#nav>.hash = ''
+PASS <a>: Setting <http://example.net>.hash = '#foo bar'
+PASS <area>: Setting <http://example.net>.hash = '#foo bar'
+PASS <a>: Setting <http://example.net>.hash = '#foo"bar'
+PASS <area>: Setting <http://example.net>.hash = '#foo"bar'
+PASS <a>: Setting <http://example.net>.hash = '#foo<bar'
+PASS <area>: Setting <http://example.net>.hash = '#foo<bar'
+PASS <a>: Setting <http://example.net>.hash = '#foo>bar'
+PASS <area>: Setting <http://example.net>.hash = '#foo>bar'
+PASS <a>: Setting <http://example.net>.hash = '#foo`bar'
+PASS <area>: Setting <http://example.net>.hash = '#foo`bar'
+FAIL <a>: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+FAIL <area>: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+PASS <a>: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS <area>: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS <a>: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS <area>: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS <a>: Setting <http://example.net>.hash = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS <area>: Setting <http://example.net>.hash = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS <a>: Setting <javascript:alert(1)>.hash = 'castle'
+PASS <area>: Setting <javascript:alert(1)>.hash = 'castle'
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/url/url-setters.any-expected.txt b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/url/url-setters.any-expected.txt
new file mode 100644
index 0000000..326d569
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/url/url-setters.any-expected.txt
@@ -0,0 +1,218 @@
+This is a testharness.js-based test.
+Found 207 tests; 129 PASS, 78 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Loading data…
+PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
+PASS URL: Setting <a://example.net>.protocol = 'b'
+PASS URL: Setting <javascript:alert(1)>.protocol = 'defuse'
+PASS URL: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased
+PASS URL: Setting <a://example.net>.protocol = 'é' Non-ASCII is rejected
+PASS URL: Setting <a://example.net>.protocol = '0b' No leading digit
+PASS URL: Setting <a://example.net>.protocol = '+b' No leading punctuation
+PASS URL: Setting <a://example.net>.protocol = 'bC0+-.'
+PASS URL: Setting <a://example.net>.protocol = 'b,c' Only some punctuation is acceptable
+PASS URL: Setting <a://example.net>.protocol = 'bé' Non-ASCII is rejected
+PASS URL: Setting <http://test@example.net>.protocol = 'file' Can’t switch from URL containing username/password/port to file
+PASS URL: Setting <https://example.net:1234>.protocol = 'file'
+PASS URL: Setting <wss://x:x@example.net:1234>.protocol = 'file'
+FAIL URL: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/"
+PASS URL: Setting <file:///test>.protocol = 'https'
+PASS URL: Setting <file:>.protocol = 'wss'
+FAIL URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "b://example.net/"
+FAIL URL: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path"
+FAIL URL: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "s://example.net/"
+FAIL URL: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/"
+FAIL URL: Setting <mailto:me@example.net>.protocol = 'http' Cannot-be-a-base URL doesn’t have a host, but URL in a special scheme must. assert_equals: expected "mailto:me@example.net" but got "http://me@example.net/"
+FAIL URL: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/"
+FAIL URL: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/"
+FAIL URL: Setting <ssh://me@example.net>.protocol = 'file' assert_equals: expected "ssh://me@example.net" but got "file://me%40example.net/"
+FAIL URL: Setting <ssh://example.net>.protocol = 'file' assert_equals: expected "ssh://example.net" but got "file://example.net/"
+FAIL URL: Setting <nonsense:///test>.protocol = 'https' assert_equals: expected "nonsense:///test" but got "https://test/"
+PASS URL: Setting <http://example.net>.protocol = 'https:foo : bar' Stuff after the first ':' is ignored
+PASS URL: Setting <data:text/html,<p>Test>.protocol = 'view-source+data:foo : bar' Stuff after the first ':' is ignored
+PASS URL: Setting <http://foo.com:443/>.protocol = 'https' Port is set to null if it is the default for new scheme.
+PASS URL: Setting <file:///home/you/index.html>.username = 'me' No host means no username
+PASS URL: Setting <unix:/run/foo.socket>.username = 'me' No host means no username
+PASS URL: Setting <mailto:you@example.net>.username = 'me' Cannot-be-a-base means no username
+PASS URL: Setting <javascript:alert(1)>.username = 'wario'
+PASS URL: Setting <http://example.net>.username = 'me'
+PASS URL: Setting <http://:secret@example.net>.username = 'me'
+PASS URL: Setting <http://me@example.net>.username = ''
+PASS URL: Setting <http://me:secret@example.net>.username = ''
+FAIL URL: Setting <http://example.net>.username = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+PASS URL: Setting <http://example.net>.username = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS URL: Setting <sc:///>.username = 'x'
+FAIL URL: Setting <javascript://x/>.username = 'wario' assert_equals: expected "javascript://wario@x/" but got "javascript://x/"
+PASS URL: Setting <file://test/>.username = 'test'
+PASS URL: Setting <file:///home/me/index.html>.password = 'secret' No host means no password
+PASS URL: Setting <unix:/run/foo.socket>.password = 'secret' No host means no password
+PASS URL: Setting <mailto:me@example.net>.password = 'secret' Cannot-be-a-base means no password
+PASS URL: Setting <http://example.net>.password = 'secret'
+PASS URL: Setting <http://me@example.net>.password = 'secret'
+PASS URL: Setting <http://:secret@example.net>.password = ''
+PASS URL: Setting <http://me:secret@example.net>.password = ''
+FAIL URL: Setting <http://example.net>.password = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://:%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://:%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+PASS URL: Setting <http://example.net>.password = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS URL: Setting <sc:///>.password = 'x'
+FAIL URL: Setting <javascript://x/>.password = 'bowser' assert_equals: expected "javascript://:bowser@x/" but got "javascript://x/"
+PASS URL: Setting <file://test/>.password = 'test'
+FAIL URL: Setting <sc://x/>.host = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.host = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = ' ' assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.host = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
+FAIL URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
+PASS URL: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
+PASS URL: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
+PASS URL: Setting <http://example.net>.host = 'example.com:8080'
+PASS URL: Setting <http://example.net:8080>.host = 'example.com' Port number is unchanged if not specified in the new value
+PASS URL: Setting <http://example.net:8080>.host = 'example.com:' Port number is unchanged if not specified
+PASS URL: Setting <http://example.net>.host = '' The empty host is not valid for special schemes
+FAIL URL: Setting <view-source+http://example.net/foo>.host = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL URL: Setting <a:/foo>.host = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+PASS URL: Setting <http://example.net>.host = '0x7F000001:8080' IPv4 address syntax is normalized
+PASS URL: Setting <http://example.net>.host = '[::0:01]:2' IPv6 address syntax is normalized
+PASS URL: Setting <http://example.net>.host = '[2001:db8::2]:4002' IPv6 literal address with port, crbug.com/1012416
+PASS URL: Setting <http://example.net>.host = 'example.com:80' Default port number is removed
+PASS URL: Setting <https://example.net>.host = 'example.com:443' Default port number is removed
+PASS URL: Setting <https://example.net>.host = 'example.com:80' Default port number is only removed for the relevant scheme
+PASS URL: Setting <http://example.net:8080>.host = 'example.com:80' Port number is removed if new port is scheme default and existing URL has a non-default port
+PASS URL: Setting <http://example.net/path>.host = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL URL: Setting <view-source+http://example.net/path>.host = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+FAIL URL: Setting <view-source+http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.com:8080/path" but got "view-source+http://example.net/path"
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.host = 'example.com:65535' Port numbers are 16 bit integers
+PASS URL: Setting <http://example.net/path>.host = 'example.com:65536' Port numbers are 16 bit integers, overflowing is an error. Hostname is still set, though.
+PASS URL: Setting <http://example.net/>.host = '[google.com]' Broken IPv6
+PASS URL: Setting <http://example.net/>.host = '[::1.2.3.4x]'
+FAIL URL: Setting <http://example.net/>.host = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL URL: Setting <http://example.net/>.host = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL URL: Setting <http://example.net/>.host = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+FAIL URL: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/"
+FAIL URL: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL URL: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL URL: Setting <sc://test@test/>.host = '' assert_equals: expected "test" but got ""
+FAIL URL: Setting <sc://test:12/>.host = '' assert_equals: expected "test:12" but got ""
+FAIL URL: Setting <sc://x/>.hostname = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.hostname = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = ' ' assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.hostname = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '@' assert_equals: expected "x" but got ""
+PASS URL: Setting <mailto:me@example.net>.hostname = 'example.com' Cannot-be-a-base means no host
+PASS URL: Setting <data:text/plain,Stuff>.hostname = 'example.net' Cannot-be-a-base means no host
+PASS URL: Setting <http://example.net:8080>.hostname = 'example.com'
+PASS URL: Setting <http://example.net>.hostname = '' The empty host is not valid for special schemes
+FAIL URL: Setting <view-source+http://example.net/foo>.hostname = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL URL: Setting <a:/foo>.hostname = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+PASS URL: Setting <http://example.net:8080>.hostname = '0x7F000001' IPv4 address syntax is normalized
+PASS URL: Setting <http://example.net>.hostname = '[::0:01]' IPv6 address syntax is normalized
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com:8080' : delimiter invalidates entire value
+PASS URL: Setting <http://example.net:8080/path>.hostname = 'example.com:' : delimiter invalidates entire value
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL URL: Setting <view-source+http://example.net/path>.hostname = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+PASS URL: Setting <http://example.net/>.hostname = '[google.com]' Broken IPv6
+PASS URL: Setting <http://example.net/>.hostname = '[::1.2.3.4x]'
+FAIL URL: Setting <http://example.net/>.hostname = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL URL: Setting <http://example.net/>.hostname = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL URL: Setting <http://example.net/>.hostname = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+PASS URL: Setting <file://y/>.hostname = 'x:123'
+FAIL URL: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL URL: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL URL: Setting <sc://test@test/>.hostname = '' assert_equals: expected "test" but got ""
+FAIL URL: Setting <sc://test:12/>.hostname = '' assert_equals: expected "test:12" but got ""
+FAIL URL: Setting <non-spec:/.//p>.hostname = 'h' Drop /. from path assert_equals: expected "non-spec://h//p" but got "non-spec:/.//p"
+FAIL URL: Setting <non-spec:/.//p>.hostname = '' assert_equals: expected "non-spec:////p" but got "non-spec:/.//p"
+PASS URL: Setting <http://example.net>.port = '8080'
+PASS URL: Setting <http://example.net:8080>.port = '' Port number is removed if empty is the new value
+PASS URL: Setting <http://example.net:8080>.port = '80' Default port number is removed
+PASS URL: Setting <https://example.net:4433>.port = '443' Default port number is removed
+PASS URL: Setting <https://example.net>.port = '80' Default port number is only removed for the relevant scheme
+PASS URL: Setting <http://example.net/path>.port = '8080/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.port = '8080?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.port = '8080#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.port = '8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL URL: Setting <view-source+http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.net:8080/path" but got "view-source+http://example.net/path"
+PASS URL: Setting <http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.port = '8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.port = '65535' Port numbers are 16 bit integers
+FAIL URL: Setting <http://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "http://example.net:8080/path" but got "http://example.net:0/path"
+FAIL URL: Setting <non-special://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "example.net:8080" but got ""
+PASS URL: Setting <file://test/>.port = '12'
+FAIL URL: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/"
+PASS URL: Setting <non-base:value>.port = '12'
+PASS URL: Setting <sc:///>.port = '12'
+FAIL URL: Setting <sc://x/>.port = '12' assert_equals: expected "sc://x:12/" but got "sc://x/"
+FAIL URL: Setting <javascript://x/>.port = '12' assert_equals: expected "javascript://x:12/" but got "javascript://x/"
+PASS URL: Setting <mailto:me@example.net>.pathname = '/foo' Cannot-be-a-base don’t have a path
+FAIL URL: Setting <unix:/run/foo.socket?timeout=10>.pathname = '/var/log/../run/bar.socket' assert_equals: expected "unix:/var/run/bar.socket?timeout=10" but got "unix:/run/foo.socket?timeout=10"
+PASS URL: Setting <https://example.net#nav>.pathname = 'home'
+PASS URL: Setting <https://example.net#nav>.pathname = '../home'
+PASS URL: Setting <http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is a segment delimiter for 'special' URLs
+FAIL URL: Setting <view-source+http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is *not* a segment delimiter for non-'special' URLs assert_equals: expected "view-source+http://example.net/\\a\\%2E\\b\\%2e.\\c?lang=fr#nav" but got "view-source+http://example.net/home?lang=fr#nav"
+FAIL URL: Setting <a:/>.pathname = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the default encode set. Tabs and newlines are removed. assert_equals: expected "a:/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/"
+FAIL URL: Setting <http://example.net>.pathname = '%2e%2E%c3%89té' Bytes already percent-encoded are left as-is, including %2E outside dotted segments. assert_equals: expected "http://example.net/%2e%2E%c3%89t%C3%A9" but got "http://example.net/..%c3%89t%C3%A9"
+PASS URL: Setting <http://example.net>.pathname = '?' ? needs to be encoded
+PASS URL: Setting <http://example.net>.pathname = '#' # needs to be encoded
+FAIL URL: Setting <sc://example.net>.pathname = '?' ? needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%3F" but got "sc://example.net"
+FAIL URL: Setting <sc://example.net>.pathname = '#' # needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%23" but got "sc://example.net"
+PASS URL: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes
+PASS URL: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes
+PASS URL: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes
+FAIL URL: Setting <non-spec:/>.pathname = '/.//p' Serialize /. in path assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL URL: Setting <non-spec:/>.pathname = '/..//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL URL: Setting <non-spec:/>.pathname = '//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL URL: Setting <non-spec:/.//>.pathname = 'p' Drop /. from path assert_equals: expected "non-spec:/p" but got "non-spec:/.//"
+PASS URL: Setting <https://example.net#nav>.search = 'lang=fr'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = 'lang=fr'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = '?lang=fr'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = '??lang=fr'
+FAIL URL: Setting <https://example.net?lang=en-US#nav>.search = '?' assert_equals: expected "https://example.net/?#nav" but got "https://example.net/#nav"
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = ''
+PASS URL: Setting <https://example.net?lang=en-US>.search = ''
+PASS URL: Setting <https://example.net>.search = ''
+FAIL URL: Setting <a:/>.search = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%09%0A%0D%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+PASS URL: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS URL: Setting <https://example.net>.hash = 'main'
+PASS URL: Setting <https://example.net#nav>.hash = 'main'
+PASS URL: Setting <https://example.net?lang=en-US>.hash = '##nav'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.hash = '#main'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.hash = '#'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.hash = ''
+PASS URL: Setting <http://example.net>.hash = '#foo bar'
+PASS URL: Setting <http://example.net>.hash = '#foo"bar'
+PASS URL: Setting <http://example.net>.hash = '#foo<bar'
+PASS URL: Setting <http://example.net>.hash = '#foo>bar'
+PASS URL: Setting <http://example.net>.hash = '#foo`bar'
+FAIL URL: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+PASS URL: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS URL: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS URL: Setting <http://example.net>.hash = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS URL: Setting <javascript:alert(1)>.hash = 'castle'
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/url/url-setters.any.worker-expected.txt b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/url/url-setters.any.worker-expected.txt
new file mode 100644
index 0000000..326d569
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/url/url-setters.any.worker-expected.txt
@@ -0,0 +1,218 @@
+This is a testharness.js-based test.
+Found 207 tests; 129 PASS, 78 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS Loading data…
+PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
+PASS URL: Setting <a://example.net>.protocol = 'b'
+PASS URL: Setting <javascript:alert(1)>.protocol = 'defuse'
+PASS URL: Setting <a://example.net>.protocol = 'B' Upper-case ASCII is lower-cased
+PASS URL: Setting <a://example.net>.protocol = 'é' Non-ASCII is rejected
+PASS URL: Setting <a://example.net>.protocol = '0b' No leading digit
+PASS URL: Setting <a://example.net>.protocol = '+b' No leading punctuation
+PASS URL: Setting <a://example.net>.protocol = 'bC0+-.'
+PASS URL: Setting <a://example.net>.protocol = 'b,c' Only some punctuation is acceptable
+PASS URL: Setting <a://example.net>.protocol = 'bé' Non-ASCII is rejected
+PASS URL: Setting <http://test@example.net>.protocol = 'file' Can’t switch from URL containing username/password/port to file
+PASS URL: Setting <https://example.net:1234>.protocol = 'file'
+PASS URL: Setting <wss://x:x@example.net:1234>.protocol = 'file'
+FAIL URL: Setting <file://localhost/>.protocol = 'http' Can’t switch from file URL with no host assert_equals: expected "file:///" but got "http://localhost/"
+PASS URL: Setting <file:///test>.protocol = 'https'
+PASS URL: Setting <file:>.protocol = 'wss'
+FAIL URL: Setting <http://example.net>.protocol = 'b' Can’t switch from special scheme to non-special assert_equals: expected "http://example.net/" but got "b://example.net/"
+FAIL URL: Setting <file://hi/path>.protocol = 's' assert_equals: expected "file://hi/path" but got "s://hi/path"
+FAIL URL: Setting <https://example.net>.protocol = 's' assert_equals: expected "https://example.net/" but got "s://example.net/"
+FAIL URL: Setting <ftp://example.net>.protocol = 'test' assert_equals: expected "ftp://example.net/" but got "test://example.net/"
+FAIL URL: Setting <mailto:me@example.net>.protocol = 'http' Cannot-be-a-base URL doesn’t have a host, but URL in a special scheme must. assert_equals: expected "mailto:me@example.net" but got "http://me@example.net/"
+FAIL URL: Setting <ssh://me@example.net>.protocol = 'http' Can’t switch from non-special scheme to special assert_equals: expected "ssh://me@example.net" but got "http://me@example.net/"
+FAIL URL: Setting <ssh://me@example.net>.protocol = 'https' assert_equals: expected "ssh://me@example.net" but got "https://me@example.net/"
+FAIL URL: Setting <ssh://me@example.net>.protocol = 'file' assert_equals: expected "ssh://me@example.net" but got "file://me%40example.net/"
+FAIL URL: Setting <ssh://example.net>.protocol = 'file' assert_equals: expected "ssh://example.net" but got "file://example.net/"
+FAIL URL: Setting <nonsense:///test>.protocol = 'https' assert_equals: expected "nonsense:///test" but got "https://test/"
+PASS URL: Setting <http://example.net>.protocol = 'https:foo : bar' Stuff after the first ':' is ignored
+PASS URL: Setting <data:text/html,<p>Test>.protocol = 'view-source+data:foo : bar' Stuff after the first ':' is ignored
+PASS URL: Setting <http://foo.com:443/>.protocol = 'https' Port is set to null if it is the default for new scheme.
+PASS URL: Setting <file:///home/you/index.html>.username = 'me' No host means no username
+PASS URL: Setting <unix:/run/foo.socket>.username = 'me' No host means no username
+PASS URL: Setting <mailto:you@example.net>.username = 'me' Cannot-be-a-base means no username
+PASS URL: Setting <javascript:alert(1)>.username = 'wario'
+PASS URL: Setting <http://example.net>.username = 'me'
+PASS URL: Setting <http://:secret@example.net>.username = 'me'
+PASS URL: Setting <http://me@example.net>.username = ''
+PASS URL: Setting <http://me:secret@example.net>.username = ''
+FAIL URL: Setting <http://example.net>.username = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+PASS URL: Setting <http://example.net>.username = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS URL: Setting <sc:///>.username = 'x'
+FAIL URL: Setting <javascript://x/>.username = 'wario' assert_equals: expected "javascript://wario@x/" but got "javascript://x/"
+PASS URL: Setting <file://test/>.username = 'test'
+PASS URL: Setting <file:///home/me/index.html>.password = 'secret' No host means no password
+PASS URL: Setting <unix:/run/foo.socket>.password = 'secret' No host means no password
+PASS URL: Setting <mailto:me@example.net>.password = 'secret' Cannot-be-a-base means no password
+PASS URL: Setting <http://example.net>.password = 'secret'
+PASS URL: Setting <http://me@example.net>.password = 'secret'
+PASS URL: Setting <http://:secret@example.net>.password = ''
+PASS URL: Setting <http://me:secret@example.net>.password = ''
+FAIL URL: Setting <http://example.net>.password = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the userinfo encode set. assert_equals: expected "http://:%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/" but got "http://:%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/"
+PASS URL: Setting <http://example.net>.password = '%c3%89té' Bytes already percent-encoded are left as-is.
+PASS URL: Setting <sc:///>.password = 'x'
+FAIL URL: Setting <javascript://x/>.password = 'bowser' assert_equals: expected "javascript://:bowser@x/" but got "javascript://x/"
+PASS URL: Setting <file://test/>.password = 'test'
+FAIL URL: Setting <sc://x/>.host = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.host = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = ' ' assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.host = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.host = '@' assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.host = 'ß' assert_equals: expected "sc://%C3%9F/" but got "sc://x/"
+FAIL URL: Setting <https://x/>.host = 'ß' IDNA Nontransitional_Processing assert_equals: expected "https://xn--zca/" but got "https://ss/"
+PASS URL: Setting <mailto:me@example.net>.host = 'example.com' Cannot-be-a-base means no host
+PASS URL: Setting <data:text/plain,Stuff>.host = 'example.net' Cannot-be-a-base means no host
+PASS URL: Setting <http://example.net>.host = 'example.com:8080'
+PASS URL: Setting <http://example.net:8080>.host = 'example.com' Port number is unchanged if not specified in the new value
+PASS URL: Setting <http://example.net:8080>.host = 'example.com:' Port number is unchanged if not specified
+PASS URL: Setting <http://example.net>.host = '' The empty host is not valid for special schemes
+FAIL URL: Setting <view-source+http://example.net/foo>.host = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL URL: Setting <a:/foo>.host = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+PASS URL: Setting <http://example.net>.host = '0x7F000001:8080' IPv4 address syntax is normalized
+PASS URL: Setting <http://example.net>.host = '[::0:01]:2' IPv6 address syntax is normalized
+PASS URL: Setting <http://example.net>.host = '[2001:db8::2]:4002' IPv6 literal address with port, crbug.com/1012416
+PASS URL: Setting <http://example.net>.host = 'example.com:80' Default port number is removed
+PASS URL: Setting <https://example.net>.host = 'example.com:443' Default port number is removed
+PASS URL: Setting <https://example.net>.host = 'example.com:80' Default port number is only removed for the relevant scheme
+PASS URL: Setting <http://example.net:8080>.host = 'example.com:80' Port number is removed if new port is scheme default and existing URL has a non-default port
+PASS URL: Setting <http://example.net/path>.host = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.host = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL URL: Setting <view-source+http://example.net/path>.host = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+FAIL URL: Setting <view-source+http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.com:8080/path" but got "view-source+http://example.net/path"
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.host = 'example.com:8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.host = 'example.com:65535' Port numbers are 16 bit integers
+PASS URL: Setting <http://example.net/path>.host = 'example.com:65536' Port numbers are 16 bit integers, overflowing is an error. Hostname is still set, though.
+PASS URL: Setting <http://example.net/>.host = '[google.com]' Broken IPv6
+PASS URL: Setting <http://example.net/>.host = '[::1.2.3.4x]'
+FAIL URL: Setting <http://example.net/>.host = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL URL: Setting <http://example.net/>.host = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL URL: Setting <http://example.net/>.host = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+FAIL URL: Setting <file://y/>.host = 'x:123' assert_equals: expected "file://y/" but got "file://x/"
+FAIL URL: Setting <file://y/>.host = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL URL: Setting <file://hi/x>.host = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL URL: Setting <sc://test@test/>.host = '' assert_equals: expected "test" but got ""
+FAIL URL: Setting <sc://test:12/>.host = '' assert_equals: expected "test:12" but got ""
+FAIL URL: Setting <sc://x/>.hostname = '\0' Non-special scheme assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.hostname = '	' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '
+' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '\r' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = ' ' assert_equals: expected "x" but got ""
+FAIL URL: Setting <sc://x/>.hostname = '#' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '/' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '?' assert_equals: expected "sc:///" but got "sc://x/"
+FAIL URL: Setting <sc://x/>.hostname = '@' assert_equals: expected "x" but got ""
+PASS URL: Setting <mailto:me@example.net>.hostname = 'example.com' Cannot-be-a-base means no host
+PASS URL: Setting <data:text/plain,Stuff>.hostname = 'example.net' Cannot-be-a-base means no host
+PASS URL: Setting <http://example.net:8080>.hostname = 'example.com'
+PASS URL: Setting <http://example.net>.hostname = '' The empty host is not valid for special schemes
+FAIL URL: Setting <view-source+http://example.net/foo>.hostname = '' The empty host is OK for non-special schemes assert_equals: expected "view-source+http:///foo" but got "view-source+http://example.net/foo"
+FAIL URL: Setting <a:/foo>.hostname = 'example.net' Path-only URLs can gain a host assert_equals: expected "a://example.net/foo" but got "a:/foo"
+PASS URL: Setting <http://example.net:8080>.hostname = '0x7F000001' IPv4 address syntax is normalized
+PASS URL: Setting <http://example.net>.hostname = '[::0:01]' IPv6 address syntax is normalized
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com:8080' : delimiter invalidates entire value
+PASS URL: Setting <http://example.net:8080/path>.hostname = 'example.com:' : delimiter invalidates entire value
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.hostname = 'example.com\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL URL: Setting <view-source+http://example.net/path>.hostname = 'example.com\stuff' \ is not a delimiter for non-special schemes, but still forbidden in hosts assert_equals: expected "example.net" but got ""
+PASS URL: Setting <http://example.net/>.hostname = '[google.com]' Broken IPv6
+PASS URL: Setting <http://example.net/>.hostname = '[::1.2.3.4x]'
+FAIL URL: Setting <http://example.net/>.hostname = '[::1.2.3.]' assert_equals: expected "http://example.net/" but got "http://[::102:3]/"
+FAIL URL: Setting <http://example.net/>.hostname = '[::1.2.]' assert_equals: expected "http://example.net/" but got "http://[::100:2]/"
+FAIL URL: Setting <http://example.net/>.hostname = '[::1.]' assert_equals: expected "http://example.net/" but got "http://[::1]/"
+PASS URL: Setting <file://y/>.hostname = 'x:123'
+FAIL URL: Setting <file://y/>.hostname = 'loc%41lhost' assert_equals: expected "file:///" but got "file://localhost/"
+FAIL URL: Setting <file://hi/x>.hostname = '' assert_equals: expected "file:///x" but got "file://hi/x"
+FAIL URL: Setting <sc://test@test/>.hostname = '' assert_equals: expected "test" but got ""
+FAIL URL: Setting <sc://test:12/>.hostname = '' assert_equals: expected "test:12" but got ""
+FAIL URL: Setting <non-spec:/.//p>.hostname = 'h' Drop /. from path assert_equals: expected "non-spec://h//p" but got "non-spec:/.//p"
+FAIL URL: Setting <non-spec:/.//p>.hostname = '' assert_equals: expected "non-spec:////p" but got "non-spec:/.//p"
+PASS URL: Setting <http://example.net>.port = '8080'
+PASS URL: Setting <http://example.net:8080>.port = '' Port number is removed if empty is the new value
+PASS URL: Setting <http://example.net:8080>.port = '80' Default port number is removed
+PASS URL: Setting <https://example.net:4433>.port = '443' Default port number is removed
+PASS URL: Setting <https://example.net>.port = '80' Default port number is only removed for the relevant scheme
+PASS URL: Setting <http://example.net/path>.port = '8080/stuff' Stuff after a / delimiter is ignored
+PASS URL: Setting <http://example.net/path>.port = '8080?stuff' Stuff after a ? delimiter is ignored
+PASS URL: Setting <http://example.net/path>.port = '8080#stuff' Stuff after a # delimiter is ignored
+PASS URL: Setting <http://example.net/path>.port = '8080\stuff' Stuff after a \ delimiter is ignored for special schemes
+FAIL URL: Setting <view-source+http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error assert_equals: expected "view-source+http://example.net:8080/path" but got "view-source+http://example.net/path"
+PASS URL: Setting <http://example.net/path>.port = '8080stuff2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.port = '8080+2' Anything other than ASCII digit stops the port parser in a setter but is not an error
+PASS URL: Setting <http://example.net/path>.port = '65535' Port numbers are 16 bit integers
+FAIL URL: Setting <http://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "http://example.net:8080/path" but got "http://example.net:0/path"
+FAIL URL: Setting <non-special://example.net:8080/path>.port = '65536' Port numbers are 16 bit integers, overflowing is an error assert_equals: expected "example.net:8080" but got ""
+PASS URL: Setting <file://test/>.port = '12'
+FAIL URL: Setting <file://localhost/>.port = '12' assert_equals: expected "file:///" but got "file://localhost/"
+PASS URL: Setting <non-base:value>.port = '12'
+PASS URL: Setting <sc:///>.port = '12'
+FAIL URL: Setting <sc://x/>.port = '12' assert_equals: expected "sc://x:12/" but got "sc://x/"
+FAIL URL: Setting <javascript://x/>.port = '12' assert_equals: expected "javascript://x:12/" but got "javascript://x/"
+PASS URL: Setting <mailto:me@example.net>.pathname = '/foo' Cannot-be-a-base don’t have a path
+FAIL URL: Setting <unix:/run/foo.socket?timeout=10>.pathname = '/var/log/../run/bar.socket' assert_equals: expected "unix:/var/run/bar.socket?timeout=10" but got "unix:/run/foo.socket?timeout=10"
+PASS URL: Setting <https://example.net#nav>.pathname = 'home'
+PASS URL: Setting <https://example.net#nav>.pathname = '../home'
+PASS URL: Setting <http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is a segment delimiter for 'special' URLs
+FAIL URL: Setting <view-source+http://example.net/home?lang=fr#nav>.pathname = '\a\%2E\b\%2e.\c' \ is *not* a segment delimiter for non-'special' URLs assert_equals: expected "view-source+http://example.net/\\a\\%2E\\b\\%2e.\\c?lang=fr#nav" but got "view-source+http://example.net/home?lang=fr#nav"
+FAIL URL: Setting <a:/>.pathname = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the default encode set. Tabs and newlines are removed. assert_equals: expected "a:/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/"
+FAIL URL: Setting <http://example.net>.pathname = '%2e%2E%c3%89té' Bytes already percent-encoded are left as-is, including %2E outside dotted segments. assert_equals: expected "http://example.net/%2e%2E%c3%89t%C3%A9" but got "http://example.net/..%c3%89t%C3%A9"
+PASS URL: Setting <http://example.net>.pathname = '?' ? needs to be encoded
+PASS URL: Setting <http://example.net>.pathname = '#' # needs to be encoded
+FAIL URL: Setting <sc://example.net>.pathname = '?' ? needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%3F" but got "sc://example.net"
+FAIL URL: Setting <sc://example.net>.pathname = '#' # needs to be encoded, non-special scheme assert_equals: expected "sc://example.net/%23" but got "sc://example.net"
+PASS URL: Setting <file://monkey/>.pathname = '\\' File URLs and (back)slashes
+PASS URL: Setting <file:///unicorn>.pathname = '//\/' File URLs and (back)slashes
+PASS URL: Setting <file:///unicorn>.pathname = '//monkey/..//' File URLs and (back)slashes
+FAIL URL: Setting <non-spec:/>.pathname = '/.//p' Serialize /. in path assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL URL: Setting <non-spec:/>.pathname = '/..//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL URL: Setting <non-spec:/>.pathname = '//p' assert_equals: expected "non-spec:/.//p" but got "non-spec:/"
+FAIL URL: Setting <non-spec:/.//>.pathname = 'p' Drop /. from path assert_equals: expected "non-spec:/p" but got "non-spec:/.//"
+PASS URL: Setting <https://example.net#nav>.search = 'lang=fr'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = 'lang=fr'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = '?lang=fr'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = '??lang=fr'
+FAIL URL: Setting <https://example.net?lang=en-US#nav>.search = '?' assert_equals: expected "https://example.net/?#nav" but got "https://example.net/#nav"
+PASS URL: Setting <https://example.net?lang=en-US#nav>.search = ''
+PASS URL: Setting <https://example.net?lang=en-US>.search = ''
+PASS URL: Setting <https://example.net>.search = ''
+FAIL URL: Setting <a:/>.search = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%09%0A%0D%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+PASS URL: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS URL: Setting <https://example.net>.hash = 'main'
+PASS URL: Setting <https://example.net#nav>.hash = 'main'
+PASS URL: Setting <https://example.net?lang=en-US>.hash = '##nav'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.hash = '#main'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.hash = '#'
+PASS URL: Setting <https://example.net?lang=en-US#nav>.hash = ''
+PASS URL: Setting <http://example.net>.hash = '#foo bar'
+PASS URL: Setting <http://example.net>.hash = '#foo"bar'
+PASS URL: Setting <http://example.net>.hash = '#foo<bar'
+PASS URL: Setting <http://example.net>.hash = '#foo>bar'
+PASS URL: Setting <http://example.net>.hash = '#foo`bar'
+FAIL URL: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+PASS URL: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS URL: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
+PASS URL: Setting <http://example.net>.hash = '%c3%89té' Bytes already percent-encoded are left as-is
+PASS URL: Setting <javascript:alert(1)>.hash = 'castle'
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/webrtc/RTCPeerConnection-mandatory-getStats.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/webrtc/RTCPeerConnection-mandatory-getStats.https-expected.txt
new file mode 100644
index 0000000..ce5ff0d0
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/webrtc/RTCPeerConnection-mandatory-getStats.https-expected.txt
@@ -0,0 +1,81 @@
+This is a testharness.js-based test.
+Found 77 tests; 70 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS getStats succeeds
+PASS Validating stats
+PASS RTCRtpStreamStats's ssrc
+PASS RTCRtpStreamStats's kind
+PASS RTCRtpStreamStats's transportId
+PASS RTCRtpStreamStats's codecId
+PASS RTCReceivedRtpStreamStats's packetsReceived
+PASS RTCReceivedRtpStreamStats's packetsLost
+PASS RTCReceivedRtpStreamStats's jitter
+FAIL RTCReceivedRtpStreamStats's packetsDiscarded assert_true: Is packetsDiscarded present expected true got false
+PASS RTCReceivedRtpStreamStats's framesDropped
+FAIL RTCInboundRtpStreamStats's receiverId assert_true: Is receiverId present expected true got false
+PASS RTCInboundRtpStreamStats's remoteId
+PASS RTCInboundRtpStreamStats's framesDecoded
+PASS RTCInboundRtpStreamStats's nackCount
+PASS RTCInboundRtpStreamStats's framesReceived
+PASS RTCInboundRtpStreamStats's bytesReceived
+PASS RTCInboundRtpStreamStats's totalAudioEnergy
+PASS RTCInboundRtpStreamStats's totalSamplesDuration
+PASS RTCRemoteInboundRtpStreamStats's localId
+PASS RTCRemoteInboundRtpStreamStats's roundTripTime
+PASS RTCSentRtpStreamStats's packetsSent
+PASS RTCSentRtpStreamStats's bytesSent
+FAIL RTCOutboundRtpStreamStats's senderId assert_true: Is senderId present expected true got false
+PASS RTCOutboundRtpStreamStats's remoteId
+PASS RTCOutboundRtpStreamStats's framesEncoded
+PASS RTCOutboundRtpStreamStats's nackCount
+PASS RTCOutboundRtpStreamStats's framesSent
+PASS RTCRemoteOutboundRtpStreamStats's localId
+PASS RTCRemoteOutboundRtpStreamStats's remoteTimestamp
+PASS RTCPeerConnectionStats's dataChannelsOpened
+PASS RTCPeerConnectionStats's dataChannelsClosed
+PASS RTCDataChannelStats's label
+PASS RTCDataChannelStats's protocol
+PASS RTCDataChannelStats's dataChannelIdentifier
+PASS RTCDataChannelStats's state
+PASS RTCDataChannelStats's messagesSent
+PASS RTCDataChannelStats's bytesSent
+PASS RTCDataChannelStats's messagesReceived
+PASS RTCDataChannelStats's bytesReceived
+PASS RTCMediaSourceStats's trackIdentifier
+PASS RTCMediaSourceStats's kind
+PASS RTCAudioSourceStats's totalAudioEnergy
+PASS RTCAudioSourceStats's totalSamplesDuration
+PASS RTCVideoSourceStats's width
+PASS RTCVideoSourceStats's height
+PASS RTCVideoSourceStats's framesPerSecond
+FAIL RTCMediaHandlerStats's trackIdentifier assert_true: Is trackIdentifier present expected true got false
+PASS RTCCodecStats's payloadType
+FAIL RTCCodecStats's codecType assert_true: Is codecType present expected true got false
+PASS RTCCodecStats's mimeType
+PASS RTCCodecStats's clockRate
+PASS RTCCodecStats's channels
+PASS RTCCodecStats's sdpFmtpLine
+PASS RTCTransportStats's bytesSent
+PASS RTCTransportStats's bytesReceived
+PASS RTCTransportStats's selectedCandidatePairId
+PASS RTCTransportStats's localCertificateId
+PASS RTCTransportStats's remoteCertificateId
+PASS RTCIceCandidatePairStats's transportId
+PASS RTCIceCandidatePairStats's localCandidateId
+PASS RTCIceCandidatePairStats's remoteCandidateId
+PASS RTCIceCandidatePairStats's state
+PASS RTCIceCandidatePairStats's nominated
+PASS RTCIceCandidatePairStats's bytesSent
+PASS RTCIceCandidatePairStats's bytesReceived
+PASS RTCIceCandidatePairStats's totalRoundTripTime
+PASS RTCIceCandidatePairStats's currentRoundTripTime
+PASS RTCIceCandidateStats's address
+PASS RTCIceCandidateStats's port
+PASS RTCIceCandidateStats's protocol
+PASS RTCIceCandidateStats's candidateType
+FAIL RTCIceCandidateStats's url assert_true: Is url present expected true got false
+PASS RTCCertificateStats's fingerprint
+PASS RTCCertificateStats's fingerprintAlgorithm
+PASS RTCCertificateStats's base64Certificate
+FAIL RTCCertificateStats's issuerCertificateId assert_true: Is issuerCertificateId present expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/webrtc/RTCPeerConnection-setLocalDescription-pranswer-expected.txt b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/webrtc/RTCPeerConnection-setLocalDescription-pranswer-expected.txt
new file mode 100644
index 0000000..f514bf73
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/webrtc/RTCPeerConnection-setLocalDescription-pranswer-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL setLocalDescription(pranswer) from stable state should reject with InvalidStateError promise_rejects_dom: function "function() { throw e }" threw object "OperationError: Failed to execute 'setLocalDescription' on 'RTCPeerConnection': Failed to set local pranswer sdp: Called in wrong state: stable" that is not a DOMException InvalidStateError: property "code" is equal to 0, expected 11
+FAIL setLocalDescription(pranswer) should succeed assert_equals: expected null but got object "[object RTCSessionDescription]"
+PASS setLocalDescription(pranswer) can be applied multiple times while still in have-local-pranswer
+PASS setLocalDescription(answer) from have-local-pranswer state should succeed
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/webxr/idlharness.https.window-expected.txt b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/webxr/idlharness.https.window-expected.txt
new file mode 100644
index 0000000..bd78477
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/external/wpt/webxr/idlharness.https.window-expected.txt
@@ -0,0 +1,285 @@
+This is a testharness.js-based test.
+Found 281 tests; 270 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS idl_test setup
+PASS idl_test validation
+PASS Partial interface Navigator: original interface defined
+PASS Partial interface Navigator: member names are unique
+PASS Partial dictionary WebGLContextAttributes: original dictionary defined
+PASS Partial dictionary WebGLContextAttributes: member names are unique
+PASS Partial interface mixin WebGLRenderingContextBase: original interface mixin defined
+PASS Partial interface mixin WebGLRenderingContextBase: member names are unique
+PASS Partial interface Navigator[2]: member names are unique
+PASS Partial interface mixin NavigatorID: member names are unique
+FAIL WebGLRenderingContext includes WebGLRenderingContextBase: member names are unique assert_true: member canvas is unique expected true got false
+FAIL WebGLRenderingContext includes WebGLRenderingContextOverloads: member names are unique assert_true: member bufferData is unique expected true got false
+PASS Navigator includes NavigatorID: member names are unique
+PASS Navigator includes NavigatorLanguage: member names are unique
+PASS Navigator includes NavigatorOnLine: member names are unique
+PASS Navigator includes NavigatorContentUtils: member names are unique
+PASS Navigator includes NavigatorCookies: member names are unique
+PASS Navigator includes NavigatorPlugins: member names are unique
+PASS Navigator includes NavigatorConcurrentHardware: member names are unique
+PASS XRSystem interface: existence and properties of interface object
+PASS XRSystem interface object length
+PASS XRSystem interface object name
+PASS XRSystem interface: existence and properties of interface prototype object
+PASS XRSystem interface: existence and properties of interface prototype object's "constructor" property
+PASS XRSystem interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRSystem interface: operation isSessionSupported(XRSessionMode)
+PASS XRSystem interface: operation requestSession(XRSessionMode, optional XRSessionInit)
+PASS XRSystem interface: attribute ondevicechange
+PASS XRSession interface: existence and properties of interface object
+PASS XRSession interface object length
+PASS XRSession interface object name
+PASS XRSession interface: existence and properties of interface prototype object
+PASS XRSession interface: existence and properties of interface prototype object's "constructor" property
+PASS XRSession interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRSession interface: attribute visibilityState
+PASS XRSession interface: attribute renderState
+PASS XRSession interface: attribute inputSources
+PASS XRSession interface: operation updateRenderState(optional XRRenderStateInit)
+PASS XRSession interface: operation requestReferenceSpace(XRReferenceSpaceType)
+PASS XRSession interface: operation requestAnimationFrame(XRFrameRequestCallback)
+PASS XRSession interface: operation cancelAnimationFrame(unsigned long)
+PASS XRSession interface: operation end()
+PASS XRSession interface: attribute onend
+PASS XRSession interface: attribute oninputsourceschange
+PASS XRSession interface: attribute onselect
+PASS XRSession interface: attribute onselectstart
+PASS XRSession interface: attribute onselectend
+PASS XRSession interface: attribute onsqueeze
+PASS XRSession interface: attribute onsqueezestart
+PASS XRSession interface: attribute onsqueezeend
+PASS XRSession interface: attribute onvisibilitychange
+PASS XRSession must be primary interface of xrSession
+PASS Stringification of xrSession
+PASS XRSession interface: xrSession must inherit property "visibilityState" with the proper type
+PASS XRSession interface: xrSession must inherit property "renderState" with the proper type
+PASS XRSession interface: xrSession must inherit property "inputSources" with the proper type
+PASS XRSession interface: xrSession must inherit property "updateRenderState(optional XRRenderStateInit)" with the proper type
+PASS XRSession interface: calling updateRenderState(optional XRRenderStateInit) on xrSession with too few arguments must throw TypeError
+PASS XRSession interface: xrSession must inherit property "requestReferenceSpace(XRReferenceSpaceType)" with the proper type
+PASS XRSession interface: calling requestReferenceSpace(XRReferenceSpaceType) on xrSession with too few arguments must throw TypeError
+PASS XRSession interface: xrSession must inherit property "requestAnimationFrame(XRFrameRequestCallback)" with the proper type
+PASS XRSession interface: calling requestAnimationFrame(XRFrameRequestCallback) on xrSession with too few arguments must throw TypeError
+PASS XRSession interface: xrSession must inherit property "cancelAnimationFrame(unsigned long)" with the proper type
+PASS XRSession interface: calling cancelAnimationFrame(unsigned long) on xrSession with too few arguments must throw TypeError
+PASS XRSession interface: xrSession must inherit property "end()" with the proper type
+PASS XRSession interface: xrSession must inherit property "onend" with the proper type
+PASS XRSession interface: xrSession must inherit property "oninputsourceschange" with the proper type
+PASS XRSession interface: xrSession must inherit property "onselect" with the proper type
+PASS XRSession interface: xrSession must inherit property "onselectstart" with the proper type
+PASS XRSession interface: xrSession must inherit property "onselectend" with the proper type
+PASS XRSession interface: xrSession must inherit property "onsqueeze" with the proper type
+PASS XRSession interface: xrSession must inherit property "onsqueezestart" with the proper type
+PASS XRSession interface: xrSession must inherit property "onsqueezeend" with the proper type
+PASS XRSession interface: xrSession must inherit property "onvisibilitychange" with the proper type
+PASS XRRenderState interface: existence and properties of interface object
+PASS XRRenderState interface object length
+PASS XRRenderState interface object name
+PASS XRRenderState interface: existence and properties of interface prototype object
+PASS XRRenderState interface: existence and properties of interface prototype object's "constructor" property
+PASS XRRenderState interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRRenderState interface: attribute depthNear
+PASS XRRenderState interface: attribute depthFar
+PASS XRRenderState interface: attribute inlineVerticalFieldOfView
+PASS XRRenderState interface: attribute baseLayer
+PASS XRRenderState must be primary interface of xrRenderState
+PASS Stringification of xrRenderState
+PASS XRRenderState interface: xrRenderState must inherit property "depthNear" with the proper type
+PASS XRRenderState interface: xrRenderState must inherit property "depthFar" with the proper type
+PASS XRRenderState interface: xrRenderState must inherit property "inlineVerticalFieldOfView" with the proper type
+PASS XRRenderState interface: xrRenderState must inherit property "baseLayer" with the proper type
+PASS XRFrame interface: existence and properties of interface object
+PASS XRFrame interface object length
+PASS XRFrame interface object name
+PASS XRFrame interface: existence and properties of interface prototype object
+PASS XRFrame interface: existence and properties of interface prototype object's "constructor" property
+PASS XRFrame interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRFrame interface: attribute session
+PASS XRFrame interface: operation getViewerPose(XRReferenceSpace)
+PASS XRFrame interface: operation getPose(XRSpace, XRSpace)
+PASS XRSpace interface: existence and properties of interface object
+PASS XRSpace interface object length
+PASS XRSpace interface object name
+PASS XRSpace interface: existence and properties of interface prototype object
+PASS XRSpace interface: existence and properties of interface prototype object's "constructor" property
+PASS XRSpace interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRReferenceSpace interface: existence and properties of interface object
+PASS XRReferenceSpace interface object length
+PASS XRReferenceSpace interface object name
+PASS XRReferenceSpace interface: existence and properties of interface prototype object
+PASS XRReferenceSpace interface: existence and properties of interface prototype object's "constructor" property
+PASS XRReferenceSpace interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRReferenceSpace interface: operation getOffsetReferenceSpace(XRRigidTransform)
+PASS XRReferenceSpace interface: attribute onreset
+PASS XRReferenceSpace must be primary interface of xrReferenceSpace
+PASS Stringification of xrReferenceSpace
+PASS XRReferenceSpace interface: xrReferenceSpace must inherit property "getOffsetReferenceSpace(XRRigidTransform)" with the proper type
+PASS XRReferenceSpace interface: calling getOffsetReferenceSpace(XRRigidTransform) on xrReferenceSpace with too few arguments must throw TypeError
+PASS XRReferenceSpace interface: xrReferenceSpace must inherit property "onreset" with the proper type
+PASS XRBoundedReferenceSpace interface: existence and properties of interface object
+PASS XRBoundedReferenceSpace interface object length
+PASS XRBoundedReferenceSpace interface object name
+PASS XRBoundedReferenceSpace interface: existence and properties of interface prototype object
+PASS XRBoundedReferenceSpace interface: existence and properties of interface prototype object's "constructor" property
+PASS XRBoundedReferenceSpace interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRBoundedReferenceSpace interface: attribute boundsGeometry
+PASS XRView interface: existence and properties of interface object
+PASS XRView interface object length
+PASS XRView interface object name
+PASS XRView interface: existence and properties of interface prototype object
+PASS XRView interface: existence and properties of interface prototype object's "constructor" property
+PASS XRView interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRView interface: attribute eye
+PASS XRView interface: attribute projectionMatrix
+PASS XRView interface: attribute transform
+PASS XRView interface: attribute recommendedViewportScale
+PASS XRView interface: operation requestViewportScale(double?)
+PASS XRViewport interface: existence and properties of interface object
+PASS XRViewport interface object length
+PASS XRViewport interface object name
+PASS XRViewport interface: existence and properties of interface prototype object
+PASS XRViewport interface: existence and properties of interface prototype object's "constructor" property
+PASS XRViewport interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRViewport interface: attribute x
+PASS XRViewport interface: attribute y
+PASS XRViewport interface: attribute width
+PASS XRViewport interface: attribute height
+PASS XRRigidTransform interface: existence and properties of interface object
+PASS XRRigidTransform interface object length
+PASS XRRigidTransform interface object name
+PASS XRRigidTransform interface: existence and properties of interface prototype object
+PASS XRRigidTransform interface: existence and properties of interface prototype object's "constructor" property
+PASS XRRigidTransform interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRRigidTransform interface: attribute position
+PASS XRRigidTransform interface: attribute orientation
+PASS XRRigidTransform interface: attribute matrix
+PASS XRRigidTransform interface: attribute inverse
+PASS XRRigidTransform must be primary interface of new XRRigidTransform()
+PASS Stringification of new XRRigidTransform()
+PASS XRRigidTransform interface: new XRRigidTransform() must inherit property "position" with the proper type
+PASS XRRigidTransform interface: new XRRigidTransform() must inherit property "orientation" with the proper type
+PASS XRRigidTransform interface: new XRRigidTransform() must inherit property "matrix" with the proper type
+PASS XRRigidTransform interface: new XRRigidTransform() must inherit property "inverse" with the proper type
+PASS XRPose interface: existence and properties of interface object
+PASS XRPose interface object length
+PASS XRPose interface object name
+PASS XRPose interface: existence and properties of interface prototype object
+PASS XRPose interface: existence and properties of interface prototype object's "constructor" property
+PASS XRPose interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRPose interface: attribute transform
+FAIL XRPose interface: attribute linearVelocity assert_true: The prototype object must have a property "linearVelocity" expected true got false
+FAIL XRPose interface: attribute angularVelocity assert_true: The prototype object must have a property "angularVelocity" expected true got false
+PASS XRPose interface: attribute emulatedPosition
+PASS XRViewerPose interface: existence and properties of interface object
+PASS XRViewerPose interface object length
+PASS XRViewerPose interface object name
+PASS XRViewerPose interface: existence and properties of interface prototype object
+PASS XRViewerPose interface: existence and properties of interface prototype object's "constructor" property
+PASS XRViewerPose interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRViewerPose interface: attribute views
+PASS XRInputSource interface: existence and properties of interface object
+PASS XRInputSource interface object length
+PASS XRInputSource interface object name
+PASS XRInputSource interface: existence and properties of interface prototype object
+PASS XRInputSource interface: existence and properties of interface prototype object's "constructor" property
+PASS XRInputSource interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRInputSource interface: attribute handedness
+PASS XRInputSource interface: attribute targetRayMode
+PASS XRInputSource interface: attribute targetRaySpace
+PASS XRInputSource interface: attribute gripSpace
+PASS XRInputSource interface: attribute profiles
+PASS XRInputSourceArray interface: existence and properties of interface object
+PASS XRInputSourceArray interface object length
+PASS XRInputSourceArray interface object name
+PASS XRInputSourceArray interface: existence and properties of interface prototype object
+PASS XRInputSourceArray interface: existence and properties of interface prototype object's "constructor" property
+PASS XRInputSourceArray interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRInputSourceArray interface: iterable<XRInputSource>
+PASS XRInputSourceArray interface: attribute length
+PASS XRInputSourceArray must be primary interface of xrInputSourceArray
+PASS Stringification of xrInputSourceArray
+PASS XRInputSourceArray interface: xrInputSourceArray must inherit property "length" with the proper type
+PASS XRLayer interface: existence and properties of interface object
+PASS XRLayer interface object length
+PASS XRLayer interface object name
+PASS XRLayer interface: existence and properties of interface prototype object
+PASS XRLayer interface: existence and properties of interface prototype object's "constructor" property
+PASS XRLayer interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRWebGLLayer interface: existence and properties of interface object
+PASS XRWebGLLayer interface object length
+PASS XRWebGLLayer interface object name
+PASS XRWebGLLayer interface: existence and properties of interface prototype object
+PASS XRWebGLLayer interface: existence and properties of interface prototype object's "constructor" property
+PASS XRWebGLLayer interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRWebGLLayer interface: attribute antialias
+PASS XRWebGLLayer interface: attribute ignoreDepthValues
+PASS XRWebGLLayer interface: attribute framebuffer
+PASS XRWebGLLayer interface: attribute framebufferWidth
+PASS XRWebGLLayer interface: attribute framebufferHeight
+PASS XRWebGLLayer interface: operation getViewport(XRView)
+PASS XRWebGLLayer interface: operation getNativeFramebufferScaleFactor(XRSession)
+PASS XRWebGLLayer must be primary interface of xrWebGLLayer
+PASS Stringification of xrWebGLLayer
+PASS XRWebGLLayer interface: xrWebGLLayer must inherit property "antialias" with the proper type
+PASS XRWebGLLayer interface: xrWebGLLayer must inherit property "ignoreDepthValues" with the proper type
+PASS XRWebGLLayer interface: xrWebGLLayer must inherit property "framebuffer" with the proper type
+PASS XRWebGLLayer interface: xrWebGLLayer must inherit property "framebufferWidth" with the proper type
+PASS XRWebGLLayer interface: xrWebGLLayer must inherit property "framebufferHeight" with the proper type
+PASS XRWebGLLayer interface: xrWebGLLayer must inherit property "getViewport(XRView)" with the proper type
+PASS XRWebGLLayer interface: calling getViewport(XRView) on xrWebGLLayer with too few arguments must throw TypeError
+PASS XRWebGLLayer interface: xrWebGLLayer must inherit property "getNativeFramebufferScaleFactor(XRSession)" with the proper type
+PASS XRWebGLLayer interface: calling getNativeFramebufferScaleFactor(XRSession) on xrWebGLLayer with too few arguments must throw TypeError
+PASS XRSessionEvent interface: existence and properties of interface object
+PASS XRSessionEvent interface object length
+PASS XRSessionEvent interface object name
+PASS XRSessionEvent interface: existence and properties of interface prototype object
+PASS XRSessionEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS XRSessionEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRSessionEvent interface: attribute session
+PASS XRSessionEvent must be primary interface of xrSessionEvent
+PASS Stringification of xrSessionEvent
+PASS XRSessionEvent interface: xrSessionEvent must inherit property "session" with the proper type
+PASS XRInputSourceEvent interface: existence and properties of interface object
+PASS XRInputSourceEvent interface object length
+PASS XRInputSourceEvent interface object name
+PASS XRInputSourceEvent interface: existence and properties of interface prototype object
+PASS XRInputSourceEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS XRInputSourceEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRInputSourceEvent interface: attribute frame
+PASS XRInputSourceEvent interface: attribute inputSource
+PASS XRInputSourcesChangeEvent interface: existence and properties of interface object
+PASS XRInputSourcesChangeEvent interface object length
+PASS XRInputSourcesChangeEvent interface object name
+PASS XRInputSourcesChangeEvent interface: existence and properties of interface prototype object
+PASS XRInputSourcesChangeEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS XRInputSourcesChangeEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRInputSourcesChangeEvent interface: attribute session
+PASS XRInputSourcesChangeEvent interface: attribute added
+PASS XRInputSourcesChangeEvent interface: attribute removed
+PASS XRInputSourcesChangeEvent must be primary interface of xrInputSourcesChangeEvent
+PASS Stringification of xrInputSourcesChangeEvent
+PASS XRInputSourcesChangeEvent interface: xrInputSourcesChangeEvent must inherit property "session" with the proper type
+PASS XRInputSourcesChangeEvent interface: xrInputSourcesChangeEvent must inherit property "added" with the proper type
+PASS XRInputSourcesChangeEvent interface: xrInputSourcesChangeEvent must inherit property "removed" with the proper type
+PASS XRReferenceSpaceEvent interface: existence and properties of interface object
+PASS XRReferenceSpaceEvent interface object length
+PASS XRReferenceSpaceEvent interface object name
+PASS XRReferenceSpaceEvent interface: existence and properties of interface prototype object
+PASS XRReferenceSpaceEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS XRReferenceSpaceEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRReferenceSpaceEvent interface: attribute referenceSpace
+PASS XRReferenceSpaceEvent interface: attribute transform
+FAIL XRPermissionStatus interface: existence and properties of interface object assert_own_property: self does not have own property "XRPermissionStatus" expected property "XRPermissionStatus" missing
+FAIL XRPermissionStatus interface object length assert_own_property: self does not have own property "XRPermissionStatus" expected property "XRPermissionStatus" missing
+FAIL XRPermissionStatus interface object name assert_own_property: self does not have own property "XRPermissionStatus" expected property "XRPermissionStatus" missing
+FAIL XRPermissionStatus interface: existence and properties of interface prototype object assert_own_property: self does not have own property "XRPermissionStatus" expected property "XRPermissionStatus" missing
+FAIL XRPermissionStatus interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "XRPermissionStatus" expected property "XRPermissionStatus" missing
+FAIL XRPermissionStatus interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "XRPermissionStatus" expected property "XRPermissionStatus" missing
+FAIL XRPermissionStatus interface: attribute granted assert_own_property: self does not have own property "XRPermissionStatus" expected property "XRPermissionStatus" missing
+PASS WebGLRenderingContextBase interface: webGLRenderingContextBase must inherit property "makeXRCompatible()" with the proper type
+PASS Navigator interface: attribute xr
+PASS Navigator interface: navigator must inherit property "xr" with the proper type
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-mandatory-getStats.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac-arm11.0/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-mandatory-getStats.https-expected.txt
new file mode 100644
index 0000000..ce5ff0d0
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-mandatory-getStats.https-expected.txt
@@ -0,0 +1,81 @@
+This is a testharness.js-based test.
+Found 77 tests; 70 PASS, 7 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS getStats succeeds
+PASS Validating stats
+PASS RTCRtpStreamStats's ssrc
+PASS RTCRtpStreamStats's kind
+PASS RTCRtpStreamStats's transportId
+PASS RTCRtpStreamStats's codecId
+PASS RTCReceivedRtpStreamStats's packetsReceived
+PASS RTCReceivedRtpStreamStats's packetsLost
+PASS RTCReceivedRtpStreamStats's jitter
+FAIL RTCReceivedRtpStreamStats's packetsDiscarded assert_true: Is packetsDiscarded present expected true got false
+PASS RTCReceivedRtpStreamStats's framesDropped
+FAIL RTCInboundRtpStreamStats's receiverId assert_true: Is receiverId present expected true got false
+PASS RTCInboundRtpStreamStats's remoteId
+PASS RTCInboundRtpStreamStats's framesDecoded
+PASS RTCInboundRtpStreamStats's nackCount
+PASS RTCInboundRtpStreamStats's framesReceived
+PASS RTCInboundRtpStreamStats's bytesReceived
+PASS RTCInboundRtpStreamStats's totalAudioEnergy
+PASS RTCInboundRtpStreamStats's totalSamplesDuration
+PASS RTCRemoteInboundRtpStreamStats's localId
+PASS RTCRemoteInboundRtpStreamStats's roundTripTime
+PASS RTCSentRtpStreamStats's packetsSent
+PASS RTCSentRtpStreamStats's bytesSent
+FAIL RTCOutboundRtpStreamStats's senderId assert_true: Is senderId present expected true got false
+PASS RTCOutboundRtpStreamStats's remoteId
+PASS RTCOutboundRtpStreamStats's framesEncoded
+PASS RTCOutboundRtpStreamStats's nackCount
+PASS RTCOutboundRtpStreamStats's framesSent
+PASS RTCRemoteOutboundRtpStreamStats's localId
+PASS RTCRemoteOutboundRtpStreamStats's remoteTimestamp
+PASS RTCPeerConnectionStats's dataChannelsOpened
+PASS RTCPeerConnectionStats's dataChannelsClosed
+PASS RTCDataChannelStats's label
+PASS RTCDataChannelStats's protocol
+PASS RTCDataChannelStats's dataChannelIdentifier
+PASS RTCDataChannelStats's state
+PASS RTCDataChannelStats's messagesSent
+PASS RTCDataChannelStats's bytesSent
+PASS RTCDataChannelStats's messagesReceived
+PASS RTCDataChannelStats's bytesReceived
+PASS RTCMediaSourceStats's trackIdentifier
+PASS RTCMediaSourceStats's kind
+PASS RTCAudioSourceStats's totalAudioEnergy
+PASS RTCAudioSourceStats's totalSamplesDuration
+PASS RTCVideoSourceStats's width
+PASS RTCVideoSourceStats's height
+PASS RTCVideoSourceStats's framesPerSecond
+FAIL RTCMediaHandlerStats's trackIdentifier assert_true: Is trackIdentifier present expected true got false
+PASS RTCCodecStats's payloadType
+FAIL RTCCodecStats's codecType assert_true: Is codecType present expected true got false
+PASS RTCCodecStats's mimeType
+PASS RTCCodecStats's clockRate
+PASS RTCCodecStats's channels
+PASS RTCCodecStats's sdpFmtpLine
+PASS RTCTransportStats's bytesSent
+PASS RTCTransportStats's bytesReceived
+PASS RTCTransportStats's selectedCandidatePairId
+PASS RTCTransportStats's localCertificateId
+PASS RTCTransportStats's remoteCertificateId
+PASS RTCIceCandidatePairStats's transportId
+PASS RTCIceCandidatePairStats's localCandidateId
+PASS RTCIceCandidatePairStats's remoteCandidateId
+PASS RTCIceCandidatePairStats's state
+PASS RTCIceCandidatePairStats's nominated
+PASS RTCIceCandidatePairStats's bytesSent
+PASS RTCIceCandidatePairStats's bytesReceived
+PASS RTCIceCandidatePairStats's totalRoundTripTime
+PASS RTCIceCandidatePairStats's currentRoundTripTime
+PASS RTCIceCandidateStats's address
+PASS RTCIceCandidateStats's port
+PASS RTCIceCandidateStats's protocol
+PASS RTCIceCandidateStats's candidateType
+FAIL RTCIceCandidateStats's url assert_true: Is url present expected true got false
+PASS RTCCertificateStats's fingerprint
+PASS RTCCertificateStats's fingerprintAlgorithm
+PASS RTCCertificateStats's base64Certificate
+FAIL RTCCertificateStats's issuerCertificateId assert_true: Is issuerCertificateId present expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac-arm11.0/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-setLocalDescription-pranswer-expected.txt b/third_party/blink/web_tests/platform/mac-mac-arm11.0/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-setLocalDescription-pranswer-expected.txt
new file mode 100644
index 0000000..f514bf73
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac-arm11.0/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-setLocalDescription-pranswer-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL setLocalDescription(pranswer) from stable state should reject with InvalidStateError promise_rejects_dom: function "function() { throw e }" threw object "OperationError: Failed to execute 'setLocalDescription' on 'RTCPeerConnection': Failed to set local pranswer sdp: Called in wrong state: stable" that is not a DOMException InvalidStateError: property "code" is equal to 0, expected 11
+FAIL setLocalDescription(pranswer) should succeed assert_equals: expected null but got object "[object RTCSessionDescription]"
+PASS setLocalDescription(pranswer) can be applied multiple times while still in have-local-pranswer
+PASS setLocalDescription(answer) from have-local-pranswer state should succeed
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac-mac10.15/virtual/layout_ng_svg_text/paint/invalidation/svg/animated-path-inside-transformed-html-expected.png b/third_party/blink/web_tests/platform/mac-mac10.15/virtual/layout_ng_svg_text/paint/invalidation/svg/animated-path-inside-transformed-html-expected.png
index 0e5d90b..26dc97b 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.15/virtual/layout_ng_svg_text/paint/invalidation/svg/animated-path-inside-transformed-html-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.15/virtual/layout_ng_svg_text/paint/invalidation/svg/animated-path-inside-transformed-html-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.15/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1-SE/text-tspan-02-b-expected.png b/third_party/blink/web_tests/platform/mac-mac10.15/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1-SE/text-tspan-02-b-expected.png
index 32822cfa..39f8b30 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.15/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1-SE/text-tspan-02-b-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.15/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1-SE/text-tspan-02-b-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.15/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/animate-elem-40-t-expected.png b/third_party/blink/web_tests/platform/mac-mac10.15/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/animate-elem-40-t-expected.png
new file mode 100644
index 0000000..c130a33
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.15/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/animate-elem-40-t-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.15/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/metadata-example-01-b-expected.png b/third_party/blink/web_tests/platform/mac-mac10.15/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/metadata-example-01-b-expected.png
new file mode 100644
index 0000000..f6ce9b5
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.15/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/metadata-example-01-b-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters-a-area.window-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters-a-area.window-expected.txt
index 21d9d9d..9a71787 100644
--- a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters-a-area.window-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters-a-area.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 413 tests; 253 PASS, 160 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 413 tests; 255 PASS, 158 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS <a>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
 PASS <area>: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
@@ -388,9 +388,9 @@
 PASS <a>: Setting <https://example.net>.search = ''
 PASS <area>: Setting <https://example.net>.search = ''
 FAIL <a>: Setting <a:/>.search = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%09%0A%0D%1F !\"#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%1F !\"#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 FAIL <area>: Setting <a:/>.search = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%09%0A%0D%1F !\"#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%1F !\"#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 PASS <a>: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
 PASS <area>: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
 PASS <a>: Setting <https://example.net>.hash = 'main'
@@ -415,10 +415,10 @@
 PASS <area>: Setting <http://example.net>.hash = '#foo>bar'
 PASS <a>: Setting <http://example.net>.hash = '#foo`bar'
 PASS <area>: Setting <http://example.net>.hash = '#foo`bar'
-FAIL <a>: Setting <a:/>.hash = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
-FAIL <area>: Setting <a:/>.hash = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+PASS <a>: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed
+PASS <area>: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed
 PASS <a>: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS <area>: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS <a>: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any-expected.txt
index 326d569..92293f7 100644
--- a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 207 tests; 129 PASS, 78 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 207 tests; 130 PASS, 77 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
 PASS URL: Setting <a://example.net>.protocol = 'b'
@@ -195,7 +195,7 @@
 PASS URL: Setting <https://example.net?lang=en-US>.search = ''
 PASS URL: Setting <https://example.net>.search = ''
 FAIL URL: Setting <a:/>.search = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%09%0A%0D%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 PASS URL: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
 PASS URL: Setting <https://example.net>.hash = 'main'
 PASS URL: Setting <https://example.net#nav>.hash = 'main'
@@ -208,8 +208,8 @@
 PASS URL: Setting <http://example.net>.hash = '#foo<bar'
 PASS URL: Setting <http://example.net>.hash = '#foo>bar'
 PASS URL: Setting <http://example.net>.hash = '#foo`bar'
-FAIL URL: Setting <a:/>.hash = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+PASS URL: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed
 PASS URL: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS URL: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS URL: Setting <http://example.net>.hash = '%c3%89té' Bytes already percent-encoded are left as-is
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any.worker-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any.worker-expected.txt
index 326d569..92293f7 100644
--- a/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any.worker-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/url/url-setters.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 207 tests; 129 PASS, 78 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 207 tests; 130 PASS, 77 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS URL: Setting <a://example.net>.protocol = '' The empty string is not a valid scheme. Setter leaves the URL unchanged.
 PASS URL: Setting <a://example.net>.protocol = 'b'
@@ -195,7 +195,7 @@
 PASS URL: Setting <https://example.net?lang=en-US>.search = ''
 PASS URL: Setting <https://example.net>.search = ''
 FAIL URL: Setting <a:/>.search = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%09%0A%0D%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/?%00%01%1F !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 PASS URL: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
 PASS URL: Setting <https://example.net>.hash = 'main'
 PASS URL: Setting <https://example.net#nav>.hash = 'main'
@@ -208,8 +208,8 @@
 PASS URL: Setting <http://example.net>.hash = '#foo<bar'
 PASS URL: Setting <http://example.net>.hash = '#foo>bar'
 PASS URL: Setting <http://example.net>.hash = '#foo`bar'
-FAIL URL: Setting <a:/>.hash = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "a:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+PASS URL: Setting <a:/>.hash = '\0	
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed
 PASS URL: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS URL: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS URL: Setting <http://example.net>.hash = '%c3%89té' Bytes already percent-encoded are left as-is
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters-a-area.window-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters-a-area.window-expected.txt
index 94720af..0ff51f4 100644
--- a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters-a-area.window-expected.txt
+++ b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters-a-area.window-expected.txt
@@ -388,9 +388,9 @@
 PASS <a>: Setting <https://example.net>.search = ''
 PASS <area>: Setting <https://example.net>.search = ''
 FAIL <a>: Setting <a:/>.search = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/?%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/?%00%01%1F%20!%22%23$%&%27()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 FAIL <area>: Setting <a:/>.search = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/?%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/?%00%01%1F%20!%22%23$%&%27()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 PASS <a>: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
 PASS <area>: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
 PASS <a>: Setting <https://example.net>.hash = 'main'
@@ -416,9 +416,9 @@
 PASS <a>: Setting <http://example.net>.hash = '#foo`bar'
 PASS <area>: Setting <http://example.net>.hash = '#foo`bar'
 FAIL <a>: Setting <a:/>.hash = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 FAIL <area>: Setting <a:/>.hash = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 PASS <a>: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS <area>: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS <a>: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any-expected.txt
index 69a42e7..27cceba 100644
--- a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any-expected.txt
+++ b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any-expected.txt
@@ -195,7 +195,7 @@
 PASS URL: Setting <https://example.net?lang=en-US>.search = ''
 PASS URL: Setting <https://example.net>.search = ''
 FAIL URL: Setting <a:/>.search = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/?%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/?%00%01%1F%20!%22%23$%&%27()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 PASS URL: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
 PASS URL: Setting <https://example.net>.hash = 'main'
 PASS URL: Setting <https://example.net#nav>.hash = 'main'
@@ -209,7 +209,7 @@
 PASS URL: Setting <http://example.net>.hash = '#foo>bar'
 PASS URL: Setting <http://example.net>.hash = '#foo`bar'
 FAIL URL: Setting <a:/>.hash = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 PASS URL: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS URL: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS URL: Setting <http://example.net>.hash = '%c3%89té' Bytes already percent-encoded are left as-is
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker-expected.txt
index 69a42e7..27cceba 100644
--- a/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker-expected.txt
+++ b/third_party/blink/web_tests/platform/win/external/wpt/url/url-setters.any.worker-expected.txt
@@ -195,7 +195,7 @@
 PASS URL: Setting <https://example.net?lang=en-US>.search = ''
 PASS URL: Setting <https://example.net>.search = ''
 FAIL URL: Setting <a:/>.search = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/?%00%01%09%0A%0D%1F%20!%22%23$%&%27()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' UTF-8 percent encoding with the query encode set. Tabs and newlines are removed. assert_equals: expected "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/?%00%01%1F%20!%22%23$%&%27()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 PASS URL: Setting <http://example.net>.search = '%c3%89té' Bytes already percent-encoded are left as-is
 PASS URL: Setting <https://example.net>.hash = 'main'
 PASS URL: Setting <https://example.net#nav>.hash = 'main'
@@ -209,7 +209,7 @@
 PASS URL: Setting <http://example.net>.hash = '#foo>bar'
 PASS URL: Setting <http://example.net>.hash = '#foo`bar'
 FAIL URL: Setting <a:/>.hash = '\0	
-\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/#%00%01%09%0A%0D%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+\r !"#$%&'()*+,-./09:;<=>?@AZ[\]^_`az{|}~€Éé' Simple percent-encoding; tabs and newlines are removed assert_equals: expected "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9" but got "file:///A:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
 PASS URL: Setting <http://example.net>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS URL: Setting <non-spec:/>.hash = 'a\0b' Percent-encode NULLs in fragment
 PASS URL: Setting <http://example.net>.hash = '%c3%89té' Bytes already percent-encoded are left as-is
diff --git a/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/paint/invalidation/svg/animated-path-inside-transformed-html-expected.png b/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/paint/invalidation/svg/animated-path-inside-transformed-html-expected.png
index 5649521..8fb2d8a 100644
--- a/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/paint/invalidation/svg/animated-path-inside-transformed-html-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/paint/invalidation/svg/animated-path-inside-transformed-html-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1-SE/text-tspan-02-b-expected.png b/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1-SE/text-tspan-02-b-expected.png
index 450f365..670c6bd9 100644
--- a/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1-SE/text-tspan-02-b-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1-SE/text-tspan-02-b-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/animate-elem-40-t-expected.png b/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/animate-elem-40-t-expected.png
index 14f5aa0..7a3617ad 100644
--- a/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/animate-elem-40-t-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/animate-elem-40-t-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/metadata-example-01-b-expected.png b/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/metadata-example-01-b-expected.png
index 53a067f7..4d14b0d 100644
--- a/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/metadata-example-01-b-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/layout_ng_svg_text/svg/W3C-SVG-1.1/metadata-example-01-b-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/accessibility/scroll-window-horiz-sends-notification-expected.txt b/third_party/blink/web_tests/platform/win7/accessibility/scroll-window-horiz-sends-notification-expected.txt
new file mode 100644
index 0000000..66b9632
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/accessibility/scroll-window-horiz-sends-notification-expected.txt
@@ -0,0 +1,5 @@
+CONSOLE MESSAGE: line 28: Got notification on web area
+This is a testharness.js-based test.
+FAIL This test ensures that scrolling the window sends a notification. assert_equals: expected 500 but got 0
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/layout-ng-grid/external/wpt/css/css-contain/README.txt b/third_party/blink/web_tests/virtual/layout-ng-grid/external/wpt/css/css-contain/README.txt
new file mode 100644
index 0000000..73fecb3
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/layout-ng-grid/external/wpt/css/css-contain/README.txt
@@ -0,0 +1,2 @@
+These tests are run with --enable-blink-features=LayoutNGGrid
+The LayoutNG project is described here: http://goo.gl/1hwhfX
diff --git a/third_party/blink/web_tests/virtual/layout-ng-grid/external/wpt/css/css-sizing/contain-intrinsic-size/README.txt b/third_party/blink/web_tests/virtual/layout-ng-grid/external/wpt/css/css-sizing/contain-intrinsic-size/README.txt
new file mode 100644
index 0000000..73fecb3
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/layout-ng-grid/external/wpt/css/css-sizing/contain-intrinsic-size/README.txt
@@ -0,0 +1,2 @@
+These tests are run with --enable-blink-features=LayoutNGGrid
+The LayoutNG project is described here: http://goo.gl/1hwhfX
diff --git a/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-mandatory-getStats.https-expected.txt b/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-mandatory-getStats.https-expected.txt
new file mode 100644
index 0000000..010886b
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-mandatory-getStats.https-expected.txt
@@ -0,0 +1,79 @@
+This is a testharness.js-based test.
+Found 75 tests; 70 PASS, 5 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS getStats succeeds
+PASS Validating stats
+PASS RTCRtpStreamStats's ssrc
+PASS RTCRtpStreamStats's kind
+PASS RTCRtpStreamStats's transportId
+PASS RTCRtpStreamStats's codecId
+PASS RTCReceivedRtpStreamStats's packetsReceived
+PASS RTCReceivedRtpStreamStats's packetsLost
+PASS RTCReceivedRtpStreamStats's jitter
+FAIL RTCReceivedRtpStreamStats's packetsDiscarded assert_true: Is packetsDiscarded present expected true got false
+PASS RTCReceivedRtpStreamStats's framesDropped
+FAIL RTCInboundRtpStreamStats's receiverId assert_true: Is receiverId present expected true got false
+PASS RTCInboundRtpStreamStats's remoteId
+PASS RTCInboundRtpStreamStats's framesDecoded
+PASS RTCInboundRtpStreamStats's nackCount
+PASS RTCInboundRtpStreamStats's framesReceived
+PASS RTCInboundRtpStreamStats's bytesReceived
+PASS RTCInboundRtpStreamStats's totalAudioEnergy
+PASS RTCInboundRtpStreamStats's totalSamplesDuration
+PASS RTCRemoteInboundRtpStreamStats's localId
+PASS RTCRemoteInboundRtpStreamStats's roundTripTime
+PASS RTCSentRtpStreamStats's packetsSent
+PASS RTCSentRtpStreamStats's bytesSent
+FAIL RTCOutboundRtpStreamStats's senderId assert_true: Is senderId present expected true got false
+PASS RTCOutboundRtpStreamStats's remoteId
+PASS RTCOutboundRtpStreamStats's framesEncoded
+PASS RTCOutboundRtpStreamStats's nackCount
+PASS RTCOutboundRtpStreamStats's framesSent
+PASS RTCRemoteOutboundRtpStreamStats's localId
+PASS RTCRemoteOutboundRtpStreamStats's remoteTimestamp
+PASS RTCPeerConnectionStats's dataChannelsOpened
+PASS RTCPeerConnectionStats's dataChannelsClosed
+PASS RTCDataChannelStats's label
+PASS RTCDataChannelStats's protocol
+PASS RTCDataChannelStats's dataChannelIdentifier
+PASS RTCDataChannelStats's state
+PASS RTCDataChannelStats's messagesSent
+PASS RTCDataChannelStats's bytesSent
+PASS RTCDataChannelStats's messagesReceived
+PASS RTCDataChannelStats's bytesReceived
+PASS RTCMediaSourceStats's trackIdentifier
+PASS RTCMediaSourceStats's kind
+PASS RTCAudioSourceStats's totalAudioEnergy
+PASS RTCAudioSourceStats's totalSamplesDuration
+PASS RTCVideoSourceStats's width
+PASS RTCVideoSourceStats's height
+PASS RTCVideoSourceStats's framesPerSecond
+FAIL RTCMediaHandlerStats's trackIdentifier assert_true: Is trackIdentifier present expected true got false
+PASS RTCCodecStats's payloadType
+PASS RTCCodecStats's mimeType
+PASS RTCCodecStats's clockRate
+PASS RTCCodecStats's channels
+PASS RTCCodecStats's sdpFmtpLine
+PASS RTCTransportStats's bytesSent
+PASS RTCTransportStats's bytesReceived
+PASS RTCTransportStats's selectedCandidatePairId
+PASS RTCTransportStats's localCertificateId
+PASS RTCTransportStats's remoteCertificateId
+PASS RTCIceCandidatePairStats's transportId
+PASS RTCIceCandidatePairStats's localCandidateId
+PASS RTCIceCandidatePairStats's remoteCandidateId
+PASS RTCIceCandidatePairStats's state
+PASS RTCIceCandidatePairStats's nominated
+PASS RTCIceCandidatePairStats's bytesSent
+PASS RTCIceCandidatePairStats's bytesReceived
+PASS RTCIceCandidatePairStats's totalRoundTripTime
+PASS RTCIceCandidatePairStats's currentRoundTripTime
+PASS RTCIceCandidateStats's address
+PASS RTCIceCandidateStats's port
+PASS RTCIceCandidateStats's protocol
+PASS RTCIceCandidateStats's candidateType
+FAIL RTCIceCandidateStats's url assert_true: Is url present expected true got false
+PASS RTCCertificateStats's fingerprint
+PASS RTCCertificateStats's fingerprintAlgorithm
+PASS RTCCertificateStats's base64Certificate
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-setLocalDescription-pranswer-expected.txt b/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-setLocalDescription-pranswer-expected.txt
new file mode 100644
index 0000000..585d803
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-setLocalDescription-pranswer-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL setLocalDescription(pranswer) from stable state should reject with InvalidStateError promise_rejects_dom: function "function() { throw e }" threw object "OperationError: Failed to execute 'setLocalDescription' on 'RTCPeerConnection': Failed to set local pranswer sdp: Called in wrong state: stable" that is not a DOMException InvalidStateError: property "code" is equal to 0, expected 11
+PASS setLocalDescription(pranswer) should succeed
+PASS setLocalDescription(pranswer) can be applied multiple times while still in have-local-pranswer
+PASS setLocalDescription(answer) from have-local-pranswer state should succeed
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/resources/message-boxes.html b/third_party/blink/web_tests/wpt_internal/prerender/resources/message-boxes.html
index 4ff0584..149df5d 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/resources/message-boxes.html
+++ b/third_party/blink/web_tests/wpt_internal/prerender/resources/message-boxes.html
@@ -1,34 +1,47 @@
 <!DOCTYPE html>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
 <script>
-
 function runAlertTest() {
   window.alert('Hello! Preprendering!');
-  bc.postMessage('no block');
+  return 'no block';
 }
 
 function runConfirmTest() {
   const result = window.confirm('Are you preprendering page?');
-  bc.postMessage('the return value is ' + (result === true ? 'yes' : 'no'));
+  return 'the return value is ' + (result === true ? 'yes' : 'no');
 }
 
 function runPromptTest() {
-  const result = window.prompt('Are you preprendering page?', 'the default value');
-  bc.postMessage('the return value is ' +
-                 (result === '' ? 'the empty string' : result));
+  const result = window.prompt('Are you preprendering page?',
+    'the default value');
+  return 'the return value is ' + (result === '' ? 'the empty string' : result);
 }
 
-const bc = new BroadcastChannel('prerender-channel');
-assert_true(document.prerendering);
 const params = new URLSearchParams(location.search);
 
-if (params.has('alert'))
-  runAlertTest();
-else if (params.has('confirm'))
-  runConfirmTest();
-else if (params.has('prompt'))
-  runPromptTest();
+// The main test page (restriction-message-boxes.html) loads the
+// initiator page, then the initiator page will prerender itself with the
+// `prerendering` parameter.
+const isPrerendering = params.has('prerendering');
 
-bc.close();
+if (isPrerendering) {
+  // Test web APIs on the pages.
+  const bc = new BroadcastChannel('prerender-channel');
+  assert_true(document.prerendering);
+  if (params.has('alert')) {
+    bc.postMessage(runAlertTest());
+  } else if (params.has('confirm'))
+    bc.postMessage(runConfirmTest());
+  else if (params.has('prompt'))
+    bc.postMessage(runPromptTest());
+  bc.close();
+  window.close();
+} else {
+  // Initiator pages should prerender the prerendering page.
+  const url = new URL(document.URL);
+  url.searchParams.append('prerendering', '');
+  startPrerendering(url);
+}
 </script>
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/resources/utils.js b/third_party/blink/web_tests/wpt_internal/prerender/resources/utils.js
index fc04bd71..fb53e73 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/resources/utils.js
+++ b/third_party/blink/web_tests/wpt_internal/prerender/resources/utils.js
@@ -2,14 +2,15 @@
 
 // Starts prerendering for `url`.
 function startPrerendering(url) {
-  // Adds <link rel=prerender> for the URL.
-  // TODO(https://crbug.com/1174978): <link rel=prerender> may not start
-  // prerendering for some reason (e.g., resource limit). Implement a WebDriver
-  // API to force prerendering.
-  const link = document.createElement('link');
-  link.rel = 'prerender';
-  link.href = url;
-  document.head.appendChild(link);
+  // Adds <script type="speculationrules"> and specifies a prerender candidate
+  // for the given URL.
+  // TODO(https://crbug.com/1174978): <script type="speculationrules"> may not
+  // start prerendering for some reason (e.g., resource limit). Implement a
+  // WebDriver API to force prerendering.
+  const script = document.createElement('script');
+  script.type = 'speculationrules';
+  script.text = `{"prerender": [{"source": "list", "urls": ["${url}"] }] }`;
+  document.head.appendChild(script);
 }
 
 // Reads the value specified by `key` from the key-value store on the server.
diff --git a/third_party/blink/web_tests/wpt_internal/prerender/restriction-message-boxes.html b/third_party/blink/web_tests/wpt_internal/prerender/restriction-message-boxes.html
index 106d8cd..1b3dbb1 100644
--- a/third_party/blink/web_tests/wpt_internal/prerender/restriction-message-boxes.html
+++ b/third_party/blink/web_tests/wpt_internal/prerender/restriction-message-boxes.html
@@ -22,7 +22,8 @@
       });
     });
 
-    startPrerendering(test_file);
+    // Open a new window to test the message box.
+    window.open(test_file, '_blank', 'noopener');
 
     // Wait for the message from the prerendering page.
     assert_equals(await gotMessage, expectation);
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/cts.html b/third_party/blink/web_tests/wpt_internal/webgpu/cts.html
index ea5ce72..619aed0 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/cts.html
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/cts.html
@@ -433,6 +433,10 @@
 <meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:max_dynamic_buffers:*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:max_resources_per_stage,in_bind_group_layout:*'>
 <meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:max_resources_per_stage,in_pipeline_layout:*'>
+<meta name=variant content='?q=webgpu:api,validation,createComputePipeline:basic_use_of_createComputePipeline:*'>
+<meta name=variant content='?q=webgpu:api,validation,createComputePipeline:shader_module_must_be_valid:*'>
+<meta name=variant content='?q=webgpu:api,validation,createComputePipeline:shader_module_stage_must_be_compute:*'>
+<meta name=variant content='?q=webgpu:api,validation,createComputePipeline:enrty_point_name_must_match:*'>
 <meta name=variant content='?q=webgpu:api,validation,createPipelineLayout:number_of_dynamic_buffers_exceeds_the_maximum_value:*'>
 <meta name=variant content='?q=webgpu:api,validation,createPipelineLayout:number_of_bind_group_layouts_exceeds_the_maximum_value:*'>
 <meta name=variant content='?q=webgpu:api,validation,createRenderPipeline:basic_use_of_createRenderPipeline:*'>
diff --git a/third_party/closure_compiler/externs/automation.js b/third_party/closure_compiler/externs/automation.js
index 94c65463..c903798 100644
--- a/third_party/closure_compiler/externs/automation.js
+++ b/third_party/closure_compiler/externs/automation.js
@@ -145,7 +145,6 @@
   ABBR: 'abbr',
   ALERT: 'alert',
   ALERT_DIALOG: 'alertDialog',
-  ANCHOR: 'anchor',
   APPLICATION: 'application',
   ARTICLE: 'article',
   AUDIO: 'audio',
diff --git a/third_party/harfbuzz-ng/BUILD.gn b/third_party/harfbuzz-ng/BUILD.gn
index 26c8e06..b9c31a7 100644
--- a/third_party/harfbuzz-ng/BUILD.gn
+++ b/third_party/harfbuzz-ng/BUILD.gn
@@ -267,7 +267,14 @@
       # TODO(https://crbug.com/949962): Remove once this is fixed upstream.
       "U_DISABLE_VERSION_SUFFIX=0",
 
-      # https:/crbug.com/1203071
+      # Letting HarfBuzz enable warnings through pragmas can block compiler
+      # upgrades in situations where say a ToT compiler build adds a new
+      # stricter warning under -Wfoowarning-subgroup. HarfBuzz pragma-enables
+      # -Wfoowarning which default-enables -Wfoowarning-subgroup implicitly but
+      # HarfBuzz upstream is not yet clean of warnings produced for
+      # -Wfoowarning-subgroup. Hence disabling pragma warning control here.
+      # See also https:/crbug.com/1203071
+      "HB_NO_PRAGMA_GCC_DIAGNOSTIC_ERROR",
       "HB_NO_PRAGMA_GCC_DIAGNOSTIC_WARNING",
     ]
 
diff --git a/third_party/wayland-protocols/BUILD.gn b/third_party/wayland-protocols/BUILD.gn
index 1e55417b5..1f1202b9 100644
--- a/third_party/wayland-protocols/BUILD.gn
+++ b/third_party/wayland-protocols/BUILD.gn
@@ -140,3 +140,7 @@
 wayland_protocol("weston_test") {
   sources = [ "unstable/weston-test/weston-test.xml" ]
 }
+
+wayland_protocol("org_kde_kwin_idle") {
+  sources = [ "kde/idle/idle.xml" ]
+}
diff --git a/third_party/wayland-protocols/kde/idle/README b/third_party/wayland-protocols/kde/idle/README
new file mode 100644
index 0000000..673e3ce
--- /dev/null
+++ b/third_party/wayland-protocols/kde/idle/README
@@ -0,0 +1,13 @@
+KDE Idle Wayland protocol extension
+
+Maintainers:
+Alexander Dunaev <adunaev@igalia.com>
+
+Additional resources:
+https://wayland.app/protocols/kde-idle
+
+Source:
+https://github.com/KDE/plasma-wayland-protocols/blob/master/src/protocols/idle.xml
+
+This protocol allows to query KDE Plasma desktop environment for the idle time,
+and to get notifications about the system entering and leaving the idle status.
diff --git a/third_party/wayland-protocols/kde/idle/idle.xml b/third_party/wayland-protocols/kde/idle/idle.xml
new file mode 100644
index 0000000..575c7eb
--- /dev/null
+++ b/third_party/wayland-protocols/kde/idle/idle.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="idle">
+  <copyright><![CDATA[
+    SPDX-FileCopyrightText: 2015 Martin Gräßlin
+
+    SPDX-License-Identifier: LGPL-2.1-or-later
+  ]]></copyright>
+  <interface  name="org_kde_kwin_idle" version="1">
+      <description summary="User idle time manager">
+        This interface allows to monitor user idle time on a given seat. The interface
+        allows to register timers which trigger after no user activity was registered
+        on the seat for a given interval. It notifies when user activity resumes.
+
+        This is useful for applications wanting to perform actions when the user is not
+        interacting with the system, e.g. chat applications setting the user as away, power
+        management features to dim screen, etc..
+      </description>
+      <request name="get_idle_timeout">
+        <arg name="id" type="new_id" interface="org_kde_kwin_idle_timeout"/>
+        <arg name="seat" type="object" interface="wl_seat"/>
+        <arg name="timeout" type="uint" summary="The idle timeout in msec"/>
+      </request>
+  </interface>
+  <interface name="org_kde_kwin_idle_timeout" version="1">
+      <request name="release" type="destructor">
+        <description summary="release the timeout object"/>
+      </request>
+      <request name="simulate_user_activity">
+          <description summary="Simulates user activity for this timeout, behaves just like real user activity on the seat"/>
+      </request>
+      <event name="idle">
+          <description summary="Triggered when there has not been any user activity in the requested idle time interval"/>
+      </event>
+      <event name="resumed">
+          <description summary="Triggered on the first user activity after an idle event"/>
+      </event>
+  </interface>
+</protocol>
diff --git a/third_party/webgpu-cts/ts_sources.txt b/third_party/webgpu-cts/ts_sources.txt
index 0be1475..238be98d 100644
--- a/third_party/webgpu-cts/ts_sources.txt
+++ b/third_party/webgpu-cts/ts_sources.txt
@@ -127,6 +127,7 @@
 src/webgpu/api/validation/attachment_compatibility.spec.ts
 src/webgpu/api/validation/createBindGroup.spec.ts
 src/webgpu/api/validation/createBindGroupLayout.spec.ts
+src/webgpu/api/validation/createComputePipeline.spec.ts
 src/webgpu/api/validation/createPipelineLayout.spec.ts
 src/webgpu/api/validation/createRenderPipeline.spec.ts
 src/webgpu/api/validation/createSampler.spec.ts
diff --git a/tools/android/modularization/loc/modularization_loc_stat.py b/tools/android/modularization/loc/modularization_loc_stat.py
index 16c9668..f975a68 100755
--- a/tools/android/modularization/loc/modularization_loc_stat.py
+++ b/tools/android/modularization/loc/modularization_loc_stat.py
@@ -15,12 +15,13 @@
 import sys
 from collections import OrderedDict
 from collections import defaultdict
-from typing import Tuple
+from typing import List, Tuple
 
 # Output json keys
 KEY_LOC_MODULARIZED = 'loc_modularized'
 KEY_LOC_LEGACY = 'loc_legacy'
-KEY_RANKINGS = 'rankings'
+KEY_RANKINGS_MODULARIZED = 'rankings'
+KEY_RANKINGS_LEGACY = 'rankings_legacy'
 KEY_START_DATE = 'start_date'
 KEY_END_DATE = 'end_date'
 
@@ -86,7 +87,8 @@
     print(f'\nSTDOUT: {e.stdout}', file=sys.stderr)
     raise
 
-  author_stat = defaultdict(int)
+  author_stat_m12n = defaultdict(int)
+  author_stat_legacy = defaultdict(int)
   total_m12n = 0
   total_legacy = 0
   prev_msg_len = 0
@@ -111,9 +113,10 @@
       diff = int(added)
       if _is_m12n_path(path):
         total_m12n += diff
-        author_stat[author] += diff
+        author_stat_m12n[author] += diff
       elif _is_legacy_path(path):
         total_legacy += diff
+        author_stat_legacy[author] += diff
 
     msg = f'\rProcessing {commit_date} by {author}'
     if not quiet: _print_progress(msg, prev_msg_len)
@@ -123,14 +126,17 @@
     _print_progress('Processing complete', prev_msg_len)
     print('\n')
 
-  rankings = OrderedDict(
-      sorted(author_stat.items(), key=lambda x: x[1], reverse=True))
+  rankings_modularized = OrderedDict(
+      sorted(author_stat_m12n.items(), key=lambda x: x[1], reverse=True))
+  rankings_legacy = OrderedDict(
+      sorted(author_stat_legacy.items(), key=lambda x: x[1], reverse=True))
 
   if json_format:
     return json.dumps({
         KEY_LOC_MODULARIZED: total_m12n,
         KEY_LOC_LEGACY: total_legacy,
-        KEY_RANKINGS: rankings,
+        KEY_RANKINGS_MODULARIZED: rankings_modularized,
+        KEY_RANKINGS_LEGACY: rankings_legacy,
         KEY_START_DATE: start_date,
         KEY_END_DATE: end_date,
     })
@@ -139,24 +145,35 @@
     total = total_m12n + total_legacy
     percentage = 100.0 * total_m12n / total if total > 0 else 0
     output.append(f'# of lines added in modularized files: {total_m12n}')
-    output.append(f'# of lines added in legacy files: {total_legacy}')
+    output.append(f'# of lines added in non-modularized files: {total_legacy}')
     output.append(f'% of lines landing in modularized files: {percentage:2.2f}')
 
-    # Shows the top 50 contributors to modularized files.
-    output.append('\nTop contributors:')
-    if rankings:
-      output.append('No  lines    %    author')
-      for rank, author in enumerate(list(rankings.keys())[:50], 1):
-        lines = rankings[author]
-        if lines == 0:
-          break
-        ratio = 100 * lines / total_m12n
-        output.append(f'{rank:2d} {lines:6d} {ratio:5.1f}  {author}')
-    else:
-      output.append('...none found.')
+    # Shows the top 50 contributors in each category.
+    output.extend(
+        _print_ranking(rankings_modularized, total_m12n,
+                       'modules and components'))
+    output.extend(
+        _print_ranking(rankings_legacy, total_legacy, 'legacy and glue'))
+
     return '\n'.join(output)
 
 
+def _print_ranking(rankings: OrderedDict, total: int, label: str) -> List[str]:
+  if not rankings:
+    return []
+
+  output = []
+  output.append(f'\nTop contributors ({label}):')
+  output.append('No  lines    %    author')
+  for rank, author in enumerate(list(rankings.keys())[:50], 1):
+    lines = rankings[author]
+    if lines == 0:
+      break
+    ratio = 100 * lines / total
+    output.append(f'{rank:2d} {lines:6d} {ratio:5.1f}  {author}')
+  return output
+
+
 def _is_m12n_path(path):
   for prefix in _M12N_DIRS:
     if path.startswith(prefix):
diff --git a/tools/clang/scripts/update.py b/tools/clang/scripts/update.py
index f738d30..2f9505d3 100755
--- a/tools/clang/scripts/update.py
+++ b/tools/clang/scripts/update.py
@@ -39,8 +39,8 @@
 # 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-10392-gd3676d4b'
-CLANG_SUB_REVISION = 2
+CLANG_REVISION = 'llvmorg-13-init-11649-g4d788fb8'
+CLANG_SUB_REVISION = 1
 
 PACKAGE_VERSION = '%s-%s' % (CLANG_REVISION, CLANG_SUB_REVISION)
 RELEASE_VERSION = '13.0.0'
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index a44bfcf..29cf4f68 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -45444,6 +45444,7 @@
   <int value="-1650769314" label="enable-webgl2-compute-context"/>
   <int value="-1649778035" label="disable-clear-browsing-data-counters"/>
   <int value="-1648216169" label="NewOmniboxAnswerTypes:disabled"/>
+  <int value="-1647433421" label="use-passthrough-command-decoder"/>
   <int value="-1646016597" label="IsolatePrerenders:disabled"/>
   <int value="-1645071473" label="ChromeColors:disabled"/>
   <int value="-1644308778" label="WASAPIRawAudioCapture:disabled"/>
@@ -45601,6 +45602,7 @@
   <int value="-1520952503" label="SearchReadyOmnibox:enabled"/>
   <int value="-1520855274" label="PWAFullCodeCache:disabled"/>
   <int value="-1520645293" label="InterestFeedNoticeCardAutoDismiss:disabled"/>
+  <int value="-1520630395" label="DefaultPassthroughCommandDecoder:disabled"/>
   <int value="-1517518406" label="force-update-menu-type"/>
   <int value="-1515415104" label="top-document-isolation:disabled"/>
   <int value="-1514943439" label="ash-enable-swipe-to-close-in-overview-mode"/>
@@ -47911,6 +47913,7 @@
   <int value="463582989" label="CompositorThreadedScrollbarScrolling:disabled"/>
   <int value="464226051" label="CrOSComponent:enabled"/>
   <int value="464773709" label="OmniboxExperimentalSuggestScoring:enabled"/>
+  <int value="465600939" label="DefaultPassthroughCommandDecoder:enabled"/>
   <int value="466248382" label="disable-push-api-background-mode"/>
   <int value="468665559" label="TrilinearFiltering:disabled"/>
   <int value="468901900" label="AppStoreBillingDebug:disabled"/>
@@ -66354,6 +66357,8 @@
   <int value="12"
       label="Exceeding maximum number of migrations on write error"/>
   <int value="13" label="Path degrading before handshake confirmed"/>
+  <int value="14" label="Idle migration period exceeded"/>
+  <int value="15" label="Migration fails due to lack of connection ID"/>
 </enum>
 
 <enum name="QuicDisabledReason">
@@ -78165,6 +78170,8 @@
 </enum>
 
 <enum name="TimeLimitPolicyType">
+<!-- App Time Limit does not cover blocked apps. -->
+
   <int value="0" label="No Time Limit"/>
   <int value="1" label="Override Time Limit"/>
   <int value="2" label="Bed Time Limit"/>
diff --git a/tools/metrics/histograms/histograms_xml/android/histograms.xml b/tools/metrics/histograms/histograms_xml/android/histograms.xml
index e9ebfc8b..43607f8 100644
--- a/tools/metrics/histograms/histograms_xml/android/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/android/histograms.xml
@@ -2462,6 +2462,27 @@
   </summary>
 </histogram>
 
+<histogram name="Android.Survey.DownloadAttemptsBeforeAccepted" units="units"
+    expires_after="2022-06-01">
+  <owner>skym@chromium.org</owner>
+  <owner>wenyufu@chromium.org</owner>
+  <owner>clank-app-team@google.com</owner>
+  <summary>
+    The number of survey download request attempts that have been made before
+    the survey is accepted. Note:
+
+    1) The download attempt might not result in a survey download due to
+    different reasons (e.g. download request failed or network failure); 2)
+    Chrome may also successfully download the survey number of times in previous
+    sessions, but the surveys were not able to be shown for min amount of times.
+    In such scenario, the total number of attempts across all sessions will be
+    recorded; 3) If the number of allowed download attempts has been saturated
+    before the survey being accepted, nothing will be recorded.
+
+    Recorded when a survey prompt is accepted. Android Only.
+  </summary>
+</histogram>
+
 <histogram name="Android.Survey.DownloadRequested" enum="BooleanRequested"
     expires_after="2021-06-01">
   <obsolete>
diff --git a/tools/metrics/histograms/histograms_xml/extensions/histograms.xml b/tools/metrics/histograms/histograms_xml/extensions/histograms.xml
index 5275658..7a7b825 100644
--- a/tools/metrics/histograms/histograms_xml/extensions/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/extensions/histograms.xml
@@ -237,6 +237,9 @@
 
 <histogram name="Extensions.Bindings.UpdateBindingsForContextTime"
     units="microseconds" expires_after="2021-01-31">
+  <obsolete>
+    Code removed 2021/06.
+  </obsolete>
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -1080,7 +1083,9 @@
 </histogram>
 
 <histogram name="Extensions.Events.DispatchToComponent" enum="ExtensionEvents"
-    expires_after="2021-06-01">
+    expires_after="never">
+<!-- expires-never: Monitoring core extensions platform behavior. -->
+
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -2543,7 +2548,9 @@
 </histogram>
 
 <histogram name="Extensions.LoadOffStoreItems" units="Number of items"
-    expires_after="2021-06-01">
+    expires_after="never">
+<!-- expires-never: Monitoring core extension usage. -->
+
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -3414,7 +3421,9 @@
 </histogram>
 
 <histogram name="Extensions.Toolbar.PinnedExtensionCount2"
-    units="pinned extensions" expires_after="2021-10-31">
+    units="pinned extensions" expires_after="never">
+<!-- expires-never: Monitoring core user behavior with extensions. -->
+
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -3425,7 +3434,9 @@
 </histogram>
 
 <histogram name="Extensions.Toolbar.PinnedExtensionPercentage3" units="%"
-    expires_after="2021-06-30">
+    expires_after="never">
+<!-- expires-never: Monitoring core user behavior with extensions. -->
+
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/histograms_xml/gpu/histograms.xml b/tools/metrics/histograms/histograms_xml/gpu/histograms.xml
index 16c5ccb..5e22f14 100644
--- a/tools/metrics/histograms/histograms_xml/gpu/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/gpu/histograms.xml
@@ -380,7 +380,11 @@
 </histogram>
 
 <histogram name="GPU.DirectComposition.CompositionMode"
-    enum="DxgiFramePresentationMode" expires_after="2021-10-04">
+    enum="DxgiFramePresentationMode" expires_after="M92">
+  <obsolete>
+    Obsoleted after M92. Unused and expired. Replaced by
+    GPU.DirectComposition.CompositionMode2.VideoOrCanvas.
+  </obsolete>
   <owner>sunnyps@chromium.org</owner>
   <owner>zmo@chromium.org</owner>
   <summary>
@@ -390,7 +394,12 @@
 </histogram>
 
 <histogram name="GPU.DirectComposition.CompositionMode.MainBuffer"
-    enum="DxgiFramePresentationMode" expires_after="2021-10-17">
+    enum="DxgiFramePresentationMode" expires_after="M92">
+  <obsolete>
+    Obsoleted after M92. Unused and expired. Replaced by
+    GPU.DirectComposition.CompositionMode2.MainBuffer.FullDamage and
+    GPU.DirectComposition.CompositionMode2.MainBuffer.PartialDamage.
+  </obsolete>
   <owner>zmo@chromium.org</owner>
   <owner>graphics-dev@chromium.org</owner>
   <summary>
@@ -399,6 +408,30 @@
   </summary>
 </histogram>
 
+<histogram name="GPU.DirectComposition.CompositionMode2.MainBuffer.{Damage}"
+    enum="DxgiFramePresentationMode" expires_after="2021-12-31">
+  <owner>zmo@chromium.org</owner>
+  <owner>graphics-dev@chromium.org</owner>
+  <summary>
+    How the Desktop Window Manager presented Chrome's main DirectComposition
+    layer to the screen using {Damage}. Only recorded on Windows.
+  </summary>
+  <token key="Damage">
+    <variant name="FullDamage" summary="full damage"/>
+    <variant name="PartialDamage" summary="partial damage"/>
+  </token>
+</histogram>
+
+<histogram name="GPU.DirectComposition.CompositionMode2.VideoOrCanvas"
+    enum="DxgiFramePresentationMode" expires_after="2021-12-31">
+  <owner>sunnyps@chromium.org</owner>
+  <owner>graphics-dev@chromium.org</owner>
+  <summary>
+    How the Desktop Window Manager presented Chrome's DirectComposition layers
+    of video or canvas elements to the screen. Only recorded on Windows.
+  </summary>
+</histogram>
+
 <histogram name="GPU.DirectComposition.CreateSwapChainForComposition"
     enum="Hresult" expires_after="2021-10-04">
   <owner>magchen@chromium.org</owner>
diff --git a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
index 427e15cb..0ed9790d 100644
--- a/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/histograms_xml/histogram_suffixes_list.xml
@@ -6544,6 +6544,9 @@
 </histogram_suffixes>
 
 <histogram_suffixes name="ExtensionContextType" separator=".">
+  <obsolete>
+    Removed 2021/06.
+  </obsolete>
   <suffix name="BlessedExtensionContext" label="Blessed Extension Context"/>
   <suffix name="BlessedWebPageContext" label="Blessed Web Page Context"/>
   <suffix name="ContentScriptContext" label="Content Script Context"/>
@@ -18431,6 +18434,7 @@
   <affected-histogram name="SoftwareReporter.RunningTime"/>
   <affected-histogram name="SoftwareReporter.RunningTimeAccordingToChrome"/>
   <affected-histogram name="SoftwareReporter.RunningTimeRegistryError"/>
+  <affected-histogram name="SoftwareReporter.RunningTimeWithoutSleep"/>
   <affected-histogram name="SoftwareReporter.Step"/>
 </histogram_suffixes>
 
diff --git a/tools/metrics/histograms/histograms_xml/others/histograms.xml b/tools/metrics/histograms/histograms_xml/others/histograms.xml
index 594487b870..0165677 100644
--- a/tools/metrics/histograms/histograms_xml/others/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/others/histograms.xml
@@ -613,9 +613,10 @@
 </histogram>
 
 <histogram name="AppBanners.BeforeInstallEvent"
-    enum="AppBannersBeforeInstallEvent" expires_after="2021-06-01">
+    enum="AppBannersBeforeInstallEvent" expires_after="2022-06-01">
   <owner>dominickn@chromium.org</owner>
   <owner>pjmclachlan@google.com</owner>
+  <owner>desktop-pwas-team@google.com</owner>
   <summary>
     App banners promote an application related to the current website, and are
     requested specifically through the current page's HTML. This stat tracks
@@ -3539,6 +3540,18 @@
   </summary>
 </histogram>
 
+<histogram name="Conversions.RedirectInterceptedFrameDetached" enum="Boolean"
+    expires_after="M95">
+  <owner>apaseltiner@chromium.org</owner>
+  <owner>johnidel@chromium.org</owner>
+  <owner>measurement-api-dev+metrics@google.com</owner>
+  <summary>
+    Measures how often conversions are not handled due to the frame being
+    detached. Recorded when an attribution trigger redirect is received. This is
+    recorded regardless of the Attribution Reporting API being enabled.
+  </summary>
+</histogram>
+
 <histogram name="Conversions.RegisteredConversionsPerPage" units="conversions"
     expires_after="2021-11-07">
   <owner>johnidel@chromium.org</owner>
@@ -5673,8 +5686,10 @@
   <owner>cros-families-eng@google.com</owner>
   <summary>
     Records whether managed sites approved list and blocked list are enabled for
-    currently active Family Link user. Reports at the beginning of the first
-    active session daily. Ignores the reports during OOBE and sign out.
+    currently active Family Link user. Prior to M93, this metric was recorded at
+    the beginning of first active session daily. In M93, this metric is now also
+    recorded when manual hosts and manual urls policies are changed. Ignores the
+    reports during OOBE and sign out.
   </summary>
 </histogram>
 
@@ -5767,7 +5782,10 @@
   <summary>
     Records what time limit policy types are enabled for the currently active
     Family Link user. Enabling multiple policies would report multiple buckets
-    to UMA. Reports at the beginning of the first active session daily.
+    to UMA. Prior to M93, this metric was recorded at the beginning of first
+    active session daily. In M93, this metric is now also recorded when bed time
+    limit, daily limit, override time limit, apps time limit and web time limit
+    policies are changed. App time limits does not include blocked apps.
   </summary>
 </histogram>
 
@@ -5820,9 +5838,10 @@
   <owner>xiqiruan@chromium.org</owner>
   <owner>cros-families-eng@google.com</owner>
   <summary>
-    Records the web filter type for currently active Family Link user. Reports
-    at the beginning of the first active session daily. Ignores the reports
-    during OOBE and sign out.
+    Records the web filter type for currently active Family Link user. Prior to
+    M93, this metric was recorded at the beginning of first active session
+    daily. In M93, this metric is now also recorded when web filter type policy
+    changed. Ignores the reports during OOBE and sign out.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/histograms_xml/software/histograms.xml b/tools/metrics/histograms/histograms_xml/software/histograms.xml
index 04c4cf1c..a5c37b4 100644
--- a/tools/metrics/histograms/histograms_xml/software/histograms.xml
+++ b/tools/metrics/histograms/histograms_xml/software/histograms.xml
@@ -413,12 +413,26 @@
 </histogram>
 
 <histogram name="SoftwareReporter.RunningTimeAccordingToChrome" units="ms"
-    expires_after="2021-07-21">
+    expires_after="2022-01-21">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
-    The amount of time it took for the software reporter to run as measured by
-    chrome. Logged just after the software reporter tool has finished.
+    The amount of time from the Software Reporter process launch to the time it
+    exits, in milliseconds. This includes time the computer was asleep or
+    hibernating. SoftwareReporter.RunningTimeWithoutSleep excludes those
+    periods.
+  </summary>
+</histogram>
+
+<histogram name="SoftwareReporter.RunningTimeWithoutSleep" units="ms"
+    expires_after="2022-01-21">
+  <owner>drubery@chromium.org</owner>
+  <owner>chrome-safebrowsing-alerts@google.com</owner>
+  <summary>
+    The amount of time from the Software Reporter process launch to the time it
+    exits, in milliseconds. This does not include time the computer was asleep
+    or hibernating. SoftwareReporter.RunningTimeAccordingToChrome includes those
+    periods.
   </summary>
 </histogram>
 
diff --git a/tools/perf/BUILD.gn b/tools/perf/BUILD.gn
index 89363eb2..344de9f 100644
--- a/tools/perf/BUILD.gn
+++ b/tools/perf/BUILD.gn
@@ -2,38 +2,31 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-# Keep in sync with group("perf_without_chrome").
 group("perf") {
   testonly = true
-  deps = [ "//tools/perf/chrome_telemetry_build:telemetry_chrome_test" ]
-  data = [
-    "//tools/perf/",
-
-    # Field trial config
-    "//tools/variations/",
-    "//testing/variations/",
-
-    # Field trial dependencies
-    "//tools/json_comment_eater/",
-    "//tools/json_to_struct/",
-    "//components/variations/service/generate_ui_string_overrider.py",
-
-    # For blink_perf benchmarks.
-    "//third_party/blink/perf_tests/",
-
-    # For smoothness.tough_canvas_cases
-    "//chrome/test/data/perf/",
-
-    # For image_decoding.measurement
-    "//chrome/test/data/image_decoding/",
-
-    # For Pylib used by VR tests
-    "//build/android/pylib/",
+  data_deps = [
+    ":perf_without_chrome",
+    "//tools/perf/chrome_telemetry_build:telemetry_chrome_test",
   ]
+}
 
-  # Runs a script which generates the ad tagging ruleset.
-  if (!is_ios) {
-    data_deps = [ "//components/subresource_filter/tools:index_ruleset" ]
+if (is_android) {
+  template("perf_android_template") {
+    forward_variables_from(invoker, [ "telemetry_target_suffix" ])
+    group(target_name) {
+      testonly = true
+      data_deps = [
+        ":perf_without_chrome",
+        "//tools/perf/chrome_telemetry_build:telemetry_chrome_test${telemetry_target_suffix}",
+      ]
+    }
+  }
+
+  import("//tools/perf/chrome_telemetry_build/android_browser_types.gni")
+  foreach(_target_suffix, telemetry_android_browser_target_suffixes) {
+    perf_android_template("perf${_target_suffix}") {
+      telemetry_target_suffix = _target_suffix
+    }
   }
 }
 
@@ -50,11 +43,10 @@
   }
 }
 
-# Group for running benchmarks without building Chrome. Keep in sync with
-# group("perf").
+# Group for running benchmarks without building Chrome.
 group("perf_without_chrome") {
   testonly = true
-  deps = [
+  data_deps = [
     "//tools/perf/chrome_telemetry_build:telemetry_chrome_test_without_chrome",
   ]
 
@@ -83,15 +75,10 @@
     "//build/android/pylib/",
   ]
 
-  data_deps = []
-
   # Runs a script which generates the ad tagging ruleset.
   if (!is_ios) {
     data_deps += [ "//components/subresource_filter/tools:index_ruleset" ]
   }
-  if (is_android) {
-    data_deps += [ "//chrome/android/webapk/shell_apk:maps_go_webapk" ]
-  }
 }
 
 # This group makes visible those targets in subdirectories that are not
diff --git a/tools/perf/chrome_telemetry_build/BUILD.gn b/tools/perf/chrome_telemetry_build/BUILD.gn
index 5d1b435..641183d 100644
--- a/tools/perf/chrome_telemetry_build/BUILD.gn
+++ b/tools/perf/chrome_telemetry_build/BUILD.gn
@@ -37,6 +37,9 @@
   data = []
 
   if (is_android) {
+    # TODO(crbug.com/1213269): Remove these APK dependencies and fully switch to
+    # the separate Android targets below once all Android uses of the old target
+    # have been cleaned up.
     data_deps += [
       ":telemetry_weblayer_apks",
       "//android_webview:system_webview_apk",
@@ -100,6 +103,52 @@
   }
 }
 
+# These telemetry_chrome_test_* targets exist to reduce the amount of data
+# included in swarming isolates. Including a bunch of different versions of
+# Chrome and their unstripped .so files that aren't actually used adds gigabytes
+# of data to the isolate, which in turn adds a non-trivial amount of swarming
+# overhead. A new one should be added each time a new type of APK is supported
+# and its suffix added to android_browser_types.gni.
+if (is_android) {
+  group("telemetry_chrome_test_android_chrome") {
+    testonly = true
+
+    data_deps = [
+      ":telemetry_chrome_test",
+      "//chrome/android:chrome_public_apk",
+    ]
+  }
+
+  group("telemetry_chrome_test_android_monochrome") {
+    testonly = true
+
+    data_deps = [
+      ":telemetry_chrome_test",
+      "//chrome/android:monochrome_public_apk",
+    ]
+  }
+
+  group("telemetry_chrome_test_android_weblayer") {
+    testonly = true
+
+    data_deps = [
+      ":telemetry_chrome_test",
+      ":telemetry_weblayer_apks",
+    ]
+  }
+
+  group("telemetry_chrome_test_android_webview") {
+    testonly = true
+
+    data_deps = [
+      ":telemetry_chrome_test",
+      "//android_webview:system_webview_apk",
+      "//android_webview/test:webview_instrumentation_apk",
+      "//android_webview/tools/system_webview_shell:system_webview_shell_apk",
+    ]
+  }
+}
+
 group("telemetry_weblayer_apks") {
   testonly = true
 
diff --git a/tools/perf/chrome_telemetry_build/android_browser_types.gni b/tools/perf/chrome_telemetry_build/android_browser_types.gni
new file mode 100644
index 0000000..cb5eb4c4
--- /dev/null
+++ b/tools/perf/chrome_telemetry_build/android_browser_types.gni
@@ -0,0 +1,10 @@
+# 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.
+
+telemetry_android_browser_target_suffixes = [
+  "_android_chrome",
+  "_android_monochrome",
+  "_android_weblayer",
+  "_android_webview",
+]
diff --git a/tools/perf/contrib/vr_benchmarks/BUILD.gn b/tools/perf/contrib/vr_benchmarks/BUILD.gn
index be25033..9521351 100644
--- a/tools/perf/contrib/vr_benchmarks/BUILD.gn
+++ b/tools/perf/contrib/vr_benchmarks/BUILD.gn
@@ -5,7 +5,7 @@
 import("//chrome/browser/vr/features.gni")
 import("//device/vr/buildflags/buildflags.gni")
 
-group("vr_perf_tests") {
+group("vr_perf_tests_base") {
   testonly = true
   data = [
     "./data/",
@@ -26,8 +26,6 @@
   ]
   data_deps = [ "//testing:run_perf_test" ]
 
-  deps = [ "//tools/perf:perf" ]
-
   if (is_android) {
     data += [
       "//chrome/android/shared_preference_files/test/",
@@ -47,7 +45,35 @@
 
   if (is_win) {
     if (enable_openxr) {
-      deps += [ "//device/vr:openxr_mock" ]
+      data_deps += [ "//device/vr:openxr_mock" ]
+    }
+  }
+}
+
+group("vr_perf_tests") {
+  testonly = true
+  data_deps = [
+    ":vr_perf_tests_base",
+    "//tools/perf:perf",
+  ]
+}
+
+if (is_android) {
+  template("vr_perf_tests_android_template") {
+    forward_variables_from(invoker, [ "telemetry_target_suffix" ])
+    group(target_name) {
+      testonly = true
+      data_deps = [
+        ":vr_perf_tests_base",
+        "//tools/perf:perf${telemetry_target_suffix}",
+      ]
+    }
+  }
+
+  import("//tools/perf/chrome_telemetry_build/android_browser_types.gni")
+  foreach(_target_suffix, telemetry_android_browser_target_suffixes) {
+    vr_perf_tests_android_template("vr_perf_tests${_target_suffix}") {
+      telemetry_target_suffix = _target_suffix
     }
   }
 }
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index f0d07b53..c2f6891 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -9,8 +9,8 @@
             "remote_path": "perfetto_binaries/trace_processor_shell/mac/bae8193de6c017394901163b7817157342914679/trace_processor_shell"
         },
         "linux": {
-            "hash": "4f34e5d1f2413ba3a665c0c07229753fca005f6c",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/5d06d8b9235d81033914a28c91cd72f0ffb4820e/trace_processor_shell"
+            "hash": "e2b8f1b68d26e0aee99c97b35b56e9b2a3279343",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/ae2171b89cb3e49f73ff100fc1647a93048ca472/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/typescript/definitions/management.d.ts b/tools/typescript/definitions/management.d.ts
new file mode 100644
index 0000000..efd4f07
--- /dev/null
+++ b/tools/typescript/definitions/management.d.ts
@@ -0,0 +1,19 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/** @fileoverview Definitions for chrome.bookmarkManagerPrivate API. */
+// TODO(crbug.com/1203307): Auto-generate this file.
+
+declare namespace chrome {
+  export namespace management {
+    export interface UninstallOptions {
+      showConfirmDialog?: boolean;
+    }
+
+    export function uninstall(
+        id: string, options?: UninstallOptions, callback?: () => void): void;
+    export function setEnabled(
+        id: string, enabled: boolean, callback?: () => void): void;
+  }
+}
diff --git a/tools/typescript/definitions/runtime.d.ts b/tools/typescript/definitions/runtime.d.ts
new file mode 100644
index 0000000..6cccfc3a
--- /dev/null
+++ b/tools/typescript/definitions/runtime.d.ts
@@ -0,0 +1,14 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/** @fileoverview Definitions for chrome.runtime API */
+// TODO(crbug.com/1203307): Auto-generate this file.
+
+declare namespace chrome {
+  export namespace runtime {
+    export let lastError: {
+      message?: string,
+    } | undefined;
+  }
+}
diff --git a/ui/accelerated_widget_mac/ca_layer_tree_unittest_mac.mm b/ui/accelerated_widget_mac/ca_layer_tree_unittest_mac.mm
index a7df344..3eb8157 100644
--- a/ui/accelerated_widget_mac/ca_layer_tree_unittest_mac.mm
+++ b/ui/accelerated_widget_mac/ca_layer_tree_unittest_mac.mm
@@ -58,7 +58,7 @@
     base::ScopedCFTypeRef<CVPixelBufferRef> cv_pixel_buffer;
     CVPixelBufferCreateWithIOSurface(nullptr, io_surface, nullptr,
                                      cv_pixel_buffer.InitializeInto());
-    gl_image->InitializeWithCVPixelBuffer(cv_pixel_buffer,
+    gl_image->InitializeWithCVPixelBuffer(cv_pixel_buffer, 0,
                                           gfx::GenericSharedMemoryId(), format);
   } else {
     gl_image->Initialize(io_surface, 0, gfx::GenericSharedMemoryId(), format);
diff --git a/ui/accessibility/ax_enum_util.cc b/ui/accessibility/ax_enum_util.cc
index 86eccc3..8d652c05 100644
--- a/ui/accessibility/ax_enum_util.cc
+++ b/ui/accessibility/ax_enum_util.cc
@@ -146,8 +146,6 @@
       return "alertDialog";
     case ax::mojom::Role::kAlert:
       return "alert";
-    case ax::mojom::Role::kAnchor:
-      return "anchor";
     case ax::mojom::Role::kApplication:
       return "application";
     case ax::mojom::Role::kArticle:
diff --git a/ui/accessibility/ax_enums.mojom b/ui/accessibility/ax_enums.mojom
index f61d7e44..f695a6b 100644
--- a/ui/accessibility/ax_enums.mojom
+++ b/ui/accessibility/ax_enums.mojom
@@ -106,7 +106,7 @@
 // Web: this attribute is only used in web content.
 //
 // Native: this attribute is only used in native UI.
-// Next value: 186
+// Next value: 185
 [Extensible, Stable, Uuid="d258eb73-e0cc-490c-b881-80ee11d3fec2"]
 enum Role {
   // Used for role="none"/"presentation" -- ignored in platform tree.
@@ -114,201 +114,200 @@
   kAbbr = 1,
   kAlert = 2,
   kAlertDialog = 3,
-  kAnchor = 4,
-  kApplication = 5,
-  kArticle = 6,
-  kAudio = 7,
-  kBanner = 8,
-  kBlockquote = 9,
-  kButton = 10,
-  kCanvas = 11,
-  kCaption = 12,
-  kCaret = 13,
-  kCell = 14,
-  kCheckBox = 15,
-  kClient = 16,
-  kCode = 17,
-  kColorWell = 18,
-  kColumn = 19,
-  kColumnHeader = 20,
-  kComboBoxGrouping = 21,
-  kComboBoxMenuButton = 22,
-  kComplementary = 23,
-  kComment = 24,
-  kContentDeletion = 25,
-  kContentInsertion = 26,
-  kContentInfo = 27,
-  kDate = 28,
-  kDateTime = 29,
-  kDefinition = 30,
-  kDescriptionList = 31,
-  kDescriptionListDetail = 32,
-  kDescriptionListTerm = 33,
-  kDesktop = 34,  // internal
-  kDetails = 35,
-  kDialog = 36,
-  kDirectory = 37,
-  kDisclosureTriangle = 38,
+  kApplication = 4,
+  kArticle = 5,
+  kAudio = 6,
+  kBanner = 7,
+  kBlockquote = 8,
+  kButton = 9,
+  kCanvas = 10,
+  kCaption = 11,
+  kCaret = 12,
+  kCell = 13,
+  kCheckBox = 14,
+  kClient = 15,
+  kCode = 16,
+  kColorWell = 17,
+  kColumn = 18,
+  kColumnHeader = 19,
+  kComboBoxGrouping = 20,
+  kComboBoxMenuButton = 21,
+  kComplementary = 22,
+  kComment = 23,
+  kContentDeletion = 24,
+  kContentInsertion = 25,
+  kContentInfo = 26,
+  kDate = 27,
+  kDateTime = 28,
+  kDefinition = 29,
+  kDescriptionList = 30,
+  kDescriptionListDetail = 31,
+  kDescriptionListTerm = 32,
+  kDesktop = 33,  // internal
+  kDetails = 34,
+  kDialog = 35,
+  kDirectory = 36,
+  kDisclosureTriangle = 37,
   // --------------------------------------------------------------
   // DPub Roles:
   // https://www.w3.org/TR/dpub-aam-1.0/#mapping_role_table
-  kDocAbstract = 39,
-  kDocAcknowledgments = 40,
-  kDocAfterword = 41,
-  kDocAppendix = 42,
-  kDocBackLink = 43,
-  kDocBiblioEntry = 44,
-  kDocBibliography = 45,
-  kDocBiblioRef = 46,
-  kDocChapter = 47,
-  kDocColophon = 48,
-  kDocConclusion = 49,
-  kDocCover = 50,
-  kDocCredit = 51,
-  kDocCredits = 52,
-  kDocDedication = 53,
-  kDocEndnote = 54,
-  kDocEndnotes = 55,
-  kDocEpigraph = 56,
-  kDocEpilogue = 57,
-  kDocErrata = 58,
-  kDocExample = 59,
-  kDocFootnote = 60,
-  kDocForeword = 61,
-  kDocGlossary = 62,
-  kDocGlossRef = 63,
-  kDocIndex = 64,
-  kDocIntroduction = 65,
-  kDocNoteRef = 66,
-  kDocNotice = 67,
-  kDocPageBreak = 68,
-  kDocPageFooter = 69,
-  kDocPageHeader = 70,
-  kDocPageList = 71,
-  kDocPart = 72,
-  kDocPreface = 73,
-  kDocPrologue = 74,
-  kDocPullquote = 75,
-  kDocQna = 76,
-  kDocSubtitle = 77,
-  kDocTip = 78,
-  kDocToc = 79,
+  kDocAbstract = 38,
+  kDocAcknowledgments = 39,
+  kDocAfterword = 40,
+  kDocAppendix = 41,
+  kDocBackLink = 42,
+  kDocBiblioEntry = 43,
+  kDocBibliography = 44,
+  kDocBiblioRef = 45,
+  kDocChapter = 46,
+  kDocColophon = 47,
+  kDocConclusion = 48,
+  kDocCover = 49,
+  kDocCredit = 50,
+  kDocCredits = 51,
+  kDocDedication = 52,
+  kDocEndnote = 53,
+  kDocEndnotes = 54,
+  kDocEpigraph = 55,
+  kDocEpilogue = 56,
+  kDocErrata = 57,
+  kDocExample = 58,
+  kDocFootnote = 59,
+  kDocForeword = 60,
+  kDocGlossary = 61,
+  kDocGlossRef = 62,
+  kDocIndex = 63,
+  kDocIntroduction = 64,
+  kDocNoteRef = 65,
+  kDocNotice = 66,
+  kDocPageBreak = 67,
+  kDocPageFooter = 68,
+  kDocPageHeader = 69,
+  kDocPageList = 70,
+  kDocPart = 71,
+  kDocPreface = 72,
+  kDocPrologue = 73,
+  kDocPullquote = 74,
+  kDocQna = 75,
+  kDocSubtitle = 76,
+  kDocTip = 77,
+  kDocToc = 78,
   // End DPub roles.
   // --------------------------------------------------------------
-  kDocument = 80,
-  kEmbeddedObject = 81,
-  kEmphasis = 82,
-  kFeed = 83,
-  kFigcaption = 84,
-  kFigure = 85,
-  kFooter = 86,
-  kFooterAsNonLandmark = 87,
-  kForm = 88,
-  kGenericContainer = 89,
+  kDocument = 79,
+  kEmbeddedObject = 80,
+  kEmphasis = 81,
+  kFeed = 82,
+  kFigcaption = 83,
+  kFigure = 84,
+  kFooter = 85,
+  kFooterAsNonLandmark = 86,
+  kForm = 87,
+  kGenericContainer = 88,
   // --------------------------------------------------------------
   // ARIA Graphics module roles:
   // https://rawgit.com/w3c/graphics-aam/master/#mapping_role_table
-  kGraphicsDocument = 90,
-  kGraphicsObject = 91,
-  kGraphicsSymbol = 92,
+  kGraphicsDocument = 89,
+  kGraphicsObject = 90,
+  kGraphicsSymbol = 91,
   // End ARIA Graphics module roles.
   // --------------------------------------------------------------
-  kGrid = 93,
-  kGroup = 94,
-  kHeader = 95,
-  kHeaderAsNonLandmark = 96,
-  kHeading = 97,
-  kIframe = 98,
-  kIframePresentational = 99,
-  kImage = 100,
-  kImeCandidate = 101,
-  kInlineTextBox = 102,
-  kInputTime = 103,
-  kKeyboard = 104,
-  kLabelText = 105,
-  kLayoutTable = 106,
-  kLayoutTableCell = 107,
-  kLayoutTableRow = 108,
-  kLegend = 109,
-  kLineBreak = 110,
-  kLink = 111,
-  kList = 112,
-  kListBox = 113,
-  kListBoxOption = 114,
+  kGrid = 92,
+  kGroup = 93,
+  kHeader = 94,
+  kHeaderAsNonLandmark = 95,
+  kHeading = 96,
+  kIframe = 97,
+  kIframePresentational = 98,
+  kImage = 99,
+  kImeCandidate = 100,
+  kInlineTextBox = 101,
+  kInputTime = 102,
+  kKeyboard = 103,
+  kLabelText = 104,
+  kLayoutTable = 105,
+  kLayoutTableCell = 106,
+  kLayoutTableRow = 107,
+  kLegend = 108,
+  kLineBreak = 109,
+  kLink = 110,
+  kList = 111,
+  kListBox = 112,
+  kListBoxOption = 113,
   // kListGrid behaves similar to an ARIA grid but is primarily used by
   // TableView and its subclasses, so that they could be exposed correctly on
   // certain platforms.
-  kListGrid = 115,  // Native
-  kListItem = 116,
-  kListMarker = 117,
-  kLog = 118,
-  kMain = 119,
-  kMark = 120,
-  kMarquee = 121,
-  kMath = 122,
-  kMenu = 123,
-  kMenuBar = 124,
-  kMenuItem = 125,
-  kMenuItemCheckBox = 126,
-  kMenuItemRadio = 127,
-  kMenuListOption = 128,
-  kMenuListPopup = 129,
-  kMeter = 130,
-  kNavigation = 131,
-  kNote = 132,
-  kPane = 133,
-  kParagraph = 134,
-  kPdfActionableHighlight = 135,
-  kPdfRoot = 136,
-  kPluginObject = 137,
-  kPopUpButton = 138,
-  kPortal = 139,
-  kPre = 140,
-  kProgressIndicator = 141,
-  kRadioButton = 142,
-  kRadioGroup = 143,
-  kRegion = 144,
-  kRootWebArea = 145,
-  kRow = 146,
-  kRowGroup = 147,
-  kRowHeader = 148,
-  kRuby = 149,
-  kRubyAnnotation = 150,
-  kScrollBar = 151,
-  kScrollView = 152,
-  kSearch = 153,
-  kSearchBox = 154,
-  kSection = 155,
-  kSlider = 156,
-  kSpinButton = 157,
-  kSplitter = 158,
-  kStaticText = 159,
-  kStatus = 160,
-  kStrong = 161,
-  kSuggestion = 162,
-  kSvgRoot = 163,
-  kSwitch = 164,
-  kTab = 165,
-  kTabList = 166,
-  kTabPanel = 167,
-  kTable = 168,
-  kTableHeaderContainer = 169,
-  kTerm = 170,
-  kTextField = 171,
-  kTextFieldWithComboBox = 172,
-  kTime = 173,
-  kTimer = 174,
-  kTitleBar = 175,
-  kToggleButton = 176,
-  kToolbar = 177,
-  kTooltip = 178,
-  kTree = 179,
-  kTreeGrid = 180,
-  kTreeItem = 181,
-  kUnknown = 182,
-  kVideo = 183,
-  kWebView = 184,
-  kWindow = 185,
+  kListGrid = 114,  // Native
+  kListItem = 115,
+  kListMarker = 116,
+  kLog = 117,
+  kMain = 118,
+  kMark = 119,
+  kMarquee = 120,
+  kMath = 121,
+  kMenu = 122,
+  kMenuBar = 123,
+  kMenuItem = 124,
+  kMenuItemCheckBox = 125,
+  kMenuItemRadio = 126,
+  kMenuListOption = 127,
+  kMenuListPopup = 128,
+  kMeter = 129,
+  kNavigation = 130,
+  kNote = 131,
+  kPane = 132,
+  kParagraph = 133,
+  kPdfActionableHighlight = 134,
+  kPdfRoot = 135,
+  kPluginObject = 136,
+  kPopUpButton = 137,
+  kPortal = 138,
+  kPre = 139,
+  kProgressIndicator = 140,
+  kRadioButton = 141,
+  kRadioGroup = 142,
+  kRegion = 143,
+  kRootWebArea = 144,
+  kRow = 145,
+  kRowGroup = 146,
+  kRowHeader = 147,
+  kRuby = 148,
+  kRubyAnnotation = 149,
+  kScrollBar = 150,
+  kScrollView = 151,
+  kSearch = 152,
+  kSearchBox = 153,
+  kSection = 154,
+  kSlider = 155,
+  kSpinButton = 156,
+  kSplitter = 157,
+  kStaticText = 158,
+  kStatus = 159,
+  kStrong = 160,
+  kSuggestion = 161,
+  kSvgRoot = 162,
+  kSwitch = 163,
+  kTab = 164,
+  kTabList = 165,
+  kTabPanel = 166,
+  kTable = 167,
+  kTableHeaderContainer = 168,
+  kTerm = 169,
+  kTextField = 170,
+  kTextFieldWithComboBox = 171,
+  kTime = 172,
+  kTimer = 173,
+  kTitleBar = 174,
+  kToggleButton = 175,
+  kToolbar = 176,
+  kTooltip = 177,
+  kTree = 178,
+  kTreeGrid = 179,
+  kTreeItem = 180,
+  kUnknown = 181,
+  kVideo = 182,
+  kWebView = 183,
+  kWindow = 184,
 };
 
 // Next value: 19
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux.cc b/ui/accessibility/platform/ax_platform_node_auralinux.cc
index 8017d30..bb3af12 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -2390,10 +2390,8 @@
   // AtkObject. It is indeed implemented by actual web hyperlinks, but also by
   // objects that will become embedded objects in ATK hypertext, so the name is
   // a bit of a misnomer from the ATK API.
-  if (IsLink(data.role) || data.role == ax::mojom::Role::kAnchor ||
-      !ui::IsText(data.role)) {
+  if (IsLink(data.role) || !ui::IsText(data.role))
     interface_mask.Add(ImplementedAtkInterfaces::Value::kHyperlink);
-  }
 
   if (data.role == ax::mojom::Role::kWindow)
     interface_mask.Add(ImplementedAtkInterfaces::Value::kWindow);
@@ -2642,8 +2640,6 @@
       return ATK_ROLE_NOTIFICATION;
     case ax::mojom::Role::kAlertDialog:
       return ATK_ROLE_ALERT;
-    case ax::mojom::Role::kAnchor:
-      return ATK_ROLE_LINK;
     case ax::mojom::Role::kComment:
     case ax::mojom::Role::kSuggestion:
       return ATK_ROLE_SECTION;
diff --git a/ui/accessibility/platform/ax_platform_node_mac.mm b/ui/accessibility/platform/ax_platform_node_mac.mm
index 22078bc..115f964 100644
--- a/ui/accessibility/platform/ax_platform_node_mac.mm
+++ b/ui/accessibility/platform/ax_platform_node_mac.mm
@@ -42,7 +42,6 @@
       {ax::mojom::Role::kAbbr, NSAccessibilityGroupRole},
       {ax::mojom::Role::kAlert, NSAccessibilityGroupRole},
       {ax::mojom::Role::kAlertDialog, NSAccessibilityGroupRole},
-      {ax::mojom::Role::kAnchor, NSAccessibilityGroupRole},
       {ax::mojom::Role::kApplication, NSAccessibilityGroupRole},
       {ax::mojom::Role::kArticle, NSAccessibilityGroupRole},
       {ax::mojom::Role::kAudio, NSAccessibilityGroupRole},
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index d62db41..8de0faf 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -5280,9 +5280,6 @@
     case ax::mojom::Role::kAlertDialog:
       return ROLE_SYSTEM_DIALOG;
 
-    case ax::mojom::Role::kAnchor:
-      return ROLE_SYSTEM_LINK;
-
     case ax::mojom::Role::kComment:
     case ax::mojom::Role::kSuggestion:
       return ROLE_SYSTEM_GROUPING;
@@ -6098,9 +6095,6 @@
       // |ax::mojom::Role::kAlertDialog| yet.
       return L"alert";
 
-    case ax::mojom::Role::kAnchor:
-      return L"link";
-
     case ax::mojom::Role::kComment:
     case ax::mojom::Role::kSuggestion:
       return L"group";
@@ -6768,9 +6762,6 @@
       // |ax::mojom::Role::kAlertDialog| yet.
       return UIA_TextControlTypeId;
 
-    case ax::mojom::Role::kAnchor:
-      return UIA_HyperlinkControlTypeId;
-
     case ax::mojom::Role::kComment:
     case ax::mojom::Role::kSuggestion:
       return UIA_GroupControlTypeId;
diff --git a/ui/base/x/selection_utils.cc b/ui/base/x/selection_utils.cc
index c6799bf..382918f 100644
--- a/ui/base/x/selection_utils.cc
+++ b/ui/base/x/selection_utils.cc
@@ -22,25 +22,21 @@
 namespace ui {
 
 std::vector<x11::Atom> GetTextAtomsFrom() {
-  std::vector<x11::Atom> atoms;
-  atoms.push_back(x11::GetAtom(kMimeTypeLinuxUtf8String));
-  atoms.push_back(x11::GetAtom(kMimeTypeLinuxString));
-  atoms.push_back(x11::GetAtom(kMimeTypeLinuxText));
-  atoms.push_back(x11::GetAtom(kMimeTypeText));
-  atoms.push_back(x11::GetAtom(kMimeTypeTextUtf8));
+  static const std::vector<x11::Atom> atoms = {
+      x11::GetAtom(kMimeTypeLinuxUtf8String),
+      x11::GetAtom(kMimeTypeLinuxString), x11::GetAtom(kMimeTypeLinuxText),
+      x11::GetAtom(kMimeTypeText), x11::GetAtom(kMimeTypeTextUtf8)};
   return atoms;
 }
 
 std::vector<x11::Atom> GetURLAtomsFrom() {
-  std::vector<x11::Atom> atoms;
-  atoms.push_back(x11::GetAtom(kMimeTypeURIList));
-  atoms.push_back(x11::GetAtom(kMimeTypeMozillaURL));
+  static const std::vector<x11::Atom> atoms = {
+      x11::GetAtom(kMimeTypeURIList), x11::GetAtom(kMimeTypeMozillaURL)};
   return atoms;
 }
 
 std::vector<x11::Atom> GetURIListAtomsFrom() {
-  std::vector<x11::Atom> atoms;
-  atoms.push_back(x11::GetAtom(kMimeTypeURIList));
+  static const std::vector<x11::Atom> atoms = {x11::GetAtom(kMimeTypeURIList)};
   return atoms;
 }
 
diff --git a/ui/base/x/x11_clipboard_helper.cc b/ui/base/x/x11_clipboard_helper.cc
index b6d23a1..34433517 100644
--- a/ui/base/x/x11_clipboard_helper.cc
+++ b/ui/base/x/x11_clipboard_helper.cc
@@ -122,7 +122,7 @@
 
   bool ContainsText() const {
     for (const auto& atom : GetTextAtomsFrom()) {
-      if (ContainsAtom(atom))
+      if (base::Contains(target_list_, atom))
         return true;
     }
     return false;
@@ -130,10 +130,6 @@
 
   bool ContainsFormat(const ClipboardFormatType& format_type) const {
     x11::Atom atom = x11::GetAtom(format_type.GetName().c_str());
-    return ContainsAtom(atom);
-  }
-
-  bool ContainsAtom(x11::Atom atom) const {
     return base::Contains(target_list_, atom);
   }
 
diff --git a/ui/gl/direct_composition_child_surface_win.cc b/ui/gl/direct_composition_child_surface_win.cc
index d8380cd7..04ec80c8 100644
--- a/ui/gl/direct_composition_child_surface_win.cc
+++ b/ui/gl/direct_composition_child_surface_win.cc
@@ -53,6 +53,10 @@
 
 bool g_direct_composition_swap_chain_failed = false;
 
+// If damage_rect / full_chrome_rect >= kForceFullDamageThreshold, present
+// the swap chain with full damage.
+float kForceFullDamageThreshold = 0.6f;
+
 bool SupportsLowLatencyPresentation() {
   return base::FeatureList::IsEnabled(
       features::kDirectCompositionLowLatencyPresentation);
@@ -74,11 +78,13 @@
     VSyncCallback vsync_callback,
     bool use_angle_texture_offset,
     size_t max_pending_frames,
-    bool force_full_damage)
+    bool force_full_damage,
+    bool force_full_damage_always)
     : vsync_callback_(std::move(vsync_callback)),
       use_angle_texture_offset_(use_angle_texture_offset),
       max_pending_frames_(max_pending_frames),
       force_full_damage_(force_full_damage),
+      force_full_damage_always_(force_full_damage_always),
       vsync_thread_(VSyncThreadWin::GetInstance()),
       task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
 
@@ -154,10 +160,22 @@
           first_swap_ || !vsync_enabled_ || use_swap_chain_tearing ? 0 : 1;
       UINT flags = use_swap_chain_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0;
 
-      TRACE_EVENT2("gpu", "DirectCompositionChildSurfaceWin::PresentSwapChain",
-                   "interval", interval, "dirty_rect",
-                   force_full_damage_ ? "full_damage" : swap_rect_.ToString());
+      bool actually_force_full_damage = false;
       if (force_full_damage_) {
+        if (force_full_damage_always_) {
+          actually_force_full_damage = true;
+        } else {
+          float percentage = swap_rect_.size().GetArea();
+          percentage /= size_.GetArea();
+          if (percentage >= kForceFullDamageThreshold)
+            actually_force_full_damage = true;
+        }
+      }
+      TRACE_EVENT2(
+          "gpu", "DirectCompositionChildSurfaceWin::PresentSwapChain",
+          "interval", interval, "dirty_rect",
+          actually_force_full_damage ? "full_damage" : swap_rect_.ToString());
+      if (actually_force_full_damage) {
         hr = swap_chain_->Present(interval, flags);
       } else {
         DXGI_PRESENT_PARAMETERS params = {};
@@ -174,7 +192,7 @@
       }
 
       Microsoft::WRL::ComPtr<IDXGISwapChainMedia> swap_chain_media;
-      if (SUCCEEDED(swap_chain_.As(&swap_chain_media))) {
+      if (force_full_damage_ && SUCCEEDED(swap_chain_.As(&swap_chain_media))) {
         DXGI_FRAME_STATISTICS_MEDIA stats = {};
         // GetFrameStatisticsMedia fails with
         // DXGI_ERROR_FRAME_STATISTICS_DISJOINT sometimes, which means an
@@ -184,9 +202,16 @@
         // Waiting for the DXGI adapter to finish presenting before calling
         // the function doesn't get rid of the failure.
         if (SUCCEEDED(swap_chain_media->GetFrameStatisticsMedia(&stats))) {
-          base::UmaHistogramSparse(
-              "GPU.DirectComposition.CompositionMode.MainBuffer",
-              stats.CompositionMode);
+          if (actually_force_full_damage) {
+            base::UmaHistogramSparse(
+                "GPU.DirectComposition.CompositionMode2.MainBuffer.FullDamage",
+                stats.CompositionMode);
+          } else {
+            base::UmaHistogramSparse(
+                "GPU.DirectComposition.CompositionMode2.MainBuffer."
+                "PartialDamage",
+                stats.CompositionMode);
+          }
         }
       }
 
diff --git a/ui/gl/direct_composition_child_surface_win.h b/ui/gl/direct_composition_child_surface_win.h
index b17ea4e..9240ae2 100644
--- a/ui/gl/direct_composition_child_surface_win.h
+++ b/ui/gl/direct_composition_child_surface_win.h
@@ -34,7 +34,8 @@
   DirectCompositionChildSurfaceWin(VSyncCallback vsync_callback,
                                    bool use_angle_texture_offset,
                                    size_t max_pending_frames,
-                                   bool force_full_damage);
+                                   bool force_full_damage,
+                                   bool force_full_damage_always);
 
   // GLSurfaceEGL implementation.
   bool Initialize(GLSurfaceFormat format) override;
@@ -142,6 +143,7 @@
   const bool use_angle_texture_offset_;
   const size_t max_pending_frames_;
   const bool force_full_damage_;
+  const bool force_full_damage_always_;
 
   VSyncThreadWin* const vsync_thread_;
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
diff --git a/ui/gl/direct_composition_surface_win.cc b/ui/gl/direct_composition_surface_win.cc
index 3828639..3f9a84c 100644
--- a/ui/gl/direct_composition_surface_win.cc
+++ b/ui/gl/direct_composition_surface_win.cc
@@ -394,7 +394,8 @@
           std::move(vsync_callback),
           settings.use_angle_texture_offset,
           settings.max_pending_frames,
-          settings.force_root_surface_full_damage)),
+          settings.force_root_surface_full_damage,
+          settings.force_root_surface_full_damage_always)),
       layer_tree_(
           std::make_unique<DCLayerTree>(settings.disable_nv12_dynamic_textures,
                                         settings.disable_vp_scaling)) {
diff --git a/ui/gl/direct_composition_surface_win.h b/ui/gl/direct_composition_surface_win.h
index 305850e..b99c7adc 100644
--- a/ui/gl/direct_composition_surface_win.h
+++ b/ui/gl/direct_composition_surface_win.h
@@ -44,6 +44,7 @@
     size_t max_pending_frames = 2;
     bool use_angle_texture_offset = false;
     bool force_root_surface_full_damage = false;
+    bool force_root_surface_full_damage_always = false;
   };
 
   DirectCompositionSurfaceWin(
diff --git a/ui/gl/gl_image_io_surface.h b/ui/gl/gl_image_io_surface.h
index 16c7594..f37f784 100644
--- a/ui/gl/gl_image_io_surface.h
+++ b/ui/gl/gl_image_io_surface.h
@@ -46,6 +46,7 @@
   // initialization will ensure that the CVPixelBuffer be retained for the
   // lifetime of the GLImage.
   bool InitializeWithCVPixelBuffer(CVPixelBufferRef cv_pixel_buffer,
+                                   uint32_t io_surface_plane,
                                    gfx::GenericSharedMemoryId io_surface_id,
                                    gfx::BufferFormat format);
 
diff --git a/ui/gl/gl_image_io_surface.mm b/ui/gl/gl_image_io_surface.mm
index 8fa7729..321f866 100644
--- a/ui/gl/gl_image_io_surface.mm
+++ b/ui/gl/gl_image_io_surface.mm
@@ -222,10 +222,10 @@
 
 bool GLImageIOSurface::InitializeWithCVPixelBuffer(
     CVPixelBufferRef cv_pixel_buffer,
+    uint32_t io_surface_plane,
     gfx::GenericSharedMemoryId io_surface_id,
     gfx::BufferFormat format) {
   IOSurfaceRef io_surface = CVPixelBufferGetIOSurface(cv_pixel_buffer);
-  const uint32_t io_surface_plane = 0;
   if (!io_surface) {
     LOG(ERROR) << "Can't init GLImage from CVPixelBuffer with no IOSurface";
     return false;
diff --git a/ui/gl/swap_chain_presenter.cc b/ui/gl/swap_chain_presenter.cc
index 00cbf63..a7ade89 100644
--- a/ui/gl/swap_chain_presenter.cc
+++ b/ui/gl/swap_chain_presenter.cc
@@ -1136,8 +1136,9 @@
     HRESULT hr = swap_chain_media->GetFrameStatisticsMedia(&stats);
     int mode = -1;
     if (SUCCEEDED(hr)) {
-      base::UmaHistogramSparse("GPU.DirectComposition.CompositionMode",
-                               stats.CompositionMode);
+      base::UmaHistogramSparse(
+          "GPU.DirectComposition.CompositionMode2.VideoOrCanvas",
+          stats.CompositionMode);
       if (frame_rate_ != 0) {
         // [1ms, 10s] covers the fps between [0.1hz, 1000hz].
         base::UmaHistogramTimes("GPU.DirectComposition.ApprovedPresentDuration",
diff --git a/ui/ozone/platform/scenic/scenic_screen.cc b/ui/ozone/platform/scenic/scenic_screen.cc
index 461c46f6..350dc0a 100644
--- a/ui/ozone/platform/scenic/scenic_screen.cc
+++ b/ui/ozone/platform/scenic/scenic_screen.cc
@@ -49,25 +49,6 @@
   display_it->set_bounds(bounds);
 }
 
-void ScenicScreen::OnWindowMetrics(int32_t window_id,
-                                   float device_pixel_ratio) {
-  if (display::Display::HasForceDeviceScaleFactor())
-    return;
-
-  auto display_it = std::find_if(displays_.begin(), displays_.end(),
-                                 [window_id](display::Display& display) {
-                                   return display.id() == window_id;
-                                 });
-  DCHECK(display_it != displays_.end());
-
-  display_it->set_device_scale_factor(device_pixel_ratio);
-  for (auto& observer : observers_) {
-    observer.OnDisplayMetricsChanged(
-        *display_it,
-        display::DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR);
-  }
-}
-
 base::WeakPtr<ScenicScreen> ScenicScreen::GetWeakPtr() {
   return weak_factory_.GetWeakPtr();
 }
diff --git a/ui/ozone/platform/scenic/scenic_screen.h b/ui/ozone/platform/scenic/scenic_screen.h
index b55e4ba..c6470ae 100644
--- a/ui/ozone/platform/scenic/scenic_screen.h
+++ b/ui/ozone/platform/scenic/scenic_screen.h
@@ -24,7 +24,6 @@
   // Processes window state change events for the ScenicWindow |window_id_|.
   void OnWindowAdded(int32_t window_id);
   void OnWindowRemoved(int32_t window_id);
-  void OnWindowMetrics(int32_t window_id, float device_pixel_ratio);
   void OnWindowBoundsChanged(int32_t window_id, gfx::Rect bounds);
 
   base::WeakPtr<ScenicScreen> GetWeakPtr();
diff --git a/ui/ozone/platform/scenic/scenic_window.cc b/ui/ozone/platform/scenic/scenic_window.cc
index fb2806f..4b371df 100644
--- a/ui/ozone/platform/scenic/scenic_window.cc
+++ b/ui/ozone/platform/scenic/scenic_window.cc
@@ -336,10 +336,6 @@
 void ScenicWindow::OnViewMetrics(const fuchsia::ui::gfx::Metrics& metrics) {
   device_pixel_ratio_ = std::max(metrics.scale_x, metrics.scale_y);
 
-  ScenicScreen* screen = manager_->screen();
-  if (screen)
-    screen->OnWindowMetrics(window_id_, device_pixel_ratio_);
-
   if (view_properties_)
     UpdateSize();
 }
diff --git a/ui/ozone/platform/scenic/scenic_window.h b/ui/ozone/platform/scenic/scenic_window.h
index f5538eee..6d674ff4 100644
--- a/ui/ozone/platform/scenic/scenic_window.h
+++ b/ui/ozone/platform/scenic/scenic_window.h
@@ -144,8 +144,11 @@
 
   std::unique_ptr<scenic::ViewHolder> surface_view_holder_;
 
-  // The ratio used for translating device-independent coordinates to absolute
-  // pixel coordinates.
+  // The scale between logical pixels and physical pixels, set based on the
+  // fuchsia::ui::gfx::Metrics event. It's used to calculate dimensions of the
+  // view in physical pixels in UpdateSize(). This value doesn't affect the
+  // device_scale_factor reported by ScenicScreen for the corresponding display
+  // (currently always 1.0, see crbug.com/1215330).
   float device_pixel_ratio_ = 0.f;
 
   // Current view size in DIPs.
diff --git a/ui/ozone/platform/wayland/BUILD.gn b/ui/ozone/platform/wayland/BUILD.gn
index 98c74c3..321e719 100644
--- a/ui/ozone/platform/wayland/BUILD.gn
+++ b/ui/ozone/platform/wayland/BUILD.gn
@@ -52,6 +52,8 @@
     "host/gtk_shell1.h",
     "host/gtk_surface1.cc",
     "host/gtk_surface1.h",
+    "host/org_kde_kwin_idle.cc",
+    "host/org_kde_kwin_idle.h",
     "host/shell_object_factory.cc",
     "host/shell_object_factory.h",
     "host/shell_popup_wrapper.cc",
@@ -191,6 +193,7 @@
     "//third_party/wayland-protocols:keyboard_extension_protocol",
     "//third_party/wayland-protocols:linux_dmabuf_protocol",
     "//third_party/wayland-protocols:linux_explicit_synchronization_protocol",
+    "//third_party/wayland-protocols:org_kde_kwin_idle",
     "//third_party/wayland-protocols:pointer_gestures_protocol",
     "//third_party/wayland-protocols:presentation_time_protocol",
     "//third_party/wayland-protocols:primary_selection_protocol",
diff --git a/ui/ozone/platform/wayland/common/wayland_object.cc b/ui/ozone/platform/wayland/common/wayland_object.cc
index 5f63eb03..7b2139b 100644
--- a/ui/ozone/platform/wayland/common/wayland_object.cc
+++ b/ui/ozone/platform/wayland/common/wayland_object.cc
@@ -9,6 +9,7 @@
 #include <extended-drag-unstable-v1-client-protocol.h>
 #include <gtk-primary-selection-client-protocol.h>
 #include <gtk-shell-client-protocol.h>
+#include <idle-client-protocol.h>
 #include <keyboard-extension-unstable-v1-client-protocol.h>
 #include <linux-dmabuf-unstable-v1-client-protocol.h>
 #include <linux-explicit-synchronization-unstable-v1-client-protocol.h>
@@ -107,6 +108,16 @@
 void (*ObjectTraits<gtk_surface1>::deleter)(gtk_surface1*) =
     &gtk_surface1_destroy;
 
+const wl_interface* ObjectTraits<org_kde_kwin_idle>::interface =
+    &org_kde_kwin_idle_interface;
+void (*ObjectTraits<org_kde_kwin_idle>::deleter)(org_kde_kwin_idle*) =
+    &org_kde_kwin_idle_destroy;
+
+const wl_interface* ObjectTraits<org_kde_kwin_idle_timeout>::interface =
+    &org_kde_kwin_idle_timeout_interface;
+void (*ObjectTraits<org_kde_kwin_idle_timeout>::deleter)(
+    org_kde_kwin_idle_timeout*) = &org_kde_kwin_idle_timeout_destroy;
+
 const wl_interface*
     ObjectTraits<zwp_primary_selection_device_manager_v1>::interface =
         &zwp_primary_selection_device_manager_v1_interface;
diff --git a/ui/ozone/platform/wayland/common/wayland_object.h b/ui/ozone/platform/wayland/common/wayland_object.h
index ef805db..85001fb 100644
--- a/ui/ozone/platform/wayland/common/wayland_object.h
+++ b/ui/ozone/platform/wayland/common/wayland_object.h
@@ -15,6 +15,8 @@
 struct gtk_primary_selection_source;
 struct gtk_shell1;
 struct gtk_surface1;
+struct org_kde_kwin_idle;
+struct org_kde_kwin_idle_timeout;
 struct zwp_primary_selection_device_v1;
 struct zwp_primary_selection_device_manager_v1;
 struct zwp_primary_selection_offer_v1;
@@ -131,6 +133,18 @@
 };
 
 template <>
+struct ObjectTraits<org_kde_kwin_idle> {
+  static const wl_interface* interface;
+  static void (*deleter)(org_kde_kwin_idle*);
+};
+
+template <>
+struct ObjectTraits<org_kde_kwin_idle_timeout> {
+  static const wl_interface* interface;
+  static void (*deleter)(org_kde_kwin_idle_timeout*);
+};
+
+template <>
 struct ObjectTraits<zwp_primary_selection_device_manager_v1> {
   static const wl_interface* interface;
   static void (*deleter)(zwp_primary_selection_device_manager_v1*);
diff --git a/ui/ozone/platform/wayland/host/org_kde_kwin_idle.cc b/ui/ozone/platform/wayland/host/org_kde_kwin_idle.cc
new file mode 100644
index 0000000..88857f43
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/org_kde_kwin_idle.cc
@@ -0,0 +1,97 @@
+// 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/ozone/platform/wayland/host/org_kde_kwin_idle.h"
+
+#include <idle-client-protocol.h>
+
+#include "ui/ozone/platform/wayland/host/wayland_connection.h"
+
+namespace ui {
+
+namespace {
+
+// After the system has gone idle, it will wait for this time before notifying
+// us.  This reduces "jitter" of the idle/active state, but also adds some lag
+// in responsiveness: when we are finally notified that the idle state has come,
+// it is already there for kIdleThresholdMs milliseconds.
+constexpr uint64_t kIdleThresholdMs = 5000;
+
+}  // namespace
+
+// Wraps the actual handling of system notifications about the idle state.
+class OrgKdeKwinIdle::Timeout {
+ public:
+  explicit Timeout(org_kde_kwin_idle_timeout* timeout);
+  Timeout(const Timeout&) = delete;
+  Timeout& operator=(const Timeout&) = delete;
+  ~Timeout();
+
+  // Returns the idle time.
+  base::TimeDelta GetIdleTime() const;
+
+ private:
+  static void Idle(void* data,
+                   struct org_kde_kwin_idle_timeout* org_kde_kwin_idle_timeout);
+  static void Resumed(
+      void* data,
+      struct org_kde_kwin_idle_timeout* org_kde_kwin_idle_timeout);
+
+  wl::Object<org_kde_kwin_idle_timeout> timeout_;
+
+  // Time when the system went into idle state.
+  base::Time idle_timestamp_;
+};
+
+OrgKdeKwinIdle::OrgKdeKwinIdle(org_kde_kwin_idle* idle,
+                               WaylandConnection* connection)
+    : idle_(idle), connection_(connection) {}
+
+OrgKdeKwinIdle::~OrgKdeKwinIdle() = default;
+
+absl::optional<base::TimeDelta> OrgKdeKwinIdle::GetIdleTime() const {
+  if (!connection_->seat())
+    return absl::nullopt;
+
+  if (!idle_timeout_) {
+    idle_timeout_ =
+        std::make_unique<Timeout>(org_kde_kwin_idle_get_idle_timeout(
+            idle_.get(), connection_->seat(), kIdleThresholdMs));
+  }
+  return idle_timeout_->GetIdleTime();
+}
+
+OrgKdeKwinIdle::Timeout::Timeout(org_kde_kwin_idle_timeout* timeout)
+    : timeout_(timeout) {
+  static const struct org_kde_kwin_idle_timeout_listener kTimeoutListener = {
+      OrgKdeKwinIdle::Timeout::Idle, OrgKdeKwinIdle::Timeout::Resumed};
+  org_kde_kwin_idle_timeout_add_listener(timeout, &kTimeoutListener, this);
+}
+
+OrgKdeKwinIdle::Timeout::~Timeout() = default;
+
+base::TimeDelta OrgKdeKwinIdle::Timeout::GetIdleTime() const {
+  if (idle_timestamp_.is_null())
+    return base::TimeDelta::FromSeconds(0);
+  return base::Time::Now() - idle_timestamp_;
+}
+
+// static
+void OrgKdeKwinIdle::Timeout::Idle(
+    void* data,
+    struct org_kde_kwin_idle_timeout* org_kde_kwin_idle_timeout) {
+  auto* self = static_cast<OrgKdeKwinIdle::Timeout*>(data);
+  self->idle_timestamp_ =
+      base::Time::Now() - base::TimeDelta::FromMicroseconds(kIdleThresholdMs);
+}
+
+// static
+void OrgKdeKwinIdle::Timeout::Resumed(
+    void* data,
+    struct org_kde_kwin_idle_timeout* org_kde_kwin_idle_timeout) {
+  auto* self = static_cast<OrgKdeKwinIdle::Timeout*>(data);
+  self->idle_timestamp_ = {};
+}
+
+}  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/org_kde_kwin_idle.h b/ui/ozone/platform/wayland/host/org_kde_kwin_idle.h
new file mode 100644
index 0000000..fd300abf
--- /dev/null
+++ b/ui/ozone/platform/wayland/host/org_kde_kwin_idle.h
@@ -0,0 +1,43 @@
+// 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_OZONE_PLATFORM_WAYLAND_HOST_ORG_KDE_KWIN_IDLE_H_
+#define UI_OZONE_PLATFORM_WAYLAND_HOST_ORG_KDE_KWIN_IDLE_H_
+
+#include <memory>
+
+#include "base/time/time.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/ozone/platform/wayland/common/wayland_object.h"
+
+namespace ui {
+
+class WaylandConnection;
+
+// Wraps the KDE Wayland user idle time manager, which is provided via
+// org_kde_kwin_idle interface.
+class OrgKdeKwinIdle {
+ public:
+  OrgKdeKwinIdle(org_kde_kwin_idle* idle, WaylandConnection* connection);
+  OrgKdeKwinIdle(const OrgKdeKwinIdle&) = delete;
+  OrgKdeKwinIdle& operator=(const OrgKdeKwinIdle&) = delete;
+  ~OrgKdeKwinIdle();
+
+  // Returns the idle time if querying it is possible, absl::nullopt otherwise.
+  absl::optional<base::TimeDelta> GetIdleTime() const;
+
+ private:
+  class Timeout;
+
+  // Wayland object wrapped by this class.
+  wl::Object<org_kde_kwin_idle> idle_;
+  // The actual idle timeout connection point.
+  mutable std::unique_ptr<Timeout> idle_timeout_;
+
+  WaylandConnection* const connection_;
+};
+
+}  // namespace ui
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_HOST_ORG_KDE_KWIN_IDLE_H_
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.cc b/ui/ozone/platform/wayland/host/wayland_connection.cc
index 9e03a64..04425c1 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection.cc
+++ b/ui/ozone/platform/wayland/host/wayland_connection.cc
@@ -27,6 +27,7 @@
 #include "ui/ozone/platform/wayland/common/wayland_object.h"
 #include "ui/ozone/platform/wayland/host/gtk_primary_selection_device_manager.h"
 #include "ui/ozone/platform/wayland/host/gtk_shell1.h"
+#include "ui/ozone/platform/wayland/host/org_kde_kwin_idle.h"
 #include "ui/ozone/platform/wayland/host/proxy/wayland_proxy_impl.h"
 #include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h"
 #include "ui/ozone/platform/wayland/host/wayland_clipboard.h"
@@ -94,6 +95,8 @@
 constexpr uint32_t kMinGtkShell1Version = 3;
 constexpr uint32_t kMaxGtkShell1Version = 4;
 
+constexpr uint32_t kMaxOrgKdeKwinIdleVersion = 1;
+
 int64_t ConvertTimespecToMicros(const struct timespec& ts) {
   // On 32-bit systems, the calculation cannot overflow int64_t.
   // 2**32 * 1000000 + 2**64 / 1000 < 2**63
@@ -651,6 +654,16 @@
       LOG(ERROR) << "Failed to bind to zcr_extended_drag_v1 global";
       return;
     }
+  } else if (!connection->org_kde_kwin_idle_ &&
+             strcmp(interface, "org_kde_kwin_idle") == 0) {
+    auto idle = wl::Bind<struct org_kde_kwin_idle>(
+        registry, name, std::min(version, kMaxOrgKdeKwinIdleVersion));
+    if (!idle) {
+      LOG(ERROR) << "Failed to bind to org_kde_kwin_idle global";
+      return;
+    }
+    connection->org_kde_kwin_idle_ =
+        std::make_unique<OrgKdeKwinIdle>(idle.release(), connection);
   }
 
   connection->ScheduleFlush();
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.h b/ui/ozone/platform/wayland/host/wayland_connection.h
index 19dca2c1c5..99b99424 100644
--- a/ui/ozone/platform/wayland/host/wayland_connection.h
+++ b/ui/ozone/platform/wayland/host/wayland_connection.h
@@ -32,6 +32,7 @@
 namespace ui {
 
 class DeviceHotplugEventObserver;
+class OrgKdeKwinIdle;
 class WaylandBufferManagerHost;
 class WaylandCursor;
 class WaylandCursorBufferListener;
@@ -187,6 +188,8 @@
 
   GtkShell1* gtk_shell1() { return gtk_shell1_.get(); }
 
+  OrgKdeKwinIdle* org_kde_kwin_idle() { return org_kde_kwin_idle_.get(); }
+
   ZwpPrimarySelectionDeviceManager* zwp_primary_selection_device_manager()
       const {
     return zwp_primary_selection_device_manager_.get();
@@ -308,6 +311,9 @@
 
   std::unique_ptr<GtkShell1> gtk_shell1_;
 
+  // Objects specific to KDE Plasma desktop environment.
+  std::unique_ptr<OrgKdeKwinIdle> org_kde_kwin_idle_;
+
   std::unique_ptr<WaylandDataDragController> data_drag_controller_;
   std::unique_ptr<WaylandWindowDragController> window_drag_controller_;
 
diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
index d59919b1..541702c 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/bind.h"
 #include "base/containers/flat_set.h"
 #include "base/strings/utf_string_conversions.h"
+#include "build/chromeos_buildflags.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/clipboard/clipboard_constants.h"
 #include "ui/base/clipboard/file_info.h"
@@ -729,9 +730,18 @@
   origin_window->SetPointerFocus(restored_focus);
 }
 
+// TODO(crbug.com/1211689): Flaky on LaCrOS.
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#define MAYBE_PopupRequestCreatesAuxiliaryWindow \
+  DISABLED_PopupRequestCreatesAuxiliaryWindow
+#else
+#define MAYBE_PopupRequestCreatesAuxiliaryWindow \
+  PopupRequestCreatesAuxiliaryWindow
+#endif
 // Ensures that requests to create a |PlatformWindowType::kPopup| during drag
 // sessions return wl_subsurface-backed windows.
-TEST_P(WaylandDataDragControllerTest, PopupRequestCreatesAuxiliaryWindow) {
+TEST_P(WaylandDataDragControllerTest,
+       MAYBE_PopupRequestCreatesAuxiliaryWindow) {
   auto* origin_window = window_.get();
   const bool restored_focus = origin_window->has_pointer_focus();
   FocusAndPressLeftPointerButton(origin_window, &delegate_);
diff --git a/ui/ozone/platform/wayland/host/wayland_screen.cc b/ui/ozone/platform/wayland/host/wayland_screen.cc
index 238ed43..9c761af9 100644
--- a/ui/ozone/platform/wayland/host/wayland_screen.cc
+++ b/ui/ozone/platform/wayland/host/wayland_screen.cc
@@ -19,6 +19,7 @@
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/native_widget_types.h"
+#include "ui/ozone/platform/wayland/host/org_kde_kwin_idle.h"
 #include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
 #include "ui/ozone/platform/wayland/host/wayland_cursor_position.h"
@@ -246,6 +247,13 @@
 }
 
 base::TimeDelta WaylandScreen::CalculateIdleTime() const {
+  // Try the org_kde_kwin_idle Wayland protocol extension (KWin).
+  if (const auto* kde_idle = connection_->org_kde_kwin_idle()) {
+    const auto idle_time = kde_idle->GetIdleTime();
+    if (idle_time)
+      return *idle_time;
+  }
+
 #if defined(USE_DBUS)
   // Try the org.gnome.Mutter.IdleMonitor D-Bus service (Mutter).
   if (!org_gnome_mutter_idle_monitor_)
@@ -256,7 +264,6 @@
     return *idle_time;
 #endif  // defined(USE_DBUS)
 
-  // Try the org_kde_kwin_idle Wayland protocol extension (KWin).
   NOTIMPLEMENTED_LOG_ONCE();
 
   // No providers.  Return 0 which means the system never gets idle.
diff --git a/ui/views/examples/BUILD.gn b/ui/views/examples/BUILD.gn
index 348ea49..587b5f1 100644
--- a/ui/views/examples/BUILD.gn
+++ b/ui/views/examples/BUILD.gn
@@ -11,6 +11,8 @@
   testonly = true
 
   sources = [
+    "animation_builder.cc",
+    "animation_builder.h",
     "animation_example.cc",
     "animation_example.h",
     "ax_example.cc",
diff --git a/ui/views/examples/animation_builder.cc b/ui/views/examples/animation_builder.cc
new file mode 100644
index 0000000..4c26439
--- /dev/null
+++ b/ui/views/examples/animation_builder.cc
@@ -0,0 +1,124 @@
+// 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 <utility>
+
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animation_element.h"
+#include "ui/compositor/layer_animation_sequence.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/views/examples/animation_builder.h"
+
+namespace views {
+
+AnimationBuilder::AnimationBuilder() = default;
+
+AnimationBuilder::~AnimationBuilder() {
+  for (auto& animation : animation_sequences_) {
+    View* view = animation.first;
+    if (!view->layer())
+      view->SetPaintToLayer();
+    std::vector<ui::LayerAnimationSequence*> sequences;
+    for (auto& s : animation.second) {
+      sequences.push_back(s.release());
+    }
+    view->layer()->GetAnimator()->StartTogether(sequences);
+  }
+}
+
+AnimationBuilder& AnimationBuilder::SetDuration(base::TimeDelta duration) {
+  if (in_sequence_)
+    old_duration_ = duration_;
+  duration_ = duration;
+  return *this;
+}
+
+AnimationBuilder& AnimationBuilder::SetOpacity(View* view,
+                                               float target_opacity) {
+  // Create an entry if it doesn't exist.
+  if (animation_sequences_.find(view) == animation_sequences_.end())
+    CreateNewEntry(view);
+
+  AddAnimation(view, ui::LayerAnimationElement::CreateOpacityElement(
+                         target_opacity, duration_));
+  return *this;
+}
+
+AnimationBuilder& AnimationBuilder::SetRoundedCorners(
+    View* view,
+    gfx::RoundedCornersF& rounded_corners) {
+  // Create an entry if it doesn't exist.
+  if (animation_sequences_.find(view) == animation_sequences_.end())
+    CreateNewEntry(view);
+
+  AddAnimation(view, ui::LayerAnimationElement::CreateRoundedCornersElement(
+                         rounded_corners, duration_));
+  return *this;
+}
+
+AnimationBuilder& AnimationBuilder::Repeat() {
+  // Go through all empty sequences added in StartSequence() and set the correct
+  // repeating behavior.
+  if (in_sequence_) {
+    is_sequence_repeating_ = true;
+    for (auto& animation : animation_sequences_) {
+      animation_sequences_[animation.first].back()->set_is_repeating(
+          is_sequence_repeating_);
+    }
+  }
+  return *this;
+}
+
+AnimationBuilder& AnimationBuilder::StartSequence() {
+  in_sequence_ = true;
+  // Add an empty sequence for all existing views.
+  for (auto& animation : animation_sequences_) {
+    std::unique_ptr<ui::LayerAnimationSequence> new_sequence =
+        std::make_unique<ui::LayerAnimationSequence>();
+    animation_sequences_[animation.first].push_back(std::move(new_sequence));
+  }
+  return *this;
+}
+
+AnimationBuilder& AnimationBuilder::EndSequence() {
+  in_sequence_ = false;
+  is_sequence_repeating_ = false;
+  duration_ = old_duration_;
+  // Remove sequences that were not added to.
+  for (auto& animation : animation_sequences_) {
+    if (animation_sequences_[animation.first].back()->size() == 0) {
+      animation_sequences_[animation.first].pop_back();
+    }
+  }
+  return *this;
+}
+
+void AnimationBuilder::CreateNewEntry(View* view) {
+  animation_sequences_[view] =
+      std::vector<std::unique_ptr<ui::LayerAnimationSequence>>();
+  if (in_sequence_) {
+    // New empty sequence has not been added in StartSequence yet
+    std::unique_ptr<ui::LayerAnimationSequence> new_sequence =
+        std::make_unique<ui::LayerAnimationSequence>();
+    new_sequence->set_is_repeating(is_sequence_repeating_);
+    animation_sequences_[view].push_back(std::move(new_sequence));
+  }
+}
+
+void AnimationBuilder::AddAnimation(
+    View* view,
+    std::unique_ptr<ui::LayerAnimationElement> element) {
+  if (in_sequence_) {
+    // Add to existing sequence so that these animations are done sequentially
+    animation_sequences_[view].back()->AddElement(std::move(element));
+  } else {
+    // Create a new sequence with one element
+    std::unique_ptr<ui::LayerAnimationSequence> new_sequence =
+        std::make_unique<ui::LayerAnimationSequence>();
+    new_sequence->AddElement(std::move(element));
+    animation_sequences_[view].push_back(std::move(new_sequence));
+  }
+}
+
+}  // namespace views
diff --git a/ui/views/examples/animation_builder.h b/ui/views/examples/animation_builder.h
new file mode 100644
index 0000000..5e3d23f5
--- /dev/null
+++ b/ui/views/examples/animation_builder.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 UI_VIEWS_EXAMPLES_ANIMATION_BUILDER_H_
+#define UI_VIEWS_EXAMPLES_ANIMATION_BUILDER_H_
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "ui/views/view.h"
+
+namespace ui {
+class LayerAnimationSequence;
+class LayerAnimationElement;
+}  // namespace ui
+
+namespace views {
+
+// This AnimationBuilder API is currently in the experimental phase and only
+// used within ui/views/examples/.
+// This class should eventually be moved out of ui/views/examples/ if we proceed
+// with this implementation.
+class AnimationBuilder {
+ public:
+  AnimationBuilder();
+  ~AnimationBuilder();
+
+  AnimationBuilder& SetDuration(base::TimeDelta duration);
+
+  // These methods should be changed to OnSetXXX if we integrate with the View
+  // base class.
+  AnimationBuilder& SetOpacity(View* view, float target_opacity);
+  AnimationBuilder& SetRoundedCorners(views::View* view,
+                                      gfx::RoundedCornersF& rounded_corners);
+
+  // No effect if called before StartSequence();
+  AnimationBuilder& Repeat();
+  // Currently does not support nested sequences
+  AnimationBuilder& StartSequence();
+  AnimationBuilder& EndSequence();
+
+ private:
+  void CreateNewEntry(View* view);
+  void AddAnimation(View* view,
+                    std::unique_ptr<ui::LayerAnimationElement> element);
+
+  std::map<View*, std::vector<std::unique_ptr<ui::LayerAnimationSequence>>>
+      animation_sequences_;
+  bool in_sequence_ = false;
+  bool is_sequence_repeating_ = false;
+  base::TimeDelta duration_ = base::TimeDelta::FromSeconds(1);
+  base::TimeDelta old_duration_;
+};
+}  // namespace views
+
+#endif  // UI_VIEWS_EXAMPLES_ANIMATION_BUILDER_H_
diff --git a/ui/views/examples/animation_example.cc b/ui/views/examples/animation_example.cc
index cff22c5..7adda485 100644
--- a/ui/views/examples/animation_example.cc
+++ b/ui/views/examples/animation_example.cc
@@ -19,8 +19,10 @@
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/rect_f.h"
+#include "ui/gfx/geometry/rounded_corners_f.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/views/background.h"
+#include "ui/views/examples/animation_builder.h"
 #include "ui/views/layout/animating_layout_manager.h"
 #include "ui/views/layout/layout_manager_base.h"
 #include "ui/views/layout/layout_provider.h"
@@ -63,14 +65,6 @@
   layer()->GetAnimator()->set_tween_type(gfx::Tween::EASE_IN_OUT);
   layer()->GetAnimator()->set_preemption_strategy(
       ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
-
-  auto opacity_sequence = std::make_unique<ui::LayerAnimationSequence>();
-  opacity_sequence->set_is_repeating(true);
-  opacity_sequence->AddElement(ui::LayerAnimationElement::CreateOpacityElement(
-      0.4f, base::TimeDelta::FromSeconds(2)));
-  opacity_sequence->AddElement(ui::LayerAnimationElement::CreateOpacityElement(
-      0.9f, base::TimeDelta::FromSeconds(2)));
-  layer()->GetAnimator()->StartAnimation(opacity_sequence.release());
 }
 
 void AnimatingSquare::OnPaint(gfx::Canvas* canvas) {
@@ -155,6 +149,21 @@
   container->SetLayoutManager(std::make_unique<SquaresLayoutManager>());
   for (size_t i = 0; i < 5; ++i)
     container->AddChildView(std::make_unique<AnimatingSquare>(i));
+
+  {
+    gfx::RoundedCornersF rounded_corners(12.0f, 12.0f, 12.0f, 12.0f);
+    AnimationBuilder b;
+    for (auto* view : container->children()) {
+      b.SetDuration(base::TimeDelta::FromSeconds(10))
+          .SetRoundedCorners(view, rounded_corners)
+          .StartSequence()
+          .Repeat()
+          .SetDuration(base::TimeDelta::FromSeconds(2))
+          .SetOpacity(view, 0.4f)
+          .SetOpacity(view, 0.9f)
+          .EndSequence();
+    }
+  }
 }
 
 }  // namespace examples
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/button_bar.html b/ui/webui/resources/cr_components/chromeos/cellular_setup/button_bar.html
index 72beca8..da3132b4 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/button_bar.html
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/button_bar.html
@@ -15,7 +15,7 @@
       :host {
         display: flex;
         justify-content: flex-end;
-        padding: 10px 0;
+        padding: 10px 0 20px 0;
       }
 
       #forward:focus {
diff --git a/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_eid_dialog.html b/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_eid_dialog.html
index a20ab4e..95d7ced 100644
--- a/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_eid_dialog.html
+++ b/ui/webui/resources/cr_components/chromeos/cellular_setup/cellular_eid_dialog.html
@@ -10,8 +10,13 @@
   <template>
     <style include="cr-shared-style iron-flex">
       :host {
+        --cr-dialog-body-padding-horizontal: 24px;
+        --cr-dialog-button-container-padding-bottom: 20px;
+        --cr-dialog-button-container-padding-horizontal: 24px;
         --cr-dialog-width: 320px;
         --cr-dialog-title-slot-padding-bottom: 12px;
+        --cr-dialog-title-slot-padding-end: 24px;
+        --cr-dialog-title-slot-padding-start: 24px;
         --cr-dialog-title-font-size: calc(16 / 13 * 100%);
       }
 
@@ -33,6 +38,9 @@
       #qrCodeCanvas {
         display: block;
         margin: 20px auto 16px auto;
+        max-width: calc(var(--cr-dialog-width) -
+            var(--cr-dialog-title-slot-padding-start) -
+            var(--cr-dialog-title-slot-padding-end));
       }
 
       #eid {
diff --git a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html
index 66a36e9..42af22f 100644
--- a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html
+++ b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html
@@ -65,7 +65,7 @@
 
       :host ::slotted([slot=body]) {
         color: var(--cr-secondary-text-color);
-        padding: 0 20px;
+        padding: 0 var(--cr-dialog-body-padding-horizontal, 20px);
       }
 
       :host ::slotted([slot=title]) {
@@ -83,9 +83,9 @@
       :host ::slotted([slot=button-container]) {
         display: flex;
         justify-content: flex-end;
-        padding-bottom: 16px;
-        padding-inline-end: 16px;
-        padding-inline-start: 16px;
+        padding-bottom: var(--cr-dialog-button-container-padding-bottom, 16px);
+        padding-inline-end: var(--cr-dialog-button-container-padding-horizontal, 16px);
+        padding-inline-start: var(--cr-dialog-button-container-padding-horizontal, 16px);
         padding-top: 24px;
       }
 
diff --git a/url/ipc/url_param_traits_unittest.cc b/url/ipc/url_param_traits_unittest.cc
index 0fbddc7..a49203d 100644
--- a/url/ipc/url_param_traits_unittest.cc
+++ b/url/ipc/url_param_traits_unittest.cc
@@ -11,63 +11,128 @@
 #include "url/gurl.h"
 #include "url/ipc/url_param_traits.h"
 
+namespace {
+
+GURL BounceUrl(const GURL& input) {
+  IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
+  IPC::ParamTraits<GURL>::Write(&msg, input);
+
+  GURL output;
+  base::PickleIterator iter(msg);
+  EXPECT_TRUE(IPC::ParamTraits<GURL>::Read(&msg, &iter, &output));
+
+  return output;
+}
+
+void ExpectSerializationRoundtrips(const GURL& input) {
+  SCOPED_TRACE(testing::Message()
+               << "Input GURL: " << input.possibly_invalid_spec());
+  GURL output = BounceUrl(input);
+
+  // We want to test each component individually to make sure its range was
+  // correctly serialized and deserialized, not just the spec.
+  EXPECT_EQ(input.possibly_invalid_spec(), output.possibly_invalid_spec());
+  EXPECT_EQ(input.is_valid(), output.is_valid());
+  EXPECT_EQ(input.scheme(), output.scheme());
+  EXPECT_EQ(input.username(), output.username());
+  EXPECT_EQ(input.password(), output.password());
+  EXPECT_EQ(input.host(), output.host());
+  EXPECT_EQ(input.port(), output.port());
+  EXPECT_EQ(input.path(), output.path());
+  EXPECT_EQ(input.query(), output.query());
+  EXPECT_EQ(input.ref(), output.ref());
+}
+
+}  // namespace
+
 // Tests that serialize/deserialize correctly understand each other.
-TEST(IPCMessageTest, Serialize) {
+TEST(IPCMessageTest, SerializeGurl_Basic) {
   const char* serialize_cases[] = {
     "http://www.google.com/",
     "http://user:pass@host.com:888/foo;bar?baz#nop",
   };
 
-  for (size_t i = 0; i < base::size(serialize_cases); i++) {
-    GURL input(serialize_cases[i]);
-    IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
-    IPC::ParamTraits<GURL>::Write(&msg, input);
-
-    GURL output;
-    base::PickleIterator iter(msg);
-    EXPECT_TRUE(IPC::ParamTraits<GURL>::Read(&msg, &iter, &output));
-
-    // We want to test each component individually to make sure its range was
-    // correctly serialized and deserialized, not just the spec.
-    EXPECT_EQ(input.possibly_invalid_spec(), output.possibly_invalid_spec());
-    EXPECT_EQ(input.is_valid(), output.is_valid());
-    EXPECT_EQ(input.scheme(), output.scheme());
-    EXPECT_EQ(input.username(), output.username());
-    EXPECT_EQ(input.password(), output.password());
-    EXPECT_EQ(input.host(), output.host());
-    EXPECT_EQ(input.port(), output.port());
-    EXPECT_EQ(input.path(), output.path());
-    EXPECT_EQ(input.query(), output.query());
-    EXPECT_EQ(input.ref(), output.ref());
+  for (const char* test_input : serialize_cases) {
+    SCOPED_TRACE(testing::Message() << "Test input: " << test_input);
+    GURL input(test_input);
+    ExpectSerializationRoundtrips(input);
   }
+}
 
-  // Test an excessively long GURL.
-  {
-    const std::string url = std::string("http://example.org/").append(
-        url::kMaxURLChars + 1, 'a');
-    GURL input(url.c_str());
-    IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
-    IPC::ParamTraits<GURL>::Write(&msg, input);
+// Test of an excessively long GURL.
+TEST(IPCMessageTest, SerializeGurl_ExcessivelyLong) {
+  const std::string url =
+      std::string("http://example.org/").append(url::kMaxURLChars + 1, 'a');
+  GURL input(url.c_str());
+  GURL output = BounceUrl(input);
+  EXPECT_TRUE(output.is_empty());
+}
 
-    GURL output;
-    base::PickleIterator iter(msg);
-    EXPECT_TRUE(IPC::ParamTraits<GURL>::Read(&msg, &iter, &output));
-    EXPECT_TRUE(output.is_empty());
-  }
+// Test of an invalid GURL.
+TEST(IPCMessageTest, SerializeGurl_InvalidUrl) {
+  IPC::Message msg;
+  msg.WriteString("#inva://idurl/");
+  GURL output;
+  base::PickleIterator iter(msg);
+  EXPECT_FALSE(IPC::ParamTraits<GURL>::Read(&msg, &iter, &output));
+}
 
-  // Test an invalid GURL.
-  {
-    IPC::Message msg;
-    msg.WriteString("#inva://idurl/");
-    GURL output;
-    base::PickleIterator iter(msg);
-    EXPECT_FALSE(IPC::ParamTraits<GURL>::Read(&msg, &iter, &output));
-  }
-
-  // Also test the corrupt case.
+// Test of a corrupt deserialization input.
+TEST(IPCMessageTest, SerializeGurl_CorruptPayload) {
   IPC::Message msg(1, 2, IPC::Message::PRIORITY_NORMAL);
   msg.WriteInt(99);
   GURL output;
   base::PickleIterator iter(msg);
   EXPECT_FALSE(IPC::ParamTraits<GURL>::Read(&msg, &iter, &output));
 }
+
+// Test for the GURL testcase based on https://crbug.com/1214098 (which in turn
+// was based on ContentSecurityPolicyBrowserTest.FileURLs).
+TEST(IPCMessageTest, SerializeGurl_WindowsDriveInPathReplacement) {
+  GURL url1("file://hostname/");
+  ExpectSerializationRoundtrips(url1);
+  EXPECT_EQ("/", url1.path());
+  EXPECT_EQ("hostname", url1.host());
+
+  // Use GURL::Replacement to create a GURL with 1) a path that starts with a C:
+  // drive letter and 2) has a non-empty hostname (inherited from `url1` above).
+  // Without GURL::Replacement we would just get `url2` below, with an empty
+  // hostname, because of how DoParseUNC resets the hostname on Win32 (for more
+  // details see https://crbug.com/1214098#c4).
+  GURL::Replacements repl;
+  const std::string kNewPath = "/C:/dir/file.txt";
+  repl.SetPath(kNewPath.c_str(), url::Component(0, kNewPath.length()));
+  GURL url1_with_replaced_path = url1.ReplaceComponents(repl);
+  EXPECT_EQ(kNewPath, url1_with_replaced_path.path());
+  EXPECT_EQ("hostname", url1_with_replaced_path.host());
+
+#ifdef WIN32
+  // TODO(https://crbug.com/1214098): All GURLs should round-trip when bounced
+  // through IPC, but this doesn't work for `url1_with_replaced_path` on
+  // Windows.
+  GURL roundtrip = BounceUrl(url1_with_replaced_path);
+  EXPECT_NE(roundtrip.host(), url1_with_replaced_path.host());
+#else
+  // This is the MAIN VERIFICATION in this test.  The fact that this
+  // verification fails on Windows is the bug tracked in
+  // https://crbug.com/1214098.
+  ExpectSerializationRoundtrips(url1_with_replaced_path);
+#endif
+
+  // On Windows, `url1_with_replaced_path` will round-trip as `url2`.  (There is
+  // nothing wrong with `url2` - its serialization round-trips just fine;  the
+  // test assertions below just help explain the lack of round-tripping of
+  // `url1_with_replaced_path` above.)
+  GURL url2("file://hostname/C:/dir/file.txt");
+  ExpectSerializationRoundtrips(url2);
+#ifdef WIN32
+  EXPECT_EQ(url2.spec(), url1_with_replaced_path.spec());
+  EXPECT_EQ(url2.path(), url1_with_replaced_path.path());
+  EXPECT_EQ(url2.host(), url1_with_replaced_path.host());
+  EXPECT_EQ("/C:/dir/file.txt", url2.path());
+  EXPECT_EQ("", url2.host());
+#else
+  EXPECT_EQ("/C:/dir/file.txt", url2.path());
+  EXPECT_EQ("hostname", url2.host());
+#endif
+}
diff --git a/url/mojom/url_gurl_mojom_traits_unittest.cc b/url/mojom/url_gurl_mojom_traits_unittest.cc
index f0b1c60b..5be941c 100644
--- a/url/mojom/url_gurl_mojom_traits_unittest.cc
+++ b/url/mojom/url_gurl_mojom_traits_unittest.cc
@@ -32,21 +32,21 @@
   mojo::Receiver<UrlTest> receiver_;
 };
 
-// Mojo version of chrome IPC test in url/ipc/url_param_traits_unittest.cc.
-TEST(MojoGURLStructTraitsTest, Basic) {
-  base::test::SingleThreadTaskEnvironment task_environment;
+class MojoGURLStructTraitsTest : public ::testing::Test {
+ public:
+  MojoGURLStructTraitsTest()
+      : url_test_impl_(url_test_remote_.BindNewPipeAndPassReceiver()) {}
 
-  mojo::Remote<mojom::UrlTest> remote;
-  UrlTestImpl impl(remote.BindNewPipeAndPassReceiver());
-
-  const char* serialize_cases[] = {
-      "http://www.google.com/", "http://user:pass@host.com:888/foo;bar?baz#nop",
-  };
-
-  for (size_t i = 0; i < base::size(serialize_cases); i++) {
-    GURL input(serialize_cases[i]);
+  GURL BounceUrl(const GURL& input) {
     GURL output;
-    EXPECT_TRUE(remote->BounceUrl(input, &output));
+    EXPECT_TRUE(url_test_remote_->BounceUrl(input, &output));
+    return output;
+  }
+
+  void ExpectSerializationRoundtrips(const GURL& input) {
+    SCOPED_TRACE(testing::Message()
+                 << "Input GURL: " << input.possibly_invalid_spec());
+    GURL output = BounceUrl(input);
 
     // We want to test each component individually to make sure its range was
     // correctly serialized and deserialized, not just the spec.
@@ -62,22 +62,97 @@
     EXPECT_EQ(input.ref(), output.ref());
   }
 
-  // Test an excessively long GURL.
-  {
-    const std::string url =
-        std::string("http://example.org/").append(kMaxURLChars + 1, 'a');
-    GURL input(url.c_str());
-    GURL output;
-    EXPECT_TRUE(remote->BounceUrl(input, &output));
-    EXPECT_TRUE(output.is_empty());
+  Origin BounceOrigin(const Origin& input) {
+    Origin output;
+    EXPECT_TRUE(url_test_remote_->BounceOrigin(input, &output));
+    return output;
   }
 
-  // Test basic Origin serialization.
+ private:
+  base::test::SingleThreadTaskEnvironment task_environment;
+  mojo::Remote<mojom::UrlTest> url_test_remote_;
+  UrlTestImpl url_test_impl_;
+};
+
+// Mojo version of chrome IPC test in url/ipc/url_param_traits_unittest.cc.
+TEST_F(MojoGURLStructTraitsTest, Basic) {
+  const char* serialize_cases[] = {
+      "http://www.google.com/",
+      "http://user:pass@host.com:888/foo;bar?baz#nop",
+  };
+
+  for (const char* test_input : serialize_cases) {
+    SCOPED_TRACE(testing::Message() << "Test input: " << test_input);
+    GURL input(test_input);
+    ExpectSerializationRoundtrips(input);
+  }
+}
+
+// Test of an excessively long GURL.
+TEST_F(MojoGURLStructTraitsTest, ExcessivelyLongUrl) {
+  const std::string url =
+      std::string("http://example.org/").append(kMaxURLChars + 1, 'a');
+  GURL input(url.c_str());
+  GURL output = BounceUrl(input);
+  EXPECT_TRUE(output.is_empty());
+}
+
+// Test for the GURL testcase based on https://crbug.com/1214098 (which in turn
+// was based on ContentSecurityPolicyBrowserTest.FileURLs).
+TEST_F(MojoGURLStructTraitsTest, WindowsDriveInPathReplacement) {
+  GURL url1("file://hostname/");
+  ExpectSerializationRoundtrips(url1);
+  EXPECT_EQ("/", url1.path());
+  EXPECT_EQ("hostname", url1.host());
+
+  // Use GURL::Replacement to create a GURL with 1) a path that starts with a C:
+  // drive letter and 2) has a non-empty hostname (inherited from `url1` above).
+  // Without GURL::Replacement we would just get `url2` below, with an empty
+  // hostname, because of how DoParseUNC resets the hostname on Win32 (for more
+  // details see https://crbug.com/1214098#c4).
+  GURL::Replacements repl;
+  const std::string kNewPath = "/C:/dir/file.txt";
+  repl.SetPath(kNewPath.c_str(), url::Component(0, kNewPath.length()));
+  GURL url1_with_replaced_path = url1.ReplaceComponents(repl);
+  EXPECT_EQ(kNewPath, url1_with_replaced_path.path());
+  EXPECT_EQ("hostname", url1_with_replaced_path.host());
+
+#ifdef WIN32
+  // TODO(https://crbug.com/1214098): All GURLs should round-trip when bounced
+  // through IPC, but this doesn't work for `url1_with_replaced_path` on
+  // Windows.
+  GURL roundtrip = BounceUrl(url1_with_replaced_path);
+  EXPECT_NE(roundtrip.host(), url1_with_replaced_path.host());
+#else
+  // This is the MAIN VERIFICATION in this test.  The fact that this
+  // verification fails on Windows is the bug tracked in
+  // https://crbug.com/1214098.
+  ExpectSerializationRoundtrips(url1_with_replaced_path);
+#endif
+
+  // On Windows, IPC will serialize/deserialze `url1_with_replaced_path` as
+  // `url2` (i.e. it won't round-trip the URL spec).  The test assertions below
+  // help illustrate why we can't assert ExpectSerializationRoundtrips above (on
+  // Windows).
+  EXPECT_EQ("file://hostname/C:/dir/file.txt", url1_with_replaced_path.spec());
+  GURL url2(url1_with_replaced_path.spec());
+#ifdef WIN32
+  EXPECT_NE(url2.spec(), url1_with_replaced_path.spec());
+  EXPECT_EQ("", url2.host());
+#else
+  EXPECT_EQ(url2.spec(), url1_with_replaced_path.spec());
+  EXPECT_EQ("hostname", url2.host());
+#endif
+  EXPECT_EQ(url2.path(), url1_with_replaced_path.path());
+  ExpectSerializationRoundtrips(url2);
+}
+
+// Test of basic Origin serialization.
+TEST_F(MojoGURLStructTraitsTest, OriginSerialization) {
   Origin non_unique = Origin::UnsafelyCreateTupleOriginWithoutNormalization(
                           "http", "www.google.com", 80)
                           .value();
-  Origin output;
-  EXPECT_TRUE(remote->BounceOrigin(non_unique, &output));
+  Origin output = BounceOrigin(non_unique);
   EXPECT_EQ(non_unique, output);
   EXPECT_FALSE(output.opaque());
 
@@ -86,11 +161,10 @@
   EXPECT_NE(unique1, unique2);
   EXPECT_NE(unique2, unique1);
   EXPECT_NE(unique2, non_unique);
-  EXPECT_TRUE(remote->BounceOrigin(unique1, &output));
+  output = BounceOrigin(unique1);
   EXPECT_TRUE(output.opaque());
   EXPECT_EQ(unique1, output);
-  Origin output2;
-  EXPECT_TRUE(remote->BounceOrigin(unique2, &output2));
+  Origin output2 = BounceOrigin(unique2);
   EXPECT_EQ(unique2, output2);
   EXPECT_NE(unique2, output);
   EXPECT_NE(unique1, output2);
@@ -98,7 +172,7 @@
   Origin normalized =
       Origin::CreateFromNormalizedTuple("http", "www.google.com", 80);
   EXPECT_EQ(normalized, non_unique);
-  EXPECT_TRUE(remote->BounceOrigin(normalized, &output));
+  output = BounceOrigin(normalized);
   EXPECT_EQ(normalized, output);
   EXPECT_EQ(non_unique, output);
   EXPECT_FALSE(output.opaque());
diff --git a/url/origin_abstract_tests.h b/url/origin_abstract_tests.h
index 26190a0b..89ed9ab02 100644
--- a/url/origin_abstract_tests.h
+++ b/url/origin_abstract_tests.h
@@ -372,6 +372,15 @@
       {"file://example.com/etc/passwd", {"file", "example.com", 0}},
       {"file:///", {"file", "", 0}},
 
+#ifdef WIN32
+      // TODO(https://crbug.com/1214098): Consider unifying URL parsing behavior
+      // on all platforms (or at least make sure that serialization always
+      // round-trips - see https://crbug.com/1214098).
+      {"file://hostname/C:/dir/file.txt", {"file", "", 0}},
+#else
+      {"file://hostname/C:/dir/file.txt", {"file", "hostname", 0}},
+#endif
+
       // HTTP URLs
       {"http://example.com/", {"http", "example.com", 80}},
       {"http://example.com:80/", {"http", "example.com", 80}},
diff --git a/weblayer/shell/android/shell_apk/AndroidManifest.xml b/weblayer/shell/android/shell_apk/AndroidManifest.xml
index a7734e0..8b47b41 100644
--- a/weblayer/shell/android/shell_apk/AndroidManifest.xml
+++ b/weblayer/shell/android/shell_apk/AndroidManifest.xml
@@ -7,6 +7,7 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     package="org.chromium.weblayer.shell">
 
     <application android:label="WebLayer shell"
@@ -63,6 +64,14 @@
       <meta-data
           android:name="org.chromium.weblayer.ENABLE_LOGGING_OF_JS_CONSOLE_MESSAGES" android:value="true"/>
 
+      <!-- Disables at startup init of Emoji2. See http://crbug.com/1205141 -->
+      <provider
+          android:authorities="org.chromium.weblayer.shell.androidx-startup"
+          android:name="androidx.startup.InitializationProvider"
+          android:exported="false"
+          tools:node="remove">
+      </provider>
+
       {% if weblayer_package is defined %}
             <meta-data android:name="org.chromium.weblayer.WebLayerPackage"
                        android:value="{{ weblayer_package }}"/>