diff --git a/DEPS b/DEPS
index a9c65e1a..c854ac67 100644
--- a/DEPS
+++ b/DEPS
@@ -311,15 +311,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'c90f939a79eab36ff08b04ae6a619c718b363b22',
+  'v8_revision': 'cd2100108ffb2ae78e46934e7d29313f00aee271',
   # 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': '19fb11b67c6f30f440112ca5d9725d1bed9b232e',
+  'angle_revision': '2265e37bf9b3679c20748fb16b1823e9b0b621d8',
   # 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': 'ab3cb3f5416b4363f4d4f0e06b35ec8c3dc4714c',
+  'swiftshader_revision': '3fdd375e1de1e81e5c9fe04a46c1870b78a55b43',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -378,7 +378,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'faaae893d5d2c4050d9ba741a2cbd70b4528c7fe',
+  'catapult_revision': '98d333e8aebd9658a465e71e51214072b0ac205e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -422,7 +422,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '18bcdebc0256ad3d14bd1290163c98c84baddfa3',
+  'dawn_revision': '72ac53e5fa8c7837750a3dfec350775da72e8aef',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -818,7 +818,7 @@
 
   'src/clank': {
     'url': 'https://chrome-internal.googlesource.com/clank/internal/apps.git' + '@' +
-    'aa0b389580b88beb9ef353b3a7bb11cfa3f8bac6',
+    '1ee22b1b79a2c01ad36b08b7b5a88beddf7a1b8d',
     'condition': 'checkout_android and checkout_src_internal and not checkout_clank_via_src_internal',
   },
 
@@ -1002,7 +1002,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': '6BqL6l_VnAEp2_3Vdr-Rzeb2pDOCpufW6SLm8GKBIDQC',
+          'version': 'B1v8mLuR3zWALlZp3ZYv3ydSWTsVvOj50O8odlD5gMMC',
       },
     ],
     'condition': 'checkout_android',
@@ -1079,7 +1079,7 @@
       'packages': [
           {
                'package': 'chromium/third_party/android_build_tools/manifest_merger',
-               'version': 'bUREd_PkCqlp2ww6zmyOLGf0jhqgbnf6GT4V1xkAZ10C',
+               'version': 'bfhl7B4_T6dP72d1sF-6RSeAQqwlw1qUx-FDEFh3sKIC',
           },
       ],
       'condition': 'checkout_android',
@@ -1245,7 +1245,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '2c0a8c736a59044e4acc7be9e172343adc5c4310',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '77e64ae61ebda9e20f1eee461149e601c65b0a8f',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1522,7 +1522,7 @@
   },
 
   'src/third_party/libunwindstack': {
-      'url': Var('chromium_git') + '/chromium/src/third_party/libunwindstack.git' + '@' + '8740b09bd1f8b81bdba92766afcb9df1d6a1f14e',
+      'url': Var('chromium_git') + '/chromium/src/third_party/libunwindstack.git' + '@' + '4dbfa0e8c844c8e243b297bc185e54a99ff94f9e',
       'condition': 'checkout_android',
   },
 
@@ -1666,7 +1666,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '7bd83a8f9af8604e932cd717fdd56e586c54d3a4',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '082982f29eab767cb18b2dfa7196b2774b3c65ee',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1811,7 +1811,7 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@2eb0a986e4f50b40c1fa1724564bc826c51a295b',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@37c23b92b3b6379ec1516466dffcbedebc301f20',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'ebe84bec02c041d28f902da0214bf442743fc907',
@@ -1848,10 +1848,10 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'd1b65aa5a88f6efd900604dfcda840154e9f16e2',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '9ff9ec53c07190a7add65392239939961a064da5',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '8ced52dedfef6c91ef5ee84bf08af00bf879a17d',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '18b95681858717f66d7dfef20c53dbadcec8d4da',
+    Var('webrtc_git') + '/src.git' + '@' + 'f52cd2bb7305dbd781fe99e2c5a3929380f4fd8d',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -1921,7 +1921,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@4bd291c858980c53e3127bcb8a7076502f87d6a1',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@5f95aa179c7691f2def9340a7798b7fc07895c2b',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 0af3237..6d90eb13 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -1340,7 +1340,10 @@
   ) | set('%s@skia-corp.google.com.iam.gserviceaccount.com' % s
           for s in ('chromium-internal-autoroll',)
   ) | set('%s@owners-cleanup-prod.google.com.iam.gserviceaccount.com' % s
-          for s in ('swarming-tasks',))
+          for s in ('swarming-tasks',)
+  ) | set('%s@fuchsia-infra.iam.gserviceaccount.com' % s
+          for s in ('global-integration-try-builder',
+                    'global-integration-ci-builder'))
 
 _INVALID_GRD_FILE_LINE = [
         (r'<file lang=.* path=.*', 'Path should come before lang in GRD files.')
diff --git a/ash/app_list/folder_image_unittest.cc b/ash/app_list/folder_image_unittest.cc
index 255e18ba..d5c74a9 100644
--- a/ash/app_list/folder_image_unittest.cc
+++ b/ash/app_list/folder_image_unittest.cc
@@ -119,9 +119,8 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     FolderImageTest,
-    ::testing::Combine(::testing::Values(AppListConfigType::kLarge,
-                                         AppListConfigType::kMedium,
-                                         AppListConfigType::kSmall),
+    ::testing::Combine(::testing::Values(AppListConfigType::kRegular,
+                                         AppListConfigType::kDense),
                        ::testing::Bool()));
 
 TEST_P(FolderImageTest, UpdateListTest) {
diff --git a/ash/app_list/model/app_list_folder_item.cc b/ash/app_list/model/app_list_folder_item.cc
index 38292c8..73df26f 100644
--- a/ash/app_list/model/app_list_folder_item.cc
+++ b/ash/app_list/model/app_list_folder_item.cc
@@ -26,13 +26,8 @@
   // Item observers are added later in OnListItemAdded().
   item_list_->AddObserver(this);
 
-  std::vector<AppListConfigType> configs;
-  if (features::IsProductivityLauncherEnabled()) {
-    configs = {AppListConfigType::kRegular, AppListConfigType::kDense};
-  } else {
-    configs = {AppListConfigType::kLarge, AppListConfigType::kMedium,
-               AppListConfigType::kSmall};
-  }
+  std::vector<AppListConfigType> configs = {AppListConfigType::kRegular,
+                                            AppListConfigType::kDense};
   EnsureIconsForAvailableConfigTypes(configs, /*request_icon_update=*/false);
   config_provider_observation_.Observe(&AppListConfigProvider::Get());
   set_is_folder(true);
diff --git a/ash/app_list/model/app_list_model_unittest.cc b/ash/app_list/model/app_list_model_unittest.cc
index 0194be3..9e251f5 100644
--- a/ash/app_list/model/app_list_model_unittest.cc
+++ b/ash/app_list/model/app_list_model_unittest.cc
@@ -270,67 +270,6 @@
   model_->MergeItems(item0->id(), folder->id());
 }
 
-TEST_F(AppListModelFolderTest, NonSharedConfigIconGeneration) {
-  // The configs tested here are not used by ProductivityLauncher. This test
-  // can be deleted when ProductivityLauncher is the default.
-  base::test::ScopedFeatureList features;
-  features.InitAndDisableFeature(features::kProductivityLauncher);
-
-  // Ensure any configs set by previous tests are cleared.
-  AppListConfigProvider::Get().ResetForTesting();
-
-  // Start with kLarge config available.
-  const AppListConfig* large_config =
-      AppListConfigProvider::Get().GetConfigForType(AppListConfigType::kLarge,
-                                                    true);
-  ASSERT_TRUE(large_config);
-
-  const size_t num_folder_apps = 5;
-  const size_t num_observed_apps = 4;
-  AppListFolderItem* folder = CreateFolderWithApps("folder1", num_folder_apps);
-
-  // Verify that the folder has folder image for large config.
-  FolderImage* large_config_image =
-      folder->GetFolderImageForTesting(AppListConfigType::kLarge);
-  ASSERT_TRUE(large_config_image);
-  EXPECT_EQ(large_config->folder_unclipped_icon_size(),
-            large_config_image->icon().size());
-
-  // Verify that the folder is observing the app list item.
-  EXPECT_TRUE(ItemObservedByFolder(
-      folder, folder->item_list()->item_at(num_observed_apps - 1),
-      AppListConfigType::kLarge));
-  EXPECT_FALSE(ItemObservedByFolder(
-      folder, folder->item_list()->item_at(num_observed_apps),
-      AppListConfigType::kLarge));
-
-  // Not medium folder image, as the config does not exist yet.
-  EXPECT_FALSE(folder->GetFolderImageForTesting(AppListConfigType::kMedium));
-
-  // Create medium config, and verify the folder image for medium config gets
-  // created.
-  const AppListConfig* medium_config =
-      AppListConfigProvider::Get().GetConfigForType(AppListConfigType::kMedium,
-                                                    true);
-  FolderImage* medium_config_image =
-      folder->GetFolderImageForTesting(AppListConfigType::kMedium);
-  ASSERT_TRUE(medium_config_image);
-  EXPECT_EQ(medium_config->folder_unclipped_icon_size(),
-            medium_config_image->icon().size());
-
-  // Verify that the folder is observing the app list item.
-  EXPECT_TRUE(ItemObservedByFolder(
-      folder, folder->item_list()->item_at(num_observed_apps - 1),
-      AppListConfigType::kMedium));
-  EXPECT_FALSE(ItemObservedByFolder(
-      folder, folder->item_list()->item_at(num_observed_apps),
-      AppListConfigType::kMedium));
-
-  EXPECT_FALSE(folder->GetFolderImageForTesting(AppListConfigType::kSmall));
-
-  AppListConfigProvider::Get().ResetForTesting();
-}
-
 // Same test as above, but for ProductivityLauncher config types.
 TEST_F(AppListModelFolderTest,
        NonSharedConfigIconGenerationProductivityLauncher) {
diff --git a/ash/app_list/views/apps_grid_view_unittest.cc b/ash/app_list/views/apps_grid_view_unittest.cc
index b978fa60a..a28c63a 100644
--- a/ash/app_list/views/apps_grid_view_unittest.cc
+++ b/ash/app_list/views/apps_grid_view_unittest.cc
@@ -62,6 +62,7 @@
 #include "base/test/icu_test_util.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/metrics/user_action_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/window.h"
@@ -723,6 +724,25 @@
 
 INSTANTIATE_TEST_SUITE_P(All, AppsGridViewDragTest, testing::Bool());
 
+class AppsGridViewDragWithShelfPartyTest : public AppsGridViewDragTest {
+ public:
+  AppsGridViewDragWithShelfPartyTest() {
+    scoped_feature_list_.InitAndEnableFeature(features::kShelfParty);
+  }
+  AppsGridViewDragWithShelfPartyTest(
+      const AppsGridViewDragWithShelfPartyTest&) = delete;
+  AppsGridViewDragWithShelfPartyTest& operator=(
+      const AppsGridViewDragWithShelfPartyTest&) = delete;
+  ~AppsGridViewDragWithShelfPartyTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         AppsGridViewDragWithShelfPartyTest,
+                         testing::Bool());
+
 // Test suite for clamshell mode, parameterized by RTL.
 class AppsGridViewClamshellTest : public AppsGridViewTest,
                                   public testing::WithParamInterface<bool> {
@@ -3919,7 +3939,7 @@
   EXPECT_TRUE(ShelfModel::Get()->items().empty());
 }
 
-TEST_P(AppsGridViewDragTest, DragAndPinItemToEmptyShelf) {
+TEST_P(AppsGridViewDragWithShelfPartyTest, DragAndPinItemToEmptyShelf) {
   model_->PopulateApps(2);
   UpdateLayout();
 
diff --git a/ash/constants/quick_settings_catalogs.h b/ash/constants/quick_settings_catalogs.h
index d9ce304..896e3e6 100644
--- a/ash/constants/quick_settings_catalogs.h
+++ b/ash/constants/quick_settings_catalogs.h
@@ -29,7 +29,8 @@
   kPowerRestartMenuButton = 13,
   kPowerSignoutMenuButton = 14,
   kPowerLockMenuButton = 15,
-  kMaxValue = kPowerLockMenuButton
+  kSupervisedButton = 16,
+  kMaxValue = kSupervisedButton
 };
 
 // A catalog that registers all the features on the Quick Settings page. This
diff --git a/ash/public/cpp/app_list/app_list_config.cc b/ash/public/cpp/app_list/app_list_config.cc
index 65462e6..4483f296 100644
--- a/ash/public/cpp/app_list/app_list_config.cc
+++ b/ash/public/cpp/app_list/app_list_config.cc
@@ -27,25 +27,15 @@
 // padding for the unclipped folder icon.
 int MinYScaleHeightAdjustmentForType(ash::AppListConfigType type) {
   switch (type) {
-    case ash::AppListConfigType::kLarge:
     case ash::AppListConfigType::kRegular:
       return 16;
-    case ash::AppListConfigType::kMedium:
     case ash::AppListConfigType::kDense:
       return 8;
-    case ash::AppListConfigType::kSmall:
-      return 4;
   }
 }
 
 int GridTileWidthForType(ash::AppListConfigType type) {
   switch (type) {
-    case ash::AppListConfigType::kLarge:
-      return 120;
-    case ash::AppListConfigType::kMedium:
-      return 88;
-    case ash::AppListConfigType::kSmall:
-      return 80;
     case ash::AppListConfigType::kRegular:
       return 96;
     case ash::AppListConfigType::kDense:
@@ -55,38 +45,24 @@
 
 int GridTileHeightForType(ash::AppListConfigType type) {
   switch (type) {
-    case ash::AppListConfigType::kLarge:
     case ash::AppListConfigType::kRegular:
       return 120;
-    case ash::AppListConfigType::kMedium:
     case ash::AppListConfigType::kDense:
       return 88;
-    case ash::AppListConfigType::kSmall:
-      return 80;
   }
 }
 
 int GridIconDimensionForType(ash::AppListConfigType type) {
   switch (type) {
-    case ash::AppListConfigType::kLarge:
     case ash::AppListConfigType::kRegular:
       return 64;
-    case ash::AppListConfigType::kMedium:
     case ash::AppListConfigType::kDense:
       return 48;
-    case ash::AppListConfigType::kSmall:
-      return 40;
   }
 }
 
 int GridTitleTopPaddingForType(ash::AppListConfigType type) {
   switch (type) {
-    case ash::AppListConfigType::kLarge:
-      return 92;
-    case ash::AppListConfigType::kMedium:
-      return 64;
-    case ash::AppListConfigType::kSmall:
-      return 56;
     case ash::AppListConfigType::kRegular:
       return 88;
     case ash::AppListConfigType::kDense:
@@ -96,11 +72,6 @@
 
 int GridTitleBottomPaddingForType(ash::AppListConfigType type) {
   switch (type) {
-    case ash::AppListConfigType::kLarge:
-      return 8;
-    case ash::AppListConfigType::kMedium:
-    case ash::AppListConfigType::kSmall:
-      return 6;
     case ash::AppListConfigType::kRegular:
       return 12;
     case ash::AppListConfigType::kDense:
@@ -110,25 +81,16 @@
 
 int GridTitleHorizontalPaddingForType(ash::AppListConfigType type) {
   switch (type) {
-    case ash::AppListConfigType::kLarge:
     case ash::AppListConfigType::kRegular:
       return 8;
-    case ash::AppListConfigType::kMedium:
     case ash::AppListConfigType::kDense:
       return 4;
-    case ash::AppListConfigType::kSmall:
-      return 0;
   }
 }
 
+// TODO(crbug.com/1372228): Remove this function.
 int GridFocusDimensionForType(ash::AppListConfigType type) {
   switch (type) {
-    case ash::AppListConfigType::kLarge:
-      return 80;
-    case ash::AppListConfigType::kMedium:
-      return 64;
-    case ash::AppListConfigType::kSmall:
-      return 56;
     // Unused for ProductivityLauncher.
     case ash::AppListConfigType::kRegular:
     case ash::AppListConfigType::kDense:
@@ -136,25 +98,10 @@
   }
 }
 
-int GridFocusCornerRadiusForType(ash::AppListConfigType type) {
-  switch (type) {
-    case ash::AppListConfigType::kLarge:
-      return 12;
-    case ash::AppListConfigType::kMedium:
-    case ash::AppListConfigType::kSmall:
-    case ash::AppListConfigType::kRegular:
-    case ash::AppListConfigType::kDense:
-      return 8;
-  }
-}
-
 int AppTitleMaxLineHeightForType(ash::AppListConfigType type) {
   switch (type) {
-    case ash::AppListConfigType::kLarge:
     case ash::AppListConfigType::kRegular:
       return 20;
-    case ash::AppListConfigType::kMedium:
-    case ash::AppListConfigType::kSmall:
     case ash::AppListConfigType::kDense:
       return 18;
   }
@@ -162,15 +109,12 @@
 
 gfx::FontList AppTitleFontForType(ash::AppListConfigType type) {
   ui::ResourceBundle::FontDetails details;
-  // TODO(https://crbug.com/1197600): Use Google Sans Text (medium weight) for
-  // ProductivityLauncher (kRegular, kDense) when that font is available.
+  // TODO(https://crbug.com/1197600): Use Google Sans Text (medium weight) when
+  // the font is available.
   switch (type) {
-    case ash::AppListConfigType::kLarge:
     case ash::AppListConfigType::kRegular:
       details.size_delta = 1;
       break;
-    case ash::AppListConfigType::kMedium:
-    case ash::AppListConfigType::kSmall:
     case ash::AppListConfigType::kDense:
       details.size_delta = 0;
       break;
@@ -181,12 +125,6 @@
 // See "App drag over folder" in go/cros-launcher-spec.
 int FolderUnclippedIconDimensionForType(ash::AppListConfigType type) {
   switch (type) {
-    case ash::AppListConfigType::kLarge:
-      return 88;
-    case ash::AppListConfigType::kMedium:
-      return 64;
-    case ash::AppListConfigType::kSmall:
-      return 56;
     case ash::AppListConfigType::kRegular:
       return 76;
     case ash::AppListConfigType::kDense:
@@ -196,12 +134,6 @@
 
 int FolderClippedIconDimensionForType(ash::AppListConfigType type) {
   switch (type) {
-    case ash::AppListConfigType::kLarge:
-      return 72;
-    case ash::AppListConfigType::kMedium:
-      return 56;
-    case ash::AppListConfigType::kSmall:
-      return 48;
     case ash::AppListConfigType::kRegular:
       return 60;
     case ash::AppListConfigType::kDense:
@@ -211,29 +143,13 @@
 
 int ItemIconInFolderIconDimensionForType(ash::AppListConfigType type) {
   switch (type) {
-    case ash::AppListConfigType::kLarge:
     case ash::AppListConfigType::kRegular:
       return 32;
-    case ash::AppListConfigType::kMedium:
-      return 28;
-    case ash::AppListConfigType::kSmall:
     case ash::AppListConfigType::kDense:
       return 24;
   }
 }
 
-int ItemIconInFolderIconMarginForType(ash::AppListConfigType type) {
-  switch (type) {
-    case ash::AppListConfigType::kLarge:
-    case ash::AppListConfigType::kRegular:
-    case ash::AppListConfigType::kMedium:
-    case ash::AppListConfigType::kDense:
-      return 4;
-    case ash::AppListConfigType::kSmall:
-      return 2;
-  }
-}
-
 }  // namespace
 
 SharedAppListConfig& SharedAppListConfig::instance() {
@@ -279,7 +195,7 @@
       grid_title_horizontal_padding_(GridTitleHorizontalPaddingForType(type)),
       grid_title_width_(grid_tile_width_),
       grid_focus_dimension_(GridFocusDimensionForType(type)),
-      grid_focus_corner_radius_(GridFocusCornerRadiusForType(type)),
+      grid_focus_corner_radius_(8),
       app_title_max_line_height_(AppTitleMaxLineHeightForType(type)),
       app_title_font_(AppTitleFontForType(type)),
       folder_bubble_radius_(FolderUnclippedIconDimensionForType(type) / 2),
@@ -290,7 +206,7 @@
       folder_background_radius_(12),
       item_icon_in_folder_icon_dimension_(
           ItemIconInFolderIconDimensionForType(type)),
-      item_icon_in_folder_icon_margin_(ItemIconInFolderIconMarginForType(type)),
+      item_icon_in_folder_icon_margin_(4),
       folder_dropping_circle_radius_(folder_bubble_radius_) {}
 
 AppListConfig::AppListConfig(const AppListConfig& base_config,
diff --git a/ash/public/cpp/app_list/app_list_config_provider.cc b/ash/public/cpp/app_list/app_list_config_provider.cc
index 25eb630..7cb2a70 100644
--- a/ash/public/cpp/app_list/app_list_config_provider.cc
+++ b/ash/public/cpp/app_list/app_list_config_provider.cc
@@ -23,29 +23,11 @@
 // size.
 ash::AppListConfigType GetConfigTypeForDisplaySize(
     const gfx::Size& display_size) {
-  if (features::IsProductivityLauncherEnabled()) {
-    // Values from go/cros-launcher-spec
-    if (display_size.height() <= 675 || display_size.width() <= 675)
-      return AppListConfigType::kDense;
+  // Values from go/cros-launcher-spec
+  if (display_size.height() <= 675 || display_size.width() <= 675)
+    return AppListConfigType::kDense;
 
-    return AppListConfigType::kRegular;
-  }
-
-  // Landscape:
-  if (display_size.width() > display_size.height()) {
-    if (display_size.width() >= 1200)
-      return ash::AppListConfigType::kLarge;
-    if (display_size.width() >= 960)
-      return ash::AppListConfigType::kMedium;
-    return ash::AppListConfigType::kSmall;
-  }
-
-  // Portrait:
-  if (display_size.width() >= 768)
-    return ash::AppListConfigType::kLarge;
-  if (display_size.width() >= 600)
-    return ash::AppListConfigType::kMedium;
-  return ash::AppListConfigType::kSmall;
+  return AppListConfigType::kRegular;
 }
 
 }  // namespace
diff --git a/ash/public/cpp/app_list/app_list_types.h b/ash/public/cpp/app_list/app_list_types.h
index 2f37203c..6af57dd 100644
--- a/ash/public/cpp/app_list/app_list_types.h
+++ b/ash/public/cpp/app_list/app_list_types.h
@@ -45,18 +45,10 @@
 
 // App list config types supported by AppListConfig.
 enum class AppListConfigType {
-  // Legacy configs, chosen based on the size of the screen.
-  // Used when ProductivityLauncher is disabled.
-  kLarge,
-  kMedium,
-  kSmall,
-
   // Config for tablet mode on typical size screens.
-  // Used when ProductivityLauncher is enabled.
   kRegular,
 
   // Config for clamshell mode. Also used for tablet mode on small screens.
-  // Used when ProductivityLauncher is enabled.
   kDense,
 };
 
diff --git a/ash/public/cpp/ash_view_ids.h b/ash/public/cpp/ash_view_ids.h
index c5c434e..611b1ac 100644
--- a/ash/public/cpp/ash_view_ids.h
+++ b/ash/public/cpp/ash_view_ids.h
@@ -44,6 +44,7 @@
   VIEW_ID_QS_POWER_SIGNOUT_MENU_BUTTON,
   VIEW_ID_QS_SETTINGS_BUTTON,
   VIEW_ID_QS_SIGN_OUT_BUTTON,
+  VIEW_ID_QS_SUPERVISED_BUTTON,
   VIEW_ID_QS_USER_AVATAR_BUTTON,
   VIEW_ID_QS_VERSION_BUTTON,
   VIEW_ID_QS_MAX = VIEW_ID_QS_VERSION_BUTTON,
diff --git a/ash/resources/vector_icons/unified_menu_dark_mode.icon b/ash/resources/vector_icons/unified_menu_dark_mode.icon
index 667abf487..148fe33 100644
--- a/ash/resources/vector_icons/unified_menu_dark_mode.icon
+++ b/ash/resources/vector_icons/unified_menu_dark_mode.icon
@@ -2,28 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-CANVAS_DIMENSIONS, 40,
-MOVE_TO, 20, 36,
-R_CUBIC_TO, 8.84f, 0, 16, -7.16f, 16, -16,
-CUBIC_TO_SHORTHAND, 28.84f, 4, 20, 4,
-CUBIC_TO_SHORTHAND, 4, 11.16f, 4, 20,
-R_CUBIC_TO, 0, 8.84f, 7.16f, 16, 16, 16,
-CLOSE,
-R_MOVE_TO, 0, -28.23f,
-R_CUBIC_TO, 6.76f, 0, 12.24f, 5.48f, 12.24f, 12.24f,
-CUBIC_TO_SHORTHAND, 26.76f, 32.24f, 20, 32.24f,
-V_LINE_TO, 7.77f,
-CLOSE
-
 CANVAS_DIMENSIONS, 20,
-MOVE_TO, 10, 18,
-R_CUBIC_TO, 4.42f, 0, 8, -3.58f, 8, -8,
-R_CUBIC_TO, 0, -4.42f, -3.58f, -8, -8, -8,
-R_CUBIC_TO, -4.42f, 0, -8, 3.58f, -8, 8,
-R_CUBIC_TO, 0, 4.42f, 3.58f, 8, 8, 8,
+MOVE_TO, 2, 10,
+R_ARC_TO, 8, 8, 0, 1, 1, 16, 0,
+R_ARC_TO, 8, 8, 0, 0, 1, -16, 0,
 CLOSE,
-R_MOVE_TO, 0, -14.12f,
-R_CUBIC_TO, 3.38f, 0, 6.12f, 2.74f, 6.12f, 6.12f,
-R_CUBIC_TO, 0, 3.38f, -2.74f, 6.12f, -6.12f, 6.12f,
+R_MOVE_TO, 1.88f, 0,
+ARC_TO, 6.12f, 6.12f, 0, 0, 0, 10, 16.12f,
 V_LINE_TO, 3.88f,
+ARC_TO, 6.12f, 6.12f, 0, 0, 0, 3.88f, 10,
 CLOSE
diff --git a/ash/shelf/shelf_view.cc b/ash/shelf/shelf_view.cc
index c417f718..73ad887 100644
--- a/ash/shelf/shelf_view.cc
+++ b/ash/shelf/shelf_view.cc
@@ -1573,7 +1573,8 @@
 }
 
 void ShelfView::MoveDragViewTo(int primary_axis_coordinate) {
-  if (visible_views_indices_.empty()) {
+  if (visible_views_indices_.empty() ||
+      !IsItemPinned(model_->items()[visible_views_indices_.front()])) {
     DCHECK(model_->in_shelf_party());
     if (shelf_->IsHorizontalAlignment()) {
       if (drag_view_->x() != app_icons_layout_offset_)
@@ -1858,7 +1859,7 @@
   if (!features::IsDragUnpinnedAppToPinEnabled())
     return false;
 
-  if (visible_views_indices_.empty()) {
+  if (!base::Contains(visible_views_indices_, dragged_view_index)) {
     DCHECK(model_->in_shelf_party());
     return false;
   }
@@ -2654,6 +2655,9 @@
 }
 
 void ShelfView::HandleShelfParty() {
+  if (!base::FeatureList::IsEnabled(features::kShelfParty))
+    return;
+
   UpdateShelfItemViewsVisibility();
   PreferredSizeChanged();
   AnimateToIdealBounds();
diff --git a/ash/shelf/shelf_view_unittest.cc b/ash/shelf/shelf_view_unittest.cc
index b87835c..63211905 100644
--- a/ash/shelf/shelf_view_unittest.cc
+++ b/ash/shelf/shelf_view_unittest.cc
@@ -3597,7 +3597,9 @@
                            std::pair<ShelfAlignment, ShelfAutoHideBehavior>> {
  public:
   ShelfPartyTest()
-      : ShelfViewTest(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+      : ShelfViewTest(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
+    scoped_feature_list_.InitAndEnableFeature(features::kShelfParty);
+  }
   ShelfPartyTest(const ShelfPartyTest&) = delete;
   ShelfPartyTest& operator=(const ShelfPartyTest&) = delete;
   ~ShelfPartyTest() override = default;
@@ -3607,6 +3609,9 @@
     shelf_view_->shelf()->SetAlignment(GetParam().first);
     shelf_view_->shelf()->SetAutoHideBehavior(GetParam().second);
   }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/ash/system/accessibility/select_to_speak/select_to_speak_tray.cc b/ash/system/accessibility/select_to_speak/select_to_speak_tray.cc
index 9fe9c87..4ef5eab 100644
--- a/ash/system/accessibility/select_to_speak/select_to_speak_tray.cc
+++ b/ash/system/accessibility/select_to_speak/select_to_speak_tray.cc
@@ -14,6 +14,7 @@
 #include "ash/system/tray/tray_container.h"
 #include "ash/system/tray/tray_utils.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/image_view.h"
@@ -26,12 +27,6 @@
 
 namespace {
 
-// This constant must be kept the same as SELECT_TO_SPEAK_TRAY_CLASS_NAME in
-// chrome/browser/resources/chromeos/accessibility/select_to_speak/
-// select_to_speak.js.
-constexpr char kSelectToSpeakTrayClassName[] =
-    "tray/TrayBackgroundView/SelectToSpeakTray";
-
 gfx::ImageSkia GetImageOnCurrentSelectToSpeakStatus() {
   auto* shell = Shell::Get();
   const SkColor color =
@@ -54,18 +49,23 @@
 
 SelectToSpeakTray::SelectToSpeakTray(Shelf* shelf,
                                      TrayBackgroundViewCatalogName catalog_name)
-    : TrayBackgroundView(shelf, catalog_name), icon_(new views::ImageView()) {
+    : TrayBackgroundView(shelf, catalog_name) {
+  SetPressedCallback(base::BindRepeating([](const ui::Event& event) {
+    Shell::Get()->accessibility_controller()->RequestSelectToSpeakStateChange();
+  }));
+
   const gfx::ImageSkia inactive_image = gfx::CreateVectorIcon(
       kSystemTraySelectToSpeakNewuiIcon,
       TrayIconColor(Shell::Get()->session_controller()->GetSessionState()));
-  icon_->SetImage(inactive_image);
+  auto icon = std::make_unique<views::ImageView>();
+  icon->SetImage(inactive_image);
   const int vertical_padding = (kTrayItemSize - inactive_image.height()) / 2;
   const int horizontal_padding = (kTrayItemSize - inactive_image.width()) / 2;
-  icon_->SetBorder(views::CreateEmptyBorder(
+  icon->SetBorder(views::CreateEmptyBorder(
       gfx::Insets::VH(vertical_padding, horizontal_padding)));
-  icon_->SetTooltipText(l10n_util::GetStringUTF16(
+  icon->SetTooltipText(l10n_util::GetStringUTF16(
       IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SELECT_TO_SPEAK));
-  tray_container()->AddChildView(icon_);
+  icon_ = tray_container()->AddChildView(std::move(icon));
 
   // Observe the accessibility controller state changes to know when Select to
   // Speak state is updated or when it is disabled/enabled.
@@ -91,15 +91,6 @@
       IDS_ASH_STATUS_TRAY_ACCESSIBILITY_SELECT_TO_SPEAK));
 }
 
-const char* SelectToSpeakTray::GetClassName() const {
-  return kSelectToSpeakTrayClassName;
-}
-
-bool SelectToSpeakTray::PerformAction(const ui::Event& event) {
-  Shell::Get()->accessibility_controller()->RequestSelectToSpeakStateChange();
-  return true;
-}
-
 void SelectToSpeakTray::OnThemeChanged() {
   TrayBackgroundView::OnThemeChanged();
   UpdateIconOnColorChanges();
@@ -134,4 +125,7 @@
   icon_->SetImage(GetImageOnCurrentSelectToSpeakStatus());
 }
 
+BEGIN_METADATA(SelectToSpeakTray, TrayBackgroundView);
+END_METADATA
+
 }  // namespace ash
diff --git a/ash/system/accessibility/select_to_speak/select_to_speak_tray.h b/ash/system/accessibility/select_to_speak/select_to_speak_tray.h
index e4f7df9..2f04cfe 100644
--- a/ash/system/accessibility/select_to_speak/select_to_speak_tray.h
+++ b/ash/system/accessibility/select_to_speak/select_to_speak_tray.h
@@ -10,7 +10,7 @@
 #include "ash/constants/tray_background_view_catalog.h"
 #include "ash/public/cpp/session/session_observer.h"
 #include "ash/system/tray/tray_background_view.h"
-#include "ui/views/controls/image_view.h"
+#include "ui/base/metadata/metadata_header_macros.h"
 
 namespace views {
 class ImageView;
@@ -18,24 +18,24 @@
 
 namespace ash {
 
+class Shelf;
+
 // A button in the tray that lets users start/stop Select-to-Speak.
 class ASH_EXPORT SelectToSpeakTray : public TrayBackgroundView,
                                      public AccessibilityObserver,
                                      public SessionObserver {
  public:
-  SelectToSpeakTray(Shelf* shelf, TrayBackgroundViewCatalogName catalog_name);
+  METADATA_HEADER(SelectToSpeakTray);
 
+  SelectToSpeakTray(Shelf* shelf, TrayBackgroundViewCatalogName catalog_name);
   SelectToSpeakTray(const SelectToSpeakTray&) = delete;
   SelectToSpeakTray& operator=(const SelectToSpeakTray&) = delete;
-
   ~SelectToSpeakTray() override;
 
   // TrayBackgroundView:
   void Initialize() override;
   std::u16string GetAccessibleNameForTray() override;
   void HandleLocaleChange() override;
-  const char* GetClassName() const override;
-  bool PerformAction(const ui::Event& event) override;
   void OnThemeChanged() override;
   // The SelectToSpeakTray does not have a bubble, so these functions are
   // no-ops.
@@ -59,8 +59,8 @@
   // Updates icon if the color of the icon changes.
   void UpdateIconOnColorChanges();
 
-  // Weak pointer, will be parented by TrayContainer for its lifetime.
-  views::ImageView* icon_;
+  // Owned by TrayContainer for its lifetime.
+  views::ImageView* icon_ = nullptr;
 
   ScopedSessionObserver session_observer_{this};
 };
diff --git a/ash/system/accessibility/select_to_speak/select_to_speak_tray_unittest.cc b/ash/system/accessibility/select_to_speak/select_to_speak_tray_unittest.cc
index afd6deab..8537635 100644
--- a/ash/system/accessibility/select_to_speak/select_to_speak_tray_unittest.cc
+++ b/ash/system/accessibility/select_to_speak/select_to_speak_tray_unittest.cc
@@ -22,6 +22,7 @@
 #include "ui/events/event.h"
 #include "ui/gfx/image/image_unittest_util.h"
 #include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/controls/image_view.h"
 #include "ui/views/controls/label.h"
 
 namespace ash {
diff --git a/ash/system/channel_indicator/channel_indicator_quick_settings_view.cc b/ash/system/channel_indicator/channel_indicator_quick_settings_view.cc
index 9909921..b2e4b51 100644
--- a/ash/system/channel_indicator/channel_indicator_quick_settings_view.cc
+++ b/ash/system/channel_indicator/channel_indicator_quick_settings_view.cc
@@ -233,7 +233,6 @@
       SetMinSize(gfx::Size(0, kVersionButtonHeight));
     }
     views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON);
-    views::InkDrop::Get(this)->SetBaseColor(GetInkDropBaseColor(channel_));
     InstallRoundedCornerHighlightPathGenerator(
         this, GetVersionButtonInkDropCorners(allow_user_feedback));
   }
@@ -261,6 +260,7 @@
 
   void OnThemeChanged() override {
     views::LabelButton::OnThemeChanged();
+    views::InkDrop::Get(this)->SetBaseColor(GetInkDropBaseColor(channel_));
     SetBackgroundAndFont();
   }
 
diff --git a/ash/system/network/network_detailed_view_controller.cc b/ash/system/network/network_detailed_view_controller.cc
index fc36427a1..0f2222bd 100644
--- a/ash/system/network/network_detailed_view_controller.cc
+++ b/ash/system/network/network_detailed_view_controller.cc
@@ -171,10 +171,14 @@
       return;
     }
 
-    // If the captive portal UI flag is enabled, the network is connected, and
-    // the network is in a portal or proxy state, the user is shown the portal
-    // signin.
+    // If the captive portal UI flag is enabled, the user is logged in, the
+    // network is connected, and the network is in a portal or proxy state, the
+    // user is shown the portal signin. We do not show portal sign in for user
+    // not logged in because it is the only way for the user to get to the
+    // network details page.
     if (features::IsCaptivePortalUI2022Enabled() &&
+        Shell::Get()->session_controller()->login_status() !=
+            LoginStatus::NOT_LOGGED_IN &&
         chromeos::network_config::StateIsConnected(network->connection_state) &&
         IsNetworkBehindPortalOrProxy(network->portal_state)) {
       RecordNetworkRowClickedAction(NetworkRowClickedAction::kOpenPortalSignin);
diff --git a/ash/system/unified/buttons.cc b/ash/system/unified/buttons.cc
index c28669c2..0374865c 100644
--- a/ash/system/unified/buttons.cc
+++ b/ash/system/unified/buttons.cc
@@ -27,11 +27,13 @@
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/chromeos/devicetype_utils.h"
 #include "ui/compositor/layer.h"
+#include "ui/gfx/canvas.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/button/button.h"
+#include "ui/views/controls/highlight_path_generator.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/layout/box_layout.h"
@@ -40,6 +42,12 @@
 
 namespace {
 
+// Constants used with QsRevamp.
+constexpr int kManagedStateButtonHeight = 32;
+constexpr int kManagedStateHighlightRadius = 16;
+constexpr SkScalar kManagedStateCornerRadii[] = {16, 16, 16, 16,
+                                                 16, 16, 16, 16};
+
 // Helper function for getting ContentLayerColor.
 inline SkColor GetContentLayerColor(AshColorProvider::ContentLayerType type) {
   return AshColorProvider::Get()->GetContentLayerColor(type);
@@ -225,21 +233,37 @@
       views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
       kUnifiedSystemInfoSpacing));
 
-  label_ = AddChildView(std::make_unique<views::Label>());
+  if (features::IsQsRevampEnabled()) {
+    // Image goes first.
+    image_ = AddChildView(std::make_unique<views::ImageView>());
+    label_ = AddChildView(std::make_unique<views::Label>());
+    layout_manager->set_minimum_cross_axis_size(kManagedStateButtonHeight);
+    layout_manager->set_main_axis_alignment(
+        views::BoxLayout::MainAxisAlignment::kCenter);
+  } else {
+    // Label goes first.
+    label_ = AddChildView(std::make_unique<views::Label>());
+    image_ = AddChildView(std::make_unique<views::ImageView>());
+    // Shrink the label if needed so the icon fits.
+    layout_manager->SetFlexForView(label_, 1);
+  }
+
   label_->SetAutoColorReadabilityEnabled(false);
   label_->SetSubpixelRenderingEnabled(false);
   label_->SetText(l10n_util::GetStringUTF16(label_id));
 
-  image_ = AddChildView(std::make_unique<views::ImageView>());
   image_->SetPreferredSize(
       gfx::Size(kUnifiedSystemInfoHeight, kUnifiedSystemInfoHeight));
 
-  // Shrink the label if needed so the icon fits.
-  layout_manager->SetFlexForView(label_, 1);
-
   SetInstallFocusRingOnFocus(true);
   views::FocusRing::Get(this)->SetColorId(ui::kColorAshFocusRing);
-  views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::OFF);
+  if (features::IsQsRevampEnabled()) {
+    views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON);
+    views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
+                                                  kManagedStateHighlightRadius);
+  } else {
+    views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::OFF);
+  }
 }
 
 views::View* ManagedStateView::GetTooltipHandlerForPoint(
@@ -255,6 +279,27 @@
   image_->SetImage(gfx::CreateVectorIcon(
       icon_, GetContentLayerColor(
                  AshColorProvider::ContentLayerType::kIconColorSecondary)));
+  if (features::IsQsRevampEnabled()) {
+    const std::pair<SkColor, float> base_color_and_opacity =
+        AshColorProvider::Get()->GetInkDropBaseColorAndOpacity();
+    views::InkDrop::Get(this)->SetBaseColor(base_color_and_opacity.first);
+  }
+}
+
+void ManagedStateView::PaintButtonContents(gfx::Canvas* canvas) {
+  if (!features::IsQsRevampEnabled())
+    return;
+  // Draw a button outline similar to ChannelIndicatorQuickSettingsView's
+  // VersionButton outline.
+  cc::PaintFlags flags;
+  flags.setColor(AshColorProvider::Get()->GetContentLayerColor(
+      ColorProvider::ContentLayerType::kSeparatorColor));
+  flags.setStyle(cc::PaintFlags::kStroke_Style);
+  flags.setAntiAlias(true);
+  canvas->DrawPath(
+      SkPath().addRoundRect(gfx::RectToSkRect(GetLocalBounds()),
+                            kManagedStateCornerRadii, SkPathDirection::kCW),
+      flags);
 }
 
 BEGIN_METADATA(ManagedStateView, views::Button)
@@ -328,6 +373,10 @@
             : base::UTF8ToUTF16(enterprise_domain_manager);
     managed_string = l10n_util::GetStringFUTF16(IDS_ASH_SHORT_MANAGED_BY,
                                                 display_domain_manager);
+    if (features::IsQsRevampEnabled()) {
+      // When the managed string is short it is used as the button label.
+      label()->SetText(managed_string);
+    }
   }
   SetTooltipText(managed_string);
 }
@@ -341,6 +390,7 @@
     : ManagedStateView(PressedCallback(),
                        IDS_ASH_STATUS_TRAY_SUPERVISED_LABEL,
                        GetSupervisedUserIcon()) {
+  SetID(VIEW_ID_QS_SUPERVISED_BUTTON);
   bool visible = Shell::Get()->session_controller()->IsUserChild();
   SetVisible(visible);
   if (visible)
@@ -350,6 +400,7 @@
   // to show a similar ui to enterprise managed accounts. Disable button
   // state for now.
   SetState(ButtonState::STATE_DISABLED);
+  views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::OFF);
 }
 
 BEGIN_METADATA(SupervisedUserView, ManagedStateView)
diff --git a/ash/system/unified/buttons.h b/ash/system/unified/buttons.h
index 79769ec..565ecf332 100644
--- a/ash/system/unified/buttons.h
+++ b/ash/system/unified/buttons.h
@@ -123,10 +123,13 @@
                    int label_id,
                    const gfx::VectorIcon& icon);
 
+  views::Label* label() { return label_; }
+
  private:
   // views::Button:
   views::View* GetTooltipHandlerForPoint(const gfx::Point& point) override;
   void OnThemeChanged() override;
+  void PaintButtonContents(gfx::Canvas* canvas) override;
 
   // Owned by views hierarchy.
   views::Label* label_ = nullptr;
diff --git a/ash/system/unified/quick_settings_header.cc b/ash/system/unified/quick_settings_header.cc
index 1ac9e39..5f5debd 100644
--- a/ash/system/unified/quick_settings_header.cc
+++ b/ash/system/unified/quick_settings_header.cc
@@ -8,12 +8,15 @@
 
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/system_tray_client.h"
+#include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/shell_delegate.h"
 #include "ash/system/channel_indicator/channel_indicator_quick_settings_view.h"
 #include "ash/system/channel_indicator/channel_indicator_utils.h"
 #include "ash/system/model/system_tray_model.h"
+#include "ash/system/unified/buttons.h"
+#include "ash/system/unified/unified_system_tray_controller.h"
 #include "components/session_manager/session_manager_types.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/views/layout/box_layout.h"
@@ -28,7 +31,8 @@
 
 }  // namespace
 
-QuickSettingsHeader::QuickSettingsHeader() {
+QuickSettingsHeader::QuickSettingsHeader(
+    UnifiedSystemTrayController* controller) {
   DCHECK(features::IsQsRevampEnabled());
 
   auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
@@ -37,6 +41,11 @@
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStretch);
 
+  enterprise_managed_view_ =
+      AddChildView(std::make_unique<EnterpriseManagedView>(controller));
+
+  supervised_view_ = AddChildView(std::make_unique<SupervisedUserView>());
+
   // If the release track is not "stable" then show the channel indicator UI.
   auto channel = Shell::Get()->shell_delegate()->GetChannel();
   if (channel_indicator_utils::IsDisplayableChannel(channel) &&
@@ -55,10 +64,13 @@
 
 QuickSettingsHeader::~QuickSettingsHeader() = default;
 
+void QuickSettingsHeader::ChildVisibilityChanged(views::View* child) {
+  UpdateVisibility();
+}
+
 void QuickSettingsHeader::UpdateVisibility() {
-  // TODO(b/251724754): Update condition when enterprise management view is
-  // added.
-  bool should_show = !!channel_view_;
+  bool should_show = enterprise_managed_view_->GetVisible() ||
+                     supervised_view_->GetVisible() || !!channel_view_;
   SetVisible(should_show);
 }
 
diff --git a/ash/system/unified/quick_settings_header.h b/ash/system/unified/quick_settings_header.h
index 00adaca2..58480b3 100644
--- a/ash/system/unified/quick_settings_header.h
+++ b/ash/system/unified/quick_settings_header.h
@@ -11,6 +11,9 @@
 namespace ash {
 
 class ChannelIndicatorQuickSettingsView;
+class EnterpriseManagedView;
+class SupervisedUserView;
+class UnifiedSystemTrayController;
 
 // The header view shown at the top of the `QuickSettingsView`. Contains an
 // optional "Managed by" button and an optional release channel indicator. Sets
@@ -19,11 +22,14 @@
  public:
   METADATA_HEADER(QuickSettingsHeader);
 
-  QuickSettingsHeader();
+  explicit QuickSettingsHeader(UnifiedSystemTrayController* controller);
   QuickSettingsHeader(const QuickSettingsHeader&) = delete;
   QuickSettingsHeader& operator=(const QuickSettingsHeader&) = delete;
   ~QuickSettingsHeader() override;
 
+  // views::View:
+  void ChildVisibilityChanged(views::View* child) override;
+
   ChannelIndicatorQuickSettingsView* channel_view_for_test() {
     return channel_view_;
   }
@@ -33,6 +39,9 @@
   // invisible so it does not consume any space.
   void UpdateVisibility();
 
+  // Owned by views hierarchy.
+  EnterpriseManagedView* enterprise_managed_view_ = nullptr;
+  SupervisedUserView* supervised_view_ = nullptr;
   ChannelIndicatorQuickSettingsView* channel_view_ = nullptr;
 };
 
diff --git a/ash/system/unified/quick_settings_header_unittest.cc b/ash/system/unified/quick_settings_header_unittest.cc
index 91c689e0..7059fef 100644
--- a/ash/system/unified/quick_settings_header_unittest.cc
+++ b/ash/system/unified/quick_settings_header_unittest.cc
@@ -7,12 +7,25 @@
 #include <memory>
 
 #include "ash/constants/ash_features.h"
+#include "ash/public/cpp/ash_view_ids.h"
+#include "ash/session/session_controller_impl.h"
+#include "ash/shell.h"
+#include "ash/system/model/enterprise_domain_model.h"
+#include "ash/system/model/system_tray_model.h"
+#include "ash/system/unified/unified_system_tray_controller.h"
+#include "ash/system/unified/unified_system_tray_model.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/test_shell_delegate.h"
 #include "base/test/scoped_feature_list.h"
+#include "components/user_manager/user_type.h"
 #include "components/version_info/channel.h"
 
 namespace ash {
+namespace {
+
+EnterpriseDomainModel* GetEnterpriseDomainModel() {
+  return Shell::Get()->system_tray_model()->enterprise_domain();
+}
 
 class QuickSettingsHeaderTest : public NoSessionAshTestBase {
  public:
@@ -25,43 +38,152 @@
     // Install a test delegate to allow overriding channel version.
     auto delegate = std::make_unique<TestShellDelegate>();
     test_shell_delegate_ = delegate.get();
-    AshTestBase::SetUp(std::move(delegate));
+    NoSessionAshTestBase::SetUp(std::move(delegate));
+
+    model_ = base::MakeRefCounted<UnifiedSystemTrayModel>(nullptr);
+    controller_ = std::make_unique<UnifiedSystemTrayController>(model_.get());
+  }
+
+  void TearDown() override {
+    header_.reset();
+    controller_.reset();
+    model_.reset();
+    NoSessionAshTestBase::TearDown();
+  }
+
+  // Creates the object under test. Not part of SetUp() because sometimes tests
+  // need to setup the shell delegate or login before creating the header.
+  void CreateQuickSettingsHeader() {
+    header_ = std::make_unique<QuickSettingsHeader>(controller_.get());
+  }
+
+  views::View* GetManagedButton() {
+    return header_->GetViewByID(VIEW_ID_QS_MANAGED_BUTTON);
+  }
+
+  views::View* GetSupervisedButton() {
+    return header_->GetViewByID(VIEW_ID_QS_SUPERVISED_BUTTON);
   }
 
   base::test::ScopedFeatureList feature_list_;
   TestShellDelegate* test_shell_delegate_ = nullptr;
+  scoped_refptr<UnifiedSystemTrayModel> model_;
+  std::unique_ptr<UnifiedSystemTrayController> controller_;
+  std::unique_ptr<QuickSettingsHeader> header_;
 };
 
 TEST_F(QuickSettingsHeaderTest, HiddenByDefaultBeforeLogin) {
-  QuickSettingsHeader header;
+  CreateQuickSettingsHeader();
+
+  EXPECT_FALSE(GetManagedButton()->GetVisible());
+  EXPECT_FALSE(GetSupervisedButton()->GetVisible());
 
   // By default, channel view is not created.
-  EXPECT_FALSE(header.channel_view_for_test());
+  EXPECT_FALSE(header_->channel_view_for_test());
 
   // Since no views are created, the header is hidden.
-  EXPECT_FALSE(header.GetVisible());
+  EXPECT_FALSE(header_->GetVisible());
 }
 
 TEST_F(QuickSettingsHeaderTest, DoesNotShowChannelViewBeforeLogin) {
   test_shell_delegate_->set_channel(version_info::Channel::BETA);
 
-  QuickSettingsHeader header;
+  CreateQuickSettingsHeader();
 
-  EXPECT_FALSE(header.channel_view_for_test());
-  EXPECT_FALSE(header.GetVisible());
+  EXPECT_FALSE(header_->channel_view_for_test());
+  EXPECT_FALSE(header_->GetVisible());
 }
 
 TEST_F(QuickSettingsHeaderTest, ShowsChannelViewAfterLogin) {
   test_shell_delegate_->set_channel(version_info::Channel::BETA);
   SimulateUserLogin("user@gmail.com");
 
-  QuickSettingsHeader header;
+  CreateQuickSettingsHeader();
 
   // Channel view is created.
-  EXPECT_TRUE(header.channel_view_for_test());
+  EXPECT_TRUE(header_->channel_view_for_test());
 
   // Header is shown.
-  EXPECT_TRUE(header.GetVisible());
+  EXPECT_TRUE(header_->GetVisible());
 }
 
+TEST_F(QuickSettingsHeaderTest, EnterpriseManagedDeviceVisible) {
+  CreateQuickSettingsHeader();
+
+  // Simulate enterprise information becoming available.
+  GetEnterpriseDomainModel()->SetDeviceEnterpriseInfo(
+      DeviceEnterpriseInfo{"example.com", /*active_directory_managed=*/false,
+                           ManagementDeviceMode::kChromeEnterprise});
+
+  EXPECT_TRUE(GetManagedButton()->GetVisible());
+  EXPECT_EQ(GetManagedButton()->GetTooltipText({}), u"Managed by example.com");
+  EXPECT_TRUE(header_->GetVisible());
+}
+
+TEST_F(QuickSettingsHeaderTest, EnterpriseManagedActiveDirectoryVisible) {
+  CreateQuickSettingsHeader();
+
+  // Simulate enterprise information becoming available.
+  GetEnterpriseDomainModel()->SetDeviceEnterpriseInfo(
+      DeviceEnterpriseInfo{"", /*active_directory_managed=*/true,
+                           ManagementDeviceMode::kChromeEnterprise});
+
+  EXPECT_TRUE(GetManagedButton()->GetVisible());
+  EXPECT_EQ(GetManagedButton()->GetTooltipText({}),
+            u"This Chrome device is enterprise managed");
+  EXPECT_TRUE(header_->GetVisible());
+}
+
+TEST_F(QuickSettingsHeaderTest, EnterpriseManagedAccountVisible) {
+  CreateQuickSettingsHeader();
+
+  // Simulate enterprise information becoming available.
+  GetEnterpriseDomainModel()->SetEnterpriseAccountDomainInfo("example.com");
+
+  EXPECT_TRUE(GetManagedButton()->GetVisible());
+  EXPECT_EQ(GetManagedButton()->GetTooltipText({}), u"Managed by example.com");
+  EXPECT_TRUE(header_->GetVisible());
+}
+
+TEST_F(QuickSettingsHeaderTest, BothChannelAndEnterpriseVisible) {
+  test_shell_delegate_->set_channel(version_info::Channel::BETA);
+  GetEnterpriseDomainModel()->SetDeviceEnterpriseInfo(
+      DeviceEnterpriseInfo{"example.com", /*active_directory_managed=*/false,
+                           ManagementDeviceMode::kChromeEnterprise});
+  SimulateUserLogin("user@gmail.com");
+
+  CreateQuickSettingsHeader();
+
+  EXPECT_TRUE(GetManagedButton()->GetVisible());
+  EXPECT_TRUE(header_->channel_view_for_test());
+  EXPECT_TRUE(header_->GetVisible());
+}
+
+TEST_F(QuickSettingsHeaderTest, ChildVisible) {
+  CreateQuickSettingsHeader();
+
+  // Before login the supervised user view is invisible.
+  EXPECT_FALSE(GetSupervisedButton()->GetVisible());
+
+  // Simulate supervised user logging in.
+  SessionControllerImpl* session = Shell::Get()->session_controller();
+  TestSessionControllerClient* client = GetSessionControllerClient();
+  client->Reset();
+  client->AddUserSession("child@test.com", user_manager::USER_TYPE_CHILD);
+  client->SetSessionState(session_manager::SessionState::ACTIVE);
+  UserSession user_session = *session->GetUserSession(0);
+  user_session.custodian_email = "parent@test.com";
+  session->UpdateUserSession(std::move(user_session));
+
+  // Recreate the header after login.
+  CreateQuickSettingsHeader();
+
+  // Now the supervised user view is visible.
+  EXPECT_TRUE(GetSupervisedButton()->GetVisible());
+  EXPECT_EQ(GetSupervisedButton()->GetTooltipText({}),
+            u"Account managed by parent@test.com");
+  EXPECT_TRUE(header_->GetVisible());
+}
+
+}  // namespace
 }  // namespace ash
diff --git a/ash/system/unified/quick_settings_view.cc b/ash/system/unified/quick_settings_view.cc
index 815534e..912f28d 100644
--- a/ash/system/unified/quick_settings_view.cc
+++ b/ash/system/unified/quick_settings_view.cc
@@ -140,7 +140,7 @@
       AddChildView(std::make_unique<SystemTrayContainer>());
 
   header_ = system_tray_container_->AddChildView(
-      std::make_unique<QuickSettingsHeader>());
+      std::make_unique<QuickSettingsHeader>(controller_));
   feature_tiles_container_ = system_tray_container_->AddChildView(
       std::make_unique<FeatureTilesContainerView>(controller_));
   page_indicator_view_ = system_tray_container_->AddChildView(
diff --git a/ash/system/unified/unified_system_info_view_unittest.cc b/ash/system/unified/unified_system_info_view_unittest.cc
index ee7a3fe..bd183a3 100644
--- a/ash/system/unified/unified_system_info_view_unittest.cc
+++ b/ash/system/unified/unified_system_info_view_unittest.cc
@@ -27,6 +27,8 @@
 // - Whether the release track UI feature is enabled, and
 // - Whether the release track is a value other than "stable"
 // The release track UI only shows if both conditions are met.
+//
+// NOTE: For QsRevamp, see similar tests in QuickSettingsHeaderTest.
 class UnifiedSystemInfoViewTest
     : public AshTestBase,
       public testing::WithParamInterface<std::tuple<bool, bool>> {
diff --git a/ash/webui/common/resources/quick_unlock/pin_keyboard.html b/ash/webui/common/resources/quick_unlock/pin_keyboard.html
index bedf3a0..18d2ddc 100644
--- a/ash/webui/common/resources/quick_unlock/pin_keyboard.html
+++ b/ash/webui/common/resources/quick_unlock/pin_keyboard.html
@@ -144,9 +144,7 @@
     outline: 0;
     position: relative;
     text-align: center;
-    width: 200px;
-
-    @apply --pin-keyboard-pin-input-style;
+    width: var(--pin-keyboard-pin-input-width, 200px);
   }
 
   #pinInput[make-contrast] {
diff --git a/ash/wm/desks/desks_unittests.cc b/ash/wm/desks/desks_unittests.cc
index f54a767..df5500f 100644
--- a/ash/wm/desks/desks_unittests.cc
+++ b/ash/wm/desks/desks_unittests.cc
@@ -79,9 +79,12 @@
 #include "ash/wm/workspace_controller.h"
 #include "base/containers/contains.h"
 #include "base/containers/cxx20_erase.h"
+#include "base/functional/callback_forward.h"
 #include "base/ranges/algorithm.h"
+#include "base/scoped_observation.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_clock.h"
@@ -99,6 +102,7 @@
 #include "ui/aura/client/window_parenting_client.h"
 #include "ui/aura/test/test_window_delegate.h"
 #include "ui/aura/window.h"
+#include "ui/aura/window_observer.h"
 #include "ui/base/clipboard/clipboard_buffer.h"
 #include "ui/base/clipboard/scoped_clipboard_writer.h"
 #include "ui/base/ime/ash/fake_ime_keyboard.h"
@@ -2523,10 +2527,11 @@
   void SetUp() override {
     DesksTest::SetUp();
 
-    // Enter tablet mode. Avoid TabletModeController::OnGetSwitchStates() from
-    // disabling tablet mode.
-    base::RunLoop().RunUntilIdle();
-    TabletModeControllerTestApi().EnterTabletMode();
+    // Enter tablet mode after detaching all mouse devices, as this is needed
+    // when running these tests on an actual workstation.
+    TabletModeControllerTestApi tablet_mode_test_api;
+    tablet_mode_test_api.DetachAllMice();
+    tablet_mode_test_api.EnterTabletMode();
   }
 
   SplitViewController* split_view_controller() {
@@ -2534,6 +2539,77 @@
   }
 };
 
+// Triggers a callback when the visibility of an observed window changes for the
+// first time since creation of this observer.
+class WindowVisibilityObserver : public aura::WindowObserver {
+ public:
+  WindowVisibilityObserver(aura::Window* window, base::OnceClosure callback)
+      : on_visibility_changed_callback_(std::move(callback)) {
+    DCHECK(on_visibility_changed_callback_);
+    observer_.Observe(window);
+  }
+  WindowVisibilityObserver(const WindowVisibilityObserver&) = delete;
+  WindowVisibilityObserver& operator=(const WindowVisibilityObserver&) = delete;
+  ~WindowVisibilityObserver() override = default;
+
+  // aura::WindowObserver:
+  void OnWindowVisibilityChanged(aura::Window* window, bool visible) override {
+    if (observer_.IsObservingSource(window) && on_visibility_changed_callback_)
+      std::move(on_visibility_changed_callback_).Run();
+  }
+
+  void OnWindowDestroying(aura::Window* window) override {
+    DCHECK(observer_.IsObservingSource(window));
+    observer_.Reset();
+  }
+
+ private:
+  base::ScopedObservation<aura::Window, aura::WindowObserver> observer_{this};
+
+  base::OnceClosure on_visibility_changed_callback_;
+};
+
+// Regression test for https://crbug.com/1368587.
+TEST_P(TabletModeDesksTest, CantDestroyBackdropWhileHiding) {
+  auto* controller = DesksController::Get();
+  ASSERT_EQ(1u, controller->desks().size());
+  const Desk* desk_1 = controller->desks()[0].get();
+
+  auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
+  wm::ActivateWindow(window.get());
+  EXPECT_EQ(window.get(), window_util::GetActiveWindow());
+
+  // Verify that there is a visible backdrop for the app window on the current
+  // desk.
+  auto* desk_1_backdrop_controller =
+      GetDeskBackdropController(desk_1, Shell::GetPrimaryRootWindow());
+  auto* backdrop_window = desk_1_backdrop_controller->backdrop_window();
+  ASSERT_TRUE(backdrop_window);
+  EXPECT_TRUE(backdrop_window->IsVisible());
+
+  // Intercept the visibility change notification of the backdrop (which will
+  // happen when we enter overview below), and hide the app window. This
+  // triggers an update of the backdrop while still in the process of hiding
+  // it. Now that the single app window available is hidden, the backdrop
+  // controller will try to destroy the backdrop. This should be prevented as
+  // we don't allow `BackdropController::Hide()` to be called recursively.
+  auto* app_window = window.get();
+  WindowVisibilityObserver observer{
+      backdrop_window,
+      base::BindLambdaForTesting([app_window, backdrop_window]() {
+        app_window->Hide();
+        EXPECT_FALSE(backdrop_window->IsVisible());
+      })};
+
+  // Enter overview and expect that the backdrop is still present for desk_1 but
+  // hidden.
+  auto* overview_controller = Shell::Get()->overview_controller();
+  EnterOverview();
+  EXPECT_TRUE(overview_controller->InOverviewSession());
+  ASSERT_TRUE(desk_1_backdrop_controller->backdrop_window());
+  EXPECT_FALSE(desk_1_backdrop_controller->backdrop_window()->IsVisible());
+}
+
 TEST_P(TabletModeDesksTest, Backdrops) {
   auto* controller = DesksController::Get();
   NewDesk();
diff --git a/ash/wm/workspace/backdrop_controller.cc b/ash/wm/workspace/backdrop_controller.cc
index 504450f0..fd91414 100644
--- a/ash/wm/workspace/backdrop_controller.cc
+++ b/ash/wm/workspace/backdrop_controller.cc
@@ -306,9 +306,8 @@
       continue;
 
     // No need to check the visibility or the activateability of the window if
-    // this is an inactive desk's container.
-    if (!desks_util::IsDeskContainer(container_) ||
-        desks_util::IsActiveDeskContainer(container_)) {
+    // this is not a desk container.
+    if (desks_util::IsDeskContainer(container_)) {
       if (!window->layer()->GetTargetVisibility())
         continue;
 
@@ -558,10 +557,12 @@
 }
 
 void BackdropController::Hide(bool destroy, bool animate) {
-  if (!backdrop_)
+  if (!backdrop_ || is_hiding_backdrop_)
     return;
 
   DCHECK(backdrop_window_);
+  base::AutoReset<bool> lock(&is_hiding_backdrop_, true);
+
   const aura::Window::Windows windows = container_->children();
   auto window_iter = base::ranges::find(windows, backdrop_window_);
   ++window_iter;
diff --git a/ash/wm/workspace/backdrop_controller.h b/ash/wm/workspace/backdrop_controller.h
index 8e98168..277c77a 100644
--- a/ash/wm/workspace/backdrop_controller.h
+++ b/ash/wm/workspace/backdrop_controller.h
@@ -183,6 +183,11 @@
   // in overview mode.
   bool pause_update_ = false;
 
+  // If true, we're inside the stack of `Hide()`. This is used to avoid
+  // recursively calling `Hide()` which may lead to destroying the backdrop
+  // widget while it's still being hidden. https://crbug.com/1368587.
+  bool is_hiding_backdrop_ = false;
+
   base::ScopedMultiSourceObservation<WindowBackdrop, WindowBackdrop::Observer>
       window_backdrop_observations_{this};
 
diff --git a/base/test/scoped_feature_list.cc b/base/test/scoped_feature_list.cc
index 26fdd561..0fb2f67 100644
--- a/base/test/scoped_feature_list.cc
+++ b/base/test/scoped_feature_list.cc
@@ -287,15 +287,14 @@
 
 }  // namespace
 
-ScopedFeatureList::FeatureAndParams::FeatureAndParams(
-    const Feature& feature,
-    const FieldTrialParams& params)
+FeatureRefAndParams::FeatureRefAndParams(const Feature& feature,
+                                         const FieldTrialParams& params)
     : feature(feature), params(params) {}
 
-ScopedFeatureList::FeatureAndParams::~FeatureAndParams() = default;
+FeatureRefAndParams::FeatureRefAndParams(const FeatureRefAndParams& other) =
+    default;
 
-ScopedFeatureList::FeatureAndParams::FeatureAndParams(
-    const FeatureAndParams& other) = default;
+FeatureRefAndParams::~FeatureRefAndParams() = default;
 
 ScopedFeatureList::ScopedFeatureList() = default;
 
@@ -438,7 +437,7 @@
 
 void ScopedFeatureList::InitWithFeaturesImpl(
     const std::vector<FeatureRef>& enabled_features,
-    const std::vector<FeatureAndParams>& enabled_features_and_params,
+    const std::vector<FeatureRefAndParams>& enabled_features_and_params,
     const std::vector<FeatureRef>& disabled_features,
     bool keep_existing_states) {
   DCHECK(!init_called_);
@@ -485,7 +484,7 @@
 }
 
 void ScopedFeatureList::InitWithFeaturesAndParameters(
-    const std::vector<FeatureAndParams>& enabled_features,
+    const std::vector<FeatureRefAndParams>& enabled_features,
     const std::vector<FeatureRef>& disabled_features) {
   InitWithFeaturesImpl({}, enabled_features, disabled_features);
 }
diff --git a/base/test/scoped_feature_list.h b/base/test/scoped_feature_list.h
index 64699d31..6d9bfa8 100644
--- a/base/test/scoped_feature_list.h
+++ b/base/test/scoped_feature_list.h
@@ -21,6 +21,20 @@
 
 namespace base::test {
 
+// A reference to a base::Feature and field trial params that should be force
+// enabled and overwritten for test purposes.
+struct FeatureRefAndParams {
+  FeatureRefAndParams(const Feature& feature ABSL_ATTRIBUTE_LIFETIME_BOUND,
+                      const FieldTrialParams& params);
+
+  FeatureRefAndParams(const FeatureRefAndParams& other);
+
+  ~FeatureRefAndParams();
+
+  const Feature& feature;
+  const FieldTrialParams params;
+};
+
 // A lightweight wrapper for a reference to a base::Feature. Allows lists of
 // features to be enabled/disabled to be easily passed without actually copying
 // the underlying base::Feature. Actual C++ references do not work well for this
@@ -67,21 +81,6 @@
   struct Features;
   struct FeatureWithStudyGroup;
 
-  // TODO(https://crbug.com/1370851): Temporary "alias" to allow incremental
-  // migration.
-  // A reference to a base::Feature and field trial params that should be force
-  // enabled and overwritten for test purposes.
-  struct FeatureAndParams {
-    FeatureAndParams(const Feature& feature ABSL_ATTRIBUTE_LIFETIME_BOUND,
-                     const FieldTrialParams& params);
-    ~FeatureAndParams();
-
-    FeatureAndParams(const FeatureAndParams& other);
-
-    const Feature& feature;
-    const FieldTrialParams params;
-  };
-
   // Constructs the instance in a non-initialized state.
   ScopedFeatureList();
 
@@ -158,7 +157,7 @@
   // Note: This creates a scoped global field trial list if there is not
   // currently one.
   void InitWithFeaturesAndParameters(
-      const std::vector<FeatureAndParams>& enabled_features,
+      const std::vector<FeatureRefAndParams>& enabled_features,
       const std::vector<FeatureRef>& disabled_features);
 
   // Initializes and registers a FeatureList instance based on the current
@@ -183,7 +182,7 @@
   // empty).
   void InitWithFeaturesImpl(
       const std::vector<FeatureRef>& enabled_features,
-      const std::vector<FeatureAndParams>& enabled_features_and_params,
+      const std::vector<FeatureRefAndParams>& enabled_features_and_params,
       const std::vector<FeatureRef>& disabled_features,
       bool keep_existing_states = true);
 
@@ -209,8 +208,6 @@
   std::unique_ptr<base::FieldTrialList> field_trial_list_;
 };
 
-using FeatureRefAndParams = ScopedFeatureList::FeatureAndParams;
-
 }  // namespace base::test
 
 #endif  // BASE_TEST_SCOPED_FEATURE_LIST_H_
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index ff776fbd..096569b 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -1108,9 +1108,10 @@
             flakiness_server=getattr(args, 'flakiness_dashboard_server',
                                      None))
 
-        if iteration_results.GetNotPass():
-          _LogRerunStatement(iteration_results.GetNotPass(),
-                             args.wrapper_script_args)
+        failed_tests = (iteration_results.GetNotPass() -
+                        iteration_results.GetSkip())
+        if failed_tests:
+          _LogRerunStatement(failed_tests, args.wrapper_script_args)
 
         if args.break_on_failure and not iteration_results.DidRunPass():
           break
@@ -1198,7 +1199,13 @@
                       'test filter file.')
     return
 
-  test_filter_file = os.path.join(os.path.relpath(constants.GetOutDirectory()),
+  output_directory = constants.GetOutDirectory()
+  if not os.path.exists(output_directory):
+    logging.error('Output directory not found. Unable to generate failing '
+                  'test filter file.')
+    return
+
+  test_filter_file = os.path.join(os.path.relpath(output_directory),
                                   _RERUN_FAILED_TESTS_FILE)
   arg_list = shlex.split(wrapper_arg_str) if wrapper_arg_str else sys.argv
   index = 0
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index f272b18..177a8d4 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -611,7 +611,7 @@
     # turned off we need the !is_nacl clause and the (is_nacl && is_clang)
     # clause, above.
     cflags_c += [ "-std=c11" ]
-    if (is_mac) {
+    if (is_apple) {
       # TODO(crbug.com/1284275): Switch to C++20 on all platforms.
       cflags_cc += [ "-std=c++20" ]
     } else {
diff --git a/chrome/VERSION b/chrome/VERSION
index efa76680..08982a32 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=109
 MINOR=0
-BUILD=5360
+BUILD=5361
 PATCH=0
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
index fdb43df..5fe3849 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
@@ -103,6 +103,18 @@
             new BooleanCachedFieldTrialParameter(ChromeFeatureList.GRID_TAB_SWITCHER_FOR_TABLETS,
                     DELAY_GTS_CREATION_PARAM, true);
 
+    // Field trial parameter for enabling folio for tab strip redesign.
+    private static final String TAB_STRIP_REDESIGN_ENABLE_FOLIO_PARAM = "enable_folio";
+    public static final BooleanCachedFieldTrialParameter TAB_STRIP_REDESIGN_ENABLE_FOLIO =
+            new BooleanCachedFieldTrialParameter(ChromeFeatureList.TAB_STRIP_REDESIGN,
+                    TAB_STRIP_REDESIGN_ENABLE_FOLIO_PARAM, false);
+
+    // Field trial parameter for enabling detached for tab strip redesign.
+    private static final String TAB_STRIP_REDESIGN_ENABLE_DETACHED_PARAM = "enable_detached";
+    public static final BooleanCachedFieldTrialParameter TAB_STRIP_REDESIGN_ENABLE_DETACHED =
+            new BooleanCachedFieldTrialParameter(ChromeFeatureList.TAB_STRIP_REDESIGN,
+                    TAB_STRIP_REDESIGN_ENABLE_DETACHED_PARAM, false);
+
     // Field trial parameter for defining tab width for tab strip improvements.
     private static final String TAB_STRIP_IMPROVEMENTS_TAB_WIDTH_PARAM = "min_tab_width";
     public static final DoubleCachedFieldTrialParameter TAB_STRIP_TAB_WIDTH =
@@ -285,6 +297,20 @@
         return ENABLE_LAUNCH_POLISH.getValue();
     }
 
+    /**
+     * @return Whether Folio for tab strip redesign is enabled.
+     */
+    public static boolean isTabStripFolioEnabled() {
+        return TAB_STRIP_REDESIGN_ENABLE_FOLIO.getValue();
+    }
+
+    /**
+     * @return Whether Detached for tab strip redesign is enabled.
+     */
+    public static boolean isTabStripDetachedEnabled() {
+        return TAB_STRIP_REDESIGN_ENABLE_DETACHED.getValue();
+    }
+
     private static Float sTabMinWidthForTesting;
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
index 7b02d0c..927bb9a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
@@ -200,6 +200,8 @@
                         add(TabUiFeatureUtilities.GRID_TAB_SWITCHER_FOR_TABLETS_POLISH);
                         add(TabUiFeatureUtilities.TAB_STRIP_TAB_WIDTH);
                         add(TabUiFeatureUtilities.ENABLE_TAB_SELECTION_EDITOR_V2_SHARE);
+                        add(TabUiFeatureUtilities.TAB_STRIP_REDESIGN_ENABLE_FOLIO);
+                        add(TabUiFeatureUtilities.TAB_STRIP_REDESIGN_ENABLE_DETACHED);
                         add(VersionNumberGetter.MIN_SDK_VERSION);
                     }
                 };
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 b86b224..7811843 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
@@ -1111,7 +1111,7 @@
                     mActivity, mActivityLifecycleDispatcher, mProfileSupplier);
             PriceTrackingButtonController priceTrackingButtonController =
                     new PriceTrackingButtonController(mActivityTabProvider,
-                            mModalDialogManagerSupplier.get(), getBottomSheetController(),
+                            mModalDialogManagerSupplier.get(),
                             AppCompatResources.getDrawable(
                                     mActivity, R.drawable.price_tracking_disabled),
                             mTabBookmarkerSupplier);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java
index 324a04fb..43f6f804 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinatorTest.java
@@ -99,6 +99,7 @@
         // default or removed if the flag is removed.
         ChromeFeatureList.SYNC_ANDROID_LIMIT_NTP_PROMO_IMPRESSIONS,
 })
+@Features.EnableFeatures({ChromeFeatureList.FEED_CLIENT_GOOD_VISITS})
 public class FeedSurfaceCoordinatorTest {
     private static final @SurfaceType int SURFACE_TYPE = SurfaceType.NEW_TAB_PAGE;
     private static final long SURFACE_CREATION_TIME_NS = 1234L;
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index c09c3d50..d978f4a 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5061,9 +5061,12 @@
         <message name="IDS_EXTENSIONS_TOOLBAR_ACTION_HOVER_CARD_FOOTER_DESCRIPTION_EXTENSION_REQUESTS_ACESSS" desc="The description in the footer of the card that shows up on mouse hover of a toolbar action when the extension requests access to the current site.">
           Click this extension's icon to read &amp; change <ph name="HOST">$1<ex>google.com</ex></ph>
         </message>
-        <message name="IDS_EXTENSIONS_TOOLBAR_ACTION_HOVER_CARD_FOOTER_POLICY_LABEL_TEXT" desc="The text in the footer of the card that shows up on mouse hover of a toolbar action when extension was pinned by the administrator.">
+        <message name="IDS_EXTENSIONS_TOOLBAR_ACTION_HOVER_CARD_FOOTER_POLICY_LABEL_PINNED_TEXT" desc="The text in the footer of the card that shows up on mouse hover of a toolbar action when extension was pinned by the administrator.">
           Pinned by your administrator
         </message>
+        <message name="IDS_EXTENSIONS_TOOLBAR_ACTION_HOVER_CARD_FOOTER_POLICY_LABEL_INSTALLED_TEXT" desc="The text in the footer of the card that shows up on mouse hover of a toolbar action when extension was installed by the administrator.">
+          Installed by your administrator
+        </message>
 
         <if expr="not use_titlecase">
           <message name="IDS_EXTENSIONS_CONTEXT_MENU_CANT_ACCESS_PAGE" desc="The label in an extension's context menu indicating the extension cannot access the current page. (sentence case)">
diff --git a/chrome/app/generated_resources_grd/IDS_EXTENSIONS_TOOLBAR_ACTION_HOVER_CARD_FOOTER_POLICY_LABEL_INSTALLED_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_EXTENSIONS_TOOLBAR_ACTION_HOVER_CARD_FOOTER_POLICY_LABEL_INSTALLED_TEXT.png.sha1
new file mode 100644
index 0000000..30df743
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_EXTENSIONS_TOOLBAR_ACTION_HOVER_CARD_FOOTER_POLICY_LABEL_INSTALLED_TEXT.png.sha1
@@ -0,0 +1 @@
+4e23789d21aed3dd15a26da2856cb4f860e9ebb4
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_EXTENSIONS_TOOLBAR_ACTION_HOVER_CARD_FOOTER_POLICY_LABEL_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_EXTENSIONS_TOOLBAR_ACTION_HOVER_CARD_FOOTER_POLICY_LABEL_PINNED_TEXT.png.sha1
similarity index 100%
rename from chrome/app/generated_resources_grd/IDS_EXTENSIONS_TOOLBAR_ACTION_HOVER_CARD_FOOTER_POLICY_LABEL_TEXT.png.sha1
rename to chrome/app/generated_resources_grd/IDS_EXTENSIONS_TOOLBAR_ACTION_HOVER_CARD_FOOTER_POLICY_LABEL_PINNED_TEXT.png.sha1
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 708b17e..5939bee 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3300,6 +3300,20 @@
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if BUILDFLAG(IS_ANDROID)
+const FeatureEntry::FeatureParam kTabStripRedesignFolio[] = {
+    {"enable_folio", "true"}};
+const FeatureEntry::FeatureParam kTabStripRedesignDetached[] = {
+    {"enable_detached", "true"}};
+
+const FeatureEntry::FeatureVariation kTabStripRedesignVariations[] = {
+    {"Folio", kTabStripRedesignFolio, std::size(kTabStripRedesignFolio),
+     nullptr},
+    {"Detached", kTabStripRedesignDetached,
+     std::size(kTabStripRedesignDetached), nullptr},
+};
+#endif  // BUILDFLAG(IS_ANDROID)
+
+#if BUILDFLAG(IS_ANDROID)
 constexpr FeatureEntry::FeatureParam kUpmAndroidShadowSyncingUsers[] = {
     {password_manager::features::kUpmExperimentVariationParam.name,
      password_manager::features::kUpmExperimentVariationOption[1].name}};
@@ -6466,7 +6480,9 @@
     {"enable-tab-strip-redesign",
      flag_descriptions::kTabStripRedesignAndroidName,
      flag_descriptions::kTabStripRedesignAndroidDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kTabStripRedesign)},
+     FEATURE_WITH_PARAMS_VALUE_TYPE(chrome::android::kTabStripRedesign,
+                                    kTabStripRedesignVariations,
+                                    "TabStripRedesignAndroid")},
 
     {"enable-conditional-tabstrip",
      flag_descriptions::kConditionalTabStripAndroidName,
diff --git a/chrome/browser/ash/accessibility/dictation_browsertest.cc b/chrome/browser/ash/accessibility/dictation_browsertest.cc
index cee6d93..59e47a71 100644
--- a/chrome/browser/ash/accessibility/dictation_browsertest.cc
+++ b/chrome/browser/ash/accessibility/dictation_browsertest.cc
@@ -1712,13 +1712,11 @@
     DictationPumpkinInstallTest,
     ::testing::Values(speech::SpeechRecognitionType::kOnDevice));
 
-// TODO(crbug.com/1368843): Test is flaky on MSAN builds.
-#if defined(MEMORY_SANITIZER)
-#define MAYBE_WaitForInstall DISABLED_WaitForInstall
-#else
-#define MAYBE_WaitForInstall WaitForInstall
-#endif
-IN_PROC_BROWSER_TEST_P(DictationPumpkinInstallTest, MAYBE_WaitForInstall) {
+// TODO(crbug.com/1368843): Test is flaky on MSAN builds. This test is
+// temporarily disabled to allow the SandboxedPumpkinTagger prototype to be
+// landed. It will be re-enabled when we can support Pumpkin from C++ tests
+// here: https://crrev.com/c/3938318
+IN_PROC_BROWSER_TEST_P(DictationPumpkinInstallTest, DISABLED_WaitForInstall) {
   // Dictation will request a Pumpkin install when it starts up. Wait for
   // the install to succeed.
   WaitForInstallToSucceed();
diff --git a/chrome/browser/ash/net/network_diagnostics/network_diagnostics_util_unittest.cc b/chrome/browser/ash/net/network_diagnostics/network_diagnostics_util_unittest.cc
index 137d318..d2691e0 100644
--- a/chrome/browser/ash/net/network_diagnostics/network_diagnostics_util_unittest.cc
+++ b/chrome/browser/ash/net/network_diagnostics/network_diagnostics_util_unittest.cc
@@ -7,7 +7,6 @@
 #include <string>
 #include <vector>
 
-#include "base/ranges/algorithm.h"
 #include "base/strings/string_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -38,7 +37,8 @@
       util::GetRandomHosts(num_hosts, prefix_length);
   // Ensure |random_hosts| has unique entries.
   std::sort(random_hosts.begin(), random_hosts.end());
-  EXPECT_TRUE(base::ranges::adjacent_find(random_hosts) == random_hosts.end());
+  EXPECT_TRUE(std::adjacent_find(random_hosts.begin(), random_hosts.end()) ==
+              random_hosts.end());
 }
 
 TEST(NetworkDiagnosticsUtilTest, TestGetRandomHostsWithScheme) {
@@ -48,7 +48,8 @@
       util::GetRandomHostsWithScheme(num_hosts, prefix_length, kHttpsScheme);
   // Ensure |random_hosts| has unique entries.
   std::sort(random_hosts.begin(), random_hosts.end());
-  EXPECT_TRUE(base::ranges::adjacent_find(random_hosts) == random_hosts.end());
+  EXPECT_TRUE(std::adjacent_find(random_hosts.begin(), random_hosts.end()) ==
+              random_hosts.end());
   // Ensure hosts in |random_hosts| start with |kHttpsScheme|.
   for (const auto& host : random_hosts) {
     EXPECT_TRUE(host.rfind(kHttpsScheme, 0) == 0);
@@ -64,7 +65,8 @@
                                                        kHttpsScheme);
   // Ensure |random_hosts| has unique entries.
   std::sort(random_hosts.begin(), random_hosts.end());
-  EXPECT_TRUE(base::ranges::adjacent_find(random_hosts) == random_hosts.end());
+  EXPECT_TRUE(std::adjacent_find(random_hosts.begin(), random_hosts.end()) ==
+              random_hosts.end());
   // Ensure:
   // (1) hosts in |random_hosts| start with |kHttpsScheme|.
   // (2) hosts in |random_hosts| end with |kGenerate204Path|.
diff --git a/chrome/browser/commerce/price_tracking/android/java/src/org/chromium/chrome/browser/price_tracking/PriceTrackingButtonController.java b/chrome/browser/commerce/price_tracking/android/java/src/org/chromium/chrome/browser/price_tracking/PriceTrackingButtonController.java
index cdc5f12..7daba3d 100644
--- a/chrome/browser/commerce/price_tracking/android/java/src/org/chromium/chrome/browser/price_tracking/PriceTrackingButtonController.java
+++ b/chrome/browser/commerce/price_tracking/android/java/src/org/chromium/chrome/browser/price_tracking/PriceTrackingButtonController.java
@@ -15,10 +15,6 @@
 import org.chromium.chrome.browser.toolbar.BaseButtonDataProvider;
 import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarFeatures.AdaptiveToolbarButtonVariant;
 import org.chromium.chrome.browser.user_education.IPHCommandBuilder;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.SheetState;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
-import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 
@@ -28,29 +24,17 @@
  */
 public class PriceTrackingButtonController extends BaseButtonDataProvider {
     private final Supplier<TabBookmarker> mTabBookmarkerSupplier;
-    private final BottomSheetController mBottomSheetController;
-    private final BottomSheetObserver mBottomSheetObserver;
 
     /** Constructor. */
     public PriceTrackingButtonController(ObservableSupplier<Tab> tabSupplier,
-            ModalDialogManager modalDialogManager, BottomSheetController bottomSheetController,
-            Drawable buttonDrawable, Supplier<TabBookmarker> tabBookmarkerSupplier) {
+            ModalDialogManager modalDialogManager, Drawable buttonDrawable,
+            Supplier<TabBookmarker> tabBookmarkerSupplier) {
         super(tabSupplier, modalDialogManager, buttonDrawable,
                 R.string.enable_price_tracking_menu_item,
                 /* actionChipLabelResId= */ R.string.enable_price_tracking_menu_item,
                 /*supportsTinting=*/true, /*iphCommandBuilder*/ null,
                 AdaptiveToolbarButtonVariant.PRICE_TRACKING);
         mTabBookmarkerSupplier = tabBookmarkerSupplier;
-        mBottomSheetController = bottomSheetController;
-
-        mBottomSheetObserver = new EmptyBottomSheetObserver() {
-            @Override
-            public void onSheetStateChanged(int newState, int reason) {
-                mButtonData.setEnabled(newState == SheetState.HIDDEN);
-                notifyObservers(mButtonData.canShow());
-            }
-        };
-        mBottomSheetController.addObserver(mBottomSheetObserver);
     }
 
     @Override
diff --git a/chrome/browser/commerce/price_tracking/android/javatests/src/org/chromium/chrome/browser/price_tracking/PriceTrackingButtonControllerUnitTest.java b/chrome/browser/commerce/price_tracking/android/javatests/src/org/chromium/chrome/browser/price_tracking/PriceTrackingButtonControllerUnitTest.java
deleted file mode 100644
index aff052f..0000000
--- a/chrome/browser/commerce/price_tracking/android/javatests/src/org/chromium/chrome/browser/price_tracking/PriceTrackingButtonControllerUnitTest.java
+++ /dev/null
@@ -1,184 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.price_tracking;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Activity;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.Robolectric;
-import org.robolectric.annotation.Config;
-
-import org.chromium.base.FeatureList;
-import org.chromium.base.supplier.ObservableSupplier;
-import org.chromium.base.supplier.Supplier;
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.bookmarks.TabBookmarker;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.toolbar.ButtonData;
-import org.chromium.chrome.browser.toolbar.ButtonDataProvider;
-import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.SheetState;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason;
-import org.chromium.components.browser_ui.bottomsheet.BottomSheetObserver;
-import org.chromium.ui.modaldialog.ModalDialogManager;
-
-/**
- * Unit test for {@link PriceTrackingButtonController}.
- */
-@RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
-@Features.EnableFeatures({ChromeFeatureList.CONTEXTUAL_PAGE_ACTION_PRICE_TRACKING,
-        ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2})
-public class PriceTrackingButtonControllerUnitTest {
-    @Rule
-    public TestRule mFeaturesProcessor = new Features.JUnitProcessor();
-
-    private Activity mActivity;
-    @Mock
-    private Tab mMockTab;
-    @Mock
-    private ObservableSupplier<Tab> mMockTabSupplier;
-    @Mock
-    private Supplier<TabBookmarker> mMockTabBookmarkerSupplier;
-    @Mock
-    private TabBookmarker mMockTabBookmarker;
-    @Mock
-    private BottomSheetController mMockBottomSheetController;
-    @Mock
-    private ModalDialogManager mMockModalDialogManager;
-    @Captor
-    private ArgumentCaptor<BottomSheetObserver> mBottomSheetObserverCaptor;
-
-    @Before
-    public void setUp() {
-        mActivity = Robolectric.setupActivity(Activity.class);
-        mActivity.setTheme(R.style.Theme_BrowserUI_DayNight);
-
-        MockitoAnnotations.initMocks(this);
-
-        when(mMockTab.getContext()).thenReturn(mActivity);
-        when(mMockTabSupplier.get()).thenReturn(mMockTab);
-        when(mMockTabBookmarkerSupplier.get()).thenReturn(mMockTabBookmarker);
-    }
-
-    @Test
-    public void testButtonData_QuietVariation() {
-        PriceTrackingButtonController priceTrackingButtonController =
-                new PriceTrackingButtonController(mMockTabSupplier, mMockModalDialogManager,
-                        mMockBottomSheetController, mock(Drawable.class),
-                        mMockTabBookmarkerSupplier);
-        ButtonData buttonData = priceTrackingButtonController.get(mMockTab);
-
-        // Quiet variation uses an IPHCommandBuilder to highlight the action.
-        Assert.assertNotNull(buttonData.getButtonSpec().getIPHCommandBuilder());
-        Assert.assertEquals(
-                Resources.ID_NULL, buttonData.getButtonSpec().getActionChipLabelResId());
-    }
-
-    @Test
-    public void testButtonData_ActionChipVariation() {
-        FeatureList.TestValues testValues = new FeatureList.TestValues();
-        testValues.addFeatureFlagOverride(ChromeFeatureList.CONTEXTUAL_PAGE_ACTIONS, true);
-        testValues.addFieldTrialParamOverride(
-                ChromeFeatureList.CONTEXTUAL_PAGE_ACTIONS, "action_chip", "true");
-        testValues.addFeatureFlagOverride(
-                ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2, true);
-        FeatureList.setTestValues(testValues);
-
-        PriceTrackingButtonController priceTrackingButtonController =
-                new PriceTrackingButtonController(mMockTabSupplier, mMockModalDialogManager,
-                        mMockBottomSheetController, mock(Drawable.class),
-                        mMockTabBookmarkerSupplier);
-        ButtonData buttonData = priceTrackingButtonController.get(mMockTab);
-
-        // Action chip variation sets a string resource as action chip label. IPH is not used.
-        Assert.assertNull(buttonData.getButtonSpec().getIPHCommandBuilder());
-        Assert.assertNotEquals(
-                Resources.ID_NULL, buttonData.getButtonSpec().getActionChipLabelResId());
-    }
-
-    @Test
-    public void testPriceTrackingButtonClick() {
-        PriceTrackingButtonController priceTrackingButtonController =
-                new PriceTrackingButtonController(mMockTabSupplier, mMockModalDialogManager,
-                        mMockBottomSheetController, mock(Drawable.class),
-                        mMockTabBookmarkerSupplier);
-        ButtonData buttonData = priceTrackingButtonController.get(mMockTab);
-
-        buttonData.getButtonSpec().getOnClickListener().onClick(null);
-
-        verify(mMockTabBookmarker).startOrModifyPriceTracking(mMockTab);
-    }
-
-    @Test
-    public void testPriceTrackingButton_IsDisabledWhenBottomSheetAppears() {
-        PriceTrackingButtonController priceTrackingButtonController =
-                new PriceTrackingButtonController(mMockTabSupplier, mMockModalDialogManager,
-                        mMockBottomSheetController, mock(Drawable.class),
-                        mMockTabBookmarkerSupplier);
-        ButtonDataProvider.ButtonDataObserver buttonDataObserver =
-                Mockito.mock(ButtonDataProvider.ButtonDataObserver.class);
-        priceTrackingButtonController.addObserver(buttonDataObserver);
-        ButtonData buttonData = priceTrackingButtonController.get(mMockTab);
-
-        // The controller should have registered an observer to listen to bottom sheet events.
-        verify(mMockBottomSheetController).addObserver(mBottomSheetObserverCaptor.capture());
-
-        mBottomSheetObserverCaptor.getValue().onSheetStateChanged(
-                SheetState.FULL, StateChangeReason.NONE);
-
-        Assert.assertFalse(buttonData.isEnabled());
-        verify(buttonDataObserver).buttonDataChanged(true);
-    }
-
-    @Test
-    public void testPriceTrackingButton_IsReenabledWhenBottomSheetDismissed() {
-        PriceTrackingButtonController priceTrackingButtonController =
-                new PriceTrackingButtonController(mMockTabSupplier, mMockModalDialogManager,
-                        mMockBottomSheetController, mock(Drawable.class),
-                        mMockTabBookmarkerSupplier);
-
-        ButtonDataProvider.ButtonDataObserver buttonDataObserver =
-                Mockito.mock(ButtonDataProvider.ButtonDataObserver.class);
-        priceTrackingButtonController.addObserver(buttonDataObserver);
-        ButtonData buttonData = priceTrackingButtonController.get(mMockTab);
-
-        // The controller should have registered an observer to listen to bottom sheet events.
-        verify(mMockBottomSheetController).addObserver(mBottomSheetObserverCaptor.capture());
-
-        // Show bottom sheet to disable button.
-        mBottomSheetObserverCaptor.getValue().onSheetStateChanged(
-                SheetState.FULL, StateChangeReason.NONE);
-
-        // Close the bottom sheet, button should be enabled again.
-        mBottomSheetObserverCaptor.getValue().onSheetStateChanged(
-                SheetState.HIDDEN, StateChangeReason.NONE);
-
-        // After the bottom sheet is closed the button should be enabled.
-        Assert.assertTrue(buttonData.isEnabled());
-        // We should have notified of changes twice (when disabled and when enabled again).
-        verify(buttonDataObserver, times(2)).buttonDataChanged(true);
-    }
-}
diff --git a/chrome/browser/commerce/price_tracking/android/test_java_sources.gni b/chrome/browser/commerce/price_tracking/android/test_java_sources.gni
index 665fa0f1..f54d7a7 100644
--- a/chrome/browser/commerce/price_tracking/android/test_java_sources.gni
+++ b/chrome/browser/commerce/price_tracking/android/test_java_sources.gni
@@ -9,6 +9,5 @@
 
 price_tracking_junit_test_java_sources = [
   "//chrome/browser/commerce/price_tracking/android/javatests/src/org/chromium/chrome/browser/price_tracking/PriceDropNotifierUnitTest.java",
-  "//chrome/browser/commerce/price_tracking/android/javatests/src/org/chromium/chrome/browser/price_tracking/PriceTrackingButtonControllerUnitTest.java",
   "//chrome/browser/commerce/price_tracking/android/javatests/src/org/chromium/chrome/browser/price_tracking/PriceTrackingNotificationBridgeUnitTest.java",
 ]
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
index 552e13c9..851e0037 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
@@ -185,6 +185,12 @@
                           "CancelledByUser", false);
   }
 
+  // Ask the binary upload service to cancel requests if it can.
+  auto cancel = std::make_unique<BinaryUploadService::CancelRequests>(
+      data_.settings.cloud_or_local_settings);
+  cancel->set_user_action_id(user_action_id_);
+  GetBinaryUploadService()->MaybeCancelRequests(std::move(cancel));
+
   // Make sure to reject everything.
   FillAllResultsWith(false);
   RunCallback();
diff --git a/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_client.cc b/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_client.cc
index 49bf52b..6a322ac0 100644
--- a/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_client.cc
+++ b/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_client.cc
@@ -34,6 +34,7 @@
 
 int FakeContentAnalysisSdkClient::CancelRequests(
     const content_analysis::sdk::ContentAnalysisCancelRequests& cancel) {
+  cancel_ = cancel;
   return cancel_status_;
 }
 
@@ -42,6 +43,11 @@
   return request_;
 }
 
+const content_analysis::sdk::ContentAnalysisCancelRequests&
+FakeContentAnalysisSdkClient::GetCancelRequests() {
+  return cancel_;
+}
+
 void FakeContentAnalysisSdkClient::SetAckStatus(int status) {
   ack_status_ = status;
 }
diff --git a/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_client.h b/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_client.h
index 30aa7ad6..506f518b 100644
--- a/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_client.h
+++ b/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_client.h
@@ -29,6 +29,10 @@
   // Get the latest request client receives.
   const content_analysis::sdk::ContentAnalysisRequest& GetRequest();
 
+  // Get the latest cancel requests receives.
+  const content_analysis::sdk::ContentAnalysisCancelRequests&
+  GetCancelRequests();
+
   // Configure response acknowledgement status.
   void SetAckStatus(int status);
 
@@ -46,6 +50,7 @@
   content_analysis::sdk::Client::Config config_;
   content_analysis::sdk::ContentAnalysisResponse response_;
   content_analysis::sdk::ContentAnalysisRequest request_;
+  content_analysis::sdk::ContentAnalysisCancelRequests cancel_;
   int send_status_ = 0;
   int ack_status_ = 0;
   int cancel_status_ = 0;
diff --git a/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_manager.cc b/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_manager.cc
index 93ff3662..8bcafed5 100644
--- a/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_manager.cc
+++ b/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_manager.cc
@@ -30,6 +30,10 @@
   ack_status_ = status;
 }
 
+void FakeContentAnalysisSdkManager::SetClientCancelStatus(int status) {
+  cancel_status_ = status;
+}
+
 std::unique_ptr<content_analysis::sdk::Client>
 FakeContentAnalysisSdkManager::CreateClient(
     const content_analysis::sdk::Client::Config& config) {
@@ -37,6 +41,7 @@
   client->SetSendStatus(send_status_);
   client->SetSendResponse(response_);
   client->SetAckStatus(ack_status_);
+  client->SetCancelStatus(cancel_status_);
   fake_clients_.insert(std::make_pair(std::move(config), client.get()));
 
   return client;
diff --git a/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_manager.h b/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_manager.h
index 9c3858e0c..7b893fa0 100644
--- a/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_manager.h
+++ b/chrome/browser/enterprise/connectors/analysis/fake_content_analysis_sdk_manager.h
@@ -31,12 +31,15 @@
 
   void SetClientAckStatus(int status);
 
+  void SetClientCancelStatus(int status);
+
   FakeContentAnalysisSdkClient* GetFakeClient(
       const content_analysis::sdk::Client::Config& config);
 
  private:
   int send_status_ = 0;
   int ack_status_ = 0;
+  int cancel_status_ = 0;
   content_analysis::sdk::ContentAnalysisResponse response_;
 
   constexpr static auto CompareConfig =
diff --git a/chrome/browser/enterprise/connectors/analysis/local_binary_upload_service.cc b/chrome/browser/enterprise/connectors/analysis/local_binary_upload_service.cc
index 62244f8..b463bbc 100644
--- a/chrome/browser/enterprise/connectors/analysis/local_binary_upload_service.cc
+++ b/chrome/browser/enterprise/connectors/analysis/local_binary_upload_service.cc
@@ -34,6 +34,14 @@
           ack->cloud_or_local_settings().user_specific()};
 }
 
+// Build a content analysis SDK client config based on the cancel requests being
+// sent.
+content_analysis::sdk::Client::Config SDKConfigFromCancel(
+    const safe_browsing::BinaryUploadService::CancelRequests* cancel) {
+  return {cancel->cloud_or_local_settings().local_path(),
+          cancel->cloud_or_local_settings().user_specific()};
+}
+
 // Convert enterprise connector ContentAnalysisRequest into the SDK equivalent.
 // SDK ContentAnalysisRequest is a strict subset of the enterprise connector
 // version, therefore the function should always work.
@@ -92,7 +100,15 @@
   return wrapped->client()->Acknowledge(sdk_ack);
 }
 
-void HandleAckResponse(
+int SendCancelToSDK(
+    scoped_refptr<ContentAnalysisSdkManager::WrappedClient> wrapped,
+    content_analysis::sdk::ContentAnalysisCancelRequests sdk_cancel) {
+  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
+                                                base::BlockingType::MAY_BLOCK);
+  return wrapped->client()->CancelRequests(sdk_cancel);
+}
+
+void HandleAckOrCancelResponse(
     scoped_refptr<ContentAnalysisSdkManager::WrappedClient> wrapped,
     int status) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -114,7 +130,24 @@
       FROM_HERE,
       {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
       base::BindOnce(&SendAckToSDK, wrapped, std::move(sdk_ack)),
-      base::BindOnce(&HandleAckResponse, wrapped));
+      base::BindOnce(&HandleAckOrCancelResponse, wrapped));
+}
+
+void DoSendCancel(
+    scoped_refptr<ContentAnalysisSdkManager::WrappedClient> wrapped,
+    std::unique_ptr<safe_browsing::BinaryUploadService::CancelRequests>
+        cancel) {
+  if (!wrapped || !wrapped->client())
+    return;
+
+  content_analysis::sdk::ContentAnalysisCancelRequests sdk_cancel;
+  sdk_cancel.set_user_action_id(cancel->get_user_action_id());
+
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE,
+      {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+      base::BindOnce(&SendCancelToSDK, wrapped, std::move(sdk_cancel)),
+      base::BindOnce(&HandleAckOrCancelResponse, wrapped));
 }
 
 // Sends a request to the local agent and waits for a response.
@@ -201,6 +234,35 @@
       std::move(ack));
 }
 
+void LocalBinaryUploadService::MaybeCancelRequests(
+    std::unique_ptr<CancelRequests> cancel) {
+  // Cancel all active requests.  If the agent returns a response for any,
+  // they will be ignored.
+  for (auto it = active_requests_.begin(); it != active_requests_.end();) {
+    if (it->second.request->user_action_id() == cancel->get_user_action_id()) {
+      it = active_requests_.erase(it);
+    } else {
+      ++it;
+    }
+  }
+
+  // Cancel all pending requests.
+  for (auto it = pending_requests_.begin(); it != pending_requests_.end();) {
+    if (it->request->user_action_id() == cancel->get_user_action_id()) {
+      it = pending_requests_.erase(it);
+    } else {
+      ++it;
+    }
+  }
+
+  // Tell agent to cancel requests.  This is a best effort only on the part of
+  // the agent.
+  auto* cancel_ptr = cancel.get();
+  DoSendCancel(ContentAnalysisSdkManager::Get()->GetClient(
+                   SDKConfigFromCancel(cancel_ptr)),
+               std::move(cancel));
+}
+
 void LocalBinaryUploadService::DoLocalContentAnalysis(RequestKey key,
                                                       Result result,
                                                       Request::Data data) {
diff --git a/chrome/browser/enterprise/connectors/analysis/local_binary_upload_service.h b/chrome/browser/enterprise/connectors/analysis/local_binary_upload_service.h
index a229f31..4dc6b78 100644
--- a/chrome/browser/enterprise/connectors/analysis/local_binary_upload_service.h
+++ b/chrome/browser/enterprise/connectors/analysis/local_binary_upload_service.h
@@ -69,6 +69,7 @@
   // Send the given file contents to local partners for deep scanning.
   void MaybeUploadForDeepScanning(std::unique_ptr<Request> request) override;
   void MaybeAcknowledge(std::unique_ptr<Ack> ack) override;
+  void MaybeCancelRequests(std::unique_ptr<CancelRequests> cancel) override;
 
   size_t GetActiveRequestCountForTesting() const {
     return active_requests_.size();
diff --git a/chrome/browser/enterprise/connectors/analysis/local_binary_upload_service_unittest.cc b/chrome/browser/enterprise/connectors/analysis/local_binary_upload_service_unittest.cc
index 38a9555..09eb670 100644
--- a/chrome/browser/enterprise/connectors/analysis/local_binary_upload_service_unittest.cc
+++ b/chrome/browser/enterprise/connectors/analysis/local_binary_upload_service_unittest.cc
@@ -23,6 +23,8 @@
 using ::testing::Return;
 using ::testing::SaveArg;
 
+constexpr char kFakeUserActionId[] = "1234567890";
+
 class MockRequest : public BinaryUploadService::Request {
  public:
   MockRequest(BinaryUploadService::ContentAnalysisCallback callback,
@@ -332,4 +334,65 @@
   EXPECT_EQ(BinaryUploadService::Result::UPLOAD_FAILURE, result);
 }
 
+TEST_F(LocalBinaryUploadServiceTest, CancelRequests) {
+  LocalAnalysisSettings local;
+  local.local_path = "local_system_path";
+  local.user_specific = false;
+
+  CloudOrLocalAnalysisSettings cloud_or_local(local);
+  LocalBinaryUploadService lbus;
+
+  // Add one more request than the max number of concurrent active requests.
+  // The remaining one should be pending.
+  LocalAnalysisSettings settings(local);
+  for (size_t i = 0; i < LocalBinaryUploadService::kMaxActiveCount + 1; ++i) {
+    auto request = std::make_unique<MockRequest>(base::DoNothing(), settings);
+    request->set_user_action_id(kFakeUserActionId);
+    lbus.MaybeUploadForDeepScanning(std::move(request));
+  }
+
+  EXPECT_EQ(LocalBinaryUploadService::kMaxActiveCount,
+            lbus.GetActiveRequestCountForTesting());
+  EXPECT_EQ(1u, lbus.GetPendingRequestCountForTesting());
+
+  auto cr = std::make_unique<LocalBinaryUploadService::CancelRequests>(
+      cloud_or_local);
+  cr->set_user_action_id(kFakeUserActionId);
+  lbus.MaybeCancelRequests(std::move(cr));
+
+  EXPECT_EQ(0u, lbus.GetActiveRequestCountForTesting());
+  EXPECT_EQ(0u, lbus.GetPendingRequestCountForTesting());
+
+  task_environment_.RunUntilIdle();
+
+  FakeContentAnalysisSdkClient* fake_client_ptr =
+      fake_sdk_manager_.GetFakeClient({local.local_path, local.user_specific});
+  EXPECT_EQ(kFakeUserActionId,
+            fake_client_ptr->GetCancelRequests().user_action_id());
+}
+
+TEST_F(LocalBinaryUploadServiceTest,
+       ClientDestroyedWhenCancelStatusIsAbnormal) {
+  fake_sdk_manager_.SetClientCancelStatus(-1);
+
+  LocalAnalysisSettings local;
+  local.local_path = "local_system_path";
+  local.user_specific = false;
+
+  CloudOrLocalAnalysisSettings cloud_or_local(local);
+  content_analysis::sdk::Client::Config config{local.local_path,
+                                               local.user_specific};
+  LocalBinaryUploadService lbus;
+
+  auto cr = std::make_unique<LocalBinaryUploadService::CancelRequests>(
+      cloud_or_local);
+  cr->set_user_action_id("1234567890");
+  lbus.MaybeCancelRequests(std::move(cr));
+
+  EXPECT_TRUE(fake_sdk_manager_.HasClientForTesting(config));
+
+  task_environment_.RunUntilIdle();
+
+  EXPECT_FALSE(fake_sdk_manager_.HasClientForTesting(config));
+}
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/extensions/api/developer_private/inspectable_views_finder.cc b/chrome/browser/extensions/api/developer_private/inspectable_views_finder.cc
index 3a1b53ab..b73cc3ca 100644
--- a/chrome/browser/extensions/api/developer_private/inspectable_views_finder.cc
+++ b/chrome/browser/extensions/api/developer_private/inspectable_views_finder.cc
@@ -181,7 +181,7 @@
     // committed (or visible) url yet. In this case, use the initial url.
     if (url.is_empty()) {
       ExtensionHost* extension_host =
-          process_manager->GetExtensionHostForRenderFrameHost(host);
+          process_manager->GetBackgroundHostForRenderFrameHost(host);
       if (extension_host)
         url = extension_host->initial_url();
     }
diff --git a/chrome/browser/feed/android/feed_stream.cc b/chrome/browser/feed/android/feed_stream.cc
index 35ea31a1..576204e6 100644
--- a/chrome/browser/feed/android/feed_stream.cc
+++ b/chrome/browser/feed/android/feed_stream.cc
@@ -12,6 +12,7 @@
 #include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
 #include "base/strings/string_piece.h"
+#include "base/time/time.h"
 #include "chrome/browser/feed/android/feed_reliability_logging_bridge.h"
 #include "chrome/browser/feed/android/jni_headers/FeedStream_jni.h"
 #include "chrome/browser/feed/android/jni_translation.h"
@@ -330,5 +331,15 @@
       (static_cast<StreamKind>(stream_kind)));
 }
 
+void FeedStream::ReportContentSliceVisibleTimeForGoodVisits(
+    JNIEnv* env,
+    const base::android::JavaParamRef<jobject>& obj,
+    jlong elapsed_ms) {
+  if (!feed_stream_api_)
+    return;
+  feed_stream_api_->ReportContentSliceVisibleTimeForGoodVisits(
+      base::Milliseconds(elapsed_ms));
+}
+
 }  // namespace android
 }  // namespace feed
diff --git a/chrome/browser/feed/android/feed_stream.h b/chrome/browser/feed/android/feed_stream.h
index 42feee3f..9608b300 100644
--- a/chrome/browser/feed/android/feed_stream.h
+++ b/chrome/browser/feed/android/feed_stream.h
@@ -141,6 +141,11 @@
       const base::android::JavaParamRef<jobject>& obj,
       jint stream_kind);
 
+  void ReportContentSliceVisibleTimeForGoodVisits(
+      JNIEnv* env,
+      const base::android::JavaParamRef<jobject>& obj,
+      jlong elapsed_ms);
+
  private:
   base::android::ScopedJavaGlobalRef<jobject> java_ref_;
   raw_ptr<FeedApi> feed_stream_api_;
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSliceViewTracker.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSliceViewTracker.java
index fff6883f..2df54ae 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSliceViewTracker.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSliceViewTracker.java
@@ -4,7 +4,9 @@
 
 package org.chromium.chrome.browser.feed;
 
+import android.app.Activity;
 import android.graphics.Rect;
+import android.os.SystemClock;
 import android.view.View;
 import android.view.ViewTreeObserver;
 
@@ -13,6 +15,7 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.recyclerview.widget.RecyclerView;
 
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.xsurface.ListLayoutHelper;
 
 import java.util.ArrayList;
@@ -26,6 +29,8 @@
 public class FeedSliceViewTracker implements ViewTreeObserver.OnPreDrawListener {
     private static final String TAG = "FeedSliceViewTracker";
     private static final float DEFAULT_VIEW_LOG_THRESHOLD = .66f;
+    private static final float GOOD_VISITS_EXPOSURE_THRESHOLD = 0.5f;
+    private static final float GOOD_VISITS_COVERAGE_THRESHOLD = 0.25f;
 
     private class VisibilityObserver {
         final float mVisibilityThreshold;
@@ -37,6 +42,7 @@
         }
     }
 
+    private final Activity mActivity;
     @Nullable
     private RecyclerView mRootView;
     @Nullable
@@ -51,28 +57,50 @@
     // changes. Each item in the waicther list consists of the view threshold percentage and the
     // callback.
     private HashMap<String, ArrayList<VisibilityObserver>> mWatchedSliceMap = new HashMap<>();
+    private boolean mTrackTimeForGoodVisits;
+    // Thresholds for counting a view as visible for calculating time spent in feed for good visits.
+    private float mGoodVisitExposureThreshold;
+    private float mGoodVisitCoverageThreshold;
+    // Timestamp for keeping track of time spent in feed for good visits.
+    private long mLastGoodVisibleTime;
 
     /** Notified the first time slices are visible */
     public interface Observer {
         // Invoked the first time a slice is 66% visible.
         void sliceVisible(String sliceId);
+        // Invoked any time at least one slice is X% exposed and all visible content slices cover Y%
+        // of the viewport (see Good Visits threshold params).
+        void reportContentSliceVisibleTime(long elapsedMs);
         // Invoked when feed content is first visible. This can happens as soon as an xsurface view
         // is partially visible.
         void feedContentVisible();
     }
 
-    public FeedSliceViewTracker(@NonNull RecyclerView rootView,
+    public FeedSliceViewTracker(@NonNull RecyclerView rootView, @NonNull Activity activity,
             @NonNull NtpListContentManager contentManager, @Nullable ListLayoutHelper layoutHelper,
             @NonNull Observer observer) {
+        mActivity = activity;
         mRootView = rootView;
         mContentManager = contentManager;
         mLayoutHelper = layoutHelper;
         mObserver = observer;
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.FEED_CLIENT_GOOD_VISITS)) {
+            mTrackTimeForGoodVisits = true;
+            mGoodVisitExposureThreshold =
+                    (float) ChromeFeatureList.getFieldTrialParamByFeatureAsDouble(
+                            ChromeFeatureList.FEED_CLIENT_GOOD_VISITS, "slice_exposure_threshold",
+                            GOOD_VISITS_EXPOSURE_THRESHOLD);
+            mGoodVisitCoverageThreshold =
+                    (float) ChromeFeatureList.getFieldTrialParamByFeatureAsDouble(
+                            ChromeFeatureList.FEED_CLIENT_GOOD_VISITS, "slice_coverage_threshold",
+                            GOOD_VISITS_COVERAGE_THRESHOLD);
+        }
     }
 
     /** Attaches the tracker to the root view. */
     public void bind() {
         mRootView.getViewTreeObserver().addOnPreDrawListener(this);
+        mLastGoodVisibleTime = 0L;
     }
 
     /** Detaches the tracker from the view. */
@@ -80,6 +108,7 @@
         if (mRootView != null && mRootView.getViewTreeObserver().isAlive()) {
             mRootView.getViewTreeObserver().removeOnPreDrawListener(this);
         }
+        reportTimeForGoodVisitsIfNeeded();
     }
 
     /** Stop observing rootView. Prevents further calls to observer. */
@@ -146,6 +175,7 @@
 
         int firstPosition = mLayoutHelper.findFirstVisibleItemPosition();
         int lastPosition = mLayoutHelper.findLastVisibleItemPosition();
+        boolean countTimeForGoodVisits = false;
         for (int i = firstPosition;
                 i <= lastPosition && i < mContentManager.getItemCount() && i >= 0; ++i) {
             String contentKey = mContentManager.getContent(i).getKey();
@@ -184,6 +214,12 @@
                 }
             }
 
+            if (mTrackTimeForGoodVisits) {
+                countTimeForGoodVisits = countTimeForGoodVisits
+                        || isViewVisible(childView, mGoodVisitExposureThreshold)
+                        || isViewCoveringViewport(childView, mGoodVisitCoverageThreshold);
+            }
+
             if (mContentKeysVisible.contains(contentKey)
                     || !isViewVisible(childView, DEFAULT_VIEW_LOG_THRESHOLD)) {
                 continue;
@@ -192,16 +228,53 @@
             mContentKeysVisible.add(contentKey);
             mObserver.sliceVisible(contentKey);
         }
+
+        if (mTrackTimeForGoodVisits) {
+            reportTimeForGoodVisitsIfNeeded();
+            if (countTimeForGoodVisits) {
+                mLastGoodVisibleTime = SystemClock.elapsedRealtime();
+            }
+        }
+
         return true;
     }
 
+    private void reportTimeForGoodVisitsIfNeeded() {
+        // Report elapsed time since we last saw that content was visible enough.
+        if (mLastGoodVisibleTime != 0L) {
+            mObserver.reportContentSliceVisibleTime(
+                    SystemClock.elapsedRealtime() - mLastGoodVisibleTime);
+            mLastGoodVisibleTime = 0L;
+        }
+    }
+
     @VisibleForTesting
     boolean isViewVisible(View childView, float threshold) {
-        Rect rect = new Rect(0, 0, childView.getWidth(), childView.getHeight());
-        int viewArea = rect.width() * rect.height();
+        int viewArea = getViewArea(childView);
         if (viewArea <= 0) return false;
-        if (!mRootView.getChildVisibleRect(childView, rect, null)) return false;
-        int visibleArea = rect.width() * rect.height();
-        return (float) visibleArea / viewArea >= threshold;
+        return (float) getVisibleArea(childView) / viewArea >= threshold;
+    }
+
+    @VisibleForTesting
+    boolean isViewCoveringViewport(View childView, float threshold) {
+        int viewportArea = getViewportArea();
+        if (viewportArea <= 0) return false;
+        return (float) getVisibleArea(childView) / viewportArea >= threshold;
+    }
+
+    private int getViewArea(View childView) {
+        return childView.getWidth() * childView.getHeight();
+    }
+
+    private int getViewportArea() {
+        Rect viewport = new Rect();
+        mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(viewport);
+        return viewport.width() * viewport.height();
+    }
+
+    private int getVisibleArea(View childView) {
+        Rect rect = new Rect();
+        if (!mRootView.getChildVisibleRect(childView, rect, null)) return 0;
+        return rect.width() * rect.height();
     }
 }
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSliceViewTrackerTest.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSliceViewTrackerTest.java
index 41ddd29..b9bc09b 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSliceViewTrackerTest.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSliceViewTrackerTest.java
@@ -9,21 +9,27 @@
 import static org.mockito.AdditionalMatchers.leq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.Activity;
 import android.graphics.Rect;
 import android.view.View;
 import android.view.ViewTreeObserver;
+import android.view.Window;
 
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.filters.SmallTest;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -35,15 +41,19 @@
 import org.mockito.stubbing.Answer;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowLog;
+import org.robolectric.shadows.ShadowSystemClock;
 
+import org.chromium.base.FeatureList;
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.xsurface.ListLayoutHelper;
 
 import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
 
 /** Unit tests for {@link FeedSliceViewTracker}. */
 @RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
+@Config(manifest = Config.NONE, shadows = {ShadowSystemClock.class})
 public class FeedSliceViewTrackerTest {
     // Mocking dependencies that are always present, but using a real FeedListContentManager.
     @Mock
@@ -56,6 +66,12 @@
     ListLayoutHelper mLayoutHelper;
     @Mock
     ViewTreeObserver mViewTreeObserver;
+    @Mock
+    Activity mActivity;
+    @Mock
+    Window mWindow;
+    @Mock
+    View mDecorView;
     NtpListContentManager mContentManager;
 
     FeedSliceViewTracker mTracker;
@@ -74,20 +90,31 @@
 
     @Before
     public void setUp() {
+        FeatureList.TestValues testValues = new FeatureList.TestValues();
+        testValues.addFeatureFlagOverride(ChromeFeatureList.FEED_CLIENT_GOOD_VISITS, true);
+        FeatureList.setTestValues(testValues);
+
         ShadowLog.stream = System.out;
         MockitoAnnotations.initMocks(this);
         mContentManager = new NtpListContentManager();
         doReturn(mLayoutManager).when(mParentView).getLayoutManager();
         doReturn(mViewTreeObserver).when(mParentView).getViewTreeObserver();
-        mTracker = Mockito.spy(
-                new FeedSliceViewTracker(mParentView, mContentManager, mLayoutHelper, mObserver));
+        doReturn(mWindow).when(mActivity).getWindow();
+        doReturn(mDecorView).when(mWindow).getDecorView();
+        mTracker = Mockito.spy(new FeedSliceViewTracker(
+                mParentView, mActivity, mContentManager, mLayoutHelper, mObserver));
+    }
+
+    @After
+    public void tearDown() {
+        ShadowSystemClock.reset();
     }
 
     @Test
     @SmallTest
     public void testIsItemVisible_JustEnoughnViewport() {
         mockViewDimensions(mChildA, 10, 10);
-        mockGetChildVisibleRect(mChildA, 0, 7);
+        mockGetChildVisibleRect(mChildA, 0, 0, 10, 7);
         Assert.assertTrue(mTracker.isViewVisible(mChildA, 0.66f));
     }
 
@@ -95,7 +122,7 @@
     @SmallTest
     public void testIsItemVisible_NotEnoughnViewport() {
         mockViewDimensions(mChildA, 10, 10);
-        mockGetChildVisibleRect(mChildA, 0, 6);
+        mockGetChildVisibleRect(mChildA, 0, 0, 10, 6);
         Assert.assertFalse(mTracker.isViewVisible(mChildA, 0.66f));
     }
 
@@ -103,7 +130,7 @@
     @SmallTest
     public void testIsItemVisible_ZeroAreaInViewport() {
         mockViewDimensions(mChildA, 10, 10);
-        mockGetChildVisibleRect(mChildA, 0, 0);
+        mockGetChildVisibleRect(mChildA, 0, 0, 0, 0);
         Assert.assertFalse(mTracker.isViewVisible(mChildA, 0.66f));
     }
 
@@ -119,12 +146,48 @@
     @SmallTest
     public void testIsItemVisible_ZeroArea() {
         mockViewDimensions(mChildA, 0, 0);
-        mockGetChildVisibleRect(mChildA, 0, 0);
+        mockGetChildVisibleRect(mChildA, 0, 0, 0, 0);
         Assert.assertFalse(mTracker.isViewVisible(mChildA, 0.66f));
     }
 
     @Test
     @SmallTest
+    public void testIsItemCoveringViewport_JustEnough() {
+        mockViewDimensions(mChildA, 100, 100);
+        mockGetChildVisibleRect(mChildA, 0, 0, 100, 26);
+        mockViewportRect(0, 0, 100, 100);
+        Assert.assertTrue(mTracker.isViewCoveringViewport(mChildA, 0.25f));
+    }
+
+    @Test
+    @SmallTest
+    public void testIsViewCoveringViewport_NotEnough() {
+        mockViewDimensions(mChildA, 100, 100);
+        mockGetChildVisibleRect(mChildA, 0, 0, 100, 24);
+        mockViewportRect(0, 0, 100, 100);
+        Assert.assertFalse(mTracker.isViewCoveringViewport(mChildA, 0.25f));
+    }
+
+    @Test
+    @SmallTest
+    public void testIsContentCoveringViewport_ZeroArea() {
+        mockViewDimensions(mChildA, 0, 0);
+        mockGetChildVisibleRect(mChildA, 0, 0, 0, 0);
+        mockViewportRect(0, 0, 100, 100);
+        Assert.assertFalse(mTracker.isViewCoveringViewport(mChildA, 0.25f));
+    }
+
+    @Test
+    @SmallTest
+    public void testIsContentCoveringViewport_NoViewport() {
+        mockViewDimensions(mChildA, 100, 100);
+        mockGetChildVisibleRect(mChildA, 0, 0, 100, 26);
+        mockViewportRect(0, 0, 0, 0);
+        Assert.assertFalse(mTracker.isViewCoveringViewport(mChildA, 0.25f));
+    }
+
+    @Test
+    @SmallTest
     public void testOnPreDraw_BothVisibleAreReportedExactlyOnce() {
         mContentManager.addContents(0,
                 Arrays.asList(new NtpListContentManager.FeedContent[] {
@@ -305,18 +368,159 @@
         assertTrue(mChildBVisibleRunnable2Called);
     }
 
-    void mockViewDimensions(View view, int width, int height) {
-        when(view.getWidth()).thenReturn(10);
-        when(view.getHeight()).thenReturn(10);
+    @Test
+    @SmallTest
+    public void testReportContentVisibleTime_visibleAndCovering() {
+        mContentManager.addContents(0,
+                Arrays.asList(new NtpListContentManager.FeedContent[] {
+                        new NtpListContentManager.NativeViewContent(0, "c/key1", mChildA),
+                        new NtpListContentManager.NativeViewContent(0, "c/key2", mChildB),
+                }));
+        doReturn(0).when(mLayoutHelper).findFirstVisibleItemPosition();
+        doReturn(1).when(mLayoutHelper).findLastVisibleItemPosition();
+        doReturn(mChildA).when(mLayoutManager).findViewByPosition(eq(0));
+        doReturn(mChildB).when(mLayoutManager).findViewByPosition(eq(1));
+
+        // Not visible or covering: no time reported.
+        doReturn(false).when(mTracker).isViewVisible(eq(mChildA), anyFloat());
+        doReturn(false).when(mTracker).isViewCoveringViewport(eq(mChildA), anyFloat());
+        mTracker.onPreDraw();
+        advanceByMs(1L);
+        mTracker.onPreDraw();
+        verify(mObserver, never()).reportContentSliceVisibleTime(anyLong());
+
+        // Visible enough; time is reported.
+        doReturn(true).when(mTracker).isViewVisible(eq(mChildA), anyFloat());
+        doReturn(false).when(mTracker).isViewCoveringViewport(eq(mChildA), anyFloat());
+        mTracker.onPreDraw();
+        advanceByMs(1L);
+        mTracker.onPreDraw();
+        verify(mObserver, times(1)).reportContentSliceVisibleTime(eq(1L));
+        reset(mObserver);
+
+        // Covering enough; time is reported.
+        doReturn(false).when(mTracker).isViewVisible(eq(mChildA), anyFloat());
+        doReturn(true).when(mTracker).isViewCoveringViewport(eq(mChildA), anyFloat());
+        advanceByMs(1L);
+        mTracker.onPreDraw();
+        verify(mObserver, times(1)).reportContentSliceVisibleTime(eq(1L));
+        reset(mObserver);
+
+        // Visible enough and covering enough: report some time spent in feed.
+        doReturn(true).when(mTracker).isViewVisible(eq(mChildA), anyFloat());
+        doReturn(true).when(mTracker).isViewCoveringViewport(eq(mChildA), anyFloat());
+        advanceByMs(1L);
+        mTracker.onPreDraw();
+        verify(mObserver, times(1)).reportContentSliceVisibleTime(eq(1L));
     }
 
-    void mockGetChildVisibleRect(View child, int rectTop, int rectBottom) {
+    @Test
+    @SmallTest
+    public void testReportContentVisibleTime_testSmallCardsCoveringEnough() {
+        mContentManager.addContents(0,
+                Arrays.asList(new NtpListContentManager.FeedContent[] {
+                        new NtpListContentManager.NativeViewContent(0, "c/key1", mChildA),
+                        new NtpListContentManager.NativeViewContent(0, "c/key2", mChildB),
+                }));
+        doReturn(0).when(mLayoutHelper).findFirstVisibleItemPosition();
+        doReturn(1).when(mLayoutHelper).findLastVisibleItemPosition();
+        doReturn(mChildA).when(mLayoutManager).findViewByPosition(eq(0));
+        doReturn(mChildB).when(mLayoutManager).findViewByPosition(eq(1));
+
+        // Views are completely exposed so time is tracked.
+        mockViewportRect(0, 0, 100, 100);
+        mockViewDimensions(mChildA, 100, 15);
+        mockGetChildVisibleRect(mChildA, 0, 0, 100, 15);
+        mockViewDimensions(mChildB, 100, 15);
+        mockGetChildVisibleRect(mChildB, 0, 15, 100, 30);
+
+        mTracker.onPreDraw();
+        advanceByMs(1L);
+        mTracker.onPreDraw();
+        verify(mObserver, times(1)).reportContentSliceVisibleTime(eq(1L));
+    }
+
+    @Test
+    @SmallTest
+    public void testReportContentVisibleTime_testBigCardCoveringEnough() {
+        mContentManager.addContents(0,
+                Arrays.asList(new NtpListContentManager.FeedContent[] {
+                        new NtpListContentManager.NativeViewContent(0, "c/key1", mChildA),
+                }));
+        doReturn(0).when(mLayoutHelper).findFirstVisibleItemPosition();
+        doReturn(0).when(mLayoutHelper).findLastVisibleItemPosition();
+        doReturn(mChildA).when(mLayoutManager).findViewByPosition(eq(0));
+
+        // View is completely exposed and covers 30% of the viewport in total.
+        mockViewportRect(0, 0, 100, 100);
+        mockViewDimensions(mChildA, 100, 26);
+        mockGetChildVisibleRect(mChildA, 0, 0, 100, 26);
+
+        mTracker.onPreDraw();
+        advanceByMs(1L);
+        mTracker.onPreDraw();
+        verify(mObserver, times(1)).reportContentSliceVisibleTime(eq(1L));
+    }
+
+    @Test
+    @SmallTest
+    public void testReportContentVisibleTime_testBigCardExposedEnough() {
+        mContentManager.addContents(0,
+                Arrays.asList(new NtpListContentManager.FeedContent[] {
+                        new NtpListContentManager.NativeViewContent(0, "c/key1", mChildA),
+                }));
+        doReturn(0).when(mLayoutHelper).findFirstVisibleItemPosition();
+        doReturn(0).when(mLayoutHelper).findLastVisibleItemPosition();
+        doReturn(mChildA).when(mLayoutManager).findViewByPosition(eq(0));
+
+        // View is completely exposed but only covers 22% of the viewport.
+        mockViewportRect(0, 0, 100, 100);
+        mockViewDimensions(mChildA, 100, 22);
+        mockGetChildVisibleRect(mChildA, 0, 0, 100, 22);
+
+        mTracker.onPreDraw();
+        advanceByMs(1L);
+        mTracker.onPreDraw();
+        verify(mObserver, times(1)).reportContentSliceVisibleTime(eq(1L));
+    }
+
+    @Test
+    @SmallTest
+    public void testReportContentVisibleTime_testReportTimeOnUnbind() {
+        mContentManager.addContents(0,
+                Arrays.asList(new NtpListContentManager.FeedContent[] {
+                        new NtpListContentManager.NativeViewContent(0, "c/key1", mChildA),
+                }));
+        doReturn(0).when(mLayoutHelper).findFirstVisibleItemPosition();
+        doReturn(0).when(mLayoutHelper).findLastVisibleItemPosition();
+        doReturn(mChildA).when(mLayoutManager).findViewByPosition(eq(0));
+
+        // View is completely exposed but only covers 22% of the viewport.
+        mockViewportRect(0, 0, 100, 100);
+        mockViewDimensions(mChildA, 100, 22);
+        mockGetChildVisibleRect(mChildA, 0, 0, 100, 22);
+
+        mTracker.onPreDraw();
+        advanceByMs(1L);
+        mTracker.unbind();
+        verify(mObserver, times(1)).reportContentSliceVisibleTime(eq(1L));
+    }
+
+    void mockViewDimensions(View view, int width, int height) {
+        when(view.getWidth()).thenReturn(width);
+        when(view.getHeight()).thenReturn(height);
+    }
+
+    void mockGetChildVisibleRect(
+            View child, int rectLeft, int rectTop, int rectRight, int rectBottom) {
         doAnswer(new Answer() {
             @Override
             public Object answer(InvocationOnMock invocation) {
                 Rect rect = (Rect) invocation.getArguments()[1];
                 rect.top = rectTop;
                 rect.bottom = rectBottom;
+                rect.left = rectLeft;
+                rect.right = rectRight;
                 return true;
             }
         })
@@ -335,6 +539,18 @@
                 .getChildVisibleRect(eq(child), any(), any());
     }
 
+    void mockViewportRect(int left, int top, int right, int bottom) {
+        doAnswer(new Answer() {
+            @Override
+            public Object answer(InvocationOnMock invocation) {
+                ((Rect) invocation.getArguments()[0]).set(new Rect(left, top, right, bottom));
+                return null;
+            }
+        })
+                .when(mDecorView)
+                .getWindowVisibleDisplayFrame(any());
+    }
+
     void clearVisibleRunnableCalledStates() {
         mChildAVisibleRunnable1Called = false;
         mChildAVisibleRunnable2Called = false;
@@ -342,4 +558,8 @@
         mChildBVisibleRunnable1Called = false;
         mChildBVisibleRunnable2Called = false;
     }
+
+    void advanceByMs(long ms) {
+        ShadowSystemClock.advanceBy(ms, TimeUnit.MILLISECONDS);
+    }
 }
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStream.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStream.java
index c61f247..65511e30 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStream.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStream.java
@@ -718,7 +718,7 @@
 
         mScrollStateToRestore = savedInstanceState;
         manager.setHandlers(mHandlersMap);
-        mSliceViewTracker = new FeedSliceViewTracker(rootView, manager,
+        mSliceViewTracker = new FeedSliceViewTracker(rootView, mActivity, manager,
                 renderer.getListLayoutHelper(), new FeedStream.ViewTrackerObserver());
         mSliceViewTracker.bind();
 
@@ -1273,6 +1273,11 @@
             FeedStreamJni.get().reportSliceViewed(mNativeFeedStream, FeedStream.this, sliceId);
         }
         @Override
+        public void reportContentSliceVisibleTime(long elapsedMs) {
+            FeedStreamJni.get().reportContentSliceVisibleTimeForGoodVisits(
+                    mNativeFeedStream, FeedStream.this, elapsedMs);
+        }
+        @Override
         public void feedContentVisible() {
             FeedStreamJni.get().reportFeedViewed(mNativeFeedStream, FeedStream.this);
         }
@@ -1369,5 +1374,7 @@
         void resetInfoCardStates(long nativeFeedStream, FeedStream caller, int type);
         void invalidateContentCacheFor(
                 long nativeFeedStream, FeedStream caller, @StreamType int feedToInvalidate);
+        void reportContentSliceVisibleTimeForGoodVisits(
+                long nativeFeedStream, FeedStream caller, long elapsedMs);
     }
 }
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStreamTest.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStreamTest.java
index 842064477..59d9012 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStreamTest.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedStreamTest.java
@@ -166,6 +166,7 @@
         Map<String, Boolean> overrides = new ArrayMap<>();
         overrides.put(ChromeFeatureList.FEED_LOADING_PLACEHOLDER, feedLoadingPlaceholderOn);
         overrides.put(ChromeFeatureList.WEB_FEED_ONBOARDING, onboardingOn);
+        overrides.put(ChromeFeatureList.FEED_CLIENT_GOOD_VISITS, true);
         FeatureList.setTestFeatures(overrides);
     }
 
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 75c5e56..c49525a 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1879,6 +1879,11 @@
     "expiry_milestone": 120
   },
   {
+    "name": "enable-compromised-passwords-muting",
+    "owners": [ "noemies@google.com", "tmartino" ],
+    "expiry_milestone": 112
+  },
+  {
     "name": "enable-conditional-tabstrip",
     "owners": [ "memex-team@google.com" ],
     "expiry_milestone": 90
@@ -5914,27 +5919,27 @@
   {
     "name": "request-desktop-site-additions",
     "owners": [ "shuyng@google.com", "twellington", "clank-app-team@google.com" ],
-    "expiry_milestone": 108
+    "expiry_milestone": 112
   },
   {
     "name": "request-desktop-site-defaults",
     "owners": [ "aishwaryarj", "twellington", "clank-app-team@google.com" ],
-    "expiry_milestone": 108
+    "expiry_milestone": 110
   },
   {
     "name": "request-desktop-site-defaults-downgrade",
     "owners": [ "aishwaryarj", "twellington", "clank-app-team@google.com" ],
-    "expiry_milestone": 108
+    "expiry_milestone": 110
   },
   {
     "name": "request-desktop-site-exceptions",
     "owners": [ "shuyng@google.com", "twellington", "clank-app-team@google.com" ],
-    "expiry_milestone": 108
+    "expiry_milestone": 110
   },
   {
     "name": "request-desktop-site-exceptions-downgrade",
     "owners": [ "aishwaryarj", "twellington", "clank-app-team@google.com" ],
-    "expiry_milestone": 108
+    "expiry_milestone": 110
   },
   {
     "name": "request-desktop-site-for-tablets",
@@ -6219,7 +6224,7 @@
   {
     "name": "shopping-list",
     "owners": [ "mdjones", "chrome-shopping-eng@google.com" ],
-    "expiry_milestone": 108
+    "expiry_milestone": 110
   },
   {
     "name": "show-autofill-type-predictions",
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index aa9beec..0b991ec3 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -140,6 +140,7 @@
     &feature_engagement::kUseClientConfigIPH,
     &feature_guide::features::kFeatureNotificationGuide,
     &feature_guide::features::kSkipCheckForLowEngagedUsers,
+    &feed::kClientGoodVisits,
     &feed::kFeedBackToTop,
     &feed::kFeedClearImageMemoryCache,
     &feed::kFeedHeaderStickToTop,
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 0f29b42..c50e572 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -360,6 +360,7 @@
     public static final String FEED_BACK_TO_TOP = "FeedBackToTop";
     public static final String FEED_CLEAR_IMAGE_MEMORY_CACHE = "FeedClearImageMemoryCache";
     public static final String FEED_HEADER_STICK_TO_TOP = "FeedHeaderStickToTop";
+    public static final String FEED_CLIENT_GOOD_VISITS = "FeedClientGoodVisits";
     public static final String FEED_IMAGE_MEMORY_CACHE_SIZE_PERCENTAGE =
             "FeedImageMemoryCacheSizePercentage";
     public static final String FEED_INTERACTIVE_REFRESH = "FeedInteractiveRefresh";
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/accessibility_common/BUILD.gn
index d6f622a..ef30dd2 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/BUILD.gn
@@ -53,7 +53,9 @@
     "dictation/metrics_utils.js",
     "dictation/parse/input_text_strategy.js",
     "dictation/parse/parse_strategy.js",
+    "dictation/parse/pumpkin/pumpkin_constants.js",
     "dictation/parse/pumpkin_parse_strategy.js",
+    "dictation/parse/sandboxed_pumpkin_tagger.js",
     "dictation/parse/simple_parse_strategy.js",
     "dictation/parse/speech_parser.js",
     "dictation/ui_controller.js",
@@ -69,7 +71,10 @@
   testonly = true
   assert(enable_extensions)
 
-  deps = [ ":accessibility_common_extjs_tests" ]
+  deps = [
+    ":accessibility_common_extjs_tests",
+    ":pumpkin_test_files",
+  ]
 
   data = [
     "$root_out_dir/chrome_100_percent.pak",
@@ -99,6 +104,7 @@
     "dictation/locale_info_test.js",
     "dictation/macros/dictation_macros_test.js",
     "dictation/parse/dictation_parse_test.js",
+    "dictation/parse/dictation_pumpkin_parse_test.js",
     "magnifier/magnifier_test.js",
   ]
   gen_include_files = [
@@ -234,7 +240,9 @@
   sources = [
     "dictation/parse/input_text_strategy.js",
     "dictation/parse/parse_strategy.js",
+    "dictation/parse/pumpkin/pumpkin_constants.js",
     "dictation/parse/pumpkin_parse_strategy.js",
+    "dictation/parse/sandboxed_pumpkin_tagger.js",
     "dictation/parse/simple_parse_strategy.js",
     "dictation/parse/speech_parser.js",
   ]
@@ -243,6 +251,7 @@
     ":dictation_locale_info",
     ":dictation_macros",
   ]
+  externs_list = [ "$externs_path/accessibility_private.js" ]
 }
 
 js_library("dictation_metrics") {
@@ -270,3 +279,25 @@
 js_library("dictation_locale_info") {
   sources = [ "dictation/locale_info.js" ]
 }
+
+action("pumpkin_test_files") {
+  testonly = true
+
+  pumpkin_output_dir = "$accessibility_common_dir/dictation/parse/pumpkin"
+  script = "dictation/parse/pumpkin/unpack_pumpkin.py"
+  sources = [ "dictation/parse/pumpkin/pumpkin-2.0.tar.xz" ]
+  files_to_extract = [
+    "$pumpkin_output_dir/js_pumpkin_tagger_bin.js",
+    "$pumpkin_output_dir/tagger_wasm_main.js",
+    "$pumpkin_output_dir/tagger_wasm_main.wasm",
+    "$pumpkin_output_dir/en_us/action_config.binarypb",
+    "$pumpkin_output_dir/en_us/pumpkin_config.binarypb",
+  ]
+  args = [
+    "--dest-dir=resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin",
+    rebase_path("dictation/parse/pumpkin/pumpkin-2.0.tar.xz", root_build_dir),
+    string_join(",", files_to_extract),
+    pumpkin_output_dir,
+  ]
+  outputs = files_to_extract
+}
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/dictation_pumpkin_parse_test.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/dictation_pumpkin_parse_test.js
new file mode 100644
index 0000000..84cb77c
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/dictation_pumpkin_parse_test.js
@@ -0,0 +1,145 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+GEN_INCLUDE(['../dictation_test_base.js']);
+
+/** A class that represents a test case for parsing text. */
+class ParseTestCase {
+  /**
+   * @param {string} text The text to be parsed
+   * @param {string|undefined} expectedMacroName The expected name of the
+   *     resulting macro.
+   * @param {number|undefined} expectedRepeat The expected repeat value of the
+   *     resulting macro.
+   * @constructor
+   */
+  constructor(text, expectedMacroName, expectedRepeat) {
+    /** @type {string} */
+    this.text = text;
+    /** @type {string|undefined} */
+    this.expectedMacroName = expectedMacroName;
+    /** @type {number|undefined} */
+    this.expectedRepeat = expectedRepeat;
+  }
+}
+
+/**
+ * Dictation tests for speech parsing with Pumpkin. These tests do not use the
+ * live Pumpkin DLC, but instead use a local tar archive that mirrors the DLC.
+ * It's important that we keep the live DLC and the local tar archive in sync.
+ * SandboxedPumpkinTagger emits several logs during the initialization
+ * phase e.g. "Pumpkin module loaded". Setup this test so that it doesn't
+ * fail when something is logged to the console.
+ * TODO(https://crbug.com/1258190): Remove DictationE2ETestAllowConsole and
+ * override the message filter so that wasm console messages don't cause the
+ * test to fail.
+ */
+DictationPumpkinParseTest = class extends DictationE2ETestAllowConsole {
+  /** @override */
+  async setUpDeferred() {
+    this.mockAccessibilityPrivate.enableFeatureForTest(
+        'dictationPumpkinParsing', true);
+    await this.mockAccessibilityPrivate.initializePumpkinData();
+    // Re-initialize PumpkinParseStrategy after mock Pumpkin data has been
+    // created.
+    this.getPumpkinParseStrategy().init_();
+    await importModule(
+        'SpeechParser',
+        '/accessibility_common/dictation/parse/speech_parser.js');
+
+    await super.setUpDeferred();
+  }
+
+  /**
+   * @return {!Promise}
+   * @private
+   */
+  async waitForPumpkinParseStrategy_() {
+    const strategy = this.getPumpkinParseStrategy();
+    // TODO(crbug.com/1258190): Consider adding an observer or callback and
+    // remove the polling below.
+    return new Promise(resolve => {
+      const intervalId = setInterval(() => {
+        if (strategy.pumpkinTaggerReady_) {
+          clearInterval(intervalId);
+          resolve();
+        }
+      }, 300);
+    });
+  }
+
+  /**
+   * @param {!ParseTestCase} testCase
+   * @return {!Promise}
+   */
+  async runParseTestCase(testCase) {
+    const expectedMacroName = testCase.expectedMacroName;
+    const expectedRepeat = testCase.expectedRepeat;
+    const macro = await this.getPumpkinParseStrategy().parse(testCase.text);
+    if (!macro) {
+      assertEquals(undefined, expectedMacroName);
+      assertEquals(undefined, expectedRepeat);
+      return;
+    }
+
+    if (expectedMacroName) {
+      assertEquals(expectedMacroName, macro.getMacroNameString());
+    }
+    if (expectedRepeat) {
+      assertEquals(expectedRepeat, macro.repeat_);
+    }
+  }
+};
+
+// Tests that we can use the SandboxedPumpkinTagger to convert speech into a
+// macro. The text to macro mapping can be found in
+// google3/chrome/chromeos/accessibility/dictation/grammars/\
+// dictation_en_us.patterns
+AX_TEST_F('DictationPumpkinParseTest', 'Parse', async function() {
+  await this.waitForPumpkinParseStrategy_();
+
+  /** @type {!Array<!ParseTestCase>} */
+  const testCases = [
+    new ParseTestCase('Hello world'),
+    new ParseTestCase('dictate delete', 'INPUT_TEXT_VIEW'),
+    new ParseTestCase('backspace', 'DELETE_PREV_CHAR'),
+    new ParseTestCase('left one character', 'NAV_PREV_CHAR'),
+    new ParseTestCase('right one character', 'NAV_NEXT_CHAR'),
+    new ParseTestCase('up one line', 'NAV_PREV_LINE'),
+    new ParseTestCase('down one line', 'NAV_NEXT_LINE'),
+    new ParseTestCase('copy selected text', 'COPY_SELECTED_TEXT'),
+    new ParseTestCase('paste copied text', 'PASTE_TEXT'),
+    new ParseTestCase('cut highlighted text', 'CUT_SELECTED_TEXT'),
+    new ParseTestCase('undo that', 'UNDO_TEXT_EDIT'),
+    new ParseTestCase('redo that', 'REDO_ACTION'),
+    new ParseTestCase('select everything', 'SELECT_ALL_TEXT'),
+    new ParseTestCase('deselect selection', 'UNSELECT_TEXT'),
+    new ParseTestCase('what can I say', 'LIST_COMMANDS'),
+    new ParseTestCase('new line'),
+  ];
+
+  for (const test of testCases) {
+    await this.runParseTestCase(test);
+  }
+});
+
+// Tests that we can use the SandboxedPumpkinTagger to parse text and yield
+// a RepeatableKeyPressMacro with a `repeat_` value greater than one.
+AX_TEST_F(
+    'DictationPumpkinParseTest', 'RepeatableKeyPressMacro', async function() {
+      await this.waitForPumpkinParseStrategy_();
+
+      /** @type {!Array<!ParseTestCase>} */
+      const testCases = [
+        new ParseTestCase('remove two characters', 'DELETE_PREV_CHAR', 2),
+        new ParseTestCase('left five characters', 'NAV_PREV_CHAR', 5),
+      ];
+
+      for (const test of testCases) {
+        await this.runParseTestCase(test);
+      }
+    });
+
+// TODO(https://crbug.com/1258190): Add test cases for when Dictation is in
+// another en-* locale (e.g. en-GB).
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/pumpkin-2.0.tar.xz b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/pumpkin-2.0.tar.xz
new file mode 100644
index 0000000..33eff10381
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/pumpkin-2.0.tar.xz
Binary files differ
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/pumpkin_constants.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/pumpkin_constants.js
new file mode 100644
index 0000000..25b20c3f
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/pumpkin_constants.js
@@ -0,0 +1,127 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Defines constants used for Pumpkin.
+ */
+
+/**
+ * The sandbox doesn't have access to extension APIs, so we need to keep a copy
+ * of the PumpkinData typedef. Copied from
+ * third_party/closure_compiler/externs/accessibility_private.js
+ * TODO(crbug.com/1258190): Consider creating a python script that would pull
+ * this definition in at build time.
+ * @typedef {{
+ *   js_pumpkin_tagger_bin_js: ArrayBuffer,
+ *   tagger_wasm_main_js: ArrayBuffer,
+ *   tagger_wasm_main_wasm: ArrayBuffer,
+ *   en_us_action_config_binarypb: ArrayBuffer,
+ *   en_us_pumpkin_config_binarypb: ArrayBuffer,
+ *   fr_fr_action_config_binarypb: ArrayBuffer,
+ *   fr_fr_pumpkin_config_binarypb: ArrayBuffer,
+ *   it_it_action_config_binarypb: ArrayBuffer,
+ *   it_it_pumpkin_config_binarypb: ArrayBuffer,
+ *   de_de_action_config_binarypb: ArrayBuffer,
+ *   de_de_pumpkin_config_binarypb: ArrayBuffer,
+ *   es_es_action_config_binarypb: ArrayBuffer,
+ *   es_es_pumpkin_config_binarypb: ArrayBuffer
+ * }}
+ */
+export let PumpkinData;
+
+/**
+ * The types of commands that can come from SandboxedPumpkinTagger.
+ * @enum {string}
+ */
+export const FromPumpkinTaggerCommand = {
+  READY: 'ready',
+  FULLY_INITIALIZED: 'fullyInitialized',
+  TAG_RESULTS: 'tagResults',
+};
+
+/**
+ * The types of commands that can be sent to SandboxedPumpkinTagger.
+ * @enum {string}
+ */
+export const ToPumpkinTaggerCommand = {
+  LOAD: 'load',
+  TAG: 'tagAndGetNBestHypotheses',
+};
+
+/**
+ * Defines the message data received from SandboxedPumpkinTagger.
+ * @typedef {{
+ *  results: (!Object|null|undefined),
+ *  type: !FromPumpkinTaggerCommand,
+ * }}
+ */
+export let FromPumpkinTagger;
+
+/**
+ * Defines the message data sent to SandboxedPumpkinTagger.
+ * @typedef {{
+ *  locale: (!PumpkinLocale|undefined),
+ *  numResults: (number|undefined),
+ *  pumpkinData: (!PumpkinData|null|undefined),
+ *  text: (string|undefined),
+ *  type: !ToPumpkinTaggerCommand,
+ * }}
+ */
+export let ToPumpkinTagger;
+
+/**
+ * Supported Pumpkin locales.
+ * @enum {string}
+ */
+export const PumpkinLocale = {
+  EN_US: 'en_us',
+  FR_FR: 'fr_fr',
+  IT_IT: 'it_it',
+  DE_DE: 'de_de',
+  ES_ES: 'es_es',
+};
+
+/**
+ * Map from BCP-47 locale code (see dictation.cc) to directory name in
+ * dictation/parse/pumpkin/ for supported Pumpkin locales.
+ * TODO(crbug.com/1264544): Determine if all en* languages can be mapped to
+ * en_us. Possible locales are listed in dictation.cc,
+ * kWebSpeechSupportedLocales.
+ * TODO(https://crbug.com/1258190): Add mappings for other locales supported by
+ * Pumpkin.
+ * @const {!Object<string, PumpkinLocale>}
+ */
+export const SUPPORTED_LOCALES = {
+  'en-US': PumpkinLocale.EN_US,
+  'en-AU': PumpkinLocale.EN_US,
+  'en-CA': PumpkinLocale.EN_US,
+  'en-GB': PumpkinLocale.EN_US,
+  'en-GH': PumpkinLocale.EN_US,
+  'en-HK': PumpkinLocale.EN_US,
+  'en-IN': PumpkinLocale.EN_US,
+  'en-KE': PumpkinLocale.EN_US,
+  'en-NG': PumpkinLocale.EN_US,
+  'en-NZ': PumpkinLocale.EN_US,
+  'en-PH': PumpkinLocale.EN_US,
+  'en-PK': PumpkinLocale.EN_US,
+  'en-SG': PumpkinLocale.EN_US,
+  'en-TZ': PumpkinLocale.EN_US,
+  'en-ZA': PumpkinLocale.EN_US,
+};
+
+/**
+ * PumpkinTagger Hypothesis argument names. These should match the variable
+ * argument placeholders in voiceaccess.patterns_template and the static strings
+ * defined in voiceaccess/utils/PumpkinUtils.java in google3.
+ * @enum {string}
+ */
+export const HypothesisArgumentName = {
+  SEM_TAG: 'SEM_TAG',
+  NUM_ARG: 'NUM_ARG',
+  OPEN_ENDED_TEXT: 'OPEN_ENDED_TEXT',
+};
+
+/** @const {string} */
+export const SANDBOXED_PUMPKIN_TAGGER_JS_FILE =
+    'dictation/parse/sandboxed_pumpkin_tagger.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/unpack_pumpkin.py b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/unpack_pumpkin.py
new file mode 100755
index 0000000..593337c8
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/unpack_pumpkin.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import optparse
+import os
+import shutil
+import sys
+import tarfile
+
+# A python script that unpacks the pumpkin tar archive by:
+# 1. Extracting the tar into a temporary directory
+# 2. Copying the file contents from the temporary directory to the destination
+# directory
+# 3. Removing the temporary directory
+#
+# We need to do the extraction indirectly because of:
+# 1. how tarfile.extractall() works
+# 2. how ninja determines dirty/stale objects
+#
+# tarfile.extractall() hits errors if the extracted file already exists.
+# If we wanted to extract directly into the destination directory, we'd need to
+# clear the directory first. However, removing and creating new directories
+# within this script would change object timestamps without ninja's
+# knowledge. This would cause ninja to always think the pumpkin test files are
+# out of date, and thus this script would run each time there is a build
+# request, even if there is no work necessary. This is all important because the
+# CQ builds all targets, then triggers the same build again and asserts that
+# it was a no-op. Without this indirect extraction, we'd fail the CQ every time.
+
+def main():
+  parser = optparse.OptionParser(description=__doc__)
+  parser.usage = '%prog [options] <tar-file_path>'
+  parser.add_option(
+      '--dest-dir',
+      action='store',
+      metavar='DEST_DIR',
+      help='Destination directory for extracted files.')
+  options, args = parser.parse_args()
+  if len(args) < 1 or not options.dest_dir:
+      print(
+          'Expected --dest-dir and the tar archive to unpack.',
+          file=sys.stderr)
+      print(str(args))
+      sys.exit(1)
+
+  tarArchive = args[0]
+  outputFiles = args[1].split(',')
+  pumpkinOutputDir = args[2];
+  destDir = options.dest_dir
+  tempDir = os.path.join(destDir, 'temp')
+
+  # Remove full path from each output file so that they're relative to
+  # pumpkinOutputDir.
+  for i in range(0, len(outputFiles)):
+    path = outputFiles[i]
+    outputFiles[i] = path.replace(pumpkinOutputDir, "")
+
+  # Extract tar into temporary directory.
+  tar = tarfile.open(tarArchive)
+  tar.extractall(path=tempDir)
+  tar.close()
+
+  # Copy file contents from tempDir to destDir.
+  for file in outputFiles:
+    source = tempDir + file
+    destination = destDir + file
+    shutil.copyfile(source, destination)
+
+  # Remove temporary directory.
+  shutil.rmtree(tempDir)
+
+if __name__ == '__main__':
+  main()
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/unzip_pumpkin.py b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/unzip_pumpkin.py
deleted file mode 100755
index d1b6a14..0000000
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/unzip_pumpkin.py
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2021 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import io
-import optparse
-import sys
-from zipfile import ZipFile
-
-# Unzips archive created with `zip -r ../pumpkin_files.zip *`.
-# Archive structure:
-#   tagger_wasm_main.wasm
-#   tagger_wasm_main.js
-#   js_pumpkin_tagger-bin.js
-#   en_us/action_config.binarypb
-#   en_us/pumpkin_config.binarypb
-
-def UnzipPumpkinFiles(filename, output_dir):
-  with ZipFile(filename, 'r') as zf:
-    zf.extractall(path=output_dir);
-
-def main():
-  parser = optparse.OptionParser(description=__doc__)
-  parser.usage = '%prog [options] <zip-file_path>'
-
-  parser.add_option(
-      '--output-dir',
-      action='store',
-      metavar='OUTPUT_DIR',
-      help='Output directory for extracted files.')
-  options, args = parser.parse_args()
-  if len(args) < 1 or not options.output_dir:
-      print(
-          'Expected --output-dir and the input filename to unzip.',
-          file=sys.stderr)
-      print(str(args))
-      sys.exit(1)
-
-  UnzipPumpkinFiles(args[0], options.output_dir)
-
-if __name__ == '__main__':
-  main()
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin_parse_strategy.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin_parse_strategy.js
index a3e63cf..f4082f2 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin_parse_strategy.js
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin_parse_strategy.js
@@ -16,26 +16,26 @@
 import * as RepeatableKeyPressMacro from '../macros/repeatable_key_press_macro.js';
 
 import {ParseStrategy} from './parse_strategy.js';
-
-const PumpkinData = chrome.accessibilityPrivate.PumpkinData;
+import * as PumpkinConstants from './pumpkin/pumpkin_constants.js';
 
 /** A parsing strategy that utilizes the Pumpkin semantic parser. */
 export class PumpkinParseStrategy extends ParseStrategy {
   /** @param {!InputController} inputController */
   constructor(inputController) {
     super(inputController);
-
-    /** @private {speech.pumpkin.api.js.PumpkinTagger.PumpkinTagger} */
-    this.pumpkinTagger_ = null;
-
-    /** @private {?Promise} */
-    this.pumpkinLoadingPromise_ = null;
-
     /**
      * Whether or not the feature flag gating this object's logic is enabled.
      * @private {boolean}
      */
     this.featureEnabled_ = false;
+    /** @private {?PumpkinConstants.PumpkinData} */
+    this.pumpkinData_ = null;
+    /** @private {boolean} */
+    this.pumpkinTaggerReady_ = false;
+    /** @private {Function} */
+    this.tagResolver_ = null;
+    /** @private {?Worker} */
+    this.worker_ = null;
 
     this.init_();
   }
@@ -46,7 +46,9 @@
                                .DICTATION_PUMPKIN_PARSING;
     chrome.accessibilityPrivate.isFeatureEnabled(pumpkinFeature, enabled => {
       this.featureEnabled_ = enabled;
-      if (!enabled) {
+      const pumpkinLocale =
+          PumpkinConstants.SUPPORTED_LOCALES[LocaleInfo.locale];
+      if (!enabled || !pumpkinLocale) {
         return;
       }
 
@@ -57,7 +59,7 @@
   }
 
   /**
-   * @param {PumpkinData} data
+   * @param {PumpkinConstants.PumpkinData} data
    * @private
    */
   onPumpkinInstalled_(data) {
@@ -69,104 +71,75 @@
     }
 
     for (const [key, value] of Object.entries(data)) {
-      if (value.byteLength === 0) {
-        console.warn(`Pumpkin data incomplete, missing data for ${key}`);
-        return;
+      if (!value || value.byteLength === 0) {
+        throw new Error(`Pumpkin data incomplete, missing data for ${key}`);
       }
     }
 
-    // TODO(akihiroota): Instantiate a sandboxed iframe for Pumpkin.
-  }
-
-  /**
-   * Initializes Pumpkin by loading the required scripts and creating the
-   * PumpkinTagger object.
-   * @param {string} locale The locale in which to init Pumpkin actions.
-   * @return {!Promise<undefined>}
-   * @private
-   */
-  async initPumpkin_(locale) {
-    if (this.pumpkinLoadingPromise_) {
-      // Already initializing.
+    const pumpkinLocale = PumpkinConstants.SUPPORTED_LOCALES[LocaleInfo.locale];
+    if (!pumpkinLocale || !this.isEnabled()) {
       return;
     }
 
-    this.pumpkinLoadingPromise_ =
-        new Promise(async (pumpkinLoadResolve, pumpkinLoadReject) => {
-          // Check for objects defined by the Pumpkin WASM.
-          if (!goog || !goog['global'] || !goog['global']['Module']) {
-            await this.loadPumpkinScripts_();
-          }
-          const success = await this.createPumpkinTagger_(locale);
-          if (success) {
-            pumpkinLoadResolve();
-          } else {
-            pumpkinLoadReject();
-          }
+    // Create SandboxedPumpkinTagger.
+    this.pumpkinTaggerReady_ = false;
+    this.pumpkinData_ = data;
+
+    this.worker_ = new Worker(
+        PumpkinConstants.SANDBOXED_PUMPKIN_TAGGER_JS_FILE, {type: 'module'});
+    this.worker_.onmessage = (message) => this.onMessage_(message);
+  }
+
+  /**
+   * Called when the SandboxedPumpkinTagger posts a message to the background
+   * context.
+   * @param {!Event} message
+   * @private
+   */
+  onMessage_(message) {
+    const command =
+        /** @type {!PumpkinConstants.FromPumpkinTagger} */ (message.data);
+    switch (command.type) {
+      case PumpkinConstants.FromPumpkinTaggerCommand.READY:
+        const pumpkinLocale =
+            PumpkinConstants.SUPPORTED_LOCALES[LocaleInfo.locale];
+        if (!pumpkinLocale) {
+          throw new Error(
+              `Can't load SandboxedPumpkinTagger in an unsupported locale ${
+                  LocaleInfo.locale}`);
+        }
+
+        this.sendToSandboxedPumpkinTagger_({
+          type: PumpkinConstants.ToPumpkinTaggerCommand.LOAD,
+          locale: pumpkinLocale,
+          pumpkinData: this.pumpkinData_,
         });
-  }
-
-  /**
-   * Creates a PumpkinTagger from a config and action frame file for a
-   * particular locale.
-   * @param {string} locale The locale in which to init Pumpkin actions.
-   * @return {!Promise<boolean>} Whether the tagger was created successfully.
-   * @private
-   */
-  async createPumpkinTagger_(locale) {
-    const pumpkinTagger =
-        new speech.pumpkin.api.js.PumpkinTagger.PumpkinTagger();
-    try {
-      const path = `${PumpkinParseStrategy.PUMPKIN_DIR}${locale}/`;
-      let success = await pumpkinTagger.initializeFromPumpkinConfig(
-          `${path}${PumpkinParseStrategy.PUMPKIN_CONFIG_PROTO_SRC}`);
-      if (!success) {
-        console.warn('Failed to load PumpkinTagger from PumpkinConfig.');
-        return false;
-      }
-      success = await pumpkinTagger.loadActionFrame(
-          `${path}${PumpkinParseStrategy.PUMPKIN_ACTION_CONFIG_PROTO_SRC}`);
-      if (!success) {
-        console.warn('Failed to load Pumpkin ActionConfig.');
-        return false;
-      }
-    } catch (e) {
-      console.warn('Error initializing PumpkinTagger', e);
-      return false;
+        this.pumpkinData_ = null;
+        return;
+      case PumpkinConstants.FromPumpkinTaggerCommand.FULLY_INITIALIZED:
+        this.pumpkinTaggerReady_ = true;
+        return;
+      case PumpkinConstants.FromPumpkinTaggerCommand.TAG_RESULTS:
+        this.tagResolver_(command.results);
+        return;
     }
-    this.pumpkinTagger_ = pumpkinTagger;
-    return true;
+
+    throw new Error(
+        `Unrecognized message received from SandboxedPumpkinTagger: ${
+            command.type}`);
   }
 
   /**
-   * Loads the Pumpkin scripts javascript in to the document.
-   * @return {!Promise<undefined>}
+   * @param {!PumpkinConstants.ToPumpkinTagger} command
    * @private
    */
-  async loadPumpkinScripts_() {
-    const pumpkinTaggerScript =
-        /** @type {!HTMLScriptElement} */ (document.createElement('script'));
-    pumpkinTaggerScript.src = PumpkinParseStrategy.PUMPKIN_TAGGER_SRC;
-    const taggerLoadPromise = new Promise((resolve, reject) => {
-      pumpkinTaggerScript.addEventListener('load', () => {
-        resolve();
-      });
-    });
-    document.head.appendChild(pumpkinTaggerScript);
-    await taggerLoadPromise;
+  sendToSandboxedPumpkinTagger_(command) {
+    if (!this.worker_) {
+      throw new Error(
+          'Worker not ready, cannot send message to SandboxedPumpkinTagger');
+    }
 
-    const wasmModuleScript =
-        /** @type {!HTMLScriptElement} */ (document.createElement('script'));
-    wasmModuleScript.src = PumpkinParseStrategy.PUMPKIN_WASM_SRC;
-    const moduleLoadPromise = new Promise((resolve, reject) => {
-      goog['global']['Module'] = {
-        onRuntimeInitialized() {
-          resolve();
-        },
-      };
-    });
-    document.head.appendChild(wasmModuleScript);
-    await moduleLoadPromise;
+    this.worker_.postMessage(command);
   }
 
   /**
@@ -187,21 +160,21 @@
     for (let i = 0; i < numArgs; i++) {
       const argument = hypothesis.actionArgumentList[i];
       // See Variable Argument Placeholders in voiceaccess.patterns_template.
-      if (argument.name ===
-          PumpkinParseStrategy.HypothesisArgumentName.SEM_TAG) {
+      if (argument.name === PumpkinConstants.HypothesisArgumentName.SEM_TAG) {
         tag = MacroName[argument.value];
       } else if (
-          argument.name ===
-          PumpkinParseStrategy.HypothesisArgumentName.NUM_ARG) {
+          argument.name === PumpkinConstants.HypothesisArgumentName.NUM_ARG) {
         repeat = argument.value;
       } else if (
           argument.name ===
-          PumpkinParseStrategy.HypothesisArgumentName.OPEN_ENDED_TEXT) {
+          PumpkinConstants.HypothesisArgumentName.OPEN_ENDED_TEXT) {
         text = argument.value;
       }
     }
+
     // TODO(crbug.com/1362842) Add all macros under the DictationMoreCommands
     // to this switch statement.
+    // TODO(crbug.com/1258190): Add support for new macros here.
     switch (tag) {
       case MacroName.INPUT_TEXT_VIEW:
         return new InputTextViewMacro(text, this.getInputController());
@@ -241,85 +214,44 @@
   }
 
   /** @override */
+  refresh() {
+    const pumpkinLocale = PumpkinConstants.SUPPORTED_LOCALES[LocaleInfo.locale];
+    this.enabled = Boolean(pumpkinLocale) && LocaleInfo.areCommandsSupported();
+    // TODO(https://crbug.com/1258190): Re-initialize SandboxedPumpkinTagger
+    // if the locale changed.
+  }
+
+  /** @override */
   async parse(text) {
-    // Pumpkin load requires several async calls. If the request to parse
-    // comes before load is complete, wait for load. This happens during
-    // browser tests which may be fast enough to start sending speech text
-    // before callbacks with user prefs have completed.
-    if (this.pumpkinLoadingPromise_) {
-      await this.pumpkinLoadingPromise_;
+    if (!this.isEnabled() || !this.pumpkinTaggerReady_) {
+      return null;
     }
 
-    // Try to get results from Pumpkin.
+    this.tagResolver_ = null;
+    // Get results from Pumpkin.
     // TODO(crbug.com/1264544): Could increase the hypotheses count from 1
     // when we are ready to implement disambiguation.
-    if (this.pumpkinTagger_) {
-      // Try to get results from Pumpkin.
-      // TODO(crbug.com/1264544): Could increase the hypotheses count from 1
-      // when we are ready to implement disambiguation.
-      // TODO(crbug.com/1362842) Add logic to check whether
-      // DictationMoreCommands is enabled or not before
-      // running the macros hidden within that flag.
-      const taggerResults =
-          this.pumpkinTagger_.tagAndGetNBestHypotheses(text, 1);
-      if (taggerResults && taggerResults.hypothesisList.length > 0) {
-        const macro =
-            this.macroFromPumpkinHypothesis_(taggerResults.hypothesisList[0]);
-        if (macro) {
-          return macro;
-        }
-      }
+    // TODO(crbug.com/1362842) Add logic to check whether
+    // DictationMoreCommands is enabled or not before
+    // running the macros hidden within that flag.
+    this.sendToSandboxedPumpkinTagger_({
+      type: PumpkinConstants.ToPumpkinTaggerCommand.TAG,
+      text,
+      numResults: 1,
+    });
+    const taggerResults = await new Promise(resolve => {
+      this.tagResolver_ = resolve;
+    });
+
+    if (!taggerResults || taggerResults.hypothesisList.length === 0) {
+      return null;
     }
 
-    return null;
+    return this.macroFromPumpkinHypothesis_(taggerResults.hypothesisList[0]);
+  }
+
+  /** @override */
+  isEnabled() {
+    return this.enabled && this.featureEnabled_;
   }
 }
-
-/**
- * PumpkinTagger Hypothesis argument names. These should match the variable
- * argument placeholders in voiceaccess.patterns_template and the static strings
- * defined in voiceaccess/utils/PumpkinUtils.java in google3.
- * @enum {string}
- */
-PumpkinParseStrategy.HypothesisArgumentName = {
-  SEM_TAG: 'SEM_TAG',
-  NUM_ARG: 'NUM_ARG',
-  OPEN_ENDED_TEXT: 'OPEN_ENDED_TEXT',
-};
-
-/**
- * The pumpkin/ directory, relative to the accessibility common base directory.
- * @type {string}
- * @const
- */
-PumpkinParseStrategy.PUMPKIN_DIR = 'dictation/parse/pumpkin/';
-
-/**
- * The path to the pumpkin tagger source file.
- * @type {string}
- * @const
- */
-PumpkinParseStrategy.PUMPKIN_TAGGER_SRC =
-    PumpkinParseStrategy.PUMPKIN_DIR + 'js_pumpkin_tagger_bin.js';
-
-/**
- * The path to the pumpkin web assembly module source file.
- * @type {string}
- * @const
- */
-PumpkinParseStrategy.PUMPKIN_WASM_SRC =
-    PumpkinParseStrategy.PUMPKIN_DIR + 'tagger_wasm_main.js';
-
-/**
- * The name of the pumpkin config binary proto file.
- * @type {string}
- * @const
- */
-PumpkinParseStrategy.PUMPKIN_CONFIG_PROTO_SRC = 'pumpkin_config.binarypb';
-
-/**
- * The name of the pumpkin action config binary proto file.
- * @type {string}
- * @const
- */
-PumpkinParseStrategy.PUMPKIN_ACTION_CONFIG_PROTO_SRC = 'action_config.binarypb';
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/sandboxed_pumpkin_tagger.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/sandboxed_pumpkin_tagger.js
new file mode 100644
index 0000000..fdb1d93a
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/sandboxed_pumpkin_tagger.js
@@ -0,0 +1,179 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import * as PumpkinConstants from './pumpkin/pumpkin_constants.js';
+
+/**
+ * A class that unpacks and loads the pumpkin semantic parser. Runs in a
+ * web worker for security purposes.
+ */
+class SandboxedPumpkinTagger {
+  constructor() {
+    /** @private {?speech.pumpkin.api.js.PumpkinTagger.PumpkinTagger} */
+    this.pumpkinTagger_ = null;
+    this.init_();
+  }
+
+  /** @private */
+  init_() {
+    globalThis.addEventListener(
+        'message', (message) => this.onMessage_(message));
+    this.sendToBackground_(
+        {type: PumpkinConstants.FromPumpkinTaggerCommand.READY});
+  }
+
+  /**
+   * Called when the background context posts a message to
+   * SandboxedPumpkinTagger's web worker.
+   * @param {!Event} message
+   * @private
+   */
+  onMessage_(message) {
+    const command =
+        /** @type {!PumpkinConstants.ToPumpkinTagger} */ (message.data);
+    switch (command.type) {
+      case PumpkinConstants.ToPumpkinTaggerCommand.LOAD:
+        const pumpkinData =
+            /** @type {!PumpkinConstants.PumpkinData} */ (command.pumpkinData);
+        const locale =
+            /** @type {!PumpkinConstants.PumpkinLocale} */ (command.locale);
+        this.load_(pumpkinData, locale);
+        return;
+      case PumpkinConstants.ToPumpkinTaggerCommand.TAG:
+        const text = /** @type {string} */ (command.text);
+        const numResults = /** @type {number} */ (command.numResults);
+        this.tagAndGetNBestHypotheses_(text, numResults);
+        return;
+    }
+
+    throw new Error(`Unrecognized message received in SandboxedPumpkinTagger: ${
+        command.type}`);
+  }
+
+  /**
+   * @param {!PumpkinConstants.FromPumpkinTagger} command
+   * @private
+   */
+  sendToBackground_(command) {
+    postMessage(command);
+  }
+
+  /**
+   * @param {string} text
+   * @param {number} numResults
+   * @private
+   */
+  tagAndGetNBestHypotheses_(text, numResults) {
+    const results =
+        this.pumpkinTagger_.tagAndGetNBestHypotheses(text, numResults);
+    this.sendToBackground_(
+        {type: PumpkinConstants.FromPumpkinTaggerCommand.TAG_RESULTS, results});
+  }
+
+  /**
+   * @param {!PumpkinConstants.PumpkinData} data
+   * @param {!PumpkinConstants.PumpkinLocale} locale
+   * @private
+   */
+  async load_(data, locale) {
+    if (!data) {
+      throw new Error(`Can't load pumpkin tagger from empty data`);
+    }
+
+    // Unpack the PumpkinTagger JS.
+    const pumpkinTaggerBytes = data.js_pumpkin_tagger_bin_js;
+    if (!pumpkinTaggerBytes) {
+      throw new Error(`Pumpkin tagger bytes must be valid`);
+    }
+    const pumpkinTaggerFile = new TextDecoder().decode(pumpkinTaggerBytes);
+    // Use indirect eval here to ensure the script works in the global scope.
+    const indirectEval = eval;
+    const pumpkinTaggerModule = indirectEval(pumpkinTaggerFile);
+    if (!pumpkinTaggerModule) {
+      throw new Error('Failed to eval pumpkin tagger file');
+    }
+    /**
+     * Closure can't recognize pumpkinTaggerModule as a constructor, so suppress
+     * the error.
+     * @suppress {checkTypes}
+     */
+    const pumpkinTagger = new pumpkinTaggerModule();
+
+    // The `taggerWasmJsFile` below expects that the corresponding .wasm file
+    // lives in the same directory as it. However, since none of these files
+    // live in the extension directory, we need to override the fetch method
+    // so that it returns the wasm file bytes from `data` when requested.
+    globalThis.fetch = async (fileName) => {
+      return new Promise(resolve => {
+        const response = new Response(null, {
+          ok: true,
+          status: 200,
+        });
+        response.arrayBuffer = async () => {
+          return new Promise(resolve => {
+            resolve(data.tagger_wasm_main_wasm);
+          });
+        };
+        resolve(response);
+      });
+    };
+
+    const taggerWasmBytes = data.tagger_wasm_main_js;
+    if (!taggerWasmBytes) {
+      throw new Error(`Pumpkin wasm bytes must be valid`);
+    }
+    const taggerWasmJsFile = new TextDecoder().decode(taggerWasmBytes);
+    // A promise that resolves once the web assembly module loads.
+    const wasmLoadPromise = new Promise((resolve) => {
+      goog['global']['Module'] = {
+        onRuntimeInitialized() {
+          resolve();
+        },
+      };
+    });
+    // Load the web assembly.
+    // Use indirect eval here to ensure the script works in the global scope.
+    indirectEval(taggerWasmJsFile);
+    await wasmLoadPromise;
+
+    // Initialize from config files.
+    let pumpkinConfig;
+    let actionConfig;
+    switch (locale) {
+      case PumpkinConstants.PumpkinLocale.EN_US:
+        pumpkinConfig = data.en_us_pumpkin_config_binarypb;
+        actionConfig = data.en_us_action_config_binarypb;
+        break;
+      case PumpkinConstants.PumpkinLocale.FR_FR:
+        pumpkinConfig = data.fr_fr_pumpkin_config_binarypb;
+        actionConfig = data.fr_fr_action_config_binarypb;
+        break;
+      case PumpkinConstants.PumpkinLocale.IT_IT:
+        pumpkinConfig = data.it_it_pumpkin_config_binarypb;
+        actionConfig = data.it_it_action_config_binarypb;
+        break;
+      case PumpkinConstants.PumpkinLocale.DE_DE:
+        pumpkinConfig = data.de_de_pumpkin_config_binarypb;
+        actionConfig = data.de_de_action_config_binarypb;
+        break;
+      case PumpkinConstants.PumpkinLocale.ES_ES:
+        pumpkinConfig = data.es_es_pumpkin_config_binarypb;
+        actionConfig = data.es_es_action_config_binarypb;
+        break;
+      default:
+        throw new Error(
+            `Can't initialize Pumpkin in unsupported locale: ${locale}`);
+    }
+
+    pumpkinTagger.initializeFromPumpkinConfig(pumpkinConfig);
+    pumpkinTagger.loadActionFrame(actionConfig);
+
+    // Save the PumpkinTagger and notify the background context.
+    this.pumpkinTagger_ = pumpkinTagger;
+    this.sendToBackground_(
+        {type: PumpkinConstants.FromPumpkinTaggerCommand.FULLY_INITIALIZED});
+  }
+}
+
+new SandboxedPumpkinTagger();
diff --git a/chrome/browser/resources/chromeos/accessibility/common/testing/mock_accessibility_private.js b/chrome/browser/resources/chromeos/accessibility/common/testing/mock_accessibility_private.js
index ae6602d..3f3460ef 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/testing/mock_accessibility_private.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/testing/mock_accessibility_private.js
@@ -12,6 +12,17 @@
  */
 let SelectToSpeakPanelState;
 
+/**
+ * @typedef {{
+ *  js_pumpkin_tagger_bin_js: !ArrayBuffer,
+ *  tagger_wasm_main_js: !ArrayBuffer,
+ *  tagger_wasm_main_wasm: !ArrayBuffer,
+ *  en_us_action_config_binarypb: !ArrayBuffer,
+ *  en_us_pumpkin_config_binarypb: !ArrayBuffer,
+ * }}
+ */
+let MockPumpkinData;
+
 /*
  * A mock AccessibilityPrivate API for tests.
  */
@@ -48,6 +59,9 @@
     /** @private {function<number, number>} */
     this.boundsListener_ = null;
 
+    /** @private {?MockPumpkinData} */
+    this.pumpkinData_ = null;
+
     /**
      * @private {function(!chrome.accessibilityPrivate.SelectToSpeakPanelAction,
      *     number=)}
@@ -242,6 +256,11 @@
    */
   sendSyntheticKeyEvent(unused) {}
 
+  /** @return {?PumpkinData} */
+  installPumpkinForDictation(callback) {
+    callback(MockAccessibilityPrivate.pumpkinData_);
+  }
+
   // Methods for testing. //
 
   /**
@@ -386,4 +405,34 @@
       this.enabledFeatures_.delete(feature);
     }
   }
+
+  /** @return {!Promise} */
+  async initializePumpkinData() {
+    /**
+     * @param {string} file
+     * @return {!Promise<!ArrayBuffer>}
+     */
+    const getFileBytes = async (file) => {
+      const response = await fetch(file);
+      if (response.status === 404) {
+        throw `Failed to fetch file: ${file}`;
+      }
+
+      return await response.arrayBuffer();
+    };
+
+    const data = {};
+    const pumpkinDir = '../../accessibility_common/dictation/parse/pumpkin';
+    data.js_pumpkin_tagger_bin_js =
+        await getFileBytes(`${pumpkinDir}/js_pumpkin_tagger_bin.js`);
+    data.tagger_wasm_main_js =
+        await getFileBytes(`${pumpkinDir}/tagger_wasm_main.js`);
+    data.tagger_wasm_main_wasm =
+        await getFileBytes(`${pumpkinDir}/tagger_wasm_main.wasm`);
+    data.en_us_action_config_binarypb =
+        await getFileBytes(`${pumpkinDir}/en_us/action_config.binarypb`);
+    data.en_us_pumpkin_config_binarypb =
+        await getFileBytes(`${pumpkinDir}/en_us/pumpkin_config.binarypb`);
+    MockAccessibilityPrivate.pumpkinData_ = data;
+  }
 }
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager.js
index f352deb..35ab707d 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager.js
@@ -16,11 +16,9 @@
 const SelectToSpeakPanelAction =
     chrome.accessibilityPrivate.SelectToSpeakPanelAction;
 
-// This must be the same as in
-// ash/system/accessibility/select_to_speak/select_to_speak_tray.cc:
-// ash::kSelectToSpeakTrayClassName.
-export const SELECT_TO_SPEAK_TRAY_CLASS_NAME =
-    'tray/TrayBackgroundView/SelectToSpeakTray';
+// This must match the name of view class that implements the SelectToSpeakTray:
+// ash/system/accessibility/select_to_speak/select_to_speak_tray.h
+export const SELECT_TO_SPEAK_TRAY_CLASS_NAME = 'SelectToSpeakTray';
 
 // This must match the name of view class that implements the menu view:
 // ash/system/accessibility/select_to_speak/select_to_speak_menu_view.h
diff --git a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html
index 8afc8c9..9be0997 100644
--- a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html
+++ b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.html
@@ -17,6 +17,21 @@
   #networkState[connected] {
     color: var(--cros-text-color-positive);
   }
+
+  #networkState[warning] {
+    color: var(--cros-text-color-warning);
+  }
+
+  .signin-button {
+    margin-inline-end: 8px;
+    padding: 8px 16px 8px 8px;
+  }
+
+  .signin-icon {
+    background-color: var(--text-color);
+    margin-inline-end: 4px;
+    margin-inline-start: 0;
+  }
 </style>
 
 <!-- Title section: Icon + name + connection state. -->
@@ -29,9 +44,18 @@
     [[getNameText_(managedProperties_)]]
   </div>
   <div id="networkState" class="title flex"
-      connected$="[[isConnectedState_(managedProperties_)]]">
+      connected$="[[showConnectedState_(managedProperties_)]]"
+      warning$="[[showRestrictedConnectivity_(managedProperties_)]]">
     [[getStateText_(managedProperties_)]]
   </div>
+  <template is="dom-if" if="[[isCaptivePortalUI2022Enabled_]]">
+    <cr-button class="signin-button" id="signinButton" on-click="onSigninTap_"
+        hidden$="[[!showSignin_(managedProperties_)]]"
+        disabled="[[disableSignin_(managedProperties_, disabled_)]]">
+      <div class="signin-icon cr-icon icon-external"></div>
+      $i18n{networkButtonSignin}
+    </cr-button>
+  </template>
   <cr-button on-click="onForgetTap_"
       hidden$="[[!showForget_(managedProperties_)]]"
       disabled="[[disabled_]]">
diff --git a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js
index 751ce6b..d1245b2 100644
--- a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js
+++ b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog.js
@@ -18,16 +18,16 @@
 import 'chrome://resources/cr_elements/cr_shared_style.css.js';
 import './strings.m.js';
 
+import {I18nBehavior} from 'chrome://resources/ash/common/i18n_behavior.js';
 import {isActiveSim} from 'chrome://resources/ash/common/network/cellular_utils.js';
 import {CrPolicyNetworkBehaviorMojo} from 'chrome://resources/ash/common/network/cr_policy_network_behavior_mojo.js';
 import {MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
 import {NetworkListenerBehavior} from 'chrome://resources/ash/common/network/network_listener_behavior.js';
 import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
-import {I18nBehavior} from 'chrome://resources/ash/common/i18n_behavior.js';
 import {assert} from 'chrome://resources/js/assert.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {ApnProperties, ConfigProperties, CrosNetworkConfigRemote, GlobalPolicy, IPConfigProperties, ManagedProperties, NetworkStateProperties, ProxySettings, StartConnectResult} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
-import {ConnectionStateType, NetworkType, OncSource} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
+import {ConnectionStateType, NetworkType, OncSource, PortalState} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {InternetDetailDialogBrowserProxy, InternetDetailDialogBrowserProxyImpl} from './internet_detail_dialog_browser_proxy.js';
@@ -76,6 +76,17 @@
             loadTimeData.getBoolean('showTechnologyBadge');
       },
     },
+    /**
+     * Return true if captivePortalUI2022 feature flag is enabled.
+     * @private
+     */
+    isCaptivePortalUI2022Enabled_: {
+      type: Boolean,
+      value() {
+        return loadTimeData.valueExists('captivePortalUI2022') &&
+            loadTimeData.getBoolean('captivePortalUI2022');
+      },
+    },
 
     /**
      * Whether network configuration properties sections should be shown. The
@@ -314,6 +325,20 @@
     if (!managedProperties) {
       return '';
     }
+
+    if (this.isCaptivePortalUI2022Enabled_ &&
+        OncMojo.connectionStateIsConnected(managedProperties.connectionState)) {
+      if (this.isPortalState_(managedProperties.portalState)) {
+        return this.i18n('networkListItemSignIn');
+      }
+      if (managedProperties.portalState === PortalState.kPortalSuspected) {
+        return this.i18n('networkListItemConnectedLimited');
+      }
+      if (managedProperties.portalState === PortalState.kNoInternet) {
+        return this.i18n('networkListItemConnectedNoConnectivity');
+      }
+    }
+
     return this.i18n(
         OncMojo.getConnectionStateString(managedProperties.connectionState));
   },
@@ -328,13 +353,61 @@
   },
 
   /**
-   * @param {!ManagedProperties} managedProperties
+   * @param {!ManagedProperties|undefined} managedProperties
    * @return {boolean} True if the network is connected.
    * @private
    */
   isConnectedState_(managedProperties) {
-    return OncMojo.connectionStateIsConnected(
-        managedProperties.connectionState);
+    return !!managedProperties &&
+        OncMojo.connectionStateIsConnected(managedProperties.connectionState);
+  },
+
+  /**
+   * @param {!ManagedProperties|undefined}
+   *     managedProperties
+   * @return {boolean} True if the network is restricted.
+   * @private
+   */
+  isRestrictedConnectivity_(managedProperties) {
+    return !!managedProperties &&
+        OncMojo.isRestrictedConnectivity(managedProperties.portalState);
+  },
+
+  /**
+   * @param {!ManagedProperties|undefined}
+   *     managedProperties
+   * @return {boolean} True if the network is connected to have connected color
+   *     for state.
+   * @private
+   */
+  showConnectedState_(managedProperties) {
+    // Only check that state is connected if feature flag is disabled.
+    if (!this.isCaptivePortalUI2022Enabled_) {
+      return this.isConnectedState_(managedProperties);
+    }
+
+    return this.isConnectedState_(managedProperties) &&
+        !this.isRestrictedConnectivity_(managedProperties);
+  },
+
+  /**
+   * @param {!ManagedProperties|undefined}
+   *     managedProperties
+   * @return {boolean} True if the network is restricted to have warning color
+   *     for state.
+   * @private
+   */
+  showRestrictedConnectivity_(managedProperties) {
+    // Do not show warning color if feature flag is disabled.
+    if (!this.isCaptivePortalUI2022Enabled_) {
+      return false;
+    }
+    if (!managedProperties) {
+      return false;
+    }
+    // State must be connected and restricted.
+    return this.isConnectedState_(managedProperties) &&
+        this.isRestrictedConnectivity_(managedProperties);
   },
 
   /**
@@ -410,6 +483,50 @@
   },
 
   /**
+   * @param {!ManagedProperties|undefined}
+   *     managedProperties
+   * @return {boolean}
+   * @private
+   */
+  showSignin_(managedProperties) {
+    if (!this.isCaptivePortalUI2022Enabled_) {
+      return false;
+    }
+    if (!managedProperties) {
+      return false;
+    }
+    if (OncMojo.connectionStateIsConnected(managedProperties.connectionState) &&
+        this.isPortalState_(managedProperties.portalState)) {
+      return true;
+    }
+    return false;
+  },
+
+  /**
+   * @param {!ManagedProperties} managedProperties
+   * @return {boolean}
+   * @private
+   */
+  disableSignin_(managedProperties) {
+    if (!this.isCaptivePortalUI2022Enabled_) {
+      return true;
+    }
+    if (this.disabled_ || !managedProperties) {
+      return true;
+    }
+    if (!OncMojo.connectionStateIsConnected(
+            managedProperties.connectionState)) {
+      return true;
+    }
+    return !this.isPortalState_(managedProperties.portalState);
+  },
+
+  /** @private */
+  onSigninTap_() {
+    this.browserProxy_.showPortalSignin(this.guid);
+  },
+
+  /**
    * @param {!ManagedProperties} managedProperties
    * @return {string}
    * @private
@@ -662,4 +779,15 @@
     // the dialog's inputs should be disabled.
     return OncMojo.deviceIsInhibited(this.deviceState_);
   },
+
+  /**
+   * Return true if portalState is either kPortal or kProxyAuthRequired.
+   * @param {!PortalState} portalState
+   * @return {boolean}
+   * @private
+   */
+  isPortalState_(portalState) {
+    return portalState === PortalState.kPortal ||
+        portalState === PortalState.kProxyAuthRequired;
+  },
 });
diff --git a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog_browser_proxy.js b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog_browser_proxy.js
index cf49d628..59ad1de8 100644
--- a/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog_browser_proxy.js
+++ b/chrome/browser/resources/chromeos/internet_detail_dialog/internet_detail_dialog_browser_proxy.js
@@ -21,6 +21,12 @@
    * Signals C++ that the dialog is closed.
    */
   closeDialog() {}
+
+  /**
+   * Shows the Portal Signin.
+   * @param {string} guid
+   */
+  showPortalSignin(guid) {}
 }
 
 /**
@@ -33,6 +39,11 @@
   }
 
   /** @override */
+  showPortalSignin(guid) {
+    chrome.send('showPortalSignin', [guid]);
+  }
+
+  /** @override */
   closeDialog() {
     chrome.send('dialogClose');
   }
diff --git a/chrome/browser/resources/chromeos/login/components/security_token_pin.html b/chrome/browser/resources/chromeos/login/components/security_token_pin.html
index 05f7c3cc..e754142 100644
--- a/chrome/browser/resources/chromeos/login/components/security_token_pin.html
+++ b/chrome/browser/resources/chromeos/login/components/security_token_pin.html
@@ -11,9 +11,7 @@
   }
 
   #pinKeyboard {
-    --pin-keyboard-pin-input-style: {
-      width: 192px;
-    };
+    --pin-keyboard-pin-input-width: 192px;
     --pin-keyboard-input-letter-spacing: 13px;
     --pin-keyboard-number-color: var(--cros-text-color-primary);
     --cr-icon-button-margin-start: 5px;
diff --git a/chrome/browser/resources/new_tab_page/lens_form.html b/chrome/browser/resources/new_tab_page/lens_form.html
index 7cb403ff..fc914058 100644
--- a/chrome/browser/resources/new_tab_page/lens_form.html
+++ b/chrome/browser/resources/new_tab_page/lens_form.html
@@ -15,4 +15,9 @@
            accept="[[supportedFileTypes_]]"
            on-change="handleFileInputChange_"></input>
   </form>
+  <form id="urlForm"
+        action="[[uploadUrlAction_]]"
+        method="GET">
+    <input name="url" value="[[uploadUrl_]]"></input>
+  </form>
 </div>
diff --git a/chrome/browser/resources/new_tab_page/lens_form.ts b/chrome/browser/resources/new_tab_page/lens_form.ts
index a67f68a1..6536071 100644
--- a/chrome/browser/resources/new_tab_page/lens_form.ts
+++ b/chrome/browser/resources/new_tab_page/lens_form.ts
@@ -7,9 +7,15 @@
 import {getTemplate} from './lens_form.html.js';
 
 /** Lens service endpoint for the Upload by File action. */
-const UPLOAD_FILE_ACTION = 'https://lens.google.com/upload';
+const UPLOAD_FILE_ACTION: string = 'https://lens.google.com/upload';
 
-const SUPPORTED_FILE_TYPES = [
+/** Lens service endpoint for the Upload by URL action. */
+const UPLOAD_BY_URL_ACTION: string = 'https://lens.google.com/uploadbyurl';
+
+/** Max length for encoded input URL. */
+const MAX_URL_LENGTH: number = 2000;
+
+const SUPPORTED_FILE_TYPES: string[] = [
   'image/bmp',
   'image/heic',
   'image/heif',
@@ -21,7 +27,7 @@
 ];
 
 /** Maximum file size support by Lens in bytes. */
-const MAX_FILE_SIZE_BYTES = 20 * 1024 * 1024;  // 20MB
+const MAX_FILE_SIZE_BYTES: number = 20 * 1024 * 1024;  // 20MB
 
 export enum LensErrorType {
   // The user attempted to upload multiple files at the same time.
@@ -32,12 +38,19 @@
   FILE_TYPE,
   // The user provided a file that is too large.
   FILE_SIZE,
+  // The user provided a url with an invalid or missing scheme.
+  INVALID_SCHEME,
+  // The user provided a string that does not parse to a valid url.
+  INVALID_URL,
+  // The user provided a string that was too long.
+  LENGTH_TOO_GREAT,
 }
 
 export interface LensFormElement {
   $: {
     fileForm: HTMLFormElement,
     fileInput: HTMLInputElement,
+    urlForm: HTMLFormElement,
   };
 }
 
@@ -62,9 +75,17 @@
         readOnly: true,
         value: UPLOAD_FILE_ACTION,
       },
+      uploadUrlAction_: {
+        type: String,
+        readOnly: true,
+        value: UPLOAD_BY_URL_ACTION,
+      },
+      uploadUrl_: String,
     };
   }
 
+  private uploadUrl_: string = '';
+
   openSystemFilePicker() {
     this.$.fileInput.click();
   }
@@ -105,10 +126,39 @@
     dataTransfer.items.add(file);
     this.$.fileInput.files = dataTransfer.files;
 
-    this.dispatchEvent(new Event('loading'));
+    this.dispatchLoading_();
     this.$.fileForm.submit();
   }
 
+  submitUrl(urlString: string) {
+    if (!urlString.startsWith('http://') && !urlString.startsWith('https://')) {
+      this.dispatchError_(LensErrorType.INVALID_SCHEME);
+      return;
+    }
+
+    let encodedUri: string;
+    try {
+      encodedUri = encodeURI(urlString);
+      new URL(urlString);  // Throws an error if fails to parse.
+    } catch (e) {
+      this.dispatchError_(LensErrorType.INVALID_URL);
+      return;
+    }
+
+    if (encodedUri.length > MAX_URL_LENGTH) {
+      this.dispatchError_(LensErrorType.LENGTH_TOO_GREAT);
+      return;
+    }
+
+    this.uploadUrl_ = encodedUri;
+    this.dispatchLoading_();
+    this.$.urlForm.submit();
+  }
+
+  private dispatchLoading_() {
+    this.dispatchEvent(new Event('loading'));
+  }
+
   private dispatchError_(errorType: LensErrorType) {
     this.dispatchEvent(new CustomEvent('error', {
       bubbles: false,
diff --git a/chrome/browser/resources/new_tab_page/lens_upload_dialog.html b/chrome/browser/resources/new_tab_page/lens_upload_dialog.html
index aa0247f..78c0486 100644
--- a/chrome/browser/resources/new_tab_page/lens_upload_dialog.html
+++ b/chrome/browser/resources/new_tab_page/lens_upload_dialog.html
@@ -284,8 +284,13 @@
           <div id="inputContainer">
             <input id="inputBox" autocomplete="false" autocorrect="false"
                 placeholder="$i18n{lensSearchUploadDialogTextPlaceholder}"
-                text="text">
-            <div id="inputSubmit" tabindex="0" role="button">
+                text="text"
+                value="{{uploadUrl_::input}}"
+                on-keydown="onUrlKeyDown_">
+            <div id="inputSubmit"
+                 tabindex="0"
+                 role="button"
+                 on-click="onSubmitUrl_">
               $i18n{lensSearchUploadDialogSearchButtonLabel}
             </div>
           </div>
diff --git a/chrome/browser/resources/new_tab_page/lens_upload_dialog.ts b/chrome/browser/resources/new_tab_page/lens_upload_dialog.ts
index ffda4a0..2e3bf2726 100644
--- a/chrome/browser/resources/new_tab_page/lens_upload_dialog.ts
+++ b/chrome/browser/resources/new_tab_page/lens_upload_dialog.ts
@@ -73,12 +73,16 @@
         computed: `computeIsOffline_(dialogState_)`,
         reflectToAttribute: true,
       },
+      uploadUrl_: {
+        type: String,
+      },
     };
   }
 
   private outsideClickHandler_: (event: MouseEvent) => void;
   private dialogState_ = DialogState.HIDDEN;
   private outsideClickHandlerAttached_ = false;
+  private uploadUrl_: string = '';
 
   private computeIsHidden_(dialogState: DialogState): boolean {
     return dialogState === DialogState.HIDDEN;
@@ -176,6 +180,20 @@
   private handleFormError_(_event: CustomEvent<LensErrorType>) {
     // TODO(crbug.com/1367506): Implement error state.
   }
+
+  private onUrlKeyDown_(event: KeyboardEvent) {
+    if (event.key === 'Enter') {
+      event.preventDefault();
+      this.onSubmitUrl_();
+    }
+  }
+
+  private onSubmitUrl_() {
+    const url = this.uploadUrl_.trim();
+    if (url.length > 0) {
+      this.$.lensForm.submitUrl(url);
+    }
+  }
 }
 declare global {
   interface HTMLElementTagNameMap {
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index 4a27d27..97414f2 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -116,6 +116,7 @@
     "//tools/typescript/definitions/quick_unlock_private.d.ts",
     "//tools/typescript/definitions/runtime.d.ts",
     "//tools/typescript/definitions/settings_private.d.ts",
+    "//tools/typescript/definitions/system_display.d.ts",
     "//tools/typescript/definitions/tabs.d.ts",
   ]
   root_dir = "$target_gen_dir/$preprocessed_ts_folder"
diff --git a/chrome/browser/resources/settings/chromeos/device_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/device_page/BUILD.gn
index 34bb216..bae20152 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/device_page/BUILD.gn
@@ -14,9 +14,6 @@
     ":audio",
     ":cros_audio_config",
     ":device_page_browser_proxy",
-    ":display",
-    ":display_layout",
-    ":display_overscan_dialog",
     ":drag_behavior",
     ":keyboard",
     ":layout_behavior",
@@ -35,6 +32,8 @@
     "../..:router",
     "//ash/webui/common/resources:i18n_behavior",
     "//chromeos/ash/components/audio/public/mojom:mojom_webui_js",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:cr.m",
   ]
 
   externs_list =
@@ -53,37 +52,6 @@
   ]
 }
 
-js_library("display") {
-  deps = [
-    "..:os_route",
-    "..:route_observer_behavior",
-    "../..:router",
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:cr.m",
-  ]
-  externs_list =
-      chrome_extension_public_externs + [
-        "$externs_path/settings_private.js",
-        "$externs_path/system_display.js",
-        "//ui/webui/resources/cr_elements/cr_slider/cr_slider_externs.js",
-        "../settings_controls_types.js",
-      ]
-}
-
-js_library("display_layout") {
-  deps = [
-    ":drag_behavior",
-    ":layout_behavior",
-  ]
-}
-
-js_library("display_overscan_dialog") {
-  deps = [
-    ":display",
-    "//ui/webui/resources/js:cr.m",
-  ]
-}
-
 js_library("drag_behavior") {
   deps = [
     "//ui/webui/resources/js:assert",
@@ -137,6 +105,7 @@
     "../..:router",
     "//ash/webui/common/resources:focus_without_ink_js",
     "//ash/webui/common/resources:web_ui_listener_behavior",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:assert",
     "//ui/webui/resources/js:load_time_data.m",
   ]
@@ -147,6 +116,7 @@
     ":device_page_browser_proxy",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources:web_ui_listener_behavior",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
 }
 
@@ -154,15 +124,14 @@
   deps = [
     "..:prefs_behavior",
     "//ash/webui/common/resources:web_ui_listener_behavior",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
+  externs_list = chrome_extension_public_externs
 }
 
 html_to_js("web_components") {
   js_files = [
     "audio.js",
-    "display_layout.js",
-    "display.js",
-    "display_overscan_dialog.js",
     "keyboard.js",
     "power.js",
     "storage_external_entry.js",
diff --git a/chrome/browser/resources/settings/chromeos/device_page/display.html b/chrome/browser/resources/settings/chromeos/device_page/display.html
index 418b3da6..b626eb8 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/display.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/display.html
@@ -121,7 +121,9 @@
       <div id="displayScreenTitle" class="start" aria-hidden="true">
         $i18n{displayScreenTitle}
       </div>
-      <select class="md-select" on-change="updatePrimaryDisplay_"
+      <select id="primaryDisplaySelect"
+          class="md-select"
+          on-change="updatePrimaryDisplay_"
           aria-labelledby="displayScreenTitle"
           value="[[getDisplaySelectMenuIndex_(
               selectedDisplay, primaryDisplayId)]]">
@@ -239,7 +241,8 @@
             icon-aria-label="$i18n{displayOrientation}">
         </cr-policy-pref-indicator>
       </template>
-      <select class="md-select"
+      <select id="orientationSelect"
+          class="md-select"
           value="[[selectedDisplay.rotation]]"
           aria-labelledby="displayOrientation"
           on-change="onOrientationChange_"
diff --git a/chrome/browser/resources/settings/chromeos/device_page/display.js b/chrome/browser/resources/settings/chromeos/device_page/display.ts
similarity index 68%
rename from chrome/browser/resources/settings/chromeos/device_page/display.js
rename to chrome/browser/resources/settings/chromeos/device_page/display.ts
index 330c819..d6a5dd0 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/display.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/display.ts
@@ -24,74 +24,83 @@
 import 'chrome://resources/cr_elements/cr_slider/cr_slider.js';
 import 'chrome://resources/cr_elements/cr_shared_style.css.js';
 
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
-import {assert} from 'chrome://resources/js/assert.js';
 import {focusWithoutInk} from 'chrome://resources/ash/common/focus_without_ink_js.js';
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
+import {CrSliderElement, SliderTick} from 'chrome://resources/cr_elements/cr_slider/cr_slider.js';
+import {I18nMixin, I18nMixinInterface} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {assert} from 'chrome://resources/js/assert_ts.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {flush, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {flush, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {DropdownMenuOptionList} from '../../controls/settings_dropdown_menu.js';
+import {SettingsSliderElement} from '../../controls/settings_slider.js';
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route} from '../../router.js';
+import {Route, RouteObserverMixin, RouteObserverMixinInterface} from '../../router.js';
+import {assertExists, cast, castExists} from '../assert_extras.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {routes} from '../os_route.js';
 import {PrefsBehavior, PrefsBehaviorInterface} from '../prefs_behavior.js';
-import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
 
 import {DevicePageBrowserProxy, DevicePageBrowserProxyImpl, getDisplayApi} from './device_page_browser_proxy.js';
+import {getTemplate} from './display.html.js';
+import {SettingsDisplayOverscanDialogElement} from './display_overscan_dialog.js';
 
-/**
- * @typedef {{
- *   value: (!{
- *     recommended: (boolean|undefined),
- *     external_width: (number|undefined),
- *     external_height: (number|undefined),
- *     external_use_native: (boolean|undefined),
- *     external_scale_percentage: (number|undefined),
- *     internal_scale_percentage: (number|undefined)
- *   }|null)
- * }}
- */
-let DisplayResolutionPrefObject;
+type DisplayLayout = chrome.system.display.DisplayLayout;
+type DisplayMode = chrome.system.display.DisplayMode;
+type DisplayProperties = chrome.system.display.DisplayProperties;
+type DisplayUnitInfo = chrome.system.display.DisplayUnitInfo;
+type GetInfoFlags = chrome.system.display.GetInfoFlags;
+type MirrorModeInfo = chrome.system.display.MirrorModeInfo;
+const MirrorMode = chrome.system.display.MirrorMode;
+
+interface DisplayResolutionPrefObject {
+  value: {
+    recommended?: boolean,
+    external_width?: number,
+    external_height?: number,
+    external_use_native?: boolean,
+    external_scale_percentage?: number,
+    internal_scale_percentage?: number,
+  }|null;
+}
 
 /**
  * The types of Night Light automatic schedule. The values of the enum values
  * are synced with the pref "prefs.ash.night_light.schedule_type".
- * @enum {number}
  */
-const NightLightScheduleType = {
-  NEVER: 0,
-  SUNSET_TO_SUNRISE: 1,
-  CUSTOM: 2,
-};
+enum NightLightScheduleType {
+  NEVER = 0,
+  SUNSET_TO_SUNRISE = 1,
+  CUSTOM = 2,
+}
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {DeepLinkingBehaviorInterface}
- * @implements {I18nBehaviorInterface}
- * @implements {PrefsBehaviorInterface}
- * @implements {RouteObserverBehaviorInterface}
- */
-const SettingsDisplayElementBase = mixinBehaviors(
-    [DeepLinkingBehavior, I18nBehavior, PrefsBehavior, RouteObserverBehavior],
-    PolymerElement);
+interface SettingsDisplayElement {
+  $: {
+    displayOverscan: SettingsDisplayOverscanDialogElement,
+    displaySizeSlider: SettingsSliderElement,
+  };
+}
 
-/** @polymer */
+const SettingsDisplayElementBase =
+    mixinBehaviors(
+        [DeepLinkingBehavior, PrefsBehavior],
+        RouteObserverMixin(I18nMixin(PolymerElement))) as {
+      new (): PolymerElement & DeepLinkingBehaviorInterface &
+          I18nMixinInterface & PrefsBehaviorInterface &
+          RouteObserverMixinInterface,
+    };
+
 class SettingsDisplayElement extends SettingsDisplayElementBase {
   static get is() {
     return 'settings-display';
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
     return {
-      /**
-       * @type {!chrome.settingsPrivate.PrefObject}
-       * @private
-       */
       selectedModePref_: {
         type: Object,
         value() {
@@ -103,10 +112,6 @@
         },
       },
 
-      /**
-       * @type {!chrome.settingsPrivate.PrefObject}
-       * @private
-       */
       selectedZoomPref_: {
         type: Object,
         value() {
@@ -118,16 +123,8 @@
         },
       },
 
-      /**
-       * Array of displays.
-       * @type {!Array<!chrome.system.display.DisplayUnitInfo>}
-       */
       displays: Array,
 
-      /**
-       * Array of display layouts.
-       * @type {!Array<!chrome.system.display.DisplayLayout>}
-       */
       layouts: Array,
 
       /**
@@ -139,7 +136,6 @@
       /** Primary display id */
       primaryDisplayId: String,
 
-      /** @type {!chrome.system.display.DisplayUnitInfo|undefined} */
       selectedDisplay: Object,
 
       /** Id passed to the overscan dialog. */
@@ -151,28 +147,24 @@
       /** Ids for mirroring destination displays. */
       mirroringDestinationIds: Array,
 
-      /** @private {!Array<number>} Mode index values for slider. */
+      /** Mode index values for slider. */
       modeValues_: Array,
 
       /**
-       * @private {!Array<SliderTick>} Display zoom slider tick
-       *     values.
+       * Display zoom slider tick values.
        */
       zoomValues_: Array,
 
-      /** @private {!DropdownMenuOptionList} */
       displayModeList_: {
         type: Array,
         value: [],
       },
 
-      /** @private {!DropdownMenuOptionList} */
       refreshRateList_: {
         type: Array,
         value: [],
       },
 
-      /** @private */
       unifiedDesktopAvailable_: {
         type: Boolean,
         value() {
@@ -180,7 +172,6 @@
         },
       },
 
-      /** @private */
       ambientColorAvailable_: {
         type: Boolean,
         value() {
@@ -188,7 +179,6 @@
         },
       },
 
-      /** @private */
       listAllDisplayModes_: {
         type: Boolean,
         value() {
@@ -196,16 +186,11 @@
         },
       },
 
-      /** @private */
       unifiedDesktopMode_: {
         type: Boolean,
         value: false,
       },
 
-      /**
-       * @type {!chrome.settingsPrivate.PrefObject}
-       * @private
-       */
       selectedParentModePref_: {
         type: Object,
         value: function() {
@@ -217,7 +202,6 @@
         },
       },
 
-      /** @private */
       scheduleTypesList_: {
         type: Array,
         value() {
@@ -239,28 +223,22 @@
         },
       },
 
-      /** @private */
       shouldOpenCustomScheduleCollapse_: {
         type: Boolean,
         value: false,
       },
 
-      /** @private */
       nightLightScheduleSubLabel_: String,
 
-      /** @private */
       logicalResolutionText_: String,
 
-      /** @private {!Array<string>} */
       displayTabNames_: Array,
 
-      /** @private */
       selectedTab_: Number,
 
       /**
        * Contains the settingId of any deep link that wasn't able to be shown,
        * null otherwise.
-       * @private {?Setting}
        */
       pendingSettingId_: {
         type: Number,
@@ -269,7 +247,6 @@
 
       /**
        * Used by DeepLinkingBehavior to focus this page's deep links.
-       * @type {!Set<!Setting>}
        */
       supportedSettingIds: {
         type: Object,
@@ -288,7 +265,6 @@
           Setting.kDisplayOverscan,
         ]),
       },
-
     };
   }
 
@@ -304,37 +280,61 @@
     ];
   }
 
-  /** @override */
+  displayIds: string;
+  displays: DisplayUnitInfo[];
+  layouts: DisplayLayout[];
+  mirroringDestinationIds: string[];
+  overscanDisplayId: string;
+  primaryDisplayId: string;
+  selectedDisplay?: DisplayUnitInfo;
+  private browserProxy_: DevicePageBrowserProxy;
+  private currentRoute_: Route|null;
+  private currentSelectedModeIndex_: number;
+  private currentSelectedParentModeIndex_: number;
+  private displayChangedListener_: EventListener|null;
+  private displayModeList_: DropdownMenuOptionList;
+  private displayTabNames_: string[];
+  private invalidDisplayId_: string;
+  private listAllDisplayModes_: boolean;
+  private logicalResolutionText_: string;
+  private modeToParentModeMap_: Map<number, number>;
+  private modeValues_: number[];
+  private nightLightScheduleSubLabel_: string;
+  private parentModeToRefreshRateMap_: Map<number, DropdownMenuOptionList>;
+  private pendingSettingId_: Setting|null;
+  private refreshRateList_: DropdownMenuOptionList;
+  private selectedModePref_: chrome.settingsPrivate.PrefObject;
+  private selectedParentModePref_: chrome.settingsPrivate.PrefObject;
+  private selectedTab_: number;
+  private selectedZoomPref_: chrome.settingsPrivate.PrefObject;
+  private shouldOpenCustomScheduleCollapse_: boolean;
+  private unifiedDesktopMode_: boolean;
+  private zoomValues_: SliderTick[];
+
   constructor() {
     super();
 
     /**
      * This represents the index of the mode with the highest refresh rate at
      * the current resolution.
-     * @private {number}
      */
     this.currentSelectedParentModeIndex_ = -1;
 
     /**
      * This is the index of the currently selected mode.
-     * @private {number} Selected mode index received from chrome.
+     * Selected mode index received from chrome.
      */
     this.currentSelectedModeIndex_ = -1;
 
     /**
      * Listener for chrome.system.display.onDisplayChanged events.
-     * @type {function(void)|undefined}
-     * @private
      */
-    this.displayChangedListener_ = undefined;
+    this.displayChangedListener_ = null;
 
-    /** @private {string} */
     this.invalidDisplayId_ = loadTimeData.getString('invalidDisplayId');
 
-    /** @private {!Route|undefined} */
-    this.currentRoute_ = undefined;
+    this.currentRoute_ = null;
 
-    /** @private {!DevicePageBrowserProxy} */
     this.browserProxy_ = DevicePageBrowserProxyImpl.getInstance();
 
     /**
@@ -343,47 +343,39 @@
      * display's mode list. Parent mode indexes represent the mode with the
      * highest refresh rate at a given resolution. There is 1 and only 1
      * parentModeIndex for each possible resolution .
-     * @private {!Map<number, DropdownMenuOptionList>}
      */
     this.parentModeToRefreshRateMap_ = new Map();
 
     /**
      * Map containing an entry for each display mode mapping its modeIndex to
      * the corresponding parentModeIndex value.
-     * @private {!Map<number, number>} Mode index values for slider.
+     * Mode index values for slider.
      */
     this.modeToParentModeMap_ = new Map();
   }
 
-  /** @override */
-  connectedCallback() {
+  override connectedCallback() {
     super.connectedCallback();
 
     this.displayChangedListener_ =
-        this.displayChangedListener_ || (() => this.getDisplayInfo_());
+        this.displayChangedListener_ || this.getDisplayInfo_.bind(this);
     getDisplayApi().onDisplayChanged.addListener(this.displayChangedListener_);
 
     this.getDisplayInfo_();
     this.$.displaySizeSlider.updateValueInstantly = false;
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     super.disconnectedCallback();
 
     getDisplayApi().onDisplayChanged.removeListener(
-        assert(this.displayChangedListener_));
+        castExists(this.displayChangedListener_));
 
     this.currentSelectedModeIndex_ = -1;
     this.currentSelectedParentModeIndex_ = -1;
   }
 
-  /**
-   * Overridden from DeepLinkingBehavior.
-   * @param {!Setting} settingId
-   * @return {boolean}
-   */
-  beforeDeepLinkAttempt(settingId) {
+  override beforeDeepLinkAttempt(_settingId: Setting): boolean {
     if (!this.displays) {
       // On initial page load, displays will not be loaded and deep link
       // attempt will fail. Suppress warnings by exiting early and try again
@@ -395,15 +387,11 @@
     return true;
   }
 
-  /**
-   * @param {!Route} newRoute
-   * @param {!Route=} opt_oldRoute
-   */
-  currentRouteChanged(newRoute, opt_oldRoute) {
+  override currentRouteChanged(newRoute: Route, oldRoute?: Route) {
     this.currentRoute_ = newRoute;
 
     // When navigating away from the page, deselect any selected display.
-    if (newRoute !== routes.DISPLAY && opt_oldRoute === routes.DISPLAY) {
+    if (newRoute !== routes.DISPLAY && oldRoute === routes.DISPLAY) {
       this.browserProxy_.highlightDisplay(this.invalidDisplayId_);
       return;
     }
@@ -425,10 +413,8 @@
 
   /**
    * Shows or hides the overscan dialog.
-   * @param {boolean} showOverscan
-   * @private
    */
-  showOverscanDialog_(showOverscan) {
+  private showOverscanDialog_(showOverscan: boolean) {
     if (showOverscan) {
       this.$.displayOverscan.open();
       this.$.displayOverscan.focus();
@@ -437,32 +423,28 @@
     }
   }
 
-  /** @private */
-  onDisplayIdsChanged_() {
+  private onDisplayIdsChanged_() {
     // Close any overscan dialog (which will cancel any overscan operation)
     // if displayIds changes.
     this.showOverscanDialog_(false);
   }
 
-  /** @private */
-  getDisplayInfo_() {
-    /** @type {chrome.system.display.GetInfoFlags} */ const flags = {
+  private getDisplayInfo_() {
+    const flags: GetInfoFlags = {
       singleUnified: true,
     };
     getDisplayApi().getInfo(
-        flags, displays => this.displayInfoFetched_(displays));
+        flags,
+        (displays: DisplayUnitInfo[]) => this.displayInfoFetched_(displays));
   }
 
-  /**
-   * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
-   * @private
-   */
-  displayInfoFetched_(displays) {
+  private displayInfoFetched_(displays: DisplayUnitInfo[]) {
     if (!displays.length) {
       return;
     }
     getDisplayApi().getDisplayLayout(
-        layouts => this.displayLayoutFetched_(displays, layouts));
+        (layouts: DisplayLayout[]) =>
+            this.displayLayoutFetched_(displays, layouts));
     if (this.isMirrored_(displays)) {
       this.mirroringDestinationIds = displays[0].mirroringDestinationIds;
     } else {
@@ -470,12 +452,8 @@
     }
   }
 
-  /**
-   * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
-   * @param {!Array<!chrome.system.display.DisplayLayout>} layouts
-   * @private
-   */
-  displayLayoutFetched_(displays, layouts) {
+  private displayLayoutFetched_(
+      displays: DisplayUnitInfo[], layouts: DisplayLayout[]) {
     this.layouts = layouts;
     this.displays = displays;
     this.displayTabNames_ = displays.map(({name}) => name);
@@ -483,12 +461,10 @@
   }
 
   /**
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @return {number} The index of the currently selected mode of the
+   * @return The index of the currently selected mode of the
    * |selectedDisplay|. If the display has no modes, returns 0.
-   * @private
    */
-  getSelectedModeIndex_(selectedDisplay) {
+  private getSelectedModeIndex_(selectedDisplay: DisplayUnitInfo): number {
     for (let i = 0; i < selectedDisplay.modes.length; ++i) {
       if (selectedDisplay.modes[i].isSelected) {
         return i;
@@ -497,69 +473,54 @@
     return 0;
   }
 
-  /**
-   * Checks if the given device policy is enabled.
-   * @param {DisplayResolutionPrefObject} policyPref
-   * @return {boolean}
-   * @private
-   */
-  isDevicePolicyEnabled_(policyPref) {
+  private isDevicePolicyEnabled_(policyPref: DisplayResolutionPrefObject):
+      boolean {
     return policyPref !== undefined && policyPref.value !== null;
   }
 
-  /**
-   * Checks if display resolution is managed by device policy.
-   * @param {DisplayResolutionPrefObject} resolutionPref
-   * @return {boolean}
-   * @private
-   */
-  isDisplayResolutionManagedByPolicy_(resolutionPref) {
+  private isDisplayResolutionManagedByPolicy_(
+      resolutionPref: DisplayResolutionPrefObject): boolean {
     return this.isDevicePolicyEnabled_(resolutionPref) &&
-        (resolutionPref.value.external_use_native !== undefined ||
-         (resolutionPref.value.external_width !== undefined &&
-          resolutionPref.value.external_height !== undefined));
+        (resolutionPref.value!.external_use_native !== undefined ||
+         (resolutionPref.value!.external_width !== undefined &&
+          resolutionPref.value!.external_height !== undefined));
   }
 
   /**
    * Checks if display resolution is managed by policy and the policy
    * is mandatory.
-   * @param {DisplayResolutionPrefObject} resolutionPref
-   * @return {boolean}
-   * @private
    */
-  isDisplayResolutionMandatory_(resolutionPref) {
+  private isDisplayResolutionMandatory_(
+      resolutionPref: DisplayResolutionPrefObject): boolean {
     return this.isDisplayResolutionManagedByPolicy_(resolutionPref) &&
-        !resolutionPref.value.recommended;
+        !resolutionPref.value!.recommended;
   }
 
   /**
    * Checks if display scale factor is managed by device policy.
-   * @param {chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @param {DisplayResolutionPrefObject} resolutionPref
-   * @return {boolean}
-   * @private
    */
-  isDisplayScaleManagedByPolicy_(selectedDisplay, resolutionPref) {
+  private isDisplayScaleManagedByPolicy_(
+      selectedDisplay: DisplayUnitInfo,
+      resolutionPref: DisplayResolutionPrefObject): boolean {
     if (!this.isDevicePolicyEnabled_(resolutionPref) || !selectedDisplay) {
       return false;
     }
     if (selectedDisplay.isInternal) {
-      return resolutionPref.value.internal_scale_percentage !== undefined;
+      return resolutionPref.value!.internal_scale_percentage !== undefined;
     }
-    return resolutionPref.value.external_scale_percentage !== undefined;
+    return resolutionPref.value!.external_scale_percentage !== undefined;
   }
 
   /**
    * Checks if display scale factor is managed by policy and the policy
    * is mandatory.
-   * @param {DisplayResolutionPrefObject} resolutionPref
-   * @return {boolean}
-   * @private
    */
-  isDisplayScaleMandatory_(selectedDisplay, resolutionPref) {
+  private isDisplayScaleMandatory_(
+      selectedDisplay: DisplayUnitInfo,
+      resolutionPref: DisplayResolutionPrefObject): boolean {
     return this.isDisplayScaleManagedByPolicy_(
                selectedDisplay, resolutionPref) &&
-        !resolutionPref.value.recommended;
+        !resolutionPref.value!.recommended;
   }
 
 
@@ -568,12 +529,10 @@
    * contain entries representing a combined resolution + refresh rate.
    * Only one parse*DisplayModes_ method must be called, depending on the
    * state of |listAllDisplayModes_|.
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @private
    */
-  parseCompoundDisplayModes_(selectedDisplay) {
+  private parseCompoundDisplayModes_(selectedDisplay: DisplayUnitInfo) {
     assert(!this.listAllDisplayModes_);
-    const optionList = [];
+    const optionList: DropdownMenuOptionList = [];
     for (let i = 0; i < selectedDisplay.modes.length; ++i) {
       const mode = selectedDisplay.modes[i];
 
@@ -596,11 +555,9 @@
    * height => refreshRate => modeIndex. modeIndex is the index of the
    * resolution + refreshRate combination in |selectedDisplay|'s mode list.
    * This is used to traverse all possible display modes in ascending order.
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @return {!Map<number, Map<number, Map<number, number>>>}
-   * @private
    */
-  createModeMap_(selectedDisplay) {
+  private createModeMap_(selectedDisplay: DisplayUnitInfo):
+      Map<number, Map<number, Map<number, number>>> {
     const modes = new Map();
     for (let i = 0; i < selectedDisplay.modes.length; ++i) {
       const mode = selectedDisplay.modes[i];
@@ -635,10 +592,8 @@
    * selected, and other possible refresh rates at that resolution are shown
    * in a dropdown. Only one parse*DisplayModes_ method must be called,
    * depending on the state of |listAllDisplayModes_|.
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @private
    */
-  parseSplitDisplayModes_(selectedDisplay) {
+  private parseSplitDisplayModes_(selectedDisplay: DisplayUnitInfo) {
     assert(this.listAllDisplayModes_);
     // Clear the mappings before recalculating.
     this.modeToParentModeMap_ = new Map();
@@ -653,13 +608,13 @@
     const widthsArr = Array.from(modes.keys()).sort();
     for (let i = 0; i < widthsArr.length; i++) {
       const width = widthsArr[i];
-      const heightsMap = modes.get(width);
+      const heightsMap = modes.get(width)!;
       const heightArr = Array.from(heightsMap.keys());
       for (let j = 0; j < heightArr.length; j++) {
         // The highest/first refresh rate for each width/height pair
         // (resolution) is the default and therefore the "parent" mode.
         const height = heightArr[j];
-        const refreshRates = heightsMap.get(height);
+        const refreshRates = heightsMap.get(height)!;
         const parentModeIndex = this.getParentModeIndex_(refreshRates);
         this.addResolution_(parentModeIndex, width, height);
 
@@ -670,7 +625,7 @@
         const refreshRatesArr = Array.from(refreshRates.keys());
         for (let k = 0; k < refreshRatesArr.length; k++) {
           const rate = refreshRatesArr[k];
-          const modeIndex = refreshRates.get(rate);
+          const modeIndex = refreshRates.get(rate)!;
           const isInterlaced = selectedDisplay.modes[modeIndex].isInterlaced;
 
           this.addRefreshRate_(parentModeIndex, modeIndex, rate, isInterlaced);
@@ -682,7 +637,7 @@
     for (let i = 0; i < selectedDisplay.modes.length; i++) {
       const mode = selectedDisplay.modes[i];
       const parentModeIndex =
-          this.getParentModeIndex_(modes.get(mode.width).get(mode.height));
+          this.getParentModeIndex_(modes.get(mode.width)!.get(mode.height)!);
       this.modeToParentModeMap_.set(i, parentModeIndex);
     }
     assert(this.modeToParentModeMap_.size === selectedDisplay.modes.length);
@@ -694,24 +649,21 @@
   /**
    * Picks the appropriate parent mode from a refresh rate -> mode index map.
    * Currently this chooses the mode with the highest refresh rate.
-   * @param {Map<number,number>} refreshRates each possible refresh rate
+   * @param refreshRates each possible refresh rate
    *   mapped to the corresponding mode index.
-   * @private
    */
-  getParentModeIndex_(refreshRates) {
+  private getParentModeIndex_(refreshRates: Map<number, number>) {
     const maxRefreshRate = Math.max(...refreshRates.keys());
-    return refreshRates.get(maxRefreshRate);
+    // maxRefreshRate always exists as a key
+    return refreshRates.get(maxRefreshRate)!;
   }
 
   /**
    * Adds a an entry in |displayModeList_| for the resolution represented by
    * |width| and |height| and possible |refreshRates|.
-   * @param {number} parentModeIndex
-   * @param {number} width
-   * @param {number} height
-   * @private
    */
-  addResolution_(parentModeIndex, width, height) {
+  private addResolution_(
+      parentModeIndex: number, width: number, height: number) {
     assert(this.listAllDisplayModes_);
 
     // Add an entry in the outer map for |parentModeIndex|. The inner
@@ -733,13 +685,10 @@
   /**
    * Adds a an entry in |parentModeToRefreshRateMap_| for the refresh rate
    * represented by |rate|.
-   * @param {number} parentModeIndex
-   * @param {number} modeIndex
-   * @param {number} rate
-   * @param {boolean|undefined} isInterlaced
-   * @private
    */
-  addRefreshRate_(parentModeIndex, modeIndex, rate, isInterlaced) {
+  private addRefreshRate_(
+      parentModeIndex: number, modeIndex: number, rate: number,
+      isInterlaced?: boolean) {
     assert(this.listAllDisplayModes_);
 
     // Truncate at two decimal places for display. If the refresh rate
@@ -754,7 +703,7 @@
 
     const refreshRateOption = this.i18n(id, refreshRate.toString());
 
-    this.parentModeToRefreshRateMap_.get(parentModeIndex).push({
+    this.parentModeToRefreshRateMap_.get(parentModeIndex)!.push({
       name: refreshRateOption,
       value: modeIndex,
     });
@@ -763,10 +712,9 @@
   /**
    * Sorts |displayModeList_| in descending order. First order sort is width,
    * second order sort is height.
-   * @private
    */
-  sortResolutionList_() {
-    const getWidthFromResolutionString = function(str) {
+  private sortResolutionList_() {
+    const getWidthFromResolutionString = function(str: string) {
       return Number(str.substr(0, str.indexOf(' ')));
     };
 
@@ -784,10 +732,8 @@
    * refresh rate combo. If |listAllDisplayModes_| is on, resolution and
    * refresh rate are parsed into separate dropdowns and
    * |parentModeToRefreshRateMap_| + |modeToParentModeMap_| are populated.
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @private
    */
-  updateDisplayModeStructures_(selectedDisplay) {
+  private updateDisplayModeStructures_(selectedDisplay: DisplayUnitInfo) {
     if (this.listAllDisplayModes_) {
       this.parseSplitDisplayModes_(selectedDisplay);
     } else {
@@ -798,11 +744,8 @@
   /**
    * Returns a value from |zoomValues_| that is closest to the display zoom
    * percentage currently selected for the |selectedDisplay|.
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @return {number}
-   * @private
    */
-  getSelectedDisplayZoom_(selectedDisplay) {
+  private getSelectedDisplayZoom_(selectedDisplay: DisplayUnitInfo): number {
     const selectedZoom = selectedDisplay.displayZoomFactor;
     let closestMatch = this.zoomValues_[0].value;
     let minimumDiff = Math.abs(closestMatch - selectedZoom);
@@ -815,16 +758,14 @@
       }
     }
 
-    return /** @type {number} */ (closestMatch);
+    return closestMatch;
   }
 
   /**
    * Given the display with the current display mode, this function lists all
    * the display zoom values and their labels to be used by the slider.
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @return {!Array<SliderTick>}
    */
-  getZoomValues_(selectedDisplay) {
+  private getZoomValues_(selectedDisplay: DisplayUnitInfo): SliderTick[] {
     return selectedDisplay.availableDisplayZoomFactors.map(value => {
       const ariaValue = Math.round(value * 100);
       return {
@@ -838,10 +779,8 @@
   /**
    * We need to call this explicitly rather than relying on change events
    * so that we can control the update order.
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @private
    */
-  setSelectedDisplay_(selectedDisplay) {
+  private setSelectedDisplay_(selectedDisplay: DisplayUnitInfo) {
     // |modeValues_| controls the resolution slider's tick values. Changing it
     // might trigger a change in the |selectedModePref_.value| if the number
     // of modes differs and the current mode index is out of range of the new
@@ -878,9 +817,9 @@
       // Now that everything is in sync, set the selected mode to its correct
       // value right before updating the pref.
       this.currentSelectedParentModeIndex_ =
-          this.modeToParentModeMap_.get(currentModeIndex);
+          this.modeToParentModeMap_.get(currentModeIndex)!;
       this.refreshRateList_ = this.parentModeToRefreshRateMap_.get(
-          this.currentSelectedParentModeIndex_);
+          this.currentSelectedParentModeIndex_)!;
     } else {
       this.currentSelectedParentModeIndex_ = currentModeIndex;
     }
@@ -888,27 +827,20 @@
     this.set(
         'selectedParentModePref_.value', this.currentSelectedParentModeIndex_);
 
-    this.updateLogicalResolutionText_(
-        /** @type {number} */ (this.selectedZoomPref_.value));
+    this.updateLogicalResolutionText_(this.selectedZoomPref_.value);
   }
 
   /**
    * Returns true if the resolution setting needs to be displayed.
-   * @param {!chrome.system.display.DisplayUnitInfo} display
-   * @return {boolean}
-   * @private
    */
-  showDropDownResolutionSetting_(display) {
+  private showDropDownResolutionSetting_(display: DisplayUnitInfo): boolean {
     return !display.isInternal;
   }
 
   /**
    * Returns true if the refresh rate setting needs to be displayed.
-   * @param {!chrome.system.display.DisplayUnitInfo} display
-   * @return {boolean}
-   * @private
    */
-  showRefreshRateSetting_(display) {
+  private showRefreshRateSetting_(display: DisplayUnitInfo): boolean {
     return this.listAllDisplayModes_ &&
         this.showDropDownResolutionSetting_(display);
   }
@@ -917,53 +849,37 @@
    * Returns true if external touch devices are connected and the current
    * display is not an internal display. If the feature is not enabled via the
    * switch, this will return false.
-   * @param {!chrome.system.display.DisplayUnitInfo} display Display being
-   *     checked for touch support.
-   * @return {boolean}
-   * @private
+   * @param display Display being checked for touch support.
    */
-  showTouchCalibrationSetting_(display) {
+  private showTouchCalibrationSetting_(display: DisplayUnitInfo): boolean {
     return !display.isInternal &&
         loadTimeData.getBoolean('enableTouchCalibrationSetting');
   }
 
   /**
    * Returns true if the overscan setting should be shown for |display|.
-   * @param {!chrome.system.display.DisplayUnitInfo} display
-   * @return {boolean}
-   * @private
    */
-  showOverscanSetting_(display) {
+  private showOverscanSetting_(display: DisplayUnitInfo): boolean {
     return !display.isInternal;
   }
 
   /**
    * Returns true if the ambient color setting should be shown for |display|.
-   * @param {boolean} ambientColorAvailable
-   * @param {chrome.system.display.DisplayUnitInfo} display
-   * @return {boolean}
-   * @private
    */
-  showAmbientColorSetting_(ambientColorAvailable, display) {
+  private showAmbientColorSetting_(
+      ambientColorAvailable: boolean, display: DisplayUnitInfo): boolean {
     return ambientColorAvailable && display && display.isInternal;
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  hasMultipleDisplays_() {
+  private hasMultipleDisplays_(): boolean {
     return this.displays.length > 1;
   }
 
   /**
    * Returns false if the display select menu has to be hidden.
-   * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @return {boolean}
-   * @private
    */
-  showDisplaySelectMenu_(displays, selectedDisplay) {
+  private showDisplaySelectMenu_(
+      displays: DisplayUnitInfo[], selectedDisplay: DisplayUnitInfo): boolean {
     if (selectedDisplay) {
       return displays.length > 1 && !selectedDisplay.isPrimary;
     }
@@ -974,12 +890,10 @@
   /**
    * Returns the select menu index indicating whether the display currently is
    * primary or extended.
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @param {string} primaryDisplayId
-   * @return {number} Returns 0 if the display is primary else returns 1.
-   * @private
+   * @return Returns 0 if the display is primary else returns 1.
    */
-  getDisplaySelectMenuIndex_(selectedDisplay, primaryDisplayId) {
+  private getDisplaySelectMenuIndex_(
+      selectedDisplay: DisplayUnitInfo, primaryDisplayId: string): number {
     if (selectedDisplay && selectedDisplay.id === primaryDisplayId) {
       return 0;
     }
@@ -988,22 +902,15 @@
 
   /**
    * Returns the i18n string for the text to be used for mirroring settings.
-   * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
-   * @return {string} i18n string for mirroring settings text.
-   * @private
+   * @return i18n string for mirroring settings text.
    */
-  getDisplayMirrorText_(displays) {
+  private getDisplayMirrorText_(displays: DisplayUnitInfo[]): string {
     return this.i18n('displayMirror', displays[0].name);
   }
 
-  /**
-   * @param {boolean} unifiedDesktopAvailable
-   * @param {boolean} unifiedDesktopMode
-   * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
-   * @return {boolean}
-   * @private
-   */
-  showUnifiedDesktop_(unifiedDesktopAvailable, unifiedDesktopMode, displays) {
+  private showUnifiedDesktop_(
+      unifiedDesktopAvailable: boolean, unifiedDesktopMode: boolean,
+      displays: DisplayUnitInfo[]): boolean {
     if (displays === undefined) {
       return false;
     }
@@ -1013,24 +920,14 @@
          !this.isMirrored_(displays));
   }
 
-  /**
-   * @param {boolean} unifiedDesktopMode
-   * @return {string}
-   * @private
-   */
-  getUnifiedDesktopText_(unifiedDesktopMode) {
+  private getUnifiedDesktopText_(unifiedDesktopMode: boolean): string {
     return this.i18n(
         unifiedDesktopMode ? 'displayUnifiedDesktopOn' :
                              'displayUnifiedDesktopOff');
   }
 
-  /**
-   * @param {boolean} unifiedDesktopMode
-   * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
-   * @return {boolean}
-   * @private
-   */
-  showMirror_(unifiedDesktopMode, displays) {
+  private showMirror_(unifiedDesktopMode: boolean, displays: DisplayUnitInfo[]):
+      boolean {
     if (displays === undefined) {
       return false;
     }
@@ -1039,53 +936,30 @@
         (!unifiedDesktopMode && displays.length > 1);
   }
 
-  /**
-   * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
-   * @return {boolean}
-   * @private
-   */
-  isMirrored_(displays) {
+  private isMirrored_(displays: DisplayUnitInfo[]): boolean {
     return displays !== undefined && displays.length > 0 &&
         !!displays[0].mirroringSourceId;
   }
 
-  /**
-   * @param {!chrome.system.display.DisplayUnitInfo} display
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @return {boolean}
-   * @private
-   */
-  isSelected_(display, selectedDisplay) {
+  private isSelected_(
+      display: DisplayUnitInfo, selectedDisplay: DisplayUnitInfo): boolean {
     return display.id === selectedDisplay.id;
   }
 
-  /**
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @return {boolean}
-   * @private
-   */
-  enableSetResolution_(selectedDisplay) {
+  private enableSetResolution_(selectedDisplay: DisplayUnitInfo): boolean {
     return selectedDisplay.modes.length > 1;
   }
 
-  /**
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @return {boolean}
-   * @private
-   */
-  enableDisplayZoomSlider_(selectedDisplay) {
+  private enableDisplayZoomSlider_(selectedDisplay: DisplayUnitInfo): boolean {
     return selectedDisplay.availableDisplayZoomFactors.length > 1;
   }
 
   /**
    * Returns true if the given mode is the best mode for the
    * |selectedDisplay|.
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @param {!chrome.system.display.DisplayMode} mode
-   * @return {boolean}
-   * @private
    */
-  isBestMode_(selectedDisplay, mode) {
+  private isBestMode_(selectedDisplay: DisplayUnitInfo, mode: DisplayMode):
+      boolean {
     if (!selectedDisplay.isInternal) {
       return mode.isNative;
     }
@@ -1093,29 +967,25 @@
     // Things work differently for full HD devices(1080p). The best mode is
     // the one with 1.25 device scale factor and 0.8 ui scale.
     if (mode.heightInNativePixels === 1080) {
-      return Math.abs(mode.uiScale - 0.8) < 0.001 &&
+      return Math.abs(mode.uiScale! - 0.8) < 0.001 &&
           Math.abs(mode.deviceScaleFactor - 1.25) < 0.001;
     }
 
     return mode.uiScale === 1.0;
   }
 
-  /**
-   * @return {string}
-   * @private
-   */
-  getResolutionText_() {
+  private getResolutionText_(): string {
+    assertExists(this.selectedDisplay);
     if (this.selectedDisplay.modes.length === 0 ||
         this.currentSelectedModeIndex_ === -1) {
-      // If currentSelectedModeIndex_ == -1, selectedDisplay and
+      // If currentSelectedModeIndex_ is -1, selectedDisplay and
       // |selectedModePref_.value| are not in sync.
       return this.i18n(
           'displayResolutionText', this.selectedDisplay.bounds.width.toString(),
           this.selectedDisplay.bounds.height.toString());
     }
-    const mode = this.selectedDisplay.modes[
-        /** @type {number} */ (this.selectedModePref_.value)];
-    assert(mode);
+    const mode =
+        castExists(this.selectedDisplay.modes[this.selectedModePref_.value]);
     const widthStr = mode.width.toString();
     const heightStr = mode.height.toString();
     if (this.isBestMode_(this.selectedDisplay, mode)) {
@@ -1129,11 +999,10 @@
   /**
    * Updates the logical resolution text to be used for the display size
    * section
-   * @param {number} zoomFactor Current zoom factor applied on the selected
-   *    display.
-   * @private
+   * @param zoomFactor Current zoom factor applied on the selected display.
    */
-  updateLogicalResolutionText_(zoomFactor) {
+  private updateLogicalResolutionText_(zoomFactor: number) {
+    assertExists(this.selectedDisplay);
     if (!this.selectedDisplay.isInternal) {
       this.logicalResolutionText_ = '';
       return;
@@ -1167,9 +1036,9 @@
    * Logical Resolution Text. Returns true if the longer edge of the
    * display's native pixels is different than the longer edge of the
    * display's current bounds.
-   * @private
    */
-  shouldSwapLogicalResolutionText_() {
+  private shouldSwapLogicalResolutionText_() {
+    assertExists(this.selectedDisplay);
     const mode = this.selectedDisplay.modes[this.currentSelectedModeIndex_];
     const bounds = this.selectedDisplay.bounds;
 
@@ -1177,30 +1046,27 @@
         mode.widthInNativePixels > mode.heightInNativePixels;
   }
 
-
   /**
    * Handles the event where the display size slider is being dragged, i.e.
    * the mouse or tap has not been released.
-   * @private
    */
-  onDisplaySizeSliderDrag_() {
+  private onDisplaySizeSliderDrag_() {
     if (!this.selectedDisplay) {
       return;
     }
 
-    const sliderValue =
-        this.$.displaySizeSlider.shadowRoot.querySelector('#slider').value;
-    const zoomFactor = this.$.displaySizeSlider.ticks[sliderValue].value;
-    this.updateLogicalResolutionText_(
-        /** @type {number} */ (zoomFactor));
+    const slider = castExists(
+        this.$.displaySizeSlider.shadowRoot!.querySelector<CrSliderElement>(
+            '#slider'));
+    const zoomFactor =
+        (this.$.displaySizeSlider.ticks as SliderTick[])[slider.value].value;
+    this.updateLogicalResolutionText_(zoomFactor);
   }
 
   /**
-   * @param {!CustomEvent<string>} e |e.detail| is the id of the selected
-   *     display.
-   * @private
+   * @param e |e.detail| is the id of the selected display.
    */
-  onSelectDisplay_(e) {
+  private onSelectDisplay_(e: CustomEvent<string>) {
     const id = e.detail;
     for (let i = 0; i < this.displays.length; ++i) {
       const display = this.displays[i];
@@ -1213,9 +1079,8 @@
     }
   }
 
-  /** @private */
-  onSelectDisplayTab_() {
-    const {selected} = this.shadowRoot.querySelector('cr-tabs');
+  private onSelectDisplayTab_() {
+    const {selected} = castExists(this.shadowRoot!.querySelector('cr-tabs'));
     if (this.selectedTab_ !== selected) {
       this.setSelectedDisplay_(this.displays[selected]);
     }
@@ -1223,31 +1088,26 @@
 
   /**
    * Handles event when a touch calibration option is selected.
-   * @param {!Event} e
-   * @private
    */
-  onTouchCalibrationTap_(e) {
-    getDisplayApi().showNativeTouchCalibration(this.selectedDisplay.id);
+  private onTouchCalibrationTap_() {
+    getDisplayApi().showNativeTouchCalibration(this.selectedDisplay!.id);
   }
 
   /**
    * Handles the event when an option from display select menu is selected.
-   * @param {!{target: !HTMLSelectElement}} e
-   * @private
    */
-  updatePrimaryDisplay_(e) {
-    /** @type {number} */ const PRIMARY_DISP_IDX = 0;
+  private updatePrimaryDisplay_(e: Event) {
     if (!this.selectedDisplay) {
       return;
     }
     if (this.selectedDisplay.id === this.primaryDisplayId) {
       return;
     }
-    if (!e.target.value) {
+    if (!(e.target as HTMLSelectElement).value) {
       return;
     }
 
-    /** @type {!chrome.system.display.DisplayProperties} */ const properties = {
+    const properties: DisplayProperties = {
       isPrimary: true,
     };
     getDisplayApi().setDisplayProperties(
@@ -1258,10 +1118,8 @@
   /**
    * Handles a change in the |selectedParentModePref| value triggered via the
    * observer.
-   * @param {number} newModeIndex The new index value
-   * @private
    */
-  onSelectedParentModeChange_(newModeIndex) {
+  private onSelectedParentModeChange_(newModeIndex: number) {
     if (this.currentSelectedParentModeIndex_ === newModeIndex) {
       return;
     }
@@ -1279,10 +1137,8 @@
   /**
    * Returns True if a new parentMode has been set and we have received an
    * update from Chrome.
-   * @return {boolean}
-   * @private
    */
-  hasNewParentModeBeenSet() {
+  private hasNewParentModeBeenSet(): boolean {
     if (this.currentSelectedParentModeIndex_ === -1) {
       return false;
     }
@@ -1294,10 +1150,8 @@
   /**
    * Returns True if a new mode has been set and we have received an update
    * from Chrome.
-   * @return {boolean}
-   * @private
    */
-  hasNewModeBeenSet() {
+  private hasNewModeBeenSet(): boolean {
     if (this.currentSelectedModeIndex_ === -1) {
       return false;
     }
@@ -1312,10 +1166,8 @@
 
   /**
    * Handles a change in |selectedModePref| triggered via the observer.
-   * @param {number} newModeIndex The new index value
-   * @private
    */
-  onSelectedModeChange_(newModeIndex) {
+  private onSelectedModeChange_(newModeIndex: number) {
     // We want to ignore all value changes to the pref due to the slider being
     // dragged. See http://crbug/845712 for more info.
     if (this.currentSelectedModeIndex_ === newModeIndex) {
@@ -1327,13 +1179,14 @@
       // update from Chrome and the mode differs from the current mode.
       return;
     }
-    /** @type {!chrome.system.display.DisplayProperties} */ const properties = {
-      displayMode: this.selectedDisplay.modes[
-          /** @type {number} */ (this.selectedModePref_.value)],
+
+    assertExists(this.selectedDisplay);
+    const properties: DisplayProperties = {
+      displayMode: this.selectedDisplay.modes[this.selectedModePref_.value],
     };
 
-    this.refreshRateList_ = this.parentModeToRefreshRateMap_.get(
-        /** @type {number} */ (this.selectedParentModePref_.value));
+    this.refreshRateList_ = castExists(this.parentModeToRefreshRateMap_.get(
+        this.selectedParentModePref_.value));
     getDisplayApi().setDisplayProperties(
         this.selectedDisplay.id, properties,
         () => this.setPropertiesCallback_());
@@ -1343,16 +1196,14 @@
    * Triggerend when the display size slider changes its value. This only
    * occurs when the value is committed (i.e. not while the slider is being
    * dragged).
-   * @private
    */
-  onSelectedZoomChange_() {
+  private onSelectedZoomChange_() {
     if (this.currentSelectedModeIndex_ === -1 || !this.selectedDisplay) {
       return;
     }
 
-    /** @type {!chrome.system.display.DisplayProperties} */ const properties = {
-      displayZoomFactor:
-          /** @type {number} */ (this.selectedZoomPref_.value),
+    const properties: DisplayProperties = {
+      displayZoomFactor: this.selectedZoomPref_.value,
     };
 
     getDisplayApi().setDisplayProperties(
@@ -1363,25 +1214,20 @@
   /**
    * Returns whether the option "Auto-rotate" is one of the shown options in
    * the rotation drop-down menu.
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @return {boolean|undefined}
-   * @private
    */
-  showAutoRotateOption_(selectedDisplay) {
+  private showAutoRotateOption_(selectedDisplay: DisplayUnitInfo): boolean
+      |undefined {
     return selectedDisplay.isAutoRotationAllowed;
   }
 
-  /**
-   * @param {!Event} event
-   * @private
-   */
-  onOrientationChange_(event) {
-    const target = /** @type {!HTMLSelectElement} */ (event.target);
-    const value = /** @type {number} */ (parseInt(target.value, 10));
+  private onOrientationChange_(event: Event) {
+    const select = cast(event.target, HTMLSelectElement);
+    const value = parseInt(select.value, 10);
 
+    assertExists(this.selectedDisplay);
     assert(value !== -1 || this.selectedDisplay.isAutoRotationAllowed);
 
-    /** @type {!chrome.system.display.DisplayProperties} */ const properties = {
+    const properties: DisplayProperties = {
       rotation: value,
     };
     getDisplayApi().setDisplayProperties(
@@ -1389,17 +1235,14 @@
         () => this.setPropertiesCallback_());
   }
 
-  /** @private */
-  onMirroredTap_(event) {
+  private onMirroredTap_(event: Event) {
     // Blur the control so that when the transition animation completes and
     // the UI is focused, the control does not receive focus. crbug.com/785070
-    event.target.blur();
+    (event.currentTarget as CrCheckboxElement).blur();
 
-    /** @type {!chrome.system.display.MirrorModeInfo} */
-    const mirrorModeInfo = {
-      mode: this.isMirrored_(this.displays) ?
-          chrome.system.display.MirrorMode.OFF :
-          chrome.system.display.MirrorMode.NORMAL,
+    const mirrorModeInfo: MirrorModeInfo = {
+      mode: this.isMirrored_(this.displays) ? MirrorMode.OFF :
+                                              MirrorMode.NORMAL,
     };
     getDisplayApi().setMirrorMode(mirrorModeInfo, () => {
       const error = chrome.runtime.lastError;
@@ -1409,35 +1252,28 @@
     });
   }
 
-  /** @private */
-  onUnifiedDesktopTap_() {
-    /** @type {!chrome.system.display.DisplayProperties} */ const properties = {
+  private onUnifiedDesktopTap_() {
+    const properties: DisplayProperties = {
       isUnified: !this.unifiedDesktopMode_,
     };
     getDisplayApi().setDisplayProperties(
         this.primaryDisplayId, properties, () => this.setPropertiesCallback_());
   }
 
-  /**
-   * @param {!Event} e
-   * @private
-   */
-  onOverscanTap_(e) {
+  private onOverscanTap_(e: Event) {
     e.preventDefault();
-    this.overscanDisplayId = this.selectedDisplay.id;
+    this.overscanDisplayId = this.selectedDisplay!.id;
     this.showOverscanDialog_(true);
   }
 
-  /** @private */
-  onCloseOverscanDialog_() {
-    focusWithoutInk(assert(this.shadowRoot.querySelector('#overscan')));
+  private onCloseOverscanDialog_() {
+    focusWithoutInk(castExists(this.shadowRoot!.getElementById('overscan')));
   }
 
-  /** @private */
-  updateDisplayInfo_() {
+  private updateDisplayInfo_() {
     let displayIds = '';
-    let primaryDisplay = undefined;
-    let selectedDisplay = undefined;
+    let primaryDisplay: DisplayUnitInfo|undefined = undefined;
+    let selectedDisplay: DisplayUnitInfo|undefined = undefined;
     for (let i = 0; i < this.displays.length; ++i) {
       const display = this.displays[i];
       if (displayIds) {
@@ -1471,8 +1307,7 @@
     });
   }
 
-  /** @private */
-  setPropertiesCallback_() {
+  private setPropertiesCallback_() {
     if (chrome.runtime.lastError) {
       console.error(
           'setDisplayProperties Error: ' + chrome.runtime.lastError.message);
@@ -1483,9 +1318,8 @@
    * Invoked when the status of Night Light or its schedule type are changed,
    * in order to update the schedule settings, such as whether to show the
    * custom schedule slider, and the schedule sub label.
-   * @private
    */
-  updateNightLightScheduleSettings_() {
+  private updateNightLightScheduleSettings_() {
     const scheduleType = this.getPref('ash.night_light.schedule_type').value;
     this.shouldOpenCustomScheduleCollapse_ =
         scheduleType === NightLightScheduleType.CUSTOM;
@@ -1500,21 +1334,16 @@
     }
   }
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  shouldShowArrangementSection_() {
+  private shouldShowArrangementSection_(): boolean {
     if (!this.displays) {
       return false;
     }
     return this.hasMultipleDisplays_() || this.isMirrored_(this.displays);
   }
 
-  /** @private */
-  onDisplaysChanged_() {
+  private onDisplaysChanged_() {
     flush();
-    const displayLayout = this.shadowRoot.querySelector('#displayLayout');
+    const displayLayout = this.shadowRoot!.querySelector('display-layout');
     if (displayLayout) {
       displayLayout.updateDisplays(
           this.displays, this.layouts, this.mirroringDestinationIds);
@@ -1522,4 +1351,10 @@
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    'settings-display': SettingsDisplayElement;
+  }
+}
+
 customElements.define(SettingsDisplayElement.is, SettingsDisplayElement);
diff --git a/chrome/browser/resources/settings/chromeos/device_page/display_layout.js b/chrome/browser/resources/settings/chromeos/device_page/display_layout.ts
similarity index 66%
rename from chrome/browser/resources/settings/chromeos/device_page/display_layout.js
rename to chrome/browser/resources/settings/chromeos/device_page/display_layout.ts
index 832c88d..046d3469 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/display_layout.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/display_layout.ts
@@ -13,60 +13,70 @@
 
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {IronResizableBehavior} from 'chrome://resources/polymer/v3_0/iron-resizable-behavior/iron-resizable-behavior.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {castExists} from '../assert_extras.js';
 
 import {DevicePageBrowserProxy, DevicePageBrowserProxyImpl} from './device_page_browser_proxy.js';
+import {getTemplate} from './display_layout.html.js';
 import {DragBehavior, DragBehaviorInterface, DragPosition} from './drag_behavior.js';
 import {LayoutBehavior, LayoutBehaviorInterface} from './layout_behavior.js';
 
+type DisplayUnitInfo = chrome.system.display.DisplayUnitInfo;
+type DisplayLayout = chrome.system.display.DisplayLayout;
+type Bounds = chrome.system.display.Bounds;
+
 /**
  * Container for DisplayUnitInfo.  Mostly here to make the DisplaySelectEvent
  * typedef more readable.
- * @typedef {{item: !chrome.system.display.DisplayUnitInfo}}
  */
-let InfoItem;
+interface InfoItem {
+  item: DisplayUnitInfo;
+}
 
 /**
  * Required member fields for events which select displays.
- * @typedef {{model: !InfoItem, target: !HTMLDivElement}}
  */
-let DisplaySelectEvent;
+interface DisplaySelectEvent {
+  model: InfoItem;
+  target: HTMLElement;
+}
 
-/** @type {number} */ const MIN_VISUAL_SCALE = .01;
+const MIN_VISUAL_SCALE = .01;
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {DragBehaviorInterface}
- * @implements {LayoutBehaviorInterface}
- */
-const DisplayLayoutElementBase = mixinBehaviors(
-    [IronResizableBehavior, DragBehavior, LayoutBehavior], PolymerElement);
+interface DisplayLayoutElement {
+  $: {
+    displayArea: HTMLElement,
+  };
+}
 
-/** @polymer */
+const DisplayLayoutElementBase =
+    mixinBehaviors(
+        [IronResizableBehavior, DragBehavior, LayoutBehavior],
+        PolymerElement) as {
+      new (): PolymerElement & DragBehaviorInterface & LayoutBehaviorInterface,
+    };
+
 class DisplayLayoutElement extends DisplayLayoutElementBase {
   static get is() {
     return 'display-layout';
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
     return {
       /**
        * Array of displays.
-       * @type {!Array<!chrome.system.display.DisplayUnitInfo>}
        */
       displays: Array,
 
-      /** @type {!chrome.system.display.DisplayUnitInfo|undefined} */
       selectedDisplay: Object,
 
       /**
        * The ratio of the display area div (in px) to DisplayUnitInfo.bounds.
-       * @type {number}
        */
       visualScale: {
         type: Number,
@@ -75,44 +85,47 @@
 
       /**
        * Ids for mirroring destination displays.
-       * @type {!Array<string>|undefined}
-       * @private
        */
       mirroringDestinationIds_: Array,
     };
   }
 
-  /** @override */
+  displays: DisplayUnitInfo[];
+  selectedDisplay?: DisplayUnitInfo;
+  visualScale: number;
+  private allowDisplayAlignmentApi_: boolean;
+  private browserProxy_: DevicePageBrowserProxy;
+  private hasDragStarted_: boolean;
+  private invalidDisplayId_: string;
+  private lastDragCoordinates_: {x: number, y: number}|null;
+  private mirroringDestinationIds_: string[];
+  private visualOffset_: {left: number, top: number};
+
   constructor() {
     super();
 
-    /** @private {!{left: number, top: number}} */
     this.visualOffset_ = {left: 0, top: 0};
 
     /**
      * Stores the previous coordinates of a display once dragging starts. Used
      * to calculate the delta during each step of the drag. Null when there is
      * no drag in progress.
-     * @private {?{x: number, y: number}}
      */
     this.lastDragCoordinates_ = null;
 
-    /** @private {!DevicePageBrowserProxy} */
     this.browserProxy_ = DevicePageBrowserProxyImpl.getInstance();
 
-    /** @private {boolean} */
     this.allowDisplayAlignmentApi_ =
         loadTimeData.getBoolean('allowDisplayAlignmentApi');
 
-    /** @private {string} */
     this.invalidDisplayId_ = loadTimeData.getString('invalidDisplayId');
 
-    /** @private {boolean} */
     this.hasDragStarted_ = false;
+
+    this.mirroringDestinationIds_ = [];
   }
 
-  /** @override */
-  disconnectedCallback() {
+  override disconnectedCallback() {
     super.disconnectedCallback();
 
     this.initializeDrag(false);
@@ -121,11 +134,10 @@
   /**
    * Called explicitly when |this.displays| and their associated |this.layouts|
    * have been fetched from chrome.
-   * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
-   * @param {!Array<!chrome.system.display.DisplayLayout>} layouts
-   * @param {!Array<string>} mirroringDestinationIds
    */
-  updateDisplays(displays, layouts, mirroringDestinationIds) {
+  updateDisplays(
+      displays: DisplayUnitInfo[], layouts: DisplayLayout[],
+      mirroringDestinationIds: string[]) {
     this.displays = displays;
     this.layouts = layouts;
     this.mirroringDestinationIds_ = mirroringDestinationIds;
@@ -152,10 +164,9 @@
    * Calculates the visual offset and scale for the display area
    * (i.e. the ratio of the display area div size to the area required to
    * contain the DisplayUnitInfo bounding boxes).
-   * @return {boolean} Whether the calculation was successful.
-   * @private
+   * @return Whether the calculation was successful.
    */
-  calculateVisualScale_() {
+  private calculateVisualScale_(): boolean {
     const displayAreaDiv = this.$.displayArea;
     if (!displayAreaDiv || !displayAreaDiv.offsetWidth || !this.displays ||
         !this.displays.length) {
@@ -211,21 +222,15 @@
     return true;
   }
 
-  /**
-   * @param {string} id
-   * @param {!chrome.system.display.Bounds} displayBounds
-   * @param {number} visualScale
-   * @param {number=} opt_offset
-   * @return {string} The style string for the div.
-   * @private
-   */
-  getDivStyle_(id, displayBounds, visualScale, opt_offset) {
+  private getDivStyle_(
+      id: string, _displayBounds: Bounds, _visualScale: number,
+      offset?: number): string {
     // This matches the size of the box-shadow or border in CSS.
-    /** @type {number} */ const BORDER = 1;
-    /** @type {number} */ const MARGIN = 4;
-    /** @type {number} */ const OFFSET = opt_offset || 0;
-    /** @type {number} */ const PADDING = 3;
-    const bounds = this.getCalculatedDisplayBounds(id, true /* notest */);
+    const BORDER = 1;
+    const MARGIN = 4;
+    const OFFSET = offset || 0;
+    const PADDING = 3;
+    const bounds = this.getCalculatedDisplayBounds(id, /* notest */ true);
     if (!bounds) {
       return '';
     }
@@ -241,17 +246,9 @@
         ' left: ' + left + 'px; top: ' + top + 'px';
   }
 
-  /**
-   * @param {number} mirroringDestinationIndex
-   * @param {number} mirroringDestinationDisplayNum
-   * @param {!Array<!chrome.system.display.DisplayUnitInfo>} displays
-   * @param {number} visualScale
-   * @return {string} The style string for the mirror div.
-   * @private
-   */
-  getMirrorDivStyle_(
-      mirroringDestinationIndex, mirroringDestinationDisplayNum, displays,
-      visualScale) {
+  private getMirrorDivStyle_(
+      mirroringDestinationIndex: number, mirroringDestinationDisplayNum: number,
+      displays: DisplayUnitInfo[], visualScale: number): string {
     // All destination displays have the same bounds as the mirroring source
     // display, but we add a little offset to each destination display's bounds
     // so that they can be distinguished from each other in the layout.
@@ -260,57 +257,29 @@
         (mirroringDestinationDisplayNum - mirroringDestinationIndex) * -4);
   }
 
-  /**
-   * @param {!chrome.system.display.DisplayUnitInfo} display
-   * @param {!chrome.system.display.DisplayUnitInfo} selectedDisplay
-   * @return {boolean}
-   * @private
-   */
-  isSelected_(display, selectedDisplay) {
+  private isSelected_(
+      display: DisplayUnitInfo, selectedDisplay: DisplayUnitInfo): boolean {
     return display.id === selectedDisplay.id;
   }
 
-  focusSelectedDisplay_() {
-    if (!this.selectedDisplay) {
-      return;
-    }
-    const children = Array.from(this.$.displayArea.children);
-    const selected =
-        children.find(display => display.id === '_' + this.selectedDisplay.id);
-    if (selected) {
-      selected.focus();
-    }
-  }
-
-  /**
-   * @param {!DisplaySelectEvent} e
-   * @private
-   */
-  onSelectDisplayTap_(e) {
-    const selectDisplayEvent = new CustomEvent(
-        'select-display', {composed: true, detail: e.model.item.id});
+  private dispatchSelectDisplayEvent_(displayId: DisplayUnitInfo['id']) {
+    const selectDisplayEvent =
+        new CustomEvent('select-display', {composed: true, detail: displayId});
     this.dispatchEvent(selectDisplayEvent);
-    // Force active in case the selected display was clicked.
-    // TODO(dpapad): Ask @stevenjb, why are we setting 'active' on a div?
-    e.target.active = true;
   }
 
-  /**
-   * @param {!DisplaySelectEvent} e
-   * @private
-   */
-  onFocus_(e) {
-    const selectDisplayEvent = new CustomEvent(
-        'select-display', {composed: true, detail: e.model.item.id});
-    this.dispatchEvent(selectDisplayEvent);
-    this.focusSelectedDisplay_();
+  private onSelectDisplayTap_(e: DisplaySelectEvent) {
+    this.dispatchSelectDisplayEvent_(e.model.item.id);
+    // Keep focused display in-sync with clicked display
+    e.target.focus();
   }
 
-  /**
-   * @param {string} id
-   * @param {?DragPosition} amount
-   */
-  onDrag_(id, amount) {
+  private onFocus_(e: DisplaySelectEvent) {
+    this.dispatchSelectDisplayEvent_(e.model.item.id);
+    e.target.focus();
+  }
+
+  private onDrag_(id: string, amount: DragPosition|null) {
     id = id.substr(1);  // Skip prefix
 
     let newBounds;
@@ -323,16 +292,12 @@
     } else {
       this.browserProxy_.highlightDisplay(id);
       // Make sure the dragged display is also selected.
-      if (id !== this.selectedDisplay.id) {
-        const selectDisplayEvent =
-            new CustomEvent('select-display', {composed: true, detail: id});
-        this.dispatchEvent(selectDisplayEvent);
+      if (id !== this.selectedDisplay!.id) {
+        this.dispatchSelectDisplayEvent_(id);
       }
 
       const calculatedBounds = this.getCalculatedDisplayBounds(id);
-      newBounds =
-          /** @type {chrome.system.display.Bounds} */ (
-              Object.assign({}, calculatedBounds));
+      newBounds = {...calculatedBounds};
       newBounds.left += Math.round(amount.x / this.visualScale);
       newBounds.top += Math.round(amount.y / this.visualScale);
 
@@ -367,10 +332,16 @@
         this.visualOffset_.left + Math.round(newBounds.left * this.visualScale);
     const top =
         this.visualOffset_.top + Math.round(newBounds.top * this.visualScale);
-    const div = this.shadowRoot.querySelector('#_' + id);
+    const div = castExists(this.shadowRoot!.getElementById(`_${id}`));
     div.style.left = '' + left + 'px';
     div.style.top = '' + top + 'px';
-    this.focusSelectedDisplay_();
+    div.focus();
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'display-layout': DisplayLayoutElement;
   }
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/device_page/display_overscan_dialog.js b/chrome/browser/resources/settings/chromeos/device_page/display_overscan_dialog.ts
similarity index 76%
rename from chrome/browser/resources/settings/chromeos/device_page/display_overscan_dialog.js
rename to chrome/browser/resources/settings/chromeos/device_page/display_overscan_dialog.ts
index 6d68a8e..6a4a4d58 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/display_overscan_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/device_page/display_overscan_dialog.ts
@@ -16,18 +16,27 @@
 import '../os_icons.js';
 import '../../settings_shared.css.js';
 
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getDisplayApi} from './device_page_browser_proxy.js';
+import {getTemplate} from './display_overscan_dialog.html.js';
 
-/** @polymer */
-class SettingsDisplayOverscanDialogElement extends PolymerElement {
+type Insets = chrome.system.display.Insets;
+
+export interface SettingsDisplayOverscanDialogElement {
+  $: {
+    dialog: CrDialogElement,
+  };
+}
+
+export class SettingsDisplayOverscanDialogElement extends PolymerElement {
   static get is() {
     return 'settings-display-overscan-dialog';
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
@@ -46,26 +55,27 @@
     };
   }
 
+  displayId: string;
+  private committed_: boolean;
+  private keyHandler_: (event: KeyboardEvent) => void;
+
   constructor() {
     super();
 
     /**
      * Keyboard event handler for overscan adjustments.
-     * @type {?function(!Event)}
-     * @private
      */
-    this.keyHandler_ = null;
+    this.keyHandler_ = this.handleKeyEvent_.bind(this);
   }
 
   open() {
-    this.keyHandler_ = this.handleKeyEvent_.bind(this);
     // We need to attach the event listener to |window|, not |this| so that
     // changing focus does not prevent key events from occurring.
     window.addEventListener('keydown', this.keyHandler_);
     this.committed_ = false;
     this.$.dialog.showModal();
     // Don't focus 'reset' by default. 'Tab' will focus 'OK'.
-    this.shadowRoot.querySelector('#reset').blur();
+    this.shadowRoot!.getElementById('reset')!.blur();
   }
 
   close() {
@@ -78,8 +88,7 @@
     }
   }
 
-  /** @private */
-  displayIdChanged_(newValue, oldValue) {
+  private displayIdChanged_(newValue: string, oldValue: string) {
     if (oldValue && !this.committed_) {
       getDisplayApi().overscanCalibrationReset(oldValue);
       getDisplayApi().overscanCalibrationComplete(oldValue);
@@ -91,23 +100,17 @@
     getDisplayApi().overscanCalibrationStart(newValue);
   }
 
-  /** @private */
-  onResetTap_() {
+  private onResetTap_() {
     getDisplayApi().overscanCalibrationReset(this.displayId);
   }
 
-  /** @private */
-  onSaveTap_() {
+  private onSaveTap_() {
     getDisplayApi().overscanCalibrationComplete(this.displayId);
     this.committed_ = true;
     this.close();
   }
 
-  /**
-   * @param {!Event} event
-   * @private
-   */
-  handleKeyEvent_(event) {
+  private handleKeyEvent_(event: KeyboardEvent) {
     if (event.altKey || event.ctrlKey || event.metaKey) {
       return;
     }
@@ -147,13 +150,8 @@
     event.preventDefault();
   }
 
-  /**
-   * @param {number} x
-   * @param {number} y
-   * @private
-   */
-  move_(x, y) {
-    /** @type {!chrome.system.display.Insets} */ const delta = {
+  private move_(x: number, y: number) {
+    const delta: Insets = {
       left: x,
       top: y,
       right: x ? -x : 0,  // negating 0 will produce a double.
@@ -162,13 +160,8 @@
     getDisplayApi().overscanCalibrationAdjust(this.displayId, delta);
   }
 
-  /**
-   * @param {number} x
-   * @param {number} y
-   * @private
-   */
-  resize_(x, y) {
-    /** @type {!chrome.system.display.Insets} */ const delta = {
+  private resize_(x: number, y: number) {
+    const delta: Insets = {
       left: x,
       top: y,
       right: x,
@@ -178,6 +171,12 @@
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    'settings-display-overscan-dialog': SettingsDisplayOverscanDialogElement;
+  }
+}
+
 customElements.define(
     SettingsDisplayOverscanDialogElement.is,
     SettingsDisplayOverscanDialogElement);
diff --git a/chrome/browser/resources/settings/chromeos/ensure_lazy_loaded.js b/chrome/browser/resources/settings/chromeos/ensure_lazy_loaded.ts
similarity index 70%
rename from chrome/browser/resources/settings/chromeos/ensure_lazy_loaded.js
rename to chrome/browser/resources/settings/chromeos/ensure_lazy_loaded.ts
index 754b4aa6..2f05590 100644
--- a/chrome/browser/resources/settings/chromeos/ensure_lazy_loaded.js
+++ b/chrome/browser/resources/settings/chromeos/ensure_lazy_loaded.ts
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-let lazyLoadPromise = null;
+let lazyLoadPromise: Promise<CustomElementConstructor[]>|null = null;
 
-/** @return {!Promise<void>} Resolves when the lazy load module is imported. */
-export function ensureLazyLoaded() {
+/** @return Resolves when the lazy load module is imported. */
+export function ensureLazyLoaded(): Promise<CustomElementConstructor[]> {
   if (!lazyLoadPromise) {
     const script = document.createElement('script');
     script.type = 'module';
@@ -22,7 +22,7 @@
     ];
 
     lazyLoadPromise = Promise.all(
-        lazyLoadPages.map(name => customElements.whenDefined(name)));
+        lazyLoadPages.map((name) => customElements.whenDefined(name)));
   }
   return lazyLoadPromise;
 }
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index dc1b84c..44f3289 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -12,6 +12,9 @@
   "chromeos/date_time_page/timezone_selector.ts",
   "chromeos/date_time_page/timezone_subpage.ts",
   "chromeos/device_page/device_page.ts",
+  "chromeos/device_page/display.ts",
+  "chromeos/device_page/display_layout.ts",
+  "chromeos/device_page/display_overscan_dialog.ts",
   "chromeos/device_page/pointers.ts",
   "chromeos/device_page/stylus.ts",
   "chromeos/google_assistant_page/google_assistant_page.ts",
@@ -132,7 +135,7 @@
   "chromeos/device_page/device_page_browser_proxy.js",
   "chromeos/device_page/drag_behavior.js",
   "chromeos/device_page/layout_behavior.js",
-  "chromeos/ensure_lazy_loaded.js",
+  "chromeos/ensure_lazy_loaded.ts",
   "chromeos/find_shortcut_behavior.js",
   "chromeos/global_scroll_target_behavior.js",
   "chromeos/google_assistant_page/google_assistant_browser_proxy.ts",
@@ -254,9 +257,6 @@
   "chromeos/crostini_page/crostini_port_forwarding_add_port_dialog.js",
   "chromeos/crostini_page/crostini_subpage.js",
   "chromeos/device_page/audio.js",
-  "chromeos/device_page/display.js",
-  "chromeos/device_page/display_layout.js",
-  "chromeos/device_page/display_overscan_dialog.js",
   "chromeos/device_page/keyboard.js",
   "chromeos/device_page/power.js",
   "chromeos/device_page/storage.js",
diff --git a/chrome/browser/safe_browsing/chrome_ui_manager_delegate.cc b/chrome/browser/safe_browsing/chrome_ui_manager_delegate.cc
index 570b888c..9cc64ca 100644
--- a/chrome/browser/safe_browsing/chrome_ui_manager_delegate.cc
+++ b/chrome/browser/safe_browsing/chrome_ui_manager_delegate.cc
@@ -67,7 +67,7 @@
     return false;
 
   extensions::ExtensionHost* extension_host =
-      extension_manager->GetExtensionHostForRenderFrameHost(
+      extension_manager->GetBackgroundHostForRenderFrameHost(
           web_contents->GetPrimaryMainFrame());
   return extension_host != nullptr;
 #else
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc
index 3dde5ee..42cad625 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc
@@ -316,6 +316,17 @@
   ack_.set_final_action(final_action);
 }
 
+BinaryUploadService::CancelRequests::CancelRequests(
+    enterprise_connectors::CloudOrLocalAnalysisSettings settings)
+    : cloud_or_local_settings_(std::move(settings)) {}
+
+BinaryUploadService::CancelRequests::~CancelRequests() = default;
+
+void BinaryUploadService::CancelRequests::set_user_action_id(
+    const std::string& user_action_id) {
+  user_action_id_ = user_action_id;
+}
+
 // static
 BinaryUploadService* BinaryUploadService::GetForProfile(
     Profile* profile,
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h
index bcc01205..90927a0 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h
@@ -242,6 +242,36 @@
         cloud_or_local_settings_;
   };
 
+  // A class to encapsulate requests to cancel.  Any request that match the
+  // given criteria is canceled.  This is best effort only, in some cases
+  // requests may have already started and can no longer be canceled.
+  class CancelRequests {
+   public:
+    explicit CancelRequests(
+        enterprise_connectors::CloudOrLocalAnalysisSettings settings);
+    virtual ~CancelRequests();
+    CancelRequests(const CancelRequests&) = delete;
+    CancelRequests& operator=(const CancelRequests&) = delete;
+    CancelRequests(CancelRequests&&) = delete;
+    CancelRequests& operator=(CancelRequests&&) = delete;
+
+    void set_user_action_id(const std::string& user_action_id);
+    const std::string& get_user_action_id() const { return user_action_id_; }
+
+    const enterprise_connectors::CloudOrLocalAnalysisSettings&
+    cloud_or_local_settings() const {
+      return cloud_or_local_settings_;
+    }
+
+   private:
+    std::string user_action_id_;
+
+    // Settings used to determine how the request is used in the cloud or
+    // locally.
+    enterprise_connectors::CloudOrLocalAnalysisSettings
+        cloud_or_local_settings_;
+  };
+
   static BinaryUploadService* GetForProfile(
       Profile* profile,
       const enterprise_connectors::AnalysisSettings& settings);
@@ -252,6 +282,11 @@
 
   // Send an acknowledgement for the request with the given token.
   virtual void MaybeAcknowledge(std::unique_ptr<Ack> ack) = 0;
+
+  // Cancel any requests that match the given criteria .  This is a best effort
+  // approach only, since it is possible that requests have been started in a
+  // way that they are no longer cancelable.
+  virtual void MaybeCancelRequests(std::unique_ptr<CancelRequests> cancel) = 0;
 };
 
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service.cc b/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service.cc
index b41ffe37..5dff763 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service.cc
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service.cc
@@ -221,6 +221,12 @@
   // Nothing to do for cloud upload service.
 }
 
+void CloudBinaryUploadService::MaybeCancelRequests(
+    std::unique_ptr<CancelRequests> cancel) {
+  // Nothing to do for cloud upload service.
+  // TODO(1374944): Might consider canceling requests in `request_queue_`.
+}
+
 void CloudBinaryUploadService::MaybeUploadForDeepScanningCallback(
     std::unique_ptr<CloudBinaryUploadService::Request> request,
     bool authorized) {
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service.h b/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service.h
index ead906e..bf49f044 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service.h
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/cloud_binary_upload_service.h
@@ -38,6 +38,7 @@
   // authorized to upload data, otherwise queue the request.
   void MaybeUploadForDeepScanning(std::unique_ptr<Request> request) override;
   void MaybeAcknowledge(std::unique_ptr<Ack> ack) override;
+  void MaybeCancelRequests(std::unique_ptr<CancelRequests> cancel) override;
 
   // Indicates whether the DM token/Connector combination is allowed to upload
   // data.
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/test_binary_upload_service.h b/chrome/browser/safe_browsing/cloud_content_scanning/test_binary_upload_service.h
index 7342ef7..57377a7 100644
--- a/chrome/browser/safe_browsing/cloud_content_scanning/test_binary_upload_service.h
+++ b/chrome/browser/safe_browsing/cloud_content_scanning/test_binary_upload_service.h
@@ -20,6 +20,7 @@
 
   void MaybeUploadForDeepScanning(std::unique_ptr<Request> request) override;
   void MaybeAcknowledge(std::unique_ptr<Ack> ack) override {}
+  void MaybeCancelRequests(std::unique_ptr<CancelRequests> cancel) override {}
   void SetResponse(Result result,
                    enterprise_connectors::ContentAnalysisResponse response);
 
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
index 3f884737..f14ff7ab 100644
--- a/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_request_unittest.cc
@@ -144,6 +144,8 @@
     ASSERT_TRUE(base::Contains(requests_tokens_, ack->ack().request_token()));
   }
 
+  void MaybeCancelRequests(std::unique_ptr<CancelRequests> cancel) override {}
+
   void SetResponse(const base::FilePath& path,
                    BinaryUploadService::Result result,
                    enterprise_connectors::ContentAnalysisResponse response) {
diff --git a/chrome/browser/safe_browsing/download_protection/download_request_maker.cc b/chrome/browser/safe_browsing/download_protection/download_request_maker.cc
index eaabbe4..565c2a6 100644
--- a/chrome/browser/safe_browsing/download_protection/download_request_maker.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_request_maker.cc
@@ -30,6 +30,11 @@
 
 namespace {
 
+// The version of this client supporting tailored warnings.
+// Please update the description of TailoredInfo field in csd.proto when
+// changing this value.
+constexpr int kTailoredWarningVersion = 1;
+
 DownloadRequestMaker::TabUrls TabUrlsFromWebContents(
     content::WebContents* web_contents) {
   DownloadRequestMaker::TabUrls result;
@@ -162,6 +167,8 @@
   request_->set_locale(g_browser_process->GetApplicationLocale());
   request_->set_file_basename(target_file_path_.BaseName().AsUTF8Unsafe());
 
+  PopulateTailoredInfo();
+
   file_analyzer_->Start(
       target_file_path_, full_path_,
       base::BindOnce(&DownloadRequestMaker::OnFileFeatureExtractionDone,
@@ -256,4 +263,10 @@
   std::move(callback_).Run(std::move(request_));
 }
 
+void DownloadRequestMaker::PopulateTailoredInfo() {
+  ClientDownloadRequest::TailoredInfo tailored_info;
+  tailored_info.set_version(kTailoredWarningVersion);
+  *request_->mutable_tailored_info() = tailored_info;
+}
+
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/download_protection/download_request_maker.h b/chrome/browser/safe_browsing/download_protection/download_request_maker.h
index 09c6941d..8cde2125 100644
--- a/chrome/browser/safe_browsing/download_protection/download_request_maker.h
+++ b/chrome/browser/safe_browsing/download_protection/download_request_maker.h
@@ -74,6 +74,9 @@
   // Callback when the history service has retrieved the tab redirects.
   void OnGotTabRedirects(history::RedirectList redirect_list);
 
+  // Populates the tailored info field for tailored warnings.
+  void PopulateTailoredInfo();
+
   raw_ptr<content::BrowserContext> browser_context_;
   std::unique_ptr<ClientDownloadRequest> request_;
   const scoped_refptr<BinaryFeatureExtractor> binary_feature_extractor_;
diff --git a/chrome/browser/safe_browsing/download_protection/download_request_maker_unittest.cc b/chrome/browser/safe_browsing/download_protection/download_request_maker_unittest.cc
index e88eb48..c69bade 100644
--- a/chrome/browser/safe_browsing/download_protection/download_request_maker_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_request_maker_unittest.cc
@@ -351,6 +351,41 @@
             ChromeUserPopulation::ENHANCED_PROTECTION);
 }
 
+TEST_F(DownloadRequestMakerTest, PopulateTailoredInfo) {
+  base::RunLoop run_loop;
+  base::FilePath tmp_path(FILE_PATH_LITERAL("temp_path"));
+
+  DownloadRequestMaker request_maker(
+      mock_feature_extractor_, &profile_, DownloadRequestMaker::TabUrls(),
+      /*target_file_path=*/base::FilePath(), tmp_path,
+      /*source_url=*/GURL(),
+      /*sha256_hash=*/"",
+      /*length=*/0,
+      /*resources=*/std::vector<ClientDownloadRequest::Resource>(),
+      /*is_user_initiated=*/true,
+      /*referrer_chain_data=*/nullptr);
+
+  EXPECT_CALL(*mock_feature_extractor_, CheckSignature(tmp_path, _))
+      .WillOnce(Return());
+  EXPECT_CALL(*mock_feature_extractor_, ExtractImageFeatures(tmp_path, _, _, _))
+      .WillRepeatedly(Return(true));
+
+  std::unique_ptr<ClientDownloadRequest> request;
+  request_maker.Start(base::BindOnce(
+      [](base::RunLoop* run_loop,
+         std::unique_ptr<ClientDownloadRequest>* request_target,
+         std::unique_ptr<ClientDownloadRequest> request) {
+        run_loop->Quit();
+        *request_target = std::move(request);
+      },
+      &run_loop, &request));
+
+  run_loop.Run();
+
+  ASSERT_NE(request, nullptr);
+  EXPECT_EQ(request->tailored_info().version(), 1);
+}
+
 TEST_F(DownloadRequestMakerTest, PopulatesFileBasename) {
   base::RunLoop run_loop;
   base::FilePath tmp_path(FILE_PATH_LITERAL("temp_path"));
diff --git a/chrome/browser/ui/ash/system_tray_client_impl_browsertest.cc b/chrome/browser/ui/ash/system_tray_client_impl_browsertest.cc
index 4c0dd3c..0937216 100644
--- a/chrome/browser/ui/ash/system_tray_client_impl_browsertest.cc
+++ b/chrome/browser/ui/ash/system_tray_client_impl_browsertest.cc
@@ -14,6 +14,7 @@
 #include "ash/system/model/system_tray_model.h"
 #include "base/i18n/time_formatting.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/ash/login/lock/screen_locker_tester.h"
@@ -52,6 +53,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/chromeos/devicetype_utils.h"
 #include "url/gurl.h"
@@ -59,7 +61,7 @@
 using ::ash::ProfileHelper;
 using user_manager::UserManager;
 
-using SystemTrayClientEnterpriseTest = policy::DevicePolicyCrosBrowserTest;
+namespace {
 
 const char kManager[] = "admin@example.com";
 const char16_t kManager16[] = u"admin@example.com";
@@ -68,7 +70,25 @@
 const char kManagedUser[] = "user@example.com";
 const char kManagedGaiaID[] = "33333";
 
-IN_PROC_BROWSER_TEST_F(SystemTrayClientEnterpriseTest, TrayEnterprise) {
+}  // namespace
+
+// Parameterized by feature QsRevamp.
+class SystemTrayClientEnterpriseTest
+    : public policy::DevicePolicyCrosBrowserTest,
+      public testing::WithParamInterface<bool> {
+ public:
+  SystemTrayClientEnterpriseTest() {
+    feature_list_.InitWithFeatureState(ash::features::kQsRevamp, GetParam());
+  }
+
+  base::test::ScopedFeatureList feature_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(QsRevamp,
+                         SystemTrayClientEnterpriseTest,
+                         testing::Bool());
+
+IN_PROC_BROWSER_TEST_P(SystemTrayClientEnterpriseTest, TrayEnterprise) {
   auto test_api = ash::SystemTrayTestApi::Create();
 
   // Managed devices show an item in the menu.
@@ -304,19 +324,19 @@
   EXPECT_TRUE(tray_test_api->Is24HourClock());
 }
 
-class SystemTrayClientEnterpriseAccountTest : public ash::LoginManagerTest {
+// Parameterized by feature QsRevamp.
+class SystemTrayClientEnterpriseAccountTest
+    : public ash::LoginManagerTest,
+      public testing::WithParamInterface<bool> {
  protected:
-  SystemTrayClientEnterpriseAccountTest() : LoginManagerTest() {
+  SystemTrayClientEnterpriseAccountTest() {
+    feature_list_.InitWithFeatureState(ash::features::kQsRevamp, GetParam());
     std::unique_ptr<ash::ScopedUserPolicyUpdate> scoped_user_policy_update =
         user_policy_mixin_.RequestPolicyUpdate();
     scoped_user_policy_update->policy_data()->set_managed_by(kManager);
   }
-  SystemTrayClientEnterpriseAccountTest(
-      const SystemTrayClientEnterpriseAccountTest&) = delete;
-  SystemTrayClientEnterpriseAccountTest& operator=(
-      const SystemTrayClientEnterpriseAccountTest&) = delete;
-  ~SystemTrayClientEnterpriseAccountTest() override = default;
 
+  base::test::ScopedFeatureList feature_list_;
   const ash::LoginManagerMixin::TestUserInfo unmanaged_user_{
       AccountId::FromUserEmailGaiaId(kNewUser, kNewGaiaID)};
   const ash::LoginManagerMixin::TestUserInfo managed_user_{
@@ -327,7 +347,11 @@
                                       {managed_user_, unmanaged_user_}};
 };
 
-IN_PROC_BROWSER_TEST_F(SystemTrayClientEnterpriseAccountTest,
+INSTANTIATE_TEST_SUITE_P(QsRevamp,
+                         SystemTrayClientEnterpriseAccountTest,
+                         testing::Bool());
+
+IN_PROC_BROWSER_TEST_P(SystemTrayClientEnterpriseAccountTest,
                        TrayEnterpriseManagedAccount) {
   auto test_api = ash::SystemTrayTestApi::Create();
 
@@ -361,7 +385,7 @@
             test_api->GetBubbleViewTooltip(ash::VIEW_ID_QS_MANAGED_BUTTON));
 }
 
-IN_PROC_BROWSER_TEST_F(SystemTrayClientEnterpriseAccountTest,
+IN_PROC_BROWSER_TEST_P(SystemTrayClientEnterpriseAccountTest,
                        TrayEnterpriseUnmanagedAccount) {
   auto test_api = ash::SystemTrayTestApi::Create();
 
@@ -381,19 +405,18 @@
   SystemTrayClientEnterpriseSessionRestoreTest() {
     login_mixin_.set_session_restore_enabled();
   }
-  SystemTrayClientEnterpriseSessionRestoreTest(
-      const SystemTrayClientEnterpriseSessionRestoreTest&) = delete;
-  SystemTrayClientEnterpriseSessionRestoreTest& operator=(
-      const SystemTrayClientEnterpriseSessionRestoreTest&) = delete;
-  ~SystemTrayClientEnterpriseSessionRestoreTest() override = default;
 };
 
-IN_PROC_BROWSER_TEST_F(SystemTrayClientEnterpriseSessionRestoreTest,
+INSTANTIATE_TEST_SUITE_P(QsRevamp,
+                         SystemTrayClientEnterpriseSessionRestoreTest,
+                         testing::Bool());
+
+IN_PROC_BROWSER_TEST_P(SystemTrayClientEnterpriseSessionRestoreTest,
                        PRE_SessionRestore) {
   LoginUser(managed_user_.account_id);
 }
 
-IN_PROC_BROWSER_TEST_F(SystemTrayClientEnterpriseSessionRestoreTest,
+IN_PROC_BROWSER_TEST_P(SystemTrayClientEnterpriseSessionRestoreTest,
                        SessionRestore) {
   auto test_api = ash::SystemTrayTestApi::Create();
 
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.cc b/chrome/browser/ui/extensions/extension_action_view_controller.cc
index 4f01922..7618f97 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.cc
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.cc
@@ -428,6 +428,24 @@
   }
 }
 
+ToolbarActionViewController::HoverCardPolicyState
+ExtensionActionViewController::GetHoverCardPolicyState() const {
+  // An extension pinned by admin is also installed by admin. Thus,
+  // "pinned by admin" has preference.
+  auto* const model = ToolbarActionsModel::Get(browser_->profile());
+  if (model->IsActionForcePinned(GetId()))
+    return HoverCardPolicyState::kPinnedByAdmin;
+
+  scoped_refptr<const extensions::Extension> extension =
+      extensions::ExtensionRegistry::Get(browser_->profile())
+          ->enabled_extensions()
+          .GetByID(GetId());
+  if (extensions::Manifest::IsPolicyLocation(extension->location()))
+    return HoverCardPolicyState::kInstalledByAdmin;
+
+  return HoverCardPolicyState::kNone;
+}
+
 bool ExtensionActionViewController::CanHandleAccelerators() const {
   if (!ExtensionIsValid())
     return false;
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.h b/chrome/browser/ui/extensions/extension_action_view_controller.h
index 8bb5494..10ba8365 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.h
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.h
@@ -72,6 +72,8 @@
   std::u16string GetTooltip(content::WebContents* web_contents) const override;
   ToolbarActionViewController::HoverCardState GetHoverCardState(
       content::WebContents* web_contents) const override;
+  ToolbarActionViewController::HoverCardPolicyState GetHoverCardPolicyState()
+      const override;
   extensions::SitePermissionsHelper::SiteInteraction GetSiteInteraction(
       content::WebContents* web_contents) const override;
   bool IsEnabled(content::WebContents* web_contents) const override;
diff --git a/chrome/browser/ui/tabs/tab_strip_model.cc b/chrome/browser/ui/tabs/tab_strip_model.cc
index 09144bcb..1a3e5076 100644
--- a/chrome/browser/ui/tabs/tab_strip_model.cc
+++ b/chrome/browser/ui/tabs/tab_strip_model.cc
@@ -1038,7 +1038,7 @@
   // Ensure that the indices are nonempty, sorted, and unique.
   DCHECK_GT(indices.size(), 0u);
   DCHECK(base::ranges::is_sorted(indices));
-  DCHECK(base::ranges::adjacent_find(indices) == indices.end());
+  DCHECK(std::adjacent_find(indices.begin(), indices.end()) == indices.end());
 
   // The odds of |new_group| colliding with an existing group are astronomically
   // low. If there is a collision, a DCHECK will fail in |AddToNewGroupImpl()|,
@@ -1058,7 +1058,7 @@
 
   // Ensure that the indices are sorted and unique.
   DCHECK(base::ranges::is_sorted(indices));
-  DCHECK(base::ranges::adjacent_find(indices) == indices.end());
+  DCHECK(std::adjacent_find(indices.begin(), indices.end()) == indices.end());
   CHECK(ContainsIndex(*(indices.begin())));
   CHECK(ContainsIndex(*(indices.rbegin())));
 
diff --git a/chrome/browser/ui/toolbar/test_toolbar_action_view_controller.cc b/chrome/browser/ui/toolbar/test_toolbar_action_view_controller.cc
index 70b6721..856bd6f7 100644
--- a/chrome/browser/ui/toolbar/test_toolbar_action_view_controller.cc
+++ b/chrome/browser/ui/toolbar/test_toolbar_action_view_controller.cc
@@ -6,11 +6,9 @@
 
 #include <string>
 
-#include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/extensions/site_permissions_helper.h"
 #include "chrome/browser/ui/toolbar/toolbar_action_view_delegate.h"
 #include "ui/gfx/image/image.h"
-#include "ui/gfx/image/image_skia.h"
 
 TestToolbarActionViewController::TestToolbarActionViewController(
     const std::string& id)
@@ -58,6 +56,11 @@
       kExtensionDoesNotWantAccess;
 }
 
+ToolbarActionViewController::HoverCardPolicyState
+TestToolbarActionViewController::GetHoverCardPolicyState() const {
+  return ToolbarActionViewController::HoverCardPolicyState::kNone;
+}
+
 bool TestToolbarActionViewController::IsEnabled(
     content::WebContents* web_contents) const {
   return is_enabled_;
diff --git a/chrome/browser/ui/toolbar/test_toolbar_action_view_controller.h b/chrome/browser/ui/toolbar/test_toolbar_action_view_controller.h
index 75e0d3c571..d297bca 100644
--- a/chrome/browser/ui/toolbar/test_toolbar_action_view_controller.h
+++ b/chrome/browser/ui/toolbar/test_toolbar_action_view_controller.h
@@ -33,6 +33,8 @@
   std::u16string GetTooltip(content::WebContents* web_contents) const override;
   ToolbarActionViewController::HoverCardState GetHoverCardState(
       content::WebContents* web_contents) const override;
+  ToolbarActionViewController::HoverCardPolicyState GetHoverCardPolicyState()
+      const override;
   bool IsEnabled(content::WebContents* web_contents) const override;
   bool IsShowingPopup() const override;
   bool IsRequestingSiteAccess(
diff --git a/chrome/browser/ui/toolbar/toolbar_action_view_controller.h b/chrome/browser/ui/toolbar/toolbar_action_view_controller.h
index 598ea8cf..607082a 100644
--- a/chrome/browser/ui/toolbar/toolbar_action_view_controller.h
+++ b/chrome/browser/ui/toolbar/toolbar_action_view_controller.h
@@ -62,7 +62,9 @@
     kMaxValue = kRequestAccessButton,
   };
 
-  // Hover card states for a toolbar action view.
+  // Site access state for the toolbar action view's hover card.
+  // TODO(emiliapaz): Change the enum name to reflect "site acesss" state, or
+  // bundle both enums in one struct.
   enum class HoverCardState {
     // All extensions are allowed on the current site by the user.
     kAllExtensionsAllowed,
@@ -80,6 +82,17 @@
     kExtensionDoesNotWantAccess,
   };
 
+  // Policy state for the toolbar action view's hover card.
+  enum class HoverCardPolicyState {
+    kNone,
+
+    // Extension is force pinned by administrator.
+    kPinnedByAdmin,
+
+    // Extension if force installed by administrator.
+    kInstalledByAdmin
+  };
+
   virtual ~ToolbarActionViewController() = default;
 
   // Returns the unique ID of this particular action. For extensions, this is
@@ -111,6 +124,8 @@
   virtual HoverCardState GetHoverCardState(
       content::WebContents* web_contents) const = 0;
 
+  virtual HoverCardPolicyState GetHoverCardPolicyState() const = 0;
+
   // Returns true if the action should be enabled on the given |web_contents|.
   virtual bool IsEnabled(content::WebContents* web_contents) const = 0;
 
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
index b6f9d4d..fd78b66 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
@@ -129,8 +129,7 @@
               : nullptr),
       display_mode_(display_mode),
       action_hover_card_controller_(
-          std::make_unique<ToolbarActionHoverCardController>(browser->profile(),
-                                                             this)) {
+          std::make_unique<ToolbarActionHoverCardController>(this)) {
   // The container shouldn't show unless / until we have extensions available.
   SetVisible(false);
 
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_interactive_uitest.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_interactive_uitest.cc
index de52759..eae19cf 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_interactive_uitest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_interactive_uitest.cc
@@ -16,9 +16,9 @@
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/common/chrome_paths.h"
 #include "extensions/browser/extension_system.h"
+#include "extensions/common/extension_builder.h"
 #include "extensions/test/test_extension_dir.h"
 #include "net/dns/mock_host_resolver.h"
-#include "ui/events/base_event_utils.h"
 #include "ui/views/layout/animating_layout_manager_test_util.h"
 #include "ui/views/view_utils.h"
 
@@ -45,7 +45,18 @@
   // Allow it to finish laying out appropriately.
   auto* container = GetExtensionsToolbarContainer();
   container->GetWidget()->LayoutRootViewIfNecessary();
+  return extension;
+}
 
+scoped_refptr<const extensions::Extension>
+ExtensionsToolbarUITest::ForceInstallExtension(const std::string& name) {
+  scoped_refptr<const extensions::Extension> extension =
+      extensions::ExtensionBuilder("extension")
+          .SetLocation(extensions::mojom::ManifestLocation::kExternalPolicy)
+          .Build();
+  extensions::ExtensionSystem::Get(browser()->profile())
+      ->extension_service()
+      ->AddExtension(extension.get());
   return extension;
 }
 
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_interactive_uitest.h b/chrome/browser/ui/views/extensions/extensions_toolbar_interactive_uitest.h
index fba08495..271c6c2 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_interactive_uitest.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_interactive_uitest.h
@@ -55,6 +55,9 @@
       const std::string& path,
       bool allow_incognito = false);
 
+  scoped_refptr<const extensions::Extension> ForceInstallExtension(
+      const std::string& name);
+
   // Loads and returns a extension given a `name`.
   scoped_refptr<const extensions::Extension> InstallExtension(
       const std::string& name);
diff --git a/chrome/browser/ui/views/performance_controls/high_efficiency_chip_view_browsertest.cc b/chrome/browser/ui/views/performance_controls/high_efficiency_chip_view_browsertest.cc
index 621c7a1..31e2d57e 100644
--- a/chrome/browser/ui/views/performance_controls/high_efficiency_chip_view_browsertest.cc
+++ b/chrome/browser/ui/views/performance_controls/high_efficiency_chip_view_browsertest.cc
@@ -65,7 +65,7 @@
     return promo_controller;
   }
 
-  PageActionIconView* GetHighEfficiencyChipView() {
+  PageActionIconView* GetPageActionIconView() {
     BrowserView* browser_view =
         BrowserView::GetBrowserViewForBrowser(browser());
     return browser_view->GetLocationBarView()
@@ -73,10 +73,9 @@
         ->GetIconView(PageActionIconType::kHighEfficiency);
   }
 
-  void ClickHighEfficiencyChip() {
+  void PressButton(views::Button* button) {
     views::test::InteractionTestUtilSimulatorViews::PressButton(
-        GetHighEfficiencyChipView(),
-        ui::test::InteractionTestUtil::InputType::kMouse);
+        button, ui::test::InteractionTestUtil::InputType::kMouse);
   }
 
   void SetTabDiscardState(bool is_discarded) {
@@ -101,31 +100,8 @@
     waiter.WaitIfNeededAndGet();
   }
 
-  user_education::HelpBubbleView* GetHelpBubbleView() {
-    return GetFeaturePromoController()
-        ->promo_bubble_for_testing()
-        ->AsA<user_education::HelpBubbleViews>()
-        ->bubble_view();
-  }
-
-  void ClickIPHCancelButton() {
-    views::test::WidgetDestroyedWaiter waiter(GetHelpBubbleView()->GetWidget());
-    views::test::InteractionTestUtilSimulatorViews::PressButton(
-        GetHelpBubbleView()->GetDefaultButtonForTesting(),
-        ui::test::InteractionTestUtil::InputType::kMouse);
-    waiter.Wait();
-  }
-
-  void ClickIPHSettingsButton() {
-    views::test::WidgetDestroyedWaiter waiter(GetHelpBubbleView()->GetWidget());
-    views::test::InteractionTestUtilSimulatorViews::PressButton(
-        GetHelpBubbleView()->GetNonDefaultButtonForTesting(0),
-        ui::test::InteractionTestUtil::InputType::kMouse);
-    waiter.Wait();
-  }
-
   views::InkDropState GetInkDropState() {
-    return views::InkDrop::Get(GetHighEfficiencyChipView())
+    return views::InkDrop::Get(GetPageActionIconView())
         ->GetInkDrop()
         ->GetTargetInkDropState();
   }
@@ -135,13 +111,17 @@
 };
 
 IN_PROC_BROWSER_TEST_F(HighEfficiencyChipViewBrowserTest,
-                       NavigatesOnIPHSettingsLinkClicked) {
+                       PromoCustomActionClicked) {
   auto lock = BrowserFeaturePromoController::BlockActiveWindowCheckForTesting();
+  auto* const promo_controller = GetFeaturePromoController();
 
   EXPECT_FALSE(GetFeaturePromoController()->IsPromoActive(
       feature_engagement::kIPHHighEfficiencyInfoModeFeature));
 
   SetTabDiscardState(true);
+  PageActionIconView* icon = GetPageActionIconView();
+  EXPECT_TRUE(icon->GetVisible());
+
   WaitForIPHToShow();
 
   EXPECT_TRUE(GetFeaturePromoController()->IsPromoActive(
@@ -149,7 +129,11 @@
 
   content::TestNavigationObserver navigation_observer(
       browser()->tab_strip_model()->GetWebContentsAt(0));
-  ClickIPHSettingsButton();
+  auto* promo_bubble = promo_controller->promo_bubble_for_testing()
+                           ->AsA<user_education::HelpBubbleViews>()
+                           ->bubble_view();
+  auto* custom_action_button = promo_bubble->GetNonDefaultButtonForTesting(0);
+  PressButton(custom_action_button);
   navigation_observer.Wait();
 
   GURL expected(chrome::kChromeUIPerformanceSettingsURL);
@@ -157,40 +141,56 @@
 }
 
 IN_PROC_BROWSER_TEST_F(HighEfficiencyChipViewBrowserTest,
-                       PromoDismissesOnCancelClick) {
+                       PromoDismissesOnChipClick) {
   auto lock = BrowserFeaturePromoController::BlockActiveWindowCheckForTesting();
 
   SetTabDiscardState(true);
+  PageActionIconView* icon = GetPageActionIconView();
   WaitForIPHToShow();
 
   EXPECT_TRUE(GetFeaturePromoController()->IsPromoActive(
       feature_engagement::kIPHHighEfficiencyInfoModeFeature));
 
-  ClickHighEfficiencyChip();
+  PressButton(icon);
 
   // Expect the bubble to be open and the promo to be closed.
   EXPECT_FALSE(GetFeaturePromoController()->IsPromoActive(
       feature_engagement::kIPHHighEfficiencyInfoModeFeature));
-  EXPECT_NE(GetHighEfficiencyChipView()->GetBubble(), nullptr);
+  EXPECT_NE(icon->GetBubble(), nullptr);
 }
 
 IN_PROC_BROWSER_TEST_F(HighEfficiencyChipViewBrowserTest,
                        ShowAndHideInkDropWithPromo) {
   auto lock = BrowserFeaturePromoController::BlockActiveWindowCheckForTesting();
+  auto* const promo_controller = GetFeaturePromoController();
 
   EXPECT_FALSE(GetFeaturePromoController()->IsPromoActive(
       feature_engagement::kIPHHighEfficiencyInfoModeFeature));
 
   SetTabDiscardState(true);
+  PageActionIconView* icon = GetPageActionIconView();
+  EXPECT_TRUE(icon->GetVisible());
+
   WaitForIPHToShow();
 
   EXPECT_TRUE(GetFeaturePromoController()->IsPromoActive(
       feature_engagement::kIPHHighEfficiencyInfoModeFeature));
+
   EXPECT_EQ(GetInkDropState(), views::InkDropState::ACTIVATED);
 
-  ClickIPHCancelButton();
+  auto* promo_bubble = promo_controller->promo_bubble_for_testing()
+                           ->AsA<user_education::HelpBubbleViews>()
+                           ->bubble_view();
 
-  EXPECT_FALSE(GetFeaturePromoController()->IsPromoActive(
+  views::test::WidgetDestroyedWaiter waiter(promo_bubble->GetWidget());
+  auto* default_action_button = promo_bubble->GetDefaultButtonForTesting();
+  PressButton(default_action_button);
+  waiter.Wait();
+
+  EXPECT_FALSE(browser()->window()->IsFeaturePromoActive(
       feature_engagement::kIPHHighEfficiencyInfoModeFeature));
-  EXPECT_TRUE(GetInkDropState() == views::InkDropState::DEACTIVATED);
+
+  views::InkDropState current_state = GetInkDropState();
+  EXPECT_TRUE(current_state == views::InkDropState::HIDDEN ||
+              current_state == views::InkDropState::DEACTIVATED);
 }
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.cc b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.cc
index 0c1d438..9977d686 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.cc
@@ -108,6 +108,25 @@
   return l10n_util::GetStringFUTF16(title_id, host);
 }
 
+std::u16string GetFootnotePolicyText(
+    ToolbarActionViewController::HoverCardPolicyState state) {
+  int text_id = -1;
+  switch (state) {
+    case ToolbarActionViewController::HoverCardPolicyState::kPinnedByAdmin:
+      text_id =
+          IDS_EXTENSIONS_TOOLBAR_ACTION_HOVER_CARD_FOOTER_POLICY_LABEL_PINNED_TEXT;
+      break;
+    case ToolbarActionViewController::HoverCardPolicyState::kInstalledByAdmin:
+      text_id =
+          IDS_EXTENSIONS_TOOLBAR_ACTION_HOVER_CARD_FOOTER_POLICY_LABEL_INSTALLED_TEXT;
+      break;
+    case ToolbarActionViewController::HoverCardPolicyState::kNone:
+      NOTREACHED();
+      break;
+  }
+  return l10n_util::GetStringUTF16(text_id);
+}
+
 // Label that renders its background in a solid color. Placed in front of a
 // normal label either by being later in the draw order or on a layer, it can
 // be used to animate a fade-out.
@@ -244,8 +263,6 @@
 
     policy_label_ = AddChildView(
         std::make_unique<FadeLabel>(views::style::CONTEXT_DIALOG_BODY_TEXT));
-    policy_label_->SetText(l10n_util::GetStringUTF16(
-        IDS_EXTENSIONS_TOOLBAR_ACTION_HOVER_CARD_FOOTER_POLICY_LABEL_TEXT));
 
     // Separator doesn't need margin to span the dialog width.
     auto style_label = [](FadeLabel* label) {
@@ -272,12 +289,16 @@
         color_provider->GetColor(ui::kColorBubbleFooterBorder)));
   }
 
-  void UpdateContent(ToolbarActionViewController::HoverCardState state,
-                     bool show_policy_label,
-                     std::u16string host) {
-    bool show_site_access_labels = state !=
+  void UpdateContent(
+      ToolbarActionViewController::HoverCardState site_access_state,
+      ToolbarActionViewController::HoverCardPolicyState policy_state,
+      std::u16string host) {
+    bool show_site_access_labels = site_access_state !=
                                    ToolbarActionViewController::HoverCardState::
                                        kExtensionDoesNotWantAccess;
+    bool show_policy_label =
+        policy_state !=
+        ToolbarActionViewController::HoverCardPolicyState::kNone;
     bool footer_visible = show_site_access_labels || show_policy_label;
     SetVisible(footer_visible);
 
@@ -290,9 +311,13 @@
     separator_->SetVisible(show_site_access_labels && show_policy_label);
 
     if (show_site_access_labels) {
-      title_label_->SetText(GetFootnoteTitle(state));
-      description_label_->SetText(GetFootnoteDescription(state, host));
+      title_label_->SetText(GetFootnoteTitle(site_access_state));
+      description_label_->SetText(
+          GetFootnoteDescription(site_access_state, host));
     }
+
+    if (show_policy_label)
+      policy_label_->SetText(GetFootnotePolicyText(policy_state));
   }
 
   void SetFade(double percent) {
@@ -317,12 +342,10 @@
 // ----------------------------------------------------------
 
 ToolbarActionHoverCardBubbleView::ToolbarActionHoverCardBubbleView(
-    ToolbarActionView* action_view,
-    Profile* profile)
+    ToolbarActionView* action_view)
     : BubbleDialogDelegateView(action_view,
                                views::BubbleBorder::TOP_LEFT,
-                               views::BubbleBorder::STANDARD_SHADOW),
-      model_(ToolbarActionsModel::Get(profile)) {
+                               views::BubbleBorder::STANDARD_SHADOW) {
   DCHECK(base::FeatureList::IsEnabled(
       extensions_features::kExtensionsMenuAccessControl));
 
@@ -401,7 +424,7 @@
   title_label_->SetText(action_controller->GetActionName());
   footnote_view_->UpdateContent(
       action_controller->GetHoverCardState(web_contents),
-      model_->IsActionForcePinned(action_controller->GetId()),
+      action_controller->GetHoverCardPolicyState(),
       GetCurrentHost(web_contents));
 }
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.h b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.h
index 320e4db..82c3ff3c 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.h
@@ -18,8 +18,7 @@
     : public views::BubbleDialogDelegateView {
  public:
   METADATA_HEADER(ToolbarActionHoverCardBubbleView);
-  explicit ToolbarActionHoverCardBubbleView(ToolbarActionView* action_view,
-                                            Profile* profile);
+  explicit ToolbarActionHoverCardBubbleView(ToolbarActionView* action_view);
   ToolbarActionHoverCardBubbleView(const ToolbarActionHoverCardBubbleView&) =
       delete;
   ToolbarActionHoverCardBubbleView& operator=(
@@ -52,9 +51,6 @@
   // views::BubbleDialogDelegateView:
   void OnThemeChanged() override;
 
-  // The associated ToolbarActionsModel. Not owned.
-  raw_ptr<ToolbarActionsModel> model_;
-
   raw_ptr<FadeLabel> title_label_ = nullptr;
   raw_ptr<FootnoteView> footnote_view_ = nullptr;
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view_interactive_uitest.cc b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view_interactive_uitest.cc
index 4c38814..b03688832 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view_interactive_uitest.cc
@@ -210,20 +210,19 @@
                        WidgetUpdatedWhenHoveringBetweenActionViews) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  // Add two extensions with no host permissions, and two with them.
-  auto simple_extension_A = InstallExtension("Simple extension A");
-  auto simple_extension_B = InstallExtension("Simple extension B");
-  auto extension_with_permissions_A = InstallExtensionWithHostPermissions(
-      "Extension with host permissions A", "<all_urls>");
-  auto extension_with_permissions_B = InstallExtensionWithHostPermissions(
-      "Extension with host permissions B", "<all_urls>");
+  // Install four extensions with different policy and site access permissions
+  // to test all the possible footnote combinations.
+  auto simple_extension = InstallExtension("Extension A");
+  auto force_installed_extension = ForceInstallExtension("Extension B");
+  auto extension_with_host_permissions =
+      InstallExtensionWithHostPermissions("Extension C", "<all_urls>");
+  auto force_pinned_extension_with_host_permissions =
+      InstallExtensionWithHostPermissions("Extension D", "<all_urls>");
 
-  // Pin extensions "A" and force pin extensions "B" in order to test all
-  // possible footer combinations.
-  PinExtension(simple_extension_A->id());
-  ForcePinExtension(simple_extension_B->id());
-  PinExtension(extension_with_permissions_A->id());
-  ForcePinExtension(extension_with_permissions_B->id());
+  PinExtension(simple_extension->id());
+  PinExtension(force_installed_extension->id());
+  PinExtension(extension_with_host_permissions->id());
+  ForcePinExtension(force_pinned_extension_with_host_permissions->id());
 
   auto action_views = GetVisibleToolbarActionViews();
   ASSERT_EQ(action_views.size(), 4u);
@@ -235,31 +234,32 @@
   // Hover over the simple extension pinned by the user.
   // Verify card anchors to its action, and it contains the extension's name and
   // no footnote.
-  ToolbarActionView* simple_action_A =
-      GetExtensionsToolbarContainer()->GetViewForId(simple_extension_A->id());
-  HoverMouseOverActionView(simple_action_A);
+  ToolbarActionView* simple_action =
+      GetExtensionsToolbarContainer()->GetViewForId(simple_extension->id());
+  HoverMouseOverActionView(simple_action);
   views::Widget* const widget = hover_card()->GetWidget();
   views::test::WidgetVisibleWaiter(widget).Wait();
   ASSERT_TRUE(widget);
   EXPECT_TRUE(widget->IsVisible());
-  EXPECT_EQ(hover_card()->GetAnchorView(), simple_action_A);
+  EXPECT_EQ(hover_card()->GetAnchorView(), simple_action);
   EXPECT_EQ(hover_card()->GetTitleTextForTesting(),
-            simple_action_A->view_controller()->GetActionName());
+            simple_action->view_controller()->GetActionName());
   EXPECT_FALSE(hover_card()->IsFooterVisible());
 
-  // Hover over the simple extension pinned by policy.
+  // Hover over the extension installed by policy and pinned by the user.
   // Verify card anchors to its action using the same widget, because it
   // transitions from one action view to the other, and it contains contains the
   // extension's name and a footnote with only policy label.
-  ToolbarActionView* simple_action_B =
-      GetExtensionsToolbarContainer()->GetViewForId(simple_extension_B->id());
-  HoverMouseOverActionView(simple_action_B);
+  ToolbarActionView* force_installed_action =
+      GetExtensionsToolbarContainer()->GetViewForId(
+          force_installed_extension->id());
+  HoverMouseOverActionView(force_installed_action);
   views::test::WidgetVisibleWaiter(widget).Wait();
   ASSERT_TRUE(widget);
   EXPECT_TRUE(widget->IsVisible());
-  EXPECT_EQ(hover_card()->GetAnchorView(), simple_action_B);
+  EXPECT_EQ(hover_card()->GetAnchorView(), force_installed_action);
   EXPECT_EQ(hover_card()->GetTitleTextForTesting(),
-            simple_action_B->view_controller()->GetActionName());
+            force_installed_action->view_controller()->GetActionName());
   EXPECT_TRUE(hover_card()->IsFooterVisible());
   EXPECT_FALSE(hover_card()->IsFooterTitleLabelVisible());
   EXPECT_FALSE(hover_card()->IsFooterDescriptionLabelVisible());
@@ -270,37 +270,39 @@
   // Verify card anchors to its action using the same widget, and it contains
   // contains the extension's name and a footnote with only title and
   // description labels.
-  ToolbarActionView* action_with_permissions_A =
+  ToolbarActionView* action_with_host_permissions =
       GetExtensionsToolbarContainer()->GetViewForId(
-          extension_with_permissions_A->id());
-  HoverMouseOverActionView(action_with_permissions_A);
+          extension_with_host_permissions->id());
+  HoverMouseOverActionView(action_with_host_permissions);
   views::test::WidgetVisibleWaiter(widget).Wait();
   ASSERT_TRUE(widget);
   EXPECT_TRUE(widget->IsVisible());
-  EXPECT_EQ(hover_card()->GetAnchorView(), action_with_permissions_A);
+  EXPECT_EQ(hover_card()->GetAnchorView(), action_with_host_permissions);
   EXPECT_EQ(hover_card()->GetTitleTextForTesting(),
-            action_with_permissions_A->view_controller()->GetActionName());
+            action_with_host_permissions->view_controller()->GetActionName());
   EXPECT_TRUE(hover_card()->IsFooterVisible());
   EXPECT_TRUE(hover_card()->IsFooterTitleLabelVisible());
   EXPECT_TRUE(hover_card()->IsFooterDescriptionLabelVisible());
   EXPECT_FALSE(hover_card()->IsFooterPolicyLabelVisible());
   EXPECT_FALSE(hover_card()->IsFooterSeparatorVisible());
 
-  // Hover over the extension with host permission pinned by policy.
-  // Verify card anchors to its action using the same widget, and it contains
-  // contains the extension's name and a footnote with both title and
+  // Hover over the extension with host permission installed and pinned by
+  // policy. Verify card anchors to its action using the same widget, and it
+  // contains contains the extension's name and a footnote with both title and
   // description labels, and policy label. Since all labels are visible,
   // separator should also be visible to distinct between them.
-  ToolbarActionView* action_with_permissions_B =
+  ToolbarActionView* force_pinned_action_with_host_permissions =
       GetExtensionsToolbarContainer()->GetViewForId(
-          extension_with_permissions_B->id());
-  HoverMouseOverActionView(action_with_permissions_B);
+          force_pinned_extension_with_host_permissions->id());
+  HoverMouseOverActionView(force_pinned_action_with_host_permissions);
   views::test::WidgetVisibleWaiter(widget).Wait();
   ASSERT_TRUE(widget);
   EXPECT_TRUE(widget->IsVisible());
-  EXPECT_EQ(hover_card()->GetAnchorView(), action_with_permissions_B);
+  EXPECT_EQ(hover_card()->GetAnchorView(),
+            force_pinned_action_with_host_permissions);
   EXPECT_EQ(hover_card()->GetTitleTextForTesting(),
-            action_with_permissions_B->view_controller()->GetActionName());
+            force_pinned_action_with_host_permissions->view_controller()
+                ->GetActionName());
   EXPECT_TRUE(hover_card()->IsFooterVisible());
   EXPECT_TRUE(hover_card()->IsFooterTitleLabelVisible());
   EXPECT_TRUE(hover_card()->IsFooterDescriptionLabelVisible());
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.cc b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.cc
index 4930157..af963b5 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.cc
@@ -10,7 +10,6 @@
 #include "base/memory/raw_ptr.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/toolbar/toolbar_action_hover_card_types.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_action_hover_card_bubble_view.h"
@@ -71,9 +70,8 @@
 // ToolbarActionHoverCardController
 
 ToolbarActionHoverCardController::ToolbarActionHoverCardController(
-    Profile* profile,
     ExtensionsToolbarContainer* extensions_container)
-    : profile_(profile), extensions_container_(extensions_container) {}
+    : extensions_container_(extensions_container) {}
 
 ToolbarActionHoverCardController::~ToolbarActionHoverCardController() = default;
 
@@ -214,7 +212,7 @@
     ToolbarActionView* action_view) {
   DCHECK(action_view);
 
-  hover_card_ = new ToolbarActionHoverCardBubbleView(action_view, profile_);
+  hover_card_ = new ToolbarActionHoverCardBubbleView(action_view);
   hover_card_observation_.Observe(hover_card_.get());
   event_sniffer_ = std::make_unique<EventSniffer>(this);
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.h b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.h
index 18d8ef7..b40704a 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.h
@@ -22,13 +22,11 @@
 class ToolbarActionHoverCardBubbleView;
 class ExtensionsToolbarContainer;
 class ToolbarActionView;
-class Profile;
 
 // Controls how hover cards are shown and hidden for toolbar actions.
 class ToolbarActionHoverCardController : public views::ViewObserver {
  public:
   explicit ToolbarActionHoverCardController(
-      Profile* profile,
       ExtensionsToolbarContainer* extensions_container);
   ~ToolbarActionHoverCardController() override;
 
@@ -74,8 +72,6 @@
   void OnViewVisibilityChanged(views::View* observed_view,
                                views::View* starting_view) override;
 
-  raw_ptr<Profile> profile_;
-
   // Timestamp of the last time the hover card is hidden by the mouse leaving
   // the tab strip. This is used for reshowing the hover card without delay if
   // the mouse reenters within a given amount of time.
diff --git a/chrome/browser/ui/webui/chromeos/internet_detail_dialog.cc b/chrome/browser/ui/webui/chromeos/internet_detail_dialog.cc
index cdccff2..9de3a76 100644
--- a/chrome/browser/ui/webui/chromeos/internet_detail_dialog.cc
+++ b/chrome/browser/ui/webui/chromeos/internet_detail_dialog.cc
@@ -16,6 +16,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/internet_detail_dialog_resources.h"
 #include "chrome/grit/internet_detail_dialog_resources_map.h"
+#include "chromeos/ash/components/network/network_connect.h"
 #include "chromeos/ash/components/network/network_handler.h"
 #include "chromeos/ash/components/network/network_state.h"
 #include "chromeos/ash/components/network/network_state_handler.h"
@@ -25,6 +26,7 @@
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/web_ui.h"
 #include "content/public/browser/web_ui_data_source.h"
+#include "content/public/browser/web_ui_message_handler.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/chromeos/strings/grit/ui_chromeos_strings.h"
 #include "ui/chromeos/strings/network_element_localized_strings_provider.h"
@@ -53,6 +55,7 @@
       {"networkButtonConnect", IDS_SETTINGS_INTERNET_BUTTON_CONNECT},
       {"networkButtonDisconnect", IDS_SETTINGS_INTERNET_BUTTON_DISCONNECT},
       {"networkButtonForget", IDS_SETTINGS_INTERNET_BUTTON_FORGET},
+      {"networkButtonSignin", IDS_SETTINGS_INTERNET_BUTTON_SIGNIN},
       {"networkIPAddress", IDS_SETTINGS_INTERNET_NETWORK_IP_ADDRESS},
       {"networkSectionNetwork", IDS_SETTINGS_INTERNET_NETWORK_SECTION_NETWORK},
       {"networkSectionProxy", IDS_SETTINGS_INTERNET_NETWORK_SECTION_PROXY},
@@ -72,6 +75,29 @@
              : network.name();
 }
 
+class PortalNetworkMessageHandler : public content::WebUIMessageHandler {
+ public:
+  PortalNetworkMessageHandler() = default;
+  ~PortalNetworkMessageHandler() override = default;
+
+  void RegisterMessages() override {
+    web_ui()->RegisterMessageCallback(
+        "showPortalSignin",
+        base::BindRepeating(&PortalNetworkMessageHandler::ShowPortalSignin,
+                            base::Unretained(this)));
+  }
+
+ private:
+  void ShowPortalSignin(const base::Value::List& args) {
+    if (args.size() < 1 || !args[0].is_string()) {
+      NOTREACHED() << "Invalid args for: ShowPortalSignin";
+      return;
+    }
+    const std::string& guid = args[0].GetString();
+    ash::NetworkConnect::Get()->ShowPortalSignin(guid);
+  }
+};
+
 }  // namespace
 
 // static
@@ -138,11 +164,15 @@
 
 InternetDetailDialogUI::InternetDetailDialogUI(content::WebUI* web_ui)
     : ui::MojoWebDialogUI(web_ui) {
+  web_ui->AddMessageHandler(std::make_unique<PortalNetworkMessageHandler>());
+
   content::WebUIDataSource* source = content::WebUIDataSource::Create(
       chrome::kChromeUIInternetDetailDialogHost);
   source->DisableTrustedTypesCSP();
   source->AddBoolean("showTechnologyBadge",
                      !ash::features::IsSeparateNetworkIconsEnabled());
+  source->AddBoolean("captivePortalUI2022",
+                     ash::features::IsCaptivePortalUI2022Enabled());
   cellular_setup::AddNonStringLoadTimeData(source);
   AddInternetStrings(source);
   source->AddLocalizedString("title", IDS_SETTINGS_INTERNET_DETAIL);
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
index f9c1bb4..0144ed2 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
@@ -540,6 +540,7 @@
       theme_service_(ThemeServiceFactory::GetForProfile(profile_)),
       ntp_custom_background_service_(
           NtpCustomBackgroundServiceFactory::GetForProfile(profile_)),
+      web_contents_(web_ui->GetWebContents()),
       // We initialize navigation_start_time_ to a reasonable value to account
       // for the unlikely case where the NewTabPageHandler is created before we
       // received the DidStartNavigation event.
@@ -583,18 +584,22 @@
   // background image available as soon as the page loads to prevent a potential
   // white flicker.
 
+  // Load time data is cached across page reloads. Listen for theme changes so
+  // that theme info is up-to-date when reloading.
+  native_theme_observation_.Observe(ui::NativeTheme::GetInstanceForNativeUi());
+  theme_service_observation_.Observe(theme_service_.get());
   ntp_custom_background_service_observation_.Observe(
       ntp_custom_background_service_.get());
 
   // Create and register customize chrome entry on unified side panel
   if (customize_chrome::IsSidePanelEnabled()) {
     auto* customize_chrome_tab_helper =
-        CustomizeChromeTabHelper::FromWebContents(web_contents());
+        CustomizeChromeTabHelper::FromWebContents(web_contents_);
     customize_chrome_tab_helper->CreateAndRegisterEntry();
   }
 
   // Populates the load time data with basic info.
-  OnColorProviderChanged();
+  OnThemeChanged();
   OnCustomBackgroundImageUpdated();
   OnLoad();
 }
@@ -605,7 +610,7 @@
   // Deregister customize chrome entry on unified side panel
   if (customize_chrome::IsSidePanelEnabled()) {
     auto* customize_chrome_tab_helper =
-        CustomizeChromeTabHelper::FromWebContents(web_contents());
+        CustomizeChromeTabHelper::FromWebContents(web_contents_);
     customize_chrome_tab_helper->DeregisterEntry();
   }
 }
@@ -674,7 +679,7 @@
 void NewTabPageUI::BindInterface(
     mojo::PendingReceiver<realbox::mojom::PageHandler> pending_page_handler) {
   realbox_handler_ = std::make_unique<RealboxHandler>(
-      std::move(pending_page_handler), profile_, web_contents());
+      std::move(pending_page_handler), profile_, web_contents_);
 }
 
 void NewTabPageUI::BindInterface(
@@ -719,7 +724,7 @@
 void NewTabPageUI::BindInterface(
     mojo::PendingReceiver<photos::mojom::PhotosHandler> pending_receiver) {
   photos_handler_ = std::make_unique<PhotosHandler>(std::move(pending_receiver),
-                                                    profile_, web_contents());
+                                                    profile_, web_contents_);
 }
 
 void NewTabPageUI::BindInterface(
@@ -739,7 +744,7 @@
     mojo::PendingReceiver<chrome_cart::mojom::CartHandler>
         pending_page_handler) {
   cart_handler_ = std::make_unique<CartHandler>(std::move(pending_page_handler),
-                                                profile_, web_contents());
+                                                profile_, web_contents_);
 }
 
 void NewTabPageUI::CreatePageHandler(
@@ -750,7 +755,7 @@
   page_handler_ = std::make_unique<NewTabPageHandler>(
       std::move(pending_page_handler), std::move(pending_page), profile_,
       ntp_custom_background_service_, theme_service_,
-      LogoServiceFactory::GetForProfile(profile_), web_contents(),
+      LogoServiceFactory::GetForProfile(profile_), web_contents_,
       navigation_start_time_);
 }
 
@@ -760,7 +765,7 @@
     mojo::PendingReceiver<customize_themes::mojom::CustomizeThemesHandler>
         pending_handler) {
   customize_themes_handler_ = std::make_unique<ChromeCustomizeThemesHandler>(
-      std::move(pending_client), std::move(pending_handler), web_contents(),
+      std::move(pending_client), std::move(pending_handler), web_contents_,
       profile_);
 }
 
@@ -784,17 +789,19 @@
   DCHECK(pending_page.is_valid());
   most_visited_page_handler_ = std::make_unique<MostVisitedHandler>(
       std::move(pending_page_handler), std::move(pending_page), profile_,
-      web_contents(), GURL(chrome::kChromeUINewTabPageURL),
+      web_contents_, GURL(chrome::kChromeUINewTabPageURL),
       navigation_start_time_);
   most_visited_page_handler_->EnableCustomLinks(IsCustomLinksEnabled());
   most_visited_page_handler_->SetShortcutsVisible(IsShortcutsVisible());
 }
 
-void NewTabPageUI::OnColorProviderChanged() {
-  if (!web_contents())
-    return;
+void NewTabPageUI::OnNativeThemeUpdated(ui::NativeTheme* observed_theme) {
+  OnThemeChanged();
+}
+
+void NewTabPageUI::OnThemeChanged() {
   base::Value::Dict update;
-  const ui::ColorProvider& color_provider = web_contents()->GetColorProvider();
+  const ui::ColorProvider& color_provider = web_contents_->GetColorProvider();
   auto background_color = color_provider.GetColor(kColorNewTabPageBackground);
   update.Set("backgroundColor", skia::SkColorToHexString(background_color));
   content::WebUIDataSource::Update(profile_, chrome::kChromeUINewTabPageHost,
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h
index b75e53f..371b26d 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.h
@@ -21,6 +21,7 @@
 #include "chrome/browser/search/background/ntp_custom_background_service.h"
 #include "chrome/browser/search/background/ntp_custom_background_service_observer.h"
 #include "chrome/browser/themes/theme_service.h"
+#include "chrome/browser/themes/theme_service_observer.h"
 #include "chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom.h"
 #include "chrome/browser/ui/webui/realbox/realbox.mojom-forward.h"
 #include "components/prefs/pref_change_registrar.h"
@@ -30,6 +31,7 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "ui/base/resource/resource_scale_factor.h"
 #include "ui/native_theme/native_theme.h"
+#include "ui/native_theme/native_theme_observer.h"
 #include "ui/webui/mojo_web_ui_controller.h"
 #include "ui/webui/resources/cr_components/color_change_listener/color_change_listener.mojom.h"
 #include "ui/webui/resources/cr_components/customize_themes/customize_themes.mojom.h"
@@ -41,6 +43,7 @@
 
 namespace content {
 class NavigationHandle;
+class WebContents;
 class WebUI;
 }  // namespace content
 
@@ -73,6 +76,8 @@
       public customize_themes::mojom::CustomizeThemesHandlerFactory,
       public most_visited::mojom::MostVisitedPageHandlerFactory,
       public browser_command::mojom::CommandHandlerFactory,
+      public ui::NativeThemeObserver,
+      public ThemeServiceObserver,
       public NtpCustomBackgroundServiceObserver,
       content::WebContentsObserver {
  public:
@@ -187,6 +192,12 @@
       mojo::PendingReceiver<most_visited::mojom::MostVisitedPageHandler>
           pending_page_handler) override;
 
+  // ui::NativeThemeObserver:
+  void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override;
+
+  // ThemeServiceObserver:
+  void OnThemeChanged() override;
+
   // NtpCustomBackgroundServiceObserver:
   void OnCustomBackgroundImageUpdated() override;
   void OnNtpCustomBackgroundServiceShuttingDown() override;
@@ -194,7 +205,6 @@
   // content::WebContentsObserver:
   void DidStartNavigation(
       content::NavigationHandle* navigation_handle) override;
-  void OnColorProviderChanged() override;
 
   bool IsCustomLinksEnabled() const;
   bool IsShortcutsVisible() const;
@@ -228,9 +238,14 @@
   raw_ptr<Profile> profile_;
   raw_ptr<ThemeService> theme_service_;
   raw_ptr<NtpCustomBackgroundService> ntp_custom_background_service_;
+  base::ScopedObservation<ui::NativeTheme, ui::NativeThemeObserver>
+      native_theme_observation_{this};
+  base::ScopedObservation<ThemeService, ThemeServiceObserver>
+      theme_service_observation_{this};
   base::ScopedObservation<NtpCustomBackgroundService,
                           NtpCustomBackgroundServiceObserver>
       ntp_custom_background_service_observation_{this};
+  raw_ptr<content::WebContents> web_contents_;
   // Time the NTP started loading. Used for logging the WebUI NTP's load
   // performance.
   base::Time navigation_start_time_;
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 3d2d0d8..bef9088e 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -99,6 +99,8 @@
     "manifest_update_manager.h",
     "manifest_update_task.cc",
     "manifest_update_task.h",
+    "manifest_update_utils.cc",
+    "manifest_update_utils.h",
     "os_integration/os_integration_manager.cc",
     "os_integration/os_integration_manager.h",
     "os_integration/os_integration_sub_manager.h",
diff --git a/chrome/browser/web_applications/commands/install_isolated_app_command.cc b/chrome/browser/web_applications/commands/install_isolated_app_command.cc
index 537b376b6..cb6691a 100644
--- a/chrome/browser/web_applications/commands/install_isolated_app_command.cc
+++ b/chrome/browser/web_applications/commands/install_isolated_app_command.cc
@@ -44,6 +44,9 @@
 
 namespace web_app {
 
+constexpr static char kGeneratedInstallPagePath[] =
+    "/.well-known/_generated_install_page.html";
+
 namespace {
 
 bool IsUrlLoadingResultSuccess(WebAppUrlLoader::Result result) {
@@ -61,7 +64,7 @@
 }  // namespace
 
 InstallIsolatedAppCommand::InstallIsolatedAppCommand(
-    const GURL& url,
+    const IsolatedWebAppUrlInfo& isolation_info,
     const IsolationData& isolation_data,
     std::unique_ptr<content::WebContents> web_contents,
     std::unique_ptr<WebAppUrlLoader> url_loader,
@@ -70,8 +73,8 @@
                                            InstallIsolatedAppCommandError>)>
         callback)
     : lock_(std::make_unique<AppLock>(
-          base::flat_set<AppId>{GenerateAppId("", GURL{url})})),
-      url_(url),
+          base::flat_set<AppId>{isolation_info.app_id()})),
+      isolation_info_(isolation_info),
       isolation_data_(isolation_data),
       web_contents_(std::move(web_contents)),
       url_loader_(std::move(url_loader)),
@@ -80,9 +83,6 @@
   DETACH_FROM_SEQUENCE(sequence_checker_);
 
   DCHECK(web_contents_ != nullptr);
-  DCHECK(url_loader_ != nullptr);
-
-  DCHECK(url_.is_valid());
   DCHECK(!callback.is_null());
 
   callback_ =
@@ -110,7 +110,6 @@
 }
 
 void InstallIsolatedAppCommand::LoadUrl() {
-  DCHECK(url_.is_valid());
   DCHECK(web_contents_ != nullptr);
 
   // |web_app::IsolatedWebAppURLLoaderFactory| uses the isolation data in order
@@ -119,7 +118,9 @@
   IsolatedWebAppPendingInstallInfo::FromWebContents(*web_contents_)
       .set_isolation_data(isolation_data_);
 
-  url_loader_->LoadUrl(url_, web_contents_.get(),
+  GURL install_page_url =
+      isolation_info_.origin().GetURL().Resolve(kGeneratedInstallPagePath);
+  url_loader_->LoadUrl(install_page_url, web_contents_.get(),
                        WebAppUrlLoader::UrlComparison::kIgnoreQueryParamsAndRef,
                        base::BindOnce(&InstallIsolatedAppCommand::OnLoadUrl,
                                       weak_factory_.GetWeakPtr()));
@@ -183,7 +184,7 @@
 
   info.manifest_id = "";
 
-  url::Origin origin = url::Origin::Create(url_);
+  url::Origin origin = isolation_info_.origin();
   if (manifest.scope != origin.GetURL()) {
     return base::unexpected{
         base::StrCat({"Scope should resolve to the origin. scope: ",
diff --git a/chrome/browser/web_applications/commands/install_isolated_app_command.h b/chrome/browser/web_applications/commands/install_isolated_app_command.h
index 24732f6..a3c454f 100644
--- a/chrome/browser/web_applications/commands/install_isolated_app_command.h
+++ b/chrome/browser/web_applications/commands/install_isolated_app_command.h
@@ -16,6 +16,7 @@
 #include "base/types/expected.h"
 #include "base/values.h"
 #include "chrome/browser/web_applications/commands/web_app_command.h"
+#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
 #include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
 #include "chrome/browser/web_applications/web_app_id.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
@@ -56,14 +57,19 @@
 // re-using web contents.
 class InstallIsolatedAppCommand : public WebAppCommand {
  public:
-  // |application_url| is the url for the app to be installed. The url must be
-  // valid.
+  //
+  // |isolation_info| holds the origin information of the app. It is
+  // randomly generated for dev-proxy and the public key of signed bundle. It is
+  // guarantee to be valid.
+  //
+  // |isolation_data| holds information about the
+  // mode(dev-mod-proxy/signed-bundle) and the source.
   //
   // |callback| must be not null.
   //
   // The `id` in the application's manifest must equal "/".
   explicit InstallIsolatedAppCommand(
-      const GURL& application_url,
+      const IsolatedWebAppUrlInfo& isolation_info,
       const IsolationData& isolation_data,
       std::unique_ptr<content::WebContents> web_contents,
       std::unique_ptr<WebAppUrlLoader> url_loader,
@@ -123,7 +129,7 @@
 
   std::unique_ptr<AppLock> lock_;
 
-  GURL url_;
+  IsolatedWebAppUrlInfo isolation_info_;
   IsolationData isolation_data_;
 
   std::unique_ptr<content::WebContents> web_contents_;
diff --git a/chrome/browser/web_applications/commands/install_isolated_app_command_unittest.cc b/chrome/browser/web_applications/commands/install_isolated_app_command_unittest.cc
index c9cc942727..907c78c5 100644
--- a/chrome/browser/web_applications/commands/install_isolated_app_command_unittest.cc
+++ b/chrome/browser/web_applications/commands/install_isolated_app_command_unittest.cc
@@ -14,6 +14,7 @@
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
+#include "base/check_op.h"
 #include "base/files/file_path.h"
 #include "base/functional/callback_forward.h"
 #include "base/functional/callback_helpers.h"
@@ -25,6 +26,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/test_future.h"
 #include "base/types/expected.h"
+#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
 #include "chrome/browser/web_applications/isolated_web_apps/pending_install_info.h"
 #include "chrome/browser/web_applications/isolation_data.h"
 #include "chrome/browser/web_applications/locks/lock.h"
@@ -42,6 +44,7 @@
 #include "chrome/browser/web_applications/web_app_install_utils.h"
 #include "chrome/browser/web_applications/web_app_url_loader.h"
 #include "chrome/test/base/testing_profile.h"
+#include "components/web_package/signed_web_bundles/signed_web_bundle_id.h"
 #include "components/webapps/browser/install_result_code.h"
 #include "components/webapps/browser/installable/installable_metrics.h"
 #include "content/public/browser/web_contents.h"
@@ -84,30 +87,40 @@
 using ::testing::VariantWith;
 using ::testing::WithArg;
 
-IsolationData CreateDefaultIsolationData(
+IsolatedWebAppUrlInfo CreateRandomIsolatedWebAppUrlInfo() {
+  web_package::SignedWebBundleId signed_web_bundle_id =
+      web_package::SignedWebBundleId::CreateRandomForDevelopment();
+  base::expected<IsolatedWebAppUrlInfo, std::string> url_info =
+      IsolatedWebAppUrlInfo::CreateFromSignedWebBundleId(signed_web_bundle_id);
+  if (!url_info.has_value()) {
+    CHECK(false) << "Failed to create testing web app url info: "
+                 << url_info.error();
+  }
+  return url_info.value();
+}
+
+IsolationData CreateIsolationDataDevProxy(
     base::StringPiece dev_mode_proxy_url = "http://default-proxy-url.org/") {
   return IsolationData{IsolationData::DevModeProxy{
       .proxy_url = std::string(dev_mode_proxy_url)}};
 }
 
-blink::mojom::ManifestPtr CreateDefaultManifest(
-    base::StringPiece application_url) {
+blink::mojom::ManifestPtr CreateDefaultManifest(const GURL& application_url) {
   auto manifest = blink::mojom::Manifest::New();
   manifest->id = u"";
-  manifest->scope = GURL{application_url}.Resolve("/");
-  manifest->start_url =
-      GURL{application_url}.Resolve("/testing-start-url.html");
+  manifest->scope = application_url.Resolve("/");
+  manifest->start_url = application_url.Resolve("/testing-start-url.html");
   manifest->display = DisplayMode::kStandalone;
   manifest->short_name = u"test short manifest name";
   return manifest;
 }
 
-GURL CreateDefaultManifestURL(base::StringPiece application_url) {
-  return GURL{application_url}.Resolve("/manifest.webmanifest");
+GURL CreateDefaultManifestURL(const GURL& application_url) {
+  return application_url.Resolve("/manifest.webmanifest");
 }
 
 auto ReturnManifest(const blink::mojom::ManifestPtr& manifest,
-                    GURL manifest_url,
+                    const GURL& manifest_url,
                     bool is_installable = true) {
   constexpr int kCallbackArgumentIndex = 2;
 
@@ -124,7 +137,7 @@
 }
 
 std::unique_ptr<MockDataRetriever> CreateDefaultDataRetriever(
-    base::StringPiece application_url) {
+    const GURL& application_url) {
   std::unique_ptr<MockDataRetriever> fake_data_retriever =
       std::make_unique<NiceMock<MockDataRetriever>>();
 
@@ -180,7 +193,7 @@
   }
 
   struct Parameters {
-    std::string url;
+    IsolatedWebAppUrlInfo url_info;
     std::unique_ptr<WebAppUrlLoader> url_loader;
     std::unique_ptr<content::WebContents> web_contents;
     absl::optional<IsolationData> isolation_data;
@@ -203,40 +216,46 @@
     }
 
     auto command = CreateCommand(
-        parameters.url, std::move(web_contents),
-        parameters.isolation_data.value_or(
-            CreateDefaultIsolationData(parameters.url)),
+        parameters.url_info, std::move(web_contents), parameters.isolation_data,
         std::move(parameters.url_loader), test_future.GetCallback());
+
     command->SetDataRetrieverForTesting(
         data_retriever != nullptr ? std::move(data_retriever)
-                                  : CreateDefaultDataRetriever(parameters.url));
+                                  : CreateDefaultDataRetriever(
+                                        parameters.url_info.origin().GetURL()));
     ScheduleCommand(std::move(command));
     return test_future.Get();
   }
 
   std::unique_ptr<InstallIsolatedAppCommand> CreateCommand(
-      base::StringPiece url,
+      const IsolatedWebAppUrlInfo& url_info,
       std::unique_ptr<content::WebContents> web_contents,
-      const IsolationData& isolation_data,
+      absl::optional<IsolationData> isolation_data,
       std::unique_ptr<WebAppUrlLoader> url_loader,
       base::OnceCallback<void(base::expected<InstallIsolatedAppCommandSuccess,
                                              InstallIsolatedAppCommandError>)>
           callback) {
-    const GURL application_url{url};
-    DCHECK(application_url.is_valid());
+    if (!isolation_data.has_value()) {
+      isolation_data = CreateIsolationDataDevProxy();
+    }
 
     return std::make_unique<InstallIsolatedAppCommand>(
-        application_url, isolation_data, std::move(web_contents),
+        url_info, isolation_data.value(), std::move(web_contents),
         std::move(url_loader), *install_finalizer_, std::move(callback));
   }
 
   base::expected<InstallIsolatedAppCommandSuccess,
                  InstallIsolatedAppCommandError>
-  ExecuteCommandWithManifest(base::StringPiece application_url,
-                             const blink::mojom::ManifestPtr& manifest) {
+  ExecuteCommandWithManifest(const IsolatedWebAppUrlInfo& url_info,
+                             const blink::mojom::ManifestPtr& manifest,
+                             absl::optional<IsolationData> isolation_data =
+                                 absl::optional<IsolationData>()) {
+    GURL application_url = url_info.origin().GetURL();
     auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-    url_loader->SetNextLoadUrlResult(GURL{application_url},
-                                     WebAppUrlLoader::Result::kUrlLoaded);
+    url_loader->SetNextLoadUrlResult(
+        url_info.origin().GetURL().Resolve(
+            ".well-known/_generated_install_page.html"),
+        WebAppUrlLoader::Result::kUrlLoaded);
 
     std::unique_ptr<MockDataRetriever> fake_data_retriever =
         CreateDefaultDataRetriever(application_url);
@@ -245,12 +264,10 @@
         .WillByDefault(ReturnManifest(
             manifest, CreateDefaultManifestURL(application_url)));
 
-    return ExecuteCommand(
-        {
-            .url = std::string(application_url),
-            .url_loader = std::move(url_loader),
-        },
-        std::move(fake_data_retriever));
+    return ExecuteCommand({.url_info = url_info,
+                           .url_loader = std::move(url_loader),
+                           .isolation_data = isolation_data},
+                          std::move(fake_data_retriever));
   }
 
   TestingProfile* profile() const { return profile_.get(); }
@@ -324,86 +341,83 @@
 
 TEST_F(InstallIsolatedAppCommandTest,
        ServiceWorkerIsNotRequiredForInstallation) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{"http://test-url-example.com"},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   std::unique_ptr<MockDataRetriever> fake_data_retriever =
-      CreateDefaultDataRetriever("http://test-url-example.com");
+      CreateDefaultDataRetriever(url_info.origin().GetURL());
 
   EXPECT_CALL(*fake_data_retriever,
               CheckInstallabilityAndRetrieveManifest(
                   _, /*bypass_service_worker_check=*/IsTrue(), _))
       .WillOnce(ReturnManifest(
-          CreateDefaultManifest("http://test-url-example.com"),
-          CreateDefaultManifestURL("http://test-url-example.com")));
+          // IsolatedWebAppUrlLoaderFactory is responsible for resolving
+          // isolated-app:// schema requests.
+          CreateDefaultManifest(url_info.origin().GetURL()),
+          CreateDefaultManifestURL(url_info.origin().GetURL())));
 
   EXPECT_THAT(ExecuteCommand(
                   Parameters{
-                      .url = "http://test-url-example.com",
+                      .url_info = url_info,
                       .url_loader = std::move(url_loader),
                   },
                   std::move(fake_data_retriever)),
               IsInstallationOk());
 }
 
-TEST_F(InstallIsolatedAppCommandTest, CommandCanBeExecutedSuccesfully) {
-  auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{"http://test-url-example.com"},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
-
-  EXPECT_THAT(ExecuteCommand(Parameters{
-                  .url = "http://test-url-example.com",
-                  .url_loader = std::move(url_loader),
-              }),
-              IsInstallationOk());
-}
-
 TEST_F(InstallIsolatedAppCommandTest, PropagateErrorWhenURLLoaderFails) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
   url_loader->SetNextLoadUrlResult(
-      GURL{"http://test-url-example.com"},
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
       WebAppUrlLoader::Result::kFailedErrorPageLoaded);
 
-  EXPECT_THAT(ExecuteCommand(Parameters{
-                  .url = "http://test-url-example.com",
-                  .url_loader = std::move(url_loader),
-              }),
+  EXPECT_THAT(ExecuteCommand(Parameters{.url_info = url_info,
+                                        .url_loader = std::move(url_loader)}),
               IsInstallationError(HasSubstr("Error during URL loading: ")));
 }
 
 TEST_F(InstallIsolatedAppCommandTest,
        PropagateErrorWhenURLLoaderFailsWithDestroyedWebContentsError) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
   url_loader->SetNextLoadUrlResult(
-      GURL{"http://test-url-example.com"},
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
       WebAppUrlLoaderResult::kFailedWebContentsDestroyed);
 
-  EXPECT_THAT(ExecuteCommand(Parameters{
-                  .url = "http://test-url-example.com",
-                  .url_loader = std::move(url_loader),
-              }),
+  EXPECT_THAT(ExecuteCommand(Parameters{.url_info = url_info,
+                                        .url_loader = std::move(url_loader)}),
               IsInstallationError(HasSubstr(
                   "Error during URL loading: FailedWebContentsDestroyed")));
 }
 
 TEST_F(InstallIsolatedAppCommandTest,
        URLLoaderIsCalledWithURLgivenToTheInstallCommand) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{"http://another-test-url-example.com"},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
-  EXPECT_THAT(ExecuteCommand(Parameters{
-                  .url = "http://another-test-url-example.com",
-                  .url_loader = std::move(url_loader),
-              }),
+  EXPECT_THAT(ExecuteCommand(Parameters{.url_info = url_info,
+                                        .url_loader = std::move(url_loader)}),
               IsInstallationOk());
 }
 
 TEST_F(InstallIsolatedAppCommandTest, URLLoaderIgnoresQueryParameters) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{"http://test-url-example.com"},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   absl::optional<WebAppUrlLoader::UrlComparison> last_url_comparison =
       absl::nullopt;
@@ -413,10 +427,8 @@
         last_url_comparison = url_comparison;
       }));
 
-  EXPECT_THAT(ExecuteCommand(Parameters{
-                  .url = "http://test-url-example.com",
-                  .url_loader = std::move(url_loader),
-              }),
+  EXPECT_THAT(ExecuteCommand(Parameters{.url_info = url_info,
+                                        .url_loader = std::move(url_loader)}),
               IsInstallationOk());
 
   EXPECT_THAT(
@@ -426,16 +438,18 @@
 
 TEST_F(InstallIsolatedAppCommandTest,
        InstallationFailsWhenFinalizerReturnNotInstallableError) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{"http://test-url-example.com"},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   install_finalizer().SetNextFinalizeInstallResult(
-      GenerateAppIdFromUnhashed("http://testing-unused-app-id.com/"),
-      webapps::InstallResultCode::kNotInstallable);
+      url_info.app_id(), webapps::InstallResultCode::kNotInstallable);
 
   EXPECT_THAT(ExecuteCommand(Parameters{
-                  .url = "http://test-url-example.com",
+                  .url_info = url_info,
                   .url_loader = std::move(url_loader),
               }),
               IsInstallationError(
@@ -444,16 +458,18 @@
 
 TEST_F(InstallIsolatedAppCommandTest,
        InstallationFailsWhenFinalizerReturnInstallURLLoadTimeOut) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{"http://test-url-example.com"},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   install_finalizer().SetNextFinalizeInstallResult(
-      GenerateAppIdFromUnhashed("http://testing-unused-app-id.com/"),
-      webapps::InstallResultCode::kInstallURLLoadTimeOut);
+      url_info.app_id(), webapps::InstallResultCode::kInstallURLLoadTimeOut);
 
   EXPECT_THAT(ExecuteCommand(Parameters{
-                  .url = "http://test-url-example.com",
+                  .url_info = url_info,
                   .url_loader = std::move(url_loader),
               }),
               IsInstallationError(HasSubstr(
@@ -462,36 +478,36 @@
 
 TEST_F(InstallIsolatedAppCommandTest,
        InstallationSucceedesWhenFinalizerReturnSuccessNewInstall) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{"http://test-url-example.com"},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   install_finalizer().SetNextFinalizeInstallResult(
-      GenerateAppIdFromUnhashed("http://testing-unused-app-id.com/"),
-      webapps::InstallResultCode::kSuccessNewInstall);
+      url_info.app_id(), webapps::InstallResultCode::kSuccessNewInstall);
 
-  EXPECT_THAT(ExecuteCommand(Parameters{
-                  .url = "http://test-url-example.com",
-                  .url_loader = std::move(url_loader),
-              }),
+  EXPECT_THAT(ExecuteCommand(Parameters{.url_info = url_info,
+                                        .url_loader = std::move(url_loader)}),
               IsInstallationOk());
 }
 
 TEST_F(InstallIsolatedAppCommandTest,
        InstallationFinalizedWithIsolatedAppDevInstallInstallSource) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{"http://test-url-example.com"},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   std::unique_ptr<MockDataRetriever> fake_data_retriever =
-      CreateDefaultDataRetriever("http://test-url-example.com");
+      CreateDefaultDataRetriever(url_info.origin().GetURL());
 
-  EXPECT_THAT(ExecuteCommand(
-                  Parameters{
-                      .url = "http://test-url-example.com",
-                      .url_loader = std::move(url_loader),
-                  },
-                  std::move(fake_data_retriever)),
+  EXPECT_THAT(ExecuteCommand(Parameters{.url_info = url_info,
+                                        .url_loader = std::move(url_loader)},
+                             std::move(fake_data_retriever)),
               IsInstallationOk());
 
   using FinalizeOptions = WebAppInstallFinalizer::FinalizeOptions;
@@ -507,12 +523,15 @@
 
 TEST_F(InstallIsolatedAppCommandTest,
        InstallationFailsWhenAppIsNotInstallable) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{"http://test-url-example.com"},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   std::unique_ptr<MockDataRetriever> fake_data_retriever =
-      CreateDefaultDataRetriever("http://test-url-example.com");
+      CreateDefaultDataRetriever(url_info.origin().GetURL());
 
   ON_CALL(*fake_data_retriever, CheckInstallabilityAndRetrieveManifest)
       .WillByDefault(
@@ -521,10 +540,7 @@
                          /*is_installable=*/false));
 
   EXPECT_THAT(ExecuteCommand(
-                  {
-                      .url = "http://test-url-example.com",
-                      .url_loader = std::move(url_loader),
-                  },
+                  {.url_info = url_info, .url_loader = std::move(url_loader)},
                   std::move(fake_data_retriever)),
               IsInstallationError(HasSubstr("App is not installable")));
 }
@@ -534,36 +550,39 @@
                                         InstallIsolatedAppCommandError>>
       test_future;
 
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto command = CreateCommand(
-      "http://test-app-id.com/",
+      url_info,
       content::WebContents::Create(
           content::WebContents::CreateParams(profile())),
-      CreateDefaultIsolationData(), std::make_unique<TestWebAppUrlLoader>(),
+      CreateIsolationDataDevProxy(), std::make_unique<TestWebAppUrlLoader>(),
       test_future.GetCallback());
-  EXPECT_THAT(command->lock(),
-              AllOf(Property(&Lock::type, Eq(Lock::Type::kApp)),
-                    Property(&Lock::app_ids,
-                             UnorderedElementsAre(GenerateAppIdFromUnhashed(
-                                 "http://test-app-id.com/")))));
+  EXPECT_THAT(
+      command->lock(),
+      AllOf(Property(&Lock::type, Eq(Lock::Type::kApp)),
+            Property(&Lock::app_ids, UnorderedElementsAre(url_info.app_id()))));
 }
 
 TEST_F(InstallIsolatedAppCommandTest,
        InstallationFailsWhenAppIsInstallableButManifestIsNull) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{"http://test-url-example.com"},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   std::unique_ptr<MockDataRetriever> fake_data_retriever =
-      CreateDefaultDataRetriever("http://test-url-example.com");
+      CreateDefaultDataRetriever(url_info.origin().GetURL());
 
   ON_CALL(*fake_data_retriever, CheckInstallabilityAndRetrieveManifest)
       .WillByDefault(ReturnManifest(
           /*manifest=*/nullptr,
-          CreateDefaultManifestURL("http://test-url-example.com")));
+          CreateDefaultManifestURL(url_info.origin().GetURL())));
 
   EXPECT_THAT(ExecuteCommand(
                   Parameters{
-                      .url = "http://test-url-example.com",
+                      .url_info = url_info,
                       .url_loader = std::move(url_loader),
                   },
                   std::move(fake_data_retriever)),
@@ -571,14 +590,15 @@
 }
 
 TEST_F(InstallIsolatedAppCommandTest, IsolationDataSentToFinalizer) {
-  std::string url("http://test-url-example.com/");
-
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{url},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   EXPECT_THAT(ExecuteCommand(Parameters{
-                  .url = url,
+                  .url_info = url_info,
                   .url_loader = std::move(url_loader),
                   .isolation_data = IsolationData{IsolationData::DevModeProxy{
                       .proxy_url = "http://some-testing-proxy-url.com/"}},
@@ -599,42 +619,42 @@
 
 TEST_F(InstallIsolatedAppCommandManifestTest,
        InstallationFailsWhenManifestHasNoId) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   blink::mojom::ManifestPtr manifest =
-      CreateDefaultManifest("http://manifest-test-url.com");
+      CreateDefaultManifest(url_info.origin().GetURL());
   manifest->id = absl::nullopt;
 
   EXPECT_THAT(
-      ExecuteCommandWithManifest("http://manifest-test-url.com",
-                                 manifest.Clone()),
+      ExecuteCommandWithManifest(url_info, manifest.Clone()),
 
       IsInstallationError(HasSubstr(
           "Manifest `id` is not present. manifest_url: " +
-          CreateDefaultManifestURL("http://manifest-test-url.com").spec())));
+          CreateDefaultManifestURL(url_info.origin().GetURL()).spec())));
 
   EXPECT_THAT(install_finalizer().web_app_info(), IsNull());
 }
 
 TEST_F(InstallIsolatedAppCommandManifestTest,
        FailsWhenManifestIdHasInvalidUTF8Character) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   blink::mojom::ManifestPtr manifest =
-      CreateDefaultManifest("http://manifest-test-url.com");
+      CreateDefaultManifest(url_info.origin().GetURL());
   char16_t invalid_utf8_chars = {0xD801};
   manifest->id = std::u16string{invalid_utf8_chars};
 
-  EXPECT_THAT(ExecuteCommandWithManifest("http://manifest-test-url.com",
-                                         manifest.Clone()),
+  EXPECT_THAT(ExecuteCommandWithManifest(url_info, manifest.Clone()),
               IsInstallationError(HasSubstr(
                   "Failed to convert manifest `id` from UTF16 to UTF8")));
 }
 
 TEST_F(InstallIsolatedAppCommandManifestTest,
        PassesManifestIdToFinalizerWhenManifestIdIsEmpty) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   blink::mojom::ManifestPtr manifest =
-      CreateDefaultManifest("http://manifest-test-url.com");
+      CreateDefaultManifest(url_info.origin().GetURL());
   manifest->id = u"";
 
-  EXPECT_THAT(ExecuteCommandWithManifest("http://manifest-test-url.com",
-                                         manifest.Clone()),
+  EXPECT_THAT(ExecuteCommandWithManifest(url_info, manifest.Clone()),
               IsInstallationOk());
 
   EXPECT_THAT(install_finalizer().web_app_info(),
@@ -643,52 +663,53 @@
 }
 
 TEST_F(InstallIsolatedAppCommandManifestTest, FailsWhenManifestIdIsNotEmpty) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   blink::mojom::ManifestPtr manifest =
-      CreateDefaultManifest("http://manifest-test-url.com");
+      CreateDefaultManifest(url_info.origin().GetURL());
   manifest->id = u"test-manifest-id";
 
-  EXPECT_THAT(ExecuteCommandWithManifest("http://manifest-test-url.com",
-                                         manifest.Clone()),
+  EXPECT_THAT(ExecuteCommandWithManifest(url_info, manifest.Clone()),
               IsInstallationError(HasSubstr(R"(Manifest `id` must be "/")")));
+  ;
   EXPECT_THAT(install_finalizer().web_app_info(), IsNull());
 }
 
 TEST_F(InstallIsolatedAppCommandManifestTest,
        FailsWhenManifestScopeIsNotSlash) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   blink::mojom::ManifestPtr manifest =
-      CreateDefaultManifest("http://manifest-test-url.com");
+      CreateDefaultManifest(url_info.origin().GetURL());
 
-  manifest->scope = GURL{"http://manifest-test-url.com"}.Resolve("/scope");
+  manifest->scope = url_info.origin().GetURL().Resolve("/scope");
 
   EXPECT_THAT(
-      ExecuteCommandWithManifest("http://manifest-test-url.com",
-                                 manifest.Clone()),
+      ExecuteCommandWithManifest(url_info, manifest.Clone()),
       IsInstallationError(HasSubstr("Scope should resolve to the origin")));
   EXPECT_THAT(install_finalizer().web_app_info(), IsNull());
 }
 
 TEST_F(InstallIsolatedAppCommandManifestTest,
        PassesManifestScopeToFinalizerWhenManifestScopeIsSlash) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   blink::mojom::ManifestPtr manifest =
-      CreateDefaultManifest("http://manifest-test-url.com");
-  manifest->scope = GURL{"http://manifest-test-url.com"}.Resolve("/");
+      CreateDefaultManifest(url_info.origin().GetURL());
+  manifest->scope = url_info.origin().GetURL().Resolve("/");
 
-  EXPECT_THAT(ExecuteCommandWithManifest("http://manifest-test-url.com",
-                                         manifest.Clone()),
+  EXPECT_THAT(ExecuteCommandWithManifest(url_info, manifest.Clone()),
               IsInstallationOk());
 
-  EXPECT_THAT(install_finalizer().web_app_info(),
-              Pointee(Field(&WebAppInstallInfo::scope,
-                            GURL{"http://manifest-test-url.com/"})));
+  EXPECT_THAT(
+      install_finalizer().web_app_info(),
+      Pointee(Field(&WebAppInstallInfo::scope, url_info.origin().GetURL())));
 }
 
 TEST_F(InstallIsolatedAppCommandManifestTest, PassesManifestNameAsTitle) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   blink::mojom::ManifestPtr manifest =
-      CreateDefaultManifest("http://manifest-test-url.com");
+      CreateDefaultManifest(url_info.origin().GetURL());
   manifest->name = u"test application name";
 
-  EXPECT_THAT(ExecuteCommandWithManifest("http://manifest-test-url.com",
-                                         manifest.Clone()),
+  EXPECT_THAT(ExecuteCommandWithManifest(url_info, manifest.Clone()),
               IsInstallationOk());
 
   EXPECT_THAT(
@@ -698,13 +719,14 @@
 
 TEST_F(InstallIsolatedAppCommandManifestTest,
        UseShortNameAsTitleWhenNameIsNotPresent) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
+
   blink::mojom::ManifestPtr manifest =
-      CreateDefaultManifest("http://manifest-test-url.com");
+      CreateDefaultManifest(url_info.origin().GetURL());
   manifest->name = absl::nullopt;
   manifest->short_name = u"test short name";
 
-  EXPECT_THAT(ExecuteCommandWithManifest("http://manifest-test-url.com",
-                                         manifest.Clone()),
+  EXPECT_THAT(ExecuteCommandWithManifest(url_info, manifest.Clone()),
               IsInstallationOk());
 
   EXPECT_THAT(install_finalizer().web_app_info(),
@@ -713,13 +735,13 @@
 
 TEST_F(InstallIsolatedAppCommandManifestTest,
        UseShortNameAsTitleWhenNameIsEmpty) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   blink::mojom::ManifestPtr manifest =
-      CreateDefaultManifest("http://manifest-test-url.com");
+      CreateDefaultManifest(url_info.origin().GetURL());
   manifest->name = u"";
   manifest->short_name = u"other test short name";
 
-  EXPECT_THAT(ExecuteCommandWithManifest("http://manifest-test-url.com",
-                                         manifest.Clone()),
+  EXPECT_THAT(ExecuteCommandWithManifest(url_info, manifest.Clone()),
               IsInstallationOk());
 
   EXPECT_THAT(
@@ -729,13 +751,13 @@
 
 TEST_F(InstallIsolatedAppCommandManifestTest,
        TitleIsmptyWhenNameAndShortNameAreNotPresent) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   blink::mojom::ManifestPtr manifest =
-      CreateDefaultManifest("http://manifest-test-url.com");
+      CreateDefaultManifest(url_info.origin().GetURL());
   manifest->name = absl::nullopt;
   manifest->short_name = absl::nullopt;
 
-  EXPECT_THAT(ExecuteCommandWithManifest("http://manifest-test-url.com",
-                                         manifest.Clone()),
+  EXPECT_THAT(ExecuteCommandWithManifest(url_info, manifest.Clone()),
               IsInstallationOk());
 
   EXPECT_THAT(install_finalizer().web_app_info(),
@@ -744,9 +766,9 @@
 
 class InstallIsolatedAppCommandManifestIconsTest
     : public InstallIsolatedAppCommandManifestTest {
+ public:
  protected:
-  static constexpr base::StringPiece kSomeTestApplicationUrl =
-      "http://manifest-test-url.com";
+  GURL kSomeTestApplicationUrl = GURL("http://manifest-test-url.com");
   void SetUp() override { InstallIsolatedAppCommandManifestTest::SetUp(); }
 
   blink::mojom::ManifestPtr CreateManifest() const {
@@ -777,7 +799,7 @@
   return bitmap;
 }
 
-blink::Manifest::ImageResource CreateImageResource(GURL image_src) {
+blink::Manifest::ImageResource CreateImageResource(const GURL& image_src) {
   blink::Manifest::ImageResource image;
   image.type = u"image/png";
   image.sizes.push_back(gfx::Size{kImageSize, kImageSize});
@@ -789,14 +811,18 @@
 }
 
 TEST_F(InstallIsolatedAppCommandManifestIconsTest, ManifestIconIsDownloaded) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
+  kSomeTestApplicationUrl = url_info.origin().GetURL();
+  GURL img_url = url_info.origin().GetURL().Resolve("icon.png");
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{kSomeTestApplicationUrl},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      kSomeTestApplicationUrl.Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   blink::mojom::ManifestPtr manifest = CreateManifest();
 
-  manifest->icons = {
-      CreateImageResource(GURL{"http://test-icon-url.com/icon.png"})};
+  manifest->icons = {CreateImageResource(img_url)};
 
   std::unique_ptr<MockDataRetriever> fake_data_retriever =
       CreateFakeDataRetriever(manifest.Clone());
@@ -806,28 +832,23 @@
           manifest, CreateDefaultManifestURL(kSomeTestApplicationUrl)));
 
   std::map<GURL, std::vector<SkBitmap>> icons = {{
-      GURL{"http://test-icon-url.com/icon.png"},
+      img_url,
       {CreateTestBitmap(SK_ColorRED)},
   }};
 
   using HttpStatusCode = int;
   std::map<GURL, HttpStatusCode> http_result = {
-      {GURL{"http://test-icon-url.com/icon.png"}, net::HttpStatusCode::HTTP_OK},
+      {img_url, net::HttpStatusCode::HTTP_OK},
   };
 
-  EXPECT_CALL(
-      *fake_data_retriever,
-      GetIcons(_,
-               UnorderedElementsAre(GURL{"http://test-icon-url.com/icon.png"}),
-               /*skip_page_favicons=*/true, IsNotNullCallback()))
+  EXPECT_CALL(*fake_data_retriever,
+              GetIcons(_, UnorderedElementsAre(img_url),
+                       /*skip_page_favicons=*/true, IsNotNullCallback()))
       .WillOnce(RunOnceCallback<3>(IconsDownloadedResult::kCompleted,
                                    std::move(icons), http_result));
 
   EXPECT_THAT(ExecuteCommand(
-                  {
-                      .url = std::string(kSomeTestApplicationUrl),
-                      .url_loader = std::move(url_loader),
-                  },
+                  {.url_info = url_info, .url_loader = std::move(url_loader)},
                   std::move(fake_data_retriever)),
               IsInstallationOk());
 
@@ -844,14 +865,18 @@
 
 TEST_F(InstallIsolatedAppCommandManifestIconsTest,
        InstallationFailsWhenIconDownloadingFails) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
+  kSomeTestApplicationUrl = url_info.origin().GetURL();
+  GURL img_url = url_info.origin().GetURL().Resolve("icon.png");
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{kSomeTestApplicationUrl},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      kSomeTestApplicationUrl.Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   blink::mojom::ManifestPtr manifest = CreateManifest();
 
-  manifest->icons = {
-      CreateImageResource(GURL{"http://test-icon-url.com/icon.png"})};
+  manifest->icons = {CreateImageResource(img_url)};
 
   std::unique_ptr<MockDataRetriever> fake_data_retriever =
       CreateFakeDataRetriever(manifest.Clone());
@@ -870,19 +895,19 @@
                                    std::move(icons), http_result));
 
   EXPECT_THAT(ExecuteCommand(
-                  {
-                      .url = std::string(kSomeTestApplicationUrl),
-                      .url_loader = std::move(url_loader),
-                  },
+                  {.url_info = url_info, .url_loader = std::move(url_loader)},
                   std::move(fake_data_retriever)),
               IsInstallationError(HasSubstr(
                   "Error during icon downloading: AbortedDueToFailure")));
 }
 
 TEST_F(InstallIsolatedAppCommandTest, SetDevModeIsolationDataBeforeUrlLoading) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{"http://test-url-example.com"},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   absl::optional<IsolationData> isolation_data = absl::nullopt;
   url_loader->TrackLoadUrlCalls(base::BindLambdaForTesting(
@@ -894,7 +919,7 @@
       }));
 
   EXPECT_THAT(ExecuteCommand({
-                  .url = "http://test-url-example.com",
+                  .url_info = url_info,
                   .url_loader = std::move(url_loader),
                   .isolation_data = IsolationData{IsolationData::DevModeProxy{
                       .proxy_url = "http://some-testing-proxy-url.com/"}},
@@ -911,9 +936,12 @@
 
 TEST_F(InstallIsolatedAppCommandTest,
        SetInstalledBundleIsolationDataBeforeUrlLoading) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{"http://test-url-example.com"},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   absl::optional<IsolationData> isolation_data = absl::nullopt;
   url_loader->TrackLoadUrlCalls(base::BindLambdaForTesting(
@@ -926,7 +954,7 @@
 
   EXPECT_THAT(
       ExecuteCommand({
-          .url = "http://test-url-example.com",
+          .url_info = url_info,
           .url_loader = std::move(url_loader),
           .isolation_data = IsolationData{IsolationData::InstalledBundle{
               .path = base::FilePath{FILE_PATH_LITERAL(
@@ -947,16 +975,17 @@
 
 TEST_F(InstallIsolatedAppCommandMetricsTest,
        ReportSuccessWhenFinishedSuccessfully) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{"http://test-url-example.com"},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   base::HistogramTester histogram_tester;
 
-  EXPECT_THAT(ExecuteCommand(Parameters{
-                  .url = "http://test-url-example.com",
-                  .url_loader = std::move(url_loader),
-              }),
+  EXPECT_THAT(ExecuteCommand(Parameters{.url_info = url_info,
+                                        .url_loader = std::move(url_loader)}),
               IsInstallationOk());
 
   EXPECT_THAT(histogram_tester.GetAllSamples("WebApp.Install.Result"),
@@ -964,17 +993,17 @@
 }
 
 TEST_F(InstallIsolatedAppCommandMetricsTest, ReportErrorWhenUrlLoaderFails) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
   url_loader->SetNextLoadUrlResult(
-      GURL{"http://test-url-example.com"},
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
       WebAppUrlLoader::Result::kFailedErrorPageLoaded);
 
   base::HistogramTester histogram_tester;
 
-  EXPECT_THAT(ExecuteCommand(Parameters{
-                  .url = "http://test-url-example.com",
-                  .url_loader = std::move(url_loader),
-              }),
+  EXPECT_THAT(ExecuteCommand(Parameters{.url_info = url_info,
+                                        .url_loader = std::move(url_loader)}),
               IsInstallationError());
 
   EXPECT_THAT(histogram_tester.GetAllSamples("WebApp.Install.Result"),
@@ -983,12 +1012,15 @@
 
 TEST_F(InstallIsolatedAppCommandMetricsTest,
        ReportFailureWhenAppIsNotInstallable) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{"http://test-url-example.com"},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   std::unique_ptr<MockDataRetriever> fake_data_retriever =
-      CreateDefaultDataRetriever("http://test-url-example.com");
+      CreateDefaultDataRetriever(url_info.origin().GetURL());
 
   ON_CALL(*fake_data_retriever, CheckInstallabilityAndRetrieveManifest)
       .WillByDefault(
@@ -999,10 +1031,7 @@
   base::HistogramTester histogram_tester;
 
   EXPECT_THAT(ExecuteCommand(
-                  {
-                      .url = "http://test-url-example.com",
-                      .url_loader = std::move(url_loader),
-                  },
+                  {.url_info = url_info, .url_loader = std::move(url_loader)},
                   std::move(fake_data_retriever)),
               IsInstallationError());
 
@@ -1011,26 +1040,26 @@
 }
 
 TEST_F(InstallIsolatedAppCommandMetricsTest, ReportFailureWhenManifestIsNull) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   auto url_loader = std::make_unique<TestWebAppUrlLoader>();
-  url_loader->SetNextLoadUrlResult(GURL{"http://test-url-example.com"},
-                                   WebAppUrlLoader::Result::kUrlLoaded);
+  url_loader->SetNextLoadUrlResult(
+      url_info.origin().GetURL().Resolve(
+          ".well-known/_generated_install_page.html"),
+      WebAppUrlLoader::Result::kUrlLoaded);
 
   std::unique_ptr<MockDataRetriever> fake_data_retriever =
-      CreateDefaultDataRetriever("http://test-url-example.com");
+      CreateDefaultDataRetriever(url_info.origin().GetURL());
 
   ON_CALL(*fake_data_retriever, CheckInstallabilityAndRetrieveManifest)
       .WillByDefault(ReturnManifest(
           /*manifest=*/nullptr,
-          CreateDefaultManifestURL("http://test-url-example.com"),
+          CreateDefaultManifestURL(url_info.origin().GetURL()),
           /*is_installable=*/false));
 
   base::HistogramTester histogram_tester;
 
   EXPECT_THAT(ExecuteCommand(
-                  {
-                      .url = "http://test-url-example.com",
-                      .url_loader = std::move(url_loader),
-                  },
+                  {.url_info = url_info, .url_loader = std::move(url_loader)},
                   std::move(fake_data_retriever)),
               IsInstallationError());
 
@@ -1040,14 +1069,14 @@
 
 TEST_F(InstallIsolatedAppCommandMetricsTest,
        ReportFailureWhenManifestIdIsNotEmpty) {
+  IsolatedWebAppUrlInfo url_info = CreateRandomIsolatedWebAppUrlInfo();
   blink::mojom::ManifestPtr manifest =
-      CreateDefaultManifest("http://manifest-test-url.com");
+      CreateDefaultManifest(url_info.origin().GetURL());
   manifest->id = u"test manifest id";
 
   base::HistogramTester histogram_tester;
 
-  EXPECT_THAT(ExecuteCommandWithManifest("http://manifest-test-url.com",
-                                         manifest.Clone()),
+  EXPECT_THAT(ExecuteCommandWithManifest(url_info, manifest.Clone()),
               IsInstallationError());
   EXPECT_THAT(histogram_tester.GetAllSamples("WebApp.Install.Result"),
               BucketsAre(base::Bucket(false, 1)));
diff --git a/chrome/browser/web_applications/isolated_web_apps/install_isolated_app_from_command_line.cc b/chrome/browser/web_applications/isolated_web_apps/install_isolated_app_from_command_line.cc
index 7fecf75..25a0fc1 100644
--- a/chrome/browser/web_applications/isolated_web_apps/install_isolated_app_from_command_line.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/install_isolated_app_from_command_line.cc
@@ -12,18 +12,22 @@
 #include "base/callback.h"
 #include "base/callback_helpers.h"
 #include "base/command_line.h"
+#include "base/files/file_util.h"
 #include "base/functional/callback_forward.h"
 #include "base/functional/callback_helpers.h"
 #include "base/no_destructor.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_piece.h"
+#include "base/types/expected.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/commands/install_isolated_app_command.h"
+#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
 #include "chrome/browser/web_applications/isolation_data.h"
 #include "chrome/browser/web_applications/web_app_command_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_url_loader.h"
 #include "chrome/common/chrome_switches.h"
+#include "components/web_package/signed_web_bundles/signed_web_bundle_id.h"
 #include "components/webapps/browser/installable/installable_manager.h"
 #include "content/public/browser/web_contents.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -61,27 +65,23 @@
   DCHECK(url.is_valid());
   DCHECK(!callback.is_null());
 
+  // TODO(zelin): move random generation up to MaybeInstallAppFromCommandLine()
+  web_package::SignedWebBundleId random_signed_web_bundle_id =
+      web_package::SignedWebBundleId::CreateRandomForDevelopment();
+  base::expected<IsolatedWebAppUrlInfo, std::string> url_info =
+      IsolatedWebAppUrlInfo::CreateFromSignedWebBundleId(
+          random_signed_web_bundle_id);
+  DCHECK(url_info.has_value());
+
   provider.command_manager().ScheduleCommand(
       std::make_unique<InstallIsolatedAppCommand>(
-          url, isolation_data, CreateWebContents(profile),
+          url_info.value(), isolation_data, CreateWebContents(profile),
           std::make_unique<WebAppUrlLoader>(), provider.install_finalizer(),
           base::BindOnce(&ReportInstallationResult).Then(std::move(callback))));
 }
 
-base::OnceClosure& GetNextDoneCallbackInstance() {
-  static base::NoDestructor<base::OnceClosure> kInstance{base::NullCallback()};
-  return *kInstance;
-}
-
-}  // namespace
-
-void SetNextInstallationDoneCallbackForTesting(  // IN-TEST
-    base::OnceClosure done_callback) {
-  GetNextDoneCallbackInstance() = std::move(done_callback);
-}
-
 base::expected<absl::optional<IsolationData>, std::string>
-GetIsolationDataFromCommandLine(const base::CommandLine& command_line) {
+GetProxyUrlFromCommandLine(const base::CommandLine& command_line) {
   std::string switch_value =
       command_line.GetSwitchValueASCII(switches::kInstallIsolatedWebAppFromUrl);
 
@@ -100,6 +100,60 @@
   return IsolationData{IsolationData::DevModeProxy{.proxy_url = url.spec()}};
 }
 
+base::expected<absl::optional<IsolationData>, std::string>
+GetBundlePathFromCommandLine(const base::CommandLine& command_line) {
+  base::FilePath switch_value =
+      command_line.GetSwitchValuePath(switches::kInstallIsolatedWebAppFromFile);
+
+  if (switch_value.empty()) {
+    return absl::nullopt;
+  }
+
+  base::FilePath absolute_path = base::MakeAbsoluteFilePath(switch_value);
+
+  if (!base::PathExists(absolute_path) ||
+      base::DirectoryExists(absolute_path)) {
+    return base::unexpected(base::StrCat(
+        {"Invalid path provided to --", switches::kInstallIsolatedWebAppFromUrl,
+         " flag: '", absolute_path.AsUTF8Unsafe(), "'"}));
+  }
+
+  return IsolationData{IsolationData::DevModeBundle{.path = absolute_path}};
+}
+
+base::OnceClosure& GetNextDoneCallbackInstance() {
+  static base::NoDestructor<base::OnceClosure> kInstance{base::NullCallback()};
+  return *kInstance;
+}
+
+}  // namespace
+
+void SetNextInstallationDoneCallbackForTesting(  // IN-TEST
+    base::OnceClosure done_callback) {
+  GetNextDoneCallbackInstance() = std::move(done_callback);
+}
+
+base::expected<absl::optional<IsolationData>, std::string>
+GetIsolationDataFromCommandLine(const base::CommandLine& command_line) {
+  base::expected<absl::optional<IsolationData>, std::string> proxy_url =
+      GetProxyUrlFromCommandLine(command_line);
+  base::expected<absl::optional<IsolationData>, std::string> bundle_path =
+      GetBundlePathFromCommandLine(command_line);
+
+  // Return an error if both flags are set.
+  bool was_proxy_url_set = !proxy_url.has_value() || proxy_url->has_value();
+  bool was_bundle_path_set =
+      !bundle_path.has_value() || bundle_path->has_value();
+  if (was_proxy_url_set && was_bundle_path_set) {
+    return base::unexpected(
+        base::StrCat({"--", switches::kInstallIsolatedWebAppFromUrl, " and --",
+                      switches::kInstallIsolatedWebAppFromFile,
+                      " cannot both be provided."}));
+  }
+
+  return was_proxy_url_set ? proxy_url : bundle_path;
+}
+
 void MaybeInstallAppFromCommandLine(const base::CommandLine& command_line,
                                     Profile& profile) {
   base::OnceClosure& next_done_callback = GetNextDoneCallbackInstance();
diff --git a/chrome/browser/web_applications/isolated_web_apps/install_isolated_app_from_command_line_browsertest.cc b/chrome/browser/web_applications/isolated_web_apps/install_isolated_app_from_command_line_browsertest.cc
index 346f478..cbe37a4 100644
--- a/chrome/browser/web_applications/isolated_web_apps/install_isolated_app_from_command_line_browsertest.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/install_isolated_app_from_command_line_browsertest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <vector>
 #include "chrome/browser/web_applications/isolated_web_apps/install_isolated_app_from_command_line.h"
 
 #include "base/command_line.h"
@@ -12,12 +13,14 @@
 #include "base/test/bind.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
 #include "chrome/browser/web_applications/web_app.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_id.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/common/content_features.h"
 #include "content/public/test/browser_test.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "url/gurl.h"
@@ -30,6 +33,7 @@
     : public InProcessBrowserTest {
  protected:
   void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(features::kIsolatedWebApps);
     embedded_test_server()->AddDefaultHandlers(
         GetChromeTestDataDir().AppendASCII("web_apps/simple_isolated_app"));
     ASSERT_TRUE(embedded_test_server()->Start());
@@ -63,15 +67,21 @@
 
  private:
   base::OneShotEvent is_installation_done_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
+// TODO(zelin): enable this browser test
 IN_PROC_BROWSER_TEST_F(InstallIsolatedAppFromCommandLineBrowserTest,
-                       AppFromCommandLineIsInstalled) {
+                       DISABLED_AppFromCommandLineIsInstalled) {
   WaitForInstallation();
 
-  const AppId app_id = GenerateAppId("", GetAppUrl());
+  ASSERT_THAT(GetWebAppRegistrar().CountUserInstalledApps(), 1);
 
-  ASSERT_THAT(GetWebAppRegistrar().IsInstalled(app_id), IsTrue());
+  std::vector<AppId> all_apps = GetWebAppRegistrar().GetAppIds();
+
+  ASSERT_THAT(all_apps.size(), 1);
+
+  AppId app_id = all_apps.front();
 
   const WebApp* web_app = GetWebAppRegistrar().GetAppById(app_id);
   EXPECT_THAT(web_app->isolation_data().has_value(), IsTrue());
diff --git a/chrome/browser/web_applications/isolated_web_apps/install_isolated_app_from_command_line_unittest.cc b/chrome/browser/web_applications/isolated_web_apps/install_isolated_app_from_command_line_unittest.cc
index a6a36c35..0dea4f4 100644
--- a/chrome/browser/web_applications/isolated_web_apps/install_isolated_app_from_command_line_unittest.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/install_isolated_app_from_command_line_unittest.cc
@@ -6,6 +6,9 @@
 
 #include "base/callback.h"
 #include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_piece_forward.h"
 #include "base/test/bind.h"
@@ -67,10 +70,62 @@
   return true;
 }
 
-base::CommandLine CreateDefaultCommandLine(base::StringPiece flag_value) {
+MATCHER_P(IsDevModeBundle,
+          bundle_path,
+          std::string(negation ? "isn't " : "Dev Mode bundle at: \"") +
+              bundle_path.AsUTF8Unsafe() + '"') {
+  if (!arg.has_value() || !arg.value().has_value()) {
+    DescribeOptionalIsolationData(result_listener, arg);
+    return false;
+  }
+  const IsolationData::DevModeBundle* bundle =
+      absl::get_if<IsolationData::DevModeBundle>(&arg.value().value().content);
+  if (bundle == nullptr || bundle->path != bundle_path) {
+    DescribeOptionalIsolationData(result_listener, arg);
+    return false;
+  }
+  return true;
+}
+
+// Sets the current working directory to a location that contains a file.
+// The working directory is restored when the object is destroyed.
+class ScopedWorkingDirectoryWithFile {
+ public:
+  ScopedWorkingDirectoryWithFile() {
+    // Rather than creating a temporary directory and file, just use the
+    // current binary, which we know will always exist.
+    CHECK(base::GetCurrentDirectory(&original_working_directory_));
+    CHECK(base::PathService::Get(base::FILE_EXE, &executable_path_));
+    CHECK(base::SetCurrentDirectory(executable_path_.DirName()));
+  }
+
+  ~ScopedWorkingDirectoryWithFile() {
+    CHECK(base::SetCurrentDirectory(original_working_directory_));
+  }
+
+  base::FilePath existing_file_path() { return executable_path_; }
+
+  base::FilePath existing_file_name() { return executable_path_.BaseName(); }
+
+  base::FilePath directory() { return executable_path_.DirName(); }
+
+ private:
+  base::FilePath original_working_directory_;
+  base::FilePath executable_path_;
+};
+
+base::CommandLine CreateCommandLine(
+    absl::optional<base::StringPiece> proxy_flag_value,
+    absl::optional<base::FilePath> bundle_flag_value) {
   base::CommandLine command_line{base::CommandLine::NoProgram::NO_PROGRAM};
-  command_line.AppendSwitchASCII("install-isolated-web-app-from-url",
-                                 flag_value);
+  if (proxy_flag_value.has_value()) {
+    command_line.AppendSwitchASCII("install-isolated-web-app-from-url",
+                                   proxy_flag_value.value());
+  }
+  if (bundle_flag_value.has_value()) {
+    command_line.AppendSwitchPath("install-isolated-web-app-from-file",
+                                  bundle_flag_value.value());
+  }
   return command_line;
 }
 
@@ -80,36 +135,138 @@
 };
 
 TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
-       InstallsAppFromCommandLineFlag) {
+       NoInstallationWhenProxyFlagAbsentAndBundleFlagAbsent) {
   EXPECT_THAT(GetIsolationDataFromCommandLine(
-                  CreateDefaultCommandLine("http://example.com")),
-              IsDevModeProxy("http://example.com"));
-}
-
-TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
-       InstallsDifferentAppFromCommandLineFlag) {
-  EXPECT_THAT(GetIsolationDataFromCommandLine(
-                  CreateDefaultCommandLine("http://different-example.com")),
-              IsDevModeProxy("http://different-example.com"));
-}
-
-TEST_F(InstallIsolatedAppFromCommandLineFlagTest, NoneForInvalidUrls) {
-  EXPECT_THAT(
-      GetIsolationDataFromCommandLine(CreateDefaultCommandLine("badurl")),
-      HasErrorWithSubstr("Invalid URL"));
-}
-
-TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
-       DoNotCallInstallationWhenFlagIsEmpty) {
-  EXPECT_THAT(GetIsolationDataFromCommandLine(CreateDefaultCommandLine("")),
+                  CreateCommandLine(absl::nullopt, absl::nullopt)),
               HasNoValue());
 }
 
 TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
-       DoNotCallInstallationWhenFlagIsNotPresent) {
-  const base::CommandLine command_line{
-      base::CommandLine::NoProgram::NO_PROGRAM};
-  EXPECT_THAT(GetIsolationDataFromCommandLine(command_line), HasNoValue());
+       NoInstallationWhenProxyFlagAbsentAndBundleFlagEmpty) {
+  EXPECT_THAT(GetIsolationDataFromCommandLine(CreateCommandLine(
+                  absl::nullopt, base::FilePath::FromUTF8Unsafe(""))),
+              HasNoValue());
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       ErrorWhenProxyFlagAbsentAndBundleFlagInvalid) {
+  EXPECT_THAT(GetIsolationDataFromCommandLine(CreateCommandLine(
+                  absl::nullopt,
+                  base::FilePath::FromUTF8Unsafe("does_not_exist.wbn)"))),
+              HasErrorWithSubstr("Invalid path provided"));
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       ErrorWhenProxyFlagAbsentAndBundleFlagIsDirectory) {
+  ScopedWorkingDirectoryWithFile cwd;
+  EXPECT_THAT(GetIsolationDataFromCommandLine(
+                  CreateCommandLine(absl::nullopt, cwd.directory())),
+              HasErrorWithSubstr("Invalid path provided"));
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       InstallsAppWhenProxyFlagAbsentAndBundleFlagValid) {
+  ScopedWorkingDirectoryWithFile cwd;
+  EXPECT_THAT(GetIsolationDataFromCommandLine(
+                  CreateCommandLine(absl::nullopt, cwd.existing_file_name())),
+              IsDevModeBundle(cwd.existing_file_path()));
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       InstallsAppWhenProxyFlagAbsentAndBundleFlagValidAndAbsolute) {
+  ScopedWorkingDirectoryWithFile cwd;
+  EXPECT_THAT(GetIsolationDataFromCommandLine(
+                  CreateCommandLine(absl::nullopt, cwd.existing_file_path())),
+              IsDevModeBundle(cwd.existing_file_path()));
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       NoInstallationWhenProxyFlagEmptyAndBundleFlagAbsent) {
+  EXPECT_THAT(
+      GetIsolationDataFromCommandLine(CreateCommandLine("", absl::nullopt)),
+      HasNoValue());
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       NoInstallationWhenProxyFlagEmptyAndBundleFlagEmpty) {
+  EXPECT_THAT(GetIsolationDataFromCommandLine(
+                  CreateCommandLine("", base::FilePath::FromUTF8Unsafe(""))),
+              HasNoValue());
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       ErrorWhenProxyFlagEmptyAndBundleFlagInvalid) {
+  EXPECT_THAT(GetIsolationDataFromCommandLine(CreateCommandLine(
+                  "", base::FilePath::FromUTF8Unsafe("does_not_exist.wbn"))),
+              HasErrorWithSubstr("Invalid path provided"));
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       InstallsAppWhenProxyFlagEmptyAndBundleFlagValid) {
+  ScopedWorkingDirectoryWithFile cwd;
+  EXPECT_THAT(GetIsolationDataFromCommandLine(
+                  CreateCommandLine("", cwd.existing_file_name())),
+              IsDevModeBundle(cwd.existing_file_path()));
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       ErrorWhenProxyFlagInvalidAndBundleFlagAbsent) {
+  EXPECT_THAT(GetIsolationDataFromCommandLine(
+                  CreateCommandLine("invalid", absl::nullopt)),
+              HasErrorWithSubstr("Invalid URL"));
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       ErrorWhenProxyFlagInvalidAndBundleFlagEmpty) {
+  EXPECT_THAT(GetIsolationDataFromCommandLine(CreateCommandLine(
+                  "invalid", base::FilePath::FromUTF8Unsafe(""))),
+              HasErrorWithSubstr("Invalid URL"));
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       ErrorWhenProxyFlagInvalidAndBundleFlagInvalid) {
+  EXPECT_THAT(
+      GetIsolationDataFromCommandLine(CreateCommandLine(
+          "invalid", base::FilePath::FromUTF8Unsafe("does_not_exist.wbn"))),
+      HasErrorWithSubstr("cannot both be provided"));
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       ErrorWhenProxyFlagInvalidAndBundleFlagValid) {
+  ScopedWorkingDirectoryWithFile cwd;
+  EXPECT_THAT(GetIsolationDataFromCommandLine(
+                  CreateCommandLine("invalid", cwd.existing_file_name())),
+              HasErrorWithSubstr("cannot both be provided"));
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       InstallsAppWhenProxyFlagValidAndBundleFlagAbsent) {
+  EXPECT_THAT(GetIsolationDataFromCommandLine(
+                  CreateCommandLine("http://example.com", absl::nullopt)),
+              IsDevModeProxy("http://example.com"));
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       InstallsAppWhenProxyFlagValidAndBundleFlagEmpty) {
+  EXPECT_THAT(GetIsolationDataFromCommandLine(CreateCommandLine(
+                  "http://example.com", base::FilePath::FromUTF8Unsafe(""))),
+              IsDevModeProxy("http://example.com"));
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       ErrorWhenProxyFlagValidAndBundleFlagInvalid) {
+  EXPECT_THAT(GetIsolationDataFromCommandLine(CreateCommandLine(
+                  "http://example.com",
+                  base::FilePath::FromUTF8Unsafe("does_not_exist.wbn"))),
+              HasErrorWithSubstr("cannot both be provided"));
+}
+
+TEST_F(InstallIsolatedAppFromCommandLineFlagTest,
+       ErrorWhenProxyFlagValidAndBundleFlagValid) {
+  ScopedWorkingDirectoryWithFile cwd;
+  EXPECT_THAT(GetIsolationDataFromCommandLine(CreateCommandLine(
+                  "http://example.com", cwd.existing_file_name())),
+              HasErrorWithSubstr("cannot both be provided"));
 }
 
 }  // namespace
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_loader_factory.cc b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_loader_factory.cc
index cee7da70..e1537c4 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_loader_factory.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_loader_factory.cc
@@ -42,6 +42,7 @@
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/mojom/fetch_api.mojom.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
 #include "services/network/public/mojom/url_loader_completion_status.mojom.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
@@ -454,8 +455,11 @@
   GURL proxy_url = proxy_url_base.Resolve(resource_request.url.path());
 
   // Create a new ResourceRequest with the proxy URL.
-  network::ResourceRequest proxy_request(resource_request);
+  network::ResourceRequest proxy_request;
   proxy_request.url = proxy_url;
+  proxy_request.method = net::HttpRequestHeaders::kGetMethod;
+  // Don't send cookies or HTTP authentication to the proxy server.
+  proxy_request.credentials_mode = network::mojom::CredentialsMode::kOmit;
 
   content::StoragePartition* storage_partition = profile_->GetStoragePartition(
       url_info.storage_partition_config(profile_), /*can_create=*/false);
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_loader_factory_unittest.cc b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_loader_factory_unittest.cc
index 4f818ec..702a62d 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_loader_factory_unittest.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_loader_factory_unittest.cc
@@ -33,6 +33,7 @@
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
 #include "services/network/public/cpp/resource_request.h"
+#include "services/network/public/mojom/fetch_api.mojom.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -103,18 +104,25 @@
       : interceptor_(base::BindRepeating(&ScopedUrlHandler::Intercept,
                                          base::Unretained(this))) {}
 
-  absl::optional<GURL> intercepted_url() const { return intercepted_url_; }
+  absl::optional<network::ResourceRequest> request() const { return request_; }
+
+  absl::optional<GURL> intercepted_url() const {
+    if (request_.has_value()) {
+      return request_->url;
+    }
+    return absl::nullopt;
+  }
 
  private:
   bool Intercept(content::URLLoaderInterceptor::RequestParams* params) {
-    intercepted_url_ = params->url_request.url;
+    request_ = params->url_request;
     content::URLLoaderInterceptor::WriteResponse(
         "HTTP/1.1 200 OK\n", "test body", params->client.get());
     return true;
   }
 
   content::URLLoaderInterceptor interceptor_;
-  absl::optional<GURL> intercepted_url_;
+  absl::optional<network::ResourceRequest> request_;
 };
 
 }  // namespace
@@ -505,6 +513,24 @@
               Eq(GURL("http://example.com/foo/bar.html")));
 }
 
+TEST_F(IsolatedWebAppURLLoaderFactoryTest, ProxyUrlRemovesOriginalRequestData) {
+  RegisterWebApp(CreateIsolatedWebApp(kAppStartUrl,
+                                      IsolationData{IsolationData::DevModeProxy{
+                                          .proxy_url = "http://example.com"}}));
+
+  CreateFactory();
+
+  auto request = std::make_unique<network::ResourceRequest>();
+  request->url = GURL("isolated-app://" + kWebBundleId + "/foo/bar.html");
+  CreateLoaderAndRun(std::move(request));
+
+  ASSERT_THAT(url_handler().intercepted_url(),
+              Eq(GURL("http://example.com/foo/bar.html")));
+  EXPECT_THAT(url_handler().request()->credentials_mode,
+              Eq(network::mojom::CredentialsMode::kOmit));
+  EXPECT_THAT(url_handler().request()->request_initiator, Eq(absl::nullopt));
+}
+
 TEST_F(IsolatedWebAppURLLoaderFactoryTest,
        DoNotReturnGeneratedPageWhenNotInstallingApplication) {
   RegisterWebApp(CreateIsolatedWebApp(kAppStartUrl,
diff --git a/chrome/browser/web_applications/manifest_update_manager.cc b/chrome/browser/web_applications/manifest_update_manager.cc
index df0d3bf..f13cad22 100644
--- a/chrome/browser/web_applications/manifest_update_manager.cc
+++ b/chrome/browser/web_applications/manifest_update_manager.cc
@@ -16,6 +16,8 @@
 #include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/web_applications/manifest_update_task.h"
+#include "chrome/browser/web_applications/manifest_update_utils.h"
 #include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
 #include "chrome/browser/web_applications/web_app_constants.h"
 #include "chrome/browser/web_applications/web_app_system_web_app_delegate_map_utils.h"
diff --git a/chrome/browser/web_applications/manifest_update_manager.h b/chrome/browser/web_applications/manifest_update_manager.h
index 2c2adf4..93f7356 100644
--- a/chrome/browser/web_applications/manifest_update_manager.h
+++ b/chrome/browser/web_applications/manifest_update_manager.h
@@ -16,6 +16,7 @@
 #include "chrome/browser/ash/system_web_apps/types/system_web_app_delegate_map.h"
 #include "chrome/browser/web_applications/app_registrar_observer.h"
 #include "chrome/browser/web_applications/manifest_update_task.h"
+#include "chrome/browser/web_applications/manifest_update_utils.h"
 #include "chrome/browser/web_applications/web_app_id.h"
 #include "chrome/browser/web_applications/web_app_install_manager.h"
 #include "chrome/browser/web_applications/web_app_install_manager_observer.h"
diff --git a/chrome/browser/web_applications/manifest_update_task.cc b/chrome/browser/web_applications/manifest_update_task.cc
index a5e0c65d..30f64a4 100644
--- a/chrome/browser/web_applications/manifest_update_task.cc
+++ b/chrome/browser/web_applications/manifest_update_task.cc
@@ -125,16 +125,6 @@
 }
 
 // Some apps, such as pre-installed apps, have been vetted and are therefore
-// considered safe and permitted to update their names.
-bool AllowUnpromptedNameUpdate(const AppId& app_id,
-                               const WebAppRegistrar& registrar) {
-  const WebApp* web_app = registrar.GetAppById(app_id);
-  if (!web_app)
-    return false;
-  return CanWebAppUpdateIdentity(web_app);
-}
-
-// Some apps, such as pre-installed apps, have been vetted and are therefore
 // considered safe and permitted to update their icon. For others, the feature
 // flag needs to be on.
 bool AllowUnpromptedIconUpdate(const AppId& app_id,
@@ -146,30 +136,6 @@
          base::FeatureList::IsEnabled(features::kWebAppManifestIconUpdating);
 }
 
-bool NeedsAppIdentityUpdateDialog(bool title_changing,
-                                  bool icons_changing,
-                                  const AppId& app_id,
-                                  const WebAppRegistrar& registrar) {
-  // Shortcut apps can trigger the update check (https://crbug.com/1366600) on
-  // subsequent runs of the app, if the user changed the title of the app when
-  // creating the shortcut. But we should never run the App Identity dialog for
-  // shortcut apps. Also, ideally we should just use IsShortcutApp here instead
-  // of checking the install source, but as per https://crbug.com/1368592 there
-  // is a bug with that where it returns the wrong thing for Shortcut apps that
-  // specify `scope`.
-  if (registrar.IsShortcutApp(app_id) ||
-      registrar.GetAppInstallSourceForMetrics(app_id) ==
-          webapps::WebappInstallSource::MENU_CREATE_SHORTCUT) {
-    return false;
-  }
-
-  if (title_changing && !AllowUnpromptedNameUpdate(app_id, registrar))
-    return true;
-  if (icons_changing && !AllowUnpromptedIconUpdate(app_id, registrar))
-    return true;
-  return false;
-}
-
 }  // namespace
 
 IconDiff HaveIconBitmapsChanged(
@@ -281,7 +247,7 @@
     DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
     return;
   }
-  stage_ = Stage::kPendingInstallableData;
+  stage_ = ManifestUpdateStage::kPendingInstallableData;
   webapps::InstallableParams params;
   params.valid_primary_icon = true;
   params.valid_manifest = true;
@@ -305,7 +271,7 @@
     DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
     return;
   }
-  DCHECK_EQ(stage_, Stage::kPendingInstallableData);
+  DCHECK_EQ(stage_, ManifestUpdateStage::kPendingInstallableData);
 
   if (!data.NoBlockingErrors()) {
     DestroySelf(ManifestUpdateResult::kAppNotEligible);
@@ -442,9 +408,9 @@
     DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
     return;
   }
-  DCHECK(stage_ == Stage::kPendingInstallableData ||
-         stage_ == Stage::kPendingAppIdentityCheck);
-  stage_ = Stage::kPendingWindowsClosed;
+  DCHECK(stage_ == ManifestUpdateStage::kPendingInstallableData ||
+         stage_ == ManifestUpdateStage::kPendingAppIdentityCheck);
+  stage_ = ManifestUpdateStage::kPendingWindowsClosed;
 
   Profile* profile =
       Profile::FromBrowserContext(web_contents_.get()->GetBrowserContext());
@@ -475,8 +441,8 @@
     DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
     return;
   }
-  DCHECK_EQ(stage_, Stage::kPendingInstallableData);
-  stage_ = Stage::kPendingIconDownload;
+  DCHECK_EQ(stage_, ManifestUpdateStage::kPendingInstallableData);
+  stage_ = ManifestUpdateStage::kPendingIconDownload;
 
   DCHECK(install_info_.has_value());
   base::flat_set<GURL> icon_urls = GetValidIconUrlsToDownload(*install_info_);
@@ -500,7 +466,7 @@
     DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
     return;
   }
-  DCHECK_EQ(stage_, Stage::kPendingIconDownload);
+  DCHECK_EQ(stage_, ManifestUpdateStage::kPendingIconDownload);
 
   // TODO(crbug.com/1238622): Report `result` and `icons_http_results` in
   // internals.
@@ -516,7 +482,7 @@
   RecordDownloadedIconsHttpResultsCodeClass(
       "WebApp.Icon.HttpStatusCodeClassOnUpdate", result, icons_http_results);
 
-  stage_ = Stage::kPendingIconReadFromDisk;
+  stage_ = ManifestUpdateStage::kPendingIconReadFromDisk;
   icon_manager_.ReadAllIcons(
       app_id_, base::BindOnce(&ManifestUpdateTask::OnAllIconsRead, AsWeakPtr(),
                               std::move(icons_map)));
@@ -531,7 +497,7 @@
     DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
     return;
   }
-  DCHECK_EQ(stage_, Stage::kPendingIconReadFromDisk);
+  DCHECK_EQ(stage_, ManifestUpdateStage::kPendingIconReadFromDisk);
 
   if (disk_icon_bitmaps.empty()) {
     DestroySelf(ManifestUpdateResult::kIconReadFromDiskFailed);
@@ -539,7 +505,7 @@
   }
   DCHECK(install_info_.has_value());
 
-  stage_ = Stage::kPendingAppIdentityCheck;
+  stage_ = ManifestUpdateStage::kPendingAppIdentityCheck;
 
   // These calls populate the |install_info_| with all icon bitmap
   // data. If this data does not match what we already have on disk, then an
@@ -677,7 +643,7 @@
     DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
     return;
   }
-  DCHECK_EQ(stage_, Stage::kPendingAppIdentityCheck);
+  DCHECK_EQ(stage_, ManifestUpdateStage::kPendingAppIdentityCheck);
 
   app_identity_update_allowed_ =
       app_identity_update_allowed == AppIdentityUpdate::kAllowed;
@@ -716,7 +682,7 @@
     DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
     return;
   }
-  DCHECK_EQ(stage_, Stage::kPendingAppIdentityCheck);
+  DCHECK_EQ(stage_, ManifestUpdateStage::kPendingAppIdentityCheck);
 
   DCHECK(install_info_.has_value());
 
@@ -765,8 +731,8 @@
 }
 
 void ManifestUpdateTask::NoManifestUpdateRequired() {
-  DCHECK_EQ(stage_, Stage::kPendingAppIdentityCheck);
-  stage_ = Stage::kPendingAssociationsUpdate;
+  DCHECK_EQ(stage_, ManifestUpdateStage::kPendingAppIdentityCheck);
+  stage_ = ManifestUpdateStage::kPendingAssociationsUpdate;
 
   if (!IsUpdateNeededForWebAppOriginAssociations()) {
     DestroySelf(ManifestUpdateResult::kAppUpToDate);
@@ -780,13 +746,13 @@
 }
 
 void ManifestUpdateTask::OnWebAppOriginAssociationsUpdated(bool success) {
-  DCHECK_EQ(stage_, Stage::kPendingAssociationsUpdate);
+  DCHECK_EQ(stage_, ManifestUpdateStage::kPendingAssociationsUpdate);
   success ? DestroySelf(ManifestUpdateResult::kAppAssociationsUpdated)
           : DestroySelf(ManifestUpdateResult::kAppAssociationsUpdateFailed);
 }
 
 void ManifestUpdateTask::OnAllAppWindowsClosed() {
-  DCHECK_EQ(stage_, Stage::kPendingWindowsClosed);
+  DCHECK_EQ(stage_, ManifestUpdateStage::kPendingWindowsClosed);
 
   DCHECK(install_info_.has_value());
 
@@ -801,7 +767,7 @@
   // Preserve the user's choice of form factor to open the app with.
   install_info_->user_display_mode = registrar_.GetAppUserDisplayMode(app_id_);
 
-  stage_ = Stage::kPendingInstallation;
+  stage_ = ManifestUpdateStage::kPendingInstallation;
 
   install_finalizer_.FinalizeUpdate(
       *install_info_,
@@ -811,7 +777,7 @@
 void ManifestUpdateTask::OnInstallationComplete(const AppId& app_id,
                                                 webapps::InstallResultCode code,
                                                 OsHooksErrors os_hooks_errors) {
-  DCHECK_EQ(stage_, Stage::kPendingInstallation);
+  DCHECK_EQ(stage_, ManifestUpdateStage::kPendingInstallation);
 
   if (!IsSuccess(code)) {
     DestroySelf(ManifestUpdateResult::kAppUpdateFailed);
diff --git a/chrome/browser/web_applications/manifest_update_task.h b/chrome/browser/web_applications/manifest_update_task.h
index bd8432d3..5b1c783 100644
--- a/chrome/browser/web_applications/manifest_update_task.h
+++ b/chrome/browser/web_applications/manifest_update_task.h
@@ -9,6 +9,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
+#include "chrome/browser/web_applications/manifest_update_utils.h"
 #include "chrome/browser/web_applications/web_app_icon_downloader.h"
 #include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "chrome/browser/web_applications/web_app_id.h"
@@ -38,26 +39,6 @@
 
 struct IconDiff;
 
-// This enum is recorded by UMA, the numeric values must not change.
-enum ManifestUpdateResult {
-  kNoAppInScope = 0,
-  kThrottled = 1,
-  kWebContentsDestroyed = 2,
-  kAppUninstalling = 3,
-  kAppIsPlaceholder = 4,
-  kAppUpToDate = 5,
-  kAppNotEligible = 6,
-  kAppUpdateFailed = 7,
-  kAppUpdated = 8,
-  kAppIsSystemWebApp = 9,
-  kIconDownloadFailed = 10,
-  kIconReadFromDiskFailed = 11,
-  kAppIdMismatch = 12,
-  kAppAssociationsUpdateFailed = 13,
-  kAppAssociationsUpdated = 14,
-  kMaxValue = kAppAssociationsUpdated,
-};
-
 enum IconDiffResult : uint32_t {
   NO_CHANGE_DETECTED = 0,
 
@@ -184,16 +165,6 @@
   void Start();
 
  private:
-  enum class Stage {
-    kPendingInstallableData,
-    kPendingIconDownload,
-    kPendingIconReadFromDisk,
-    kPendingAppIdentityCheck,
-    kPendingWindowsClosed,
-    kPendingMaybeReadExistingIcons,
-    kPendingInstallation,
-    kPendingAssociationsUpdate,
-  };
   // We perform this check for the following Stages:
   // kPendingInstallableData
   // kPendingIconDownload
@@ -237,7 +208,7 @@
   OsIntegrationManager& os_integration_manager_;
   raw_ptr<WebAppSyncBridge> sync_bridge_ = nullptr;
 
-  Stage stage_;
+  ManifestUpdateStage stage_;
   absl::optional<WebAppInstallInfo> install_info_;
   absl::optional<WebAppIconDownloader> icon_downloader_;
 
diff --git a/chrome/browser/web_applications/manifest_update_utils.cc b/chrome/browser/web_applications/manifest_update_utils.cc
new file mode 100644
index 0000000..84251f6e
--- /dev/null
+++ b/chrome/browser/web_applications/manifest_update_utils.cc
@@ -0,0 +1,225 @@
+
+// Copyright 2022 The Chromium Authors
+// 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/manifest_update_utils.h"
+
+#include <ostream>
+#include <string>
+
+#include "base/feature_list.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/web_applications/web_app.h"
+#include "chrome/browser/web_applications/web_app_id.h"
+#include "chrome/browser/web_applications/web_app_install_utils.h"
+#include "chrome/browser/web_applications/web_app_registrar.h"
+#include "chrome/common/chrome_features.h"
+#include "third_party/blink/public/common/features.h"
+
+namespace web_app {
+
+namespace {
+
+// Some apps, such as pre-installed apps, have been vetted and are therefore
+// considered safe and permitted to update their icon. For others, the
+// feature flag needs to be on.
+bool AllowUnpromptedIconUpdate(const AppId& app_id,
+                               const WebAppRegistrar& registrar) {
+  const WebApp* web_app = registrar.GetAppById(app_id);
+  if (!web_app)
+    return false;
+  return CanWebAppUpdateIdentity(web_app) ||
+         base::FeatureList::IsEnabled(features::kWebAppManifestIconUpdating);
+}
+
+}  // namespace
+
+std::ostream& operator<<(std::ostream& os, ManifestUpdateResult result) {
+  switch (result) {
+    case ManifestUpdateResult::kNoAppInScope:
+      return os << "kNoAppInScope";
+    case ManifestUpdateResult::kThrottled:
+      return os << "kThrottled";
+    case ManifestUpdateResult::kWebContentsDestroyed:
+      return os << "kWebContentsDestroyed";
+    case ManifestUpdateResult::kAppUninstalling:
+      return os << "kAppUninstalling";
+    case ManifestUpdateResult::kAppIsPlaceholder:
+      return os << "kAppIsPlaceholder";
+    case ManifestUpdateResult::kAppUpToDate:
+      return os << "kAppUpToDate";
+    case ManifestUpdateResult::kAppNotEligible:
+      return os << "kAppNotEligible";
+    case ManifestUpdateResult::kAppUpdateFailed:
+      return os << "kAppUpdateFailed";
+    case ManifestUpdateResult::kAppUpdated:
+      return os << "kAppUpdated";
+    case ManifestUpdateResult::kAppIsSystemWebApp:
+      return os << "kAppIsSystemWebApp";
+    case ManifestUpdateResult::kIconDownloadFailed:
+      return os << "kIconDownloadFailed";
+    case ManifestUpdateResult::kIconReadFromDiskFailed:
+      return os << "kIconReadFromDiskFailed";
+    case ManifestUpdateResult::kAppIdMismatch:
+      return os << "kAppIdMismatch";
+    case ManifestUpdateResult::kAppAssociationsUpdateFailed:
+      return os << "kAppAssociationsUpdateFailed";
+    case ManifestUpdateResult::kAppAssociationsUpdated:
+      return os << "kAppAssociationsUpdated";
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, ManifestUpdateStage stage) {
+  switch (stage) {
+    case ManifestUpdateStage::kPendingInstallableData:
+      return os << "kPendingInstallableData";
+    case ManifestUpdateStage::kPendingIconDownload:
+      return os << "kPendingIconDownload";
+    case ManifestUpdateStage::kPendingIconReadFromDisk:
+      return os << "kPendingIconReadFromDisk";
+    case ManifestUpdateStage::kPendingAppIdentityCheck:
+      return os << "kPendingAppIdentityCheck";
+    case ManifestUpdateStage::kPendingMaybeReadExistingIcons:
+      return os << "kPendingMaybeReadExistingIcons";
+    case ManifestUpdateStage::kPendingAssociationsUpdate:
+      return os << "kPendingAssociationsUpdate";
+    case ManifestUpdateStage::kPendingWindowsClosed:
+      return os << "kPendingWindowsClosed";
+    case ManifestUpdateStage::kPendingInstallation:
+      return os << "kPendingInstallation";
+  }
+}
+
+bool AllowUnpromptedNameUpdate(const AppId& app_id,
+                               const WebAppRegistrar& registrar) {
+  const WebApp* web_app = registrar.GetAppById(app_id);
+  if (!web_app)
+    return false;
+  return CanWebAppUpdateIdentity(web_app);
+}
+
+bool NeedsAppIdentityUpdateDialog(bool title_changing,
+                                  bool icons_changing,
+                                  const AppId& app_id,
+                                  const WebAppRegistrar& registrar) {
+  // Shortcut apps can trigger the update check (https://crbug.com/1366600)
+  // on subsequent runs of the app, if the user changed the title of the app
+  // when creating the shortcut. But we should never run the App Identity dialog
+  // for shortcut apps. Also, ideally we should just use IsShortcutApp here
+  // instead of checking the install source, but as per
+  // https://crbug.com/1368592 there is a bug with that where it returns the
+  // wrong thing for Shortcut apps that specify `scope`.
+  if (registrar.IsShortcutApp(app_id) ||
+      registrar.GetAppInstallSourceForMetrics(app_id) ==
+          webapps::WebappInstallSource::MENU_CREATE_SHORTCUT) {
+    return false;
+  }
+  if (title_changing && !AllowUnpromptedNameUpdate(app_id, registrar))
+    return true;
+  if (icons_changing && !AllowUnpromptedIconUpdate(app_id, registrar))
+    return true;
+  return false;
+}
+
+bool IsUpdateNeededForManifest(const AppId& app_id,
+                               const WebAppInstallInfo& install_info,
+                               const WebAppRegistrar& registrar) {
+  const WebApp* app = registrar.GetAppById(app_id);
+  DCHECK(app);
+
+  // TODO(crbug.com/1259777): Check whether translations have been updated.
+  bool title_changing =
+      install_info.title != base::UTF8ToUTF16(app->untranslated_name());
+  bool icons_changing = install_info.manifest_icons != app->manifest_icons();
+  if (!NeedsAppIdentityUpdateDialog(title_changing, icons_changing, app_id,
+                                    registrar)) {
+    if (title_changing && AllowUnpromptedNameUpdate(app_id, registrar)) {
+      return true;
+    }
+    if (icons_changing && AllowUnpromptedIconUpdate(app_id, registrar)) {
+      return true;
+    }
+  }
+
+  // Allows updating start_url and manifest_id. Both fields are allowed to
+  // change as long as the app_id generated from them doesn't change.
+  {
+    if (install_info.manifest_id != app->manifest_id())
+      return true;
+    if (install_info.start_url != app->start_url())
+      return true;
+  }
+
+  if (install_info.theme_color != app->theme_color())
+    return true;
+
+  if (install_info.scope != app->scope())
+    return true;
+
+  if (install_info.display_mode != app->display_mode())
+    return true;
+
+  if (install_info.display_override != app->display_mode_override())
+    return true;
+
+  if (install_info.shortcuts_menu_item_infos !=
+      app->shortcuts_menu_item_infos()) {
+    return true;
+  }
+
+  if (install_info.share_target != app->share_target())
+    return true;
+
+  if (install_info.protocol_handlers != app->protocol_handlers())
+    return true;
+
+  if (install_info.url_handlers != app->url_handlers())
+    return true;
+
+  if (base::FeatureList::IsEnabled(
+          blink::features::kWebAppManifestLockScreen) &&
+      install_info.lock_screen_start_url != app->lock_screen_start_url()) {
+    return true;
+  }
+
+  if (install_info.note_taking_new_note_url !=
+      app->note_taking_new_note_url()) {
+    return true;
+  }
+
+  if (install_info.capture_links != app->capture_links())
+    return true;
+
+  if (app->file_handlers() != install_info.file_handlers)
+    return true;
+
+  if (install_info.background_color != app->background_color())
+    return true;
+
+  if (install_info.dark_mode_theme_color != app->dark_mode_theme_color()) {
+    return true;
+  }
+
+  if (install_info.dark_mode_background_color !=
+      app->dark_mode_background_color()) {
+    return true;
+  }
+
+  if (install_info.manifest_url != app->manifest_url())
+    return true;
+
+  if (install_info.launch_handler != app->launch_handler())
+    return true;
+
+  if (install_info.permissions_policy != app->permissions_policy())
+    return true;
+
+  // TODO(crbug.com/897314): Check changes to tab_strip field once icons are
+  // stored.
+  // TODO(crbug.com/1212849): Handle changes to is_storage_isolated.
+  // TODO(crbug.com/926083): Check more manifest fields.
+  return false;
+}
+
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/manifest_update_utils.h b/chrome/browser/web_applications/manifest_update_utils.h
new file mode 100644
index 0000000..804ec06
--- /dev/null
+++ b/chrome/browser/web_applications/manifest_update_utils.h
@@ -0,0 +1,71 @@
+// Copyright 2022 The Chromium Authors
+// 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_MANIFEST_UPDATE_UTILS_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_MANIFEST_UPDATE_UTILS_H_
+
+#include <iosfwd>
+#include <string>
+
+#include "chrome/browser/web_applications/web_app_id.h"
+#include "chrome/browser/web_applications/web_app_install_info.h"
+
+namespace web_app {
+
+class WebAppRegistrar;
+
+// This enum is recorded by UMA, the numeric values must not change.
+enum ManifestUpdateResult {
+  kNoAppInScope = 0,
+  kThrottled = 1,
+  kWebContentsDestroyed = 2,
+  kAppUninstalling = 3,
+  kAppIsPlaceholder = 4,
+  kAppUpToDate = 5,
+  kAppNotEligible = 6,
+  kAppUpdateFailed = 7,
+  kAppUpdated = 8,
+  kAppIsSystemWebApp = 9,
+  kIconDownloadFailed = 10,
+  kIconReadFromDiskFailed = 11,
+  kAppIdMismatch = 12,
+  kAppAssociationsUpdateFailed = 13,
+  kAppAssociationsUpdated = 14,
+  kMaxValue = kAppAssociationsUpdated,
+};
+
+std::ostream& operator<<(std::ostream& os, ManifestUpdateResult result);
+
+enum ManifestUpdateStage {
+  kPendingInstallableData = 0,
+  kPendingIconDownload = 1,
+  kPendingIconReadFromDisk = 2,
+  kPendingAppIdentityCheck = 3,
+  kPendingMaybeReadExistingIcons = 4,
+  kPendingAssociationsUpdate = 5,
+  kPendingWindowsClosed = 6,
+  kPendingInstallation = 7,
+};
+
+std::ostream& operator<<(std::ostream& os, ManifestUpdateStage stage);
+
+// Some apps, such as pre-installed apps, have been vetted and are therefore
+// considered safe and permitted to update their names.
+bool AllowUnpromptedNameUpdate(const AppId& app_id,
+                               const WebAppRegistrar& registrar);
+
+bool NeedsAppIdentityUpdateDialog(bool title_changing,
+                                  bool icons_changing,
+                                  const AppId& app_id,
+                                  const WebAppRegistrar& registrar);
+
+// Checks if a manifest update is required by reading the web_app fields and
+// comparing it with the passed install_info.
+bool IsUpdateNeededForManifest(const AppId& app_id,
+                               const WebAppInstallInfo& install_info,
+                               const WebAppRegistrar& registrar);
+
+}  // namespace web_app
+
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_MANIFEST_UPDATE_UTILS_H_
diff --git a/chrome/browser/webauthn/chrome_webauthn_autofill_interactive_uitest.cc b/chrome/browser/webauthn/chrome_webauthn_autofill_interactive_uitest.cc
new file mode 100644
index 0000000..0721f39
--- /dev/null
+++ b/chrome/browser/webauthn/chrome_webauthn_autofill_interactive_uitest.cc
@@ -0,0 +1,316 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
+#include "chrome/browser/password_manager/password_store_factory.h"
+#include "chrome/browser/ssl/cert_verifier_browser_test.h"
+#include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h"
+#include "chrome/browser/ui/autofill/chrome_autofill_client.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/autofill/core/browser/ui/popup_item_ids.h"
+#include "components/autofill/core/browser/ui/popup_types.h"
+#include "components/network_session_configurator/common/network_switches.h"
+#include "components/password_manager/core/browser/password_store_interface.h"
+#include "components/password_manager/core/browser/password_ui_utils.h"
+#include "content/public/browser/authenticator_environment.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/common/content_features.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "device/fido/fido_transport_protocol.h"
+#include "device/fido/virtual_ctap2_device.h"
+#include "device/fido/virtual_fido_device_factory.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "url/gurl.h"
+
+#if BUILDFLAG(IS_WIN)
+#include "device/fido/win/fake_webauthn_api.h"
+#endif  // BUILDFLAG(IS_WIN)
+
+namespace {
+
+static constexpr uint8_t kCredentialID[] = {1, 2, 3, 4};
+
+static constexpr char kConditionalUIRequest[] = R"((() => {
+window.requestAbortController = new AbortController();
+navigator.credentials.get({
+  signal: window.requestAbortController.signal,
+  mediation: 'conditional',
+  publicKey: {
+    challenge: new Uint8Array([1,2,3,4]),
+    timeout: 10000,
+    allowCredentials: [],
+  }}).then(c => window.domAutomationController.send('webauthn: OK'),
+           e => window.domAutomationController.send('error ' + e));
+})())";
+
+// Autofill integration tests. This file contains end-to-end tests for
+// integration between WebAuthn and Autofill. These tests are sensitive to focus
+// changes, so they are interactive UI tests.
+
+// Base class for autofill integration tests, contains the actual test code but
+// no setup.
+class WebAuthnAutofillIntegrationTest : public CertVerifierBrowserTest {
+ public:
+  WebAuthnAutofillIntegrationTest() = default;
+
+  WebAuthnAutofillIntegrationTest(const WebAuthnAutofillIntegrationTest&) =
+      delete;
+  WebAuthnAutofillIntegrationTest& operator=(
+      const WebAuthnAutofillIntegrationTest&) = delete;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    CertVerifierBrowserTest::SetUpCommandLine(command_line);
+    command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
+  }
+
+  void SetUp() override {
+    ASSERT_TRUE(https_server_.InitializeAndListen());
+    CertVerifierBrowserTest::SetUp();
+  }
+
+  void SetUpOnMainThread() override {
+    CertVerifierBrowserTest::SetUpOnMainThread();
+
+    https_server_.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
+    https_server_.StartAcceptingConnections();
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    // Allowlist all certs for the HTTPS server.
+    auto cert = https_server_.GetCertificate();
+    net::CertVerifyResult verify_result;
+    verify_result.cert_status = 0;
+    verify_result.verified_cert = cert;
+    mock_cert_verifier()->AddResultForCert(cert.get(), verify_result, net::OK);
+
+    // Save a credential to the password store. This will let us wait on the
+    // popup to appear after aborting the request.
+    password_manager::PasswordStoreInterface* password_store =
+        PasswordStoreFactory::GetForProfile(browser()->profile(),
+                                            ServiceAccessType::EXPLICIT_ACCESS)
+            .get();
+    password_manager::PasswordForm signin_form;
+    GURL url = https_server_.GetURL("www.example.com", "/");
+    signin_form.signon_realm = url.spec();
+    signin_form.url = url;
+    signin_form.action = url;
+    signin_form.username_value = u"remilia";
+    signin_form.password_value = u"shouldbeusingapasskeyinstead";
+    base::RunLoop run_loop;
+    password_store->AddLogin(signin_form, run_loop.QuitClosure());
+
+    ASSERT_TRUE(ui_test_utils::NavigateToURL(
+        browser(),
+        https_server_.GetURL("www.example.com",
+                             "/webauthn_conditional_mediation.html")));
+  }
+
+  void RunSelectAccountTest() {
+    // Make sure input events cannot close the autofill popup.
+    content::WebContents* web_contents =
+        browser()->tab_strip_model()->GetActiveWebContents();
+    autofill::ChromeAutofillClient* autofill_client =
+        autofill::ChromeAutofillClient::FromWebContents(web_contents);
+    autofill_client->KeepPopupOpenForTesting();
+
+    // Execute the Conditional UI request.
+    content::DOMMessageQueue message_queue(web_contents);
+    content::ExecuteScriptAsync(web_contents, kConditionalUIRequest);
+
+    // Interact with the username field until the popup shows up. This has the
+    // effect of waiting for the browser to send the renderer the password
+    // information, and waiting for the UI to render.
+    base::WeakPtr<autofill::AutofillPopupController> popup_controller;
+    while (!popup_controller) {
+      content::SimulateMouseClickOrTapElementWithId(web_contents, "username");
+      popup_controller = autofill_client->popup_controller_for_testing();
+    }
+
+    // Find the webauthn credential on the suggestions list.
+    auto suggestions = popup_controller->GetSuggestions();
+    size_t suggestion_index;
+    autofill::Suggestion webauthn_entry;
+    for (suggestion_index = 0; suggestion_index < suggestions.size();
+         ++suggestion_index) {
+      if (suggestions[suggestion_index].frontend_id ==
+          autofill::PopupItemId::POPUP_ITEM_ID_WEBAUTHN_CREDENTIAL) {
+        webauthn_entry = suggestions[suggestion_index];
+        break;
+      }
+    }
+    ASSERT_LT(suggestion_index, suggestions.size())
+        << "WebAuthn entry not found";
+    EXPECT_EQ(webauthn_entry.main_text.value, u"flandre");
+    EXPECT_EQ(webauthn_entry.labels.at(0).at(0).value,
+              l10n_util::GetStringUTF16(
+                  password_manager::GetPlatformAuthenticatorLabel()));
+    EXPECT_EQ(webauthn_entry.icon, "globeIcon");
+
+    // Click the credential.
+    popup_controller->AcceptSuggestion(suggestion_index);
+    std::string result;
+    ASSERT_TRUE(message_queue.WaitForMessage(&result));
+    EXPECT_EQ(result, "\"webauthn: OK\"");
+  }
+
+  void RunAbortTest() {
+    // Make sure input events cannot close the autofill popup.
+    content::WebContents* web_contents =
+        browser()->tab_strip_model()->GetActiveWebContents();
+    autofill::ChromeAutofillClient* autofill_client =
+        autofill::ChromeAutofillClient::FromWebContents(web_contents);
+    autofill_client->KeepPopupOpenForTesting();
+
+    // Execute the Conditional UI request.
+    content::DOMMessageQueue message_queue(web_contents);
+    content::ExecuteScriptAsync(web_contents, kConditionalUIRequest);
+
+    // Interact with the username field until the popup shows up. This has the
+    // effect of waiting for the browser to send the renderer the password
+    // information, and waiting for the UI to render.
+    base::WeakPtr<autofill::AutofillPopupController> popup_controller;
+    while (!popup_controller) {
+      content::SimulateMouseClickOrTapElementWithId(web_contents, "username");
+      popup_controller = autofill_client->popup_controller_for_testing();
+    }
+
+    // Find the webauthn credential on the suggestions list.
+    auto suggestions = popup_controller->GetSuggestions();
+    size_t suggestion_index;
+    autofill::Suggestion webauthn_entry;
+    for (suggestion_index = 0; suggestion_index < suggestions.size();
+         ++suggestion_index) {
+      if (suggestions[suggestion_index].frontend_id ==
+          autofill::PopupItemId::POPUP_ITEM_ID_WEBAUTHN_CREDENTIAL) {
+        webauthn_entry = suggestions[suggestion_index];
+        break;
+      }
+    }
+    ASSERT_LT(suggestion_index, suggestions.size())
+        << "WebAuthn entry not found";
+    EXPECT_EQ(webauthn_entry.main_text.value, u"flandre");
+    EXPECT_EQ(webauthn_entry.labels.at(0).at(0).value,
+              l10n_util::GetStringUTF16(
+                  password_manager::GetPlatformAuthenticatorLabel()));
+    EXPECT_EQ(webauthn_entry.icon, "globeIcon");
+
+    // Abort the request.
+    content::ExecuteScriptAsync(web_contents,
+                                "window.requestAbortController.abort()");
+    std::string result;
+    ASSERT_TRUE(message_queue.WaitForMessage(&result));
+    EXPECT_EQ(result, "\"error AbortError: signal is aborted without reason\"");
+
+    // The popup may have gone away while waiting. If not, make sure it's gone.
+    if (popup_controller) {
+      popup_controller->Hide(autofill::PopupHidingReason::kUserAborted);
+    }
+
+    // Interact with the username field. Since there is still a saved password,
+    // the popup should eventually show up.
+    while (!popup_controller) {
+      content::SimulateMouseClickOrTapElementWithId(web_contents, "username");
+      popup_controller = autofill_client->popup_controller_for_testing();
+    }
+    for (const auto& suggestion : popup_controller->GetSuggestions()) {
+      EXPECT_NE(suggestion.frontend_id,
+                autofill::POPUP_ITEM_ID_WEBAUTHN_CREDENTIAL);
+      EXPECT_NE(suggestion.frontend_id,
+                autofill::POPUP_ITEM_ID_WEBAUTHN_SIGN_IN_WITH_ANOTHER_DEVICE);
+    }
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_{
+      features::kWebAuthConditionalUI};
+  raw_ptr<device::test::VirtualFidoDeviceFactory> virtual_device_factory_;
+  net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
+};
+
+// Autofill integration test using the devtools virtual environment.
+class WebAuthnDevtoolsAutofillIntegrationTest
+    : public WebAuthnAutofillIntegrationTest {
+ public:
+  void SetUpOnMainThread() override {
+    WebAuthnAutofillIntegrationTest::SetUpOnMainThread();
+
+    // Set up a fake virtual device.
+    auto virtual_device_factory =
+        std::make_unique<device::test::VirtualFidoDeviceFactory>();
+    virtual_device_factory->SetTransport(
+        device::FidoTransportProtocol::kInternal);
+    virtual_device_factory_ = virtual_device_factory.get();
+    virtual_device_factory->mutable_state()->InjectResidentKey(
+        kCredentialID, "www.example.com", std::vector<uint8_t>{5, 6, 7, 8},
+        "flandre", "Flandre Scarlet");
+    virtual_device_factory->mutable_state()->fingerprints_enrolled = true;
+    device::VirtualCtap2Device::Config config;
+    config.resident_key_support = true;
+    config.internal_uv_support = true;
+    virtual_device_factory->SetCtap2Config(std::move(config));
+    content::AuthenticatorEnvironment::GetInstance()
+        ->ReplaceDefaultDiscoveryFactoryForTesting(
+            std::move(virtual_device_factory));
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(WebAuthnDevtoolsAutofillIntegrationTest, SelectAccount) {
+  RunSelectAccountTest();
+}
+
+IN_PROC_BROWSER_TEST_F(WebAuthnDevtoolsAutofillIntegrationTest, Abort) {
+  RunAbortTest();
+}
+
+#if BUILDFLAG(IS_WIN)
+// Autofill integration test using the Windows fake API.
+class WebAuthnWindowsAutofillIntegrationTest
+    : public WebAuthnAutofillIntegrationTest {
+ public:
+  void SetUpOnMainThread() override {
+    WebAuthnAutofillIntegrationTest::SetUpOnMainThread();
+
+    // Set up the fake Windows platform authenticator.
+    fake_webauthn_api_ = std::make_unique<device::FakeWinWebAuthnApi>();
+    fake_webauthn_api_->set_version(WEBAUTHN_API_VERSION_4);
+    fake_webauthn_api_->set_is_uvpaa(true);
+    fake_webauthn_api_->set_supports_silent_discovery(true);
+    device::PublicKeyCredentialUserEntity user({1, 2, 3, 4}, "flandre",
+                                               "Flandre Scarlet");
+    device::PublicKeyCredentialRpEntity rp("www.example.com");
+    fake_webauthn_api_->InjectDiscoverableCredential(
+        kCredentialID, std::move(rp), std::move(user));
+
+    // Inject the fake Windows platform authenticator.
+    auto device_factory =
+        std::make_unique<device::test::VirtualFidoDeviceFactory>();
+    device_factory->set_win_webauthn_api(fake_webauthn_api_.get());
+    content::AuthenticatorEnvironment::GetInstance()
+        ->ReplaceDefaultDiscoveryFactoryForTesting(std::move(device_factory));
+  }
+
+ protected:
+  std::unique_ptr<device::FakeWinWebAuthnApi> fake_webauthn_api_;
+};
+
+IN_PROC_BROWSER_TEST_F(WebAuthnWindowsAutofillIntegrationTest, SelectAccount) {
+  RunSelectAccountTest();
+}
+
+IN_PROC_BROWSER_TEST_F(WebAuthnWindowsAutofillIntegrationTest, Abort) {
+  RunAbortTest();
+}
+#endif  // BUILDFLAG(IS_WIN)
+
+}  // namespace
diff --git a/chrome/browser/webauthn/chrome_webauthn_mac_browsertest.mm b/chrome/browser/webauthn/chrome_webauthn_autofill_mac_interactive_uitest.mm
similarity index 100%
rename from chrome/browser/webauthn/chrome_webauthn_mac_browsertest.mm
rename to chrome/browser/webauthn/chrome_webauthn_autofill_mac_interactive_uitest.mm
diff --git a/chrome/browser/webauthn/chrome_webauthn_browsertest.cc b/chrome/browser/webauthn/chrome_webauthn_browsertest.cc
index 05a8b2cb..24d56669 100644
--- a/chrome/browser/webauthn/chrome_webauthn_browsertest.cc
+++ b/chrome/browser/webauthn/chrome_webauthn_browsertest.cc
@@ -19,21 +19,13 @@
 #include "chrome/browser/extensions/install_verifier.h"
 #include "chrome/browser/extensions/test_extension_system.h"
 #include "chrome/browser/password_manager/chrome_webauthn_credentials_delegate.h"
-#include "chrome/browser/password_manager/password_store_factory.h"
 #include "chrome/browser/ssl/cert_verifier_browser_test.h"
-#include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h"
-#include "chrome/browser/ui/autofill/chrome_autofill_client.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
 #include "chrome/browser/webauthn/chrome_authenticator_request_delegate.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
-#include "components/autofill/core/browser/ui/popup_item_ids.h"
-#include "components/autofill/core/browser/ui/popup_types.h"
 #include "components/network_session_configurator/common/network_switches.h"
-#include "components/password_manager/core/browser/password_form_manager.h"
-#include "components/password_manager/core/browser/password_store_interface.h"
-#include "components/password_manager/core/browser/password_ui_utils.h"
 #include "content/public/browser/authenticator_environment.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/common/content_features.h"
@@ -42,14 +34,12 @@
 #include "device/fido/cable/cable_discovery_data.h"
 #include "device/fido/features.h"
 #include "device/fido/fido_transport_protocol.h"
-#include "device/fido/public_key_credential_user_entity.h"
 #include "device/fido/virtual_ctap2_device.h"
 #include "device/fido/virtual_fido_device.h"
 #include "device/fido/virtual_fido_device_factory.h"
 #include "extensions/common/extension_builder.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
-#include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
 
 #if BUILDFLAG(IS_WIN)
@@ -345,231 +335,6 @@
   EXPECT_EQ(observer_->accounts_.at(0), "01020304");
 }
 
-// Autofill integration tests --------------------------------------------------
-
-// Base class for autofill integration tests, contains the actual test code but
-// no setup.
-class WebAuthnAutofillIntegrationTest : public WebAuthnBrowserTest {
- protected:
-  void SetUpOnMainThread() override {
-    WebAuthnBrowserTest::SetUpOnMainThread();
-    // Save a credential to the password store. This will let us wait on the
-    // popup to appear after aborting the request.
-    password_manager::PasswordStoreInterface* password_store =
-        PasswordStoreFactory::GetForProfile(browser()->profile(),
-                                            ServiceAccessType::EXPLICIT_ACCESS)
-            .get();
-    password_manager::PasswordForm signin_form;
-    GURL url = https_server_.GetURL("www.example.com", "/");
-    signin_form.signon_realm = url.spec();
-    signin_form.url = url;
-    signin_form.action = url;
-    signin_form.username_value = u"remilia";
-    signin_form.password_value = u"shouldbeusingapasskeyinstead";
-    base::RunLoop run_loop;
-    password_store->AddLogin(signin_form, run_loop.QuitClosure());
-
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(
-        browser(),
-        https_server_.GetURL("www.example.com",
-                             "/webauthn_conditional_mediation.html")));
-  }
-
-  void RunSelectAccountTest() {
-    // Make sure input events cannot close the autofill popup.
-    content::WebContents* web_contents =
-        browser()->tab_strip_model()->GetActiveWebContents();
-    autofill::ChromeAutofillClient* autofill_client =
-        autofill::ChromeAutofillClient::FromWebContents(web_contents);
-    autofill_client->KeepPopupOpenForTesting();
-
-    // Execute the Conditional UI request.
-    content::DOMMessageQueue message_queue(web_contents);
-    content::ExecuteScriptAsync(web_contents, kConditionalUIRequest);
-
-    // Interact with the username field until the popup shows up. This has the
-    // effect of waiting for the browser to send the renderer the password
-    // information, and waiting for the UI to render.
-    base::WeakPtr<autofill::AutofillPopupController> popup_controller;
-    while (!popup_controller) {
-      content::SimulateMouseClickOrTapElementWithId(web_contents, "username");
-      popup_controller = autofill_client->popup_controller_for_testing();
-    }
-
-    // Find the webauthn credential on the suggestions list.
-    auto suggestions = popup_controller->GetSuggestions();
-    size_t suggestion_index;
-    autofill::Suggestion webauthn_entry;
-    for (suggestion_index = 0; suggestion_index < suggestions.size();
-         ++suggestion_index) {
-      if (suggestions[suggestion_index].frontend_id ==
-          autofill::PopupItemId::POPUP_ITEM_ID_WEBAUTHN_CREDENTIAL) {
-        webauthn_entry = suggestions[suggestion_index];
-        break;
-      }
-    }
-    ASSERT_LT(suggestion_index, suggestions.size())
-        << "WebAuthn entry not found";
-    EXPECT_EQ(webauthn_entry.main_text.value, u"flandre");
-    EXPECT_EQ(webauthn_entry.labels.at(0).at(0).value,
-              l10n_util::GetStringUTF16(
-                  password_manager::GetPlatformAuthenticatorLabel()));
-    EXPECT_EQ(webauthn_entry.icon, "globeIcon");
-
-    // Click the credential.
-    popup_controller->AcceptSuggestion(suggestion_index);
-    std::string result;
-    ASSERT_TRUE(message_queue.WaitForMessage(&result));
-    EXPECT_EQ(result, "\"webauthn: OK\"");
-  }
-
-  void RunAbortTest() {
-    // Make sure input events cannot close the autofill popup.
-    content::WebContents* web_contents =
-        browser()->tab_strip_model()->GetActiveWebContents();
-    autofill::ChromeAutofillClient* autofill_client =
-        autofill::ChromeAutofillClient::FromWebContents(web_contents);
-    autofill_client->KeepPopupOpenForTesting();
-
-    // Execute the Conditional UI request.
-    content::DOMMessageQueue message_queue(web_contents);
-    content::ExecuteScriptAsync(web_contents, kConditionalUIRequest);
-
-    // Interact with the username field until the popup shows up. This has the
-    // effect of waiting for the browser to send the renderer the password
-    // information, and waiting for the UI to render.
-    base::WeakPtr<autofill::AutofillPopupController> popup_controller;
-    while (!popup_controller) {
-      content::SimulateMouseClickOrTapElementWithId(web_contents, "username");
-      popup_controller = autofill_client->popup_controller_for_testing();
-    }
-
-    // Find the webauthn credential on the suggestions list.
-    auto suggestions = popup_controller->GetSuggestions();
-    size_t suggestion_index;
-    autofill::Suggestion webauthn_entry;
-    for (suggestion_index = 0; suggestion_index < suggestions.size();
-         ++suggestion_index) {
-      if (suggestions[suggestion_index].frontend_id ==
-          autofill::PopupItemId::POPUP_ITEM_ID_WEBAUTHN_CREDENTIAL) {
-        webauthn_entry = suggestions[suggestion_index];
-        break;
-      }
-    }
-    ASSERT_LT(suggestion_index, suggestions.size())
-        << "WebAuthn entry not found";
-    EXPECT_EQ(webauthn_entry.main_text.value, u"flandre");
-    EXPECT_EQ(webauthn_entry.labels.at(0).at(0).value,
-              l10n_util::GetStringUTF16(
-                  password_manager::GetPlatformAuthenticatorLabel()));
-    EXPECT_EQ(webauthn_entry.icon, "globeIcon");
-
-    // Abort the request.
-    content::ExecuteScriptAsync(web_contents,
-                                "window.requestAbortController.abort()");
-    std::string result;
-    ASSERT_TRUE(message_queue.WaitForMessage(&result));
-    EXPECT_EQ(result, "\"error AbortError: signal is aborted without reason\"");
-
-    // The popup may have gone away while waiting. If not, make sure it's gone.
-    if (popup_controller) {
-      popup_controller->Hide(autofill::PopupHidingReason::kUserAborted);
-    }
-
-    // Interact with the username field. Since there is still a saved password,
-    // the popup should eventually show up.
-    while (!popup_controller) {
-      content::SimulateMouseClickOrTapElementWithId(web_contents, "username");
-      popup_controller = autofill_client->popup_controller_for_testing();
-    }
-    for (const auto& suggestion : popup_controller->GetSuggestions()) {
-      EXPECT_NE(suggestion.frontend_id,
-                autofill::POPUP_ITEM_ID_WEBAUTHN_CREDENTIAL);
-      EXPECT_NE(suggestion.frontend_id,
-                autofill::POPUP_ITEM_ID_WEBAUTHN_SIGN_IN_WITH_ANOTHER_DEVICE);
-    }
-  }
-
-  base::test::ScopedFeatureList scoped_feature_list_{
-      features::kWebAuthConditionalUI};
-  raw_ptr<device::test::VirtualFidoDeviceFactory> virtual_device_factory_;
-};
-
-// Autofill integration test using the devtools virtual environment.
-class WebAuthnDevtoolsAutofillIntegrationTest
-    : public WebAuthnAutofillIntegrationTest {
- public:
-  void SetUpOnMainThread() override {
-    WebAuthnAutofillIntegrationTest::SetUpOnMainThread();
-
-    // Set up a fake virtual device.
-    auto virtual_device_factory =
-        std::make_unique<device::test::VirtualFidoDeviceFactory>();
-    virtual_device_factory->SetTransport(
-        device::FidoTransportProtocol::kInternal);
-    virtual_device_factory_ = virtual_device_factory.get();
-    virtual_device_factory->mutable_state()->InjectResidentKey(
-        kCredentialID, "www.example.com", std::vector<uint8_t>{5, 6, 7, 8},
-        "flandre", "Flandre Scarlet");
-    virtual_device_factory->mutable_state()->fingerprints_enrolled = true;
-    device::VirtualCtap2Device::Config config;
-    config.resident_key_support = true;
-    config.internal_uv_support = true;
-    virtual_device_factory->SetCtap2Config(std::move(config));
-    content::AuthenticatorEnvironment::GetInstance()
-        ->ReplaceDefaultDiscoveryFactoryForTesting(
-            std::move(virtual_device_factory));
-  }
-};
-
-IN_PROC_BROWSER_TEST_F(WebAuthnDevtoolsAutofillIntegrationTest, SelectAccount) {
-  RunSelectAccountTest();
-}
-
-IN_PROC_BROWSER_TEST_F(WebAuthnDevtoolsAutofillIntegrationTest, Abort) {
-  RunAbortTest();
-}
-
-#if BUILDFLAG(IS_WIN)
-// Autofill integration test using the Windows fake API.
-class WebAuthnWindowsAutofillIntegrationTest
-    : public WebAuthnAutofillIntegrationTest {
- public:
-  void SetUpOnMainThread() override {
-    WebAuthnAutofillIntegrationTest::SetUpOnMainThread();
-
-    // Set up the fake Windows platform authenticator.
-    fake_webauthn_api_ = std::make_unique<device::FakeWinWebAuthnApi>();
-    fake_webauthn_api_->set_version(WEBAUTHN_API_VERSION_4);
-    fake_webauthn_api_->set_is_uvpaa(true);
-    fake_webauthn_api_->set_supports_silent_discovery(true);
-    device::PublicKeyCredentialUserEntity user({1, 2, 3, 4}, "flandre",
-                                               "Flandre Scarlet");
-    device::PublicKeyCredentialRpEntity rp("www.example.com");
-    fake_webauthn_api_->InjectDiscoverableCredential(
-        kCredentialID, std::move(rp), std::move(user));
-
-    // Inject the fake Windows platform authenticator.
-    auto device_factory =
-        std::make_unique<device::test::VirtualFidoDeviceFactory>();
-    device_factory->set_win_webauthn_api(fake_webauthn_api_.get());
-    content::AuthenticatorEnvironment::GetInstance()
-        ->ReplaceDefaultDiscoveryFactoryForTesting(std::move(device_factory));
-  }
-
- protected:
-  std::unique_ptr<device::FakeWinWebAuthnApi> fake_webauthn_api_;
-};
-
-IN_PROC_BROWSER_TEST_F(WebAuthnWindowsAutofillIntegrationTest, SelectAccount) {
-  RunSelectAccountTest();
-}
-
-IN_PROC_BROWSER_TEST_F(WebAuthnWindowsAutofillIntegrationTest, Abort) {
-  RunAbortTest();
-}
-#endif  // BUILDFLAG(IS_WIN)
-
 // WebAuthnCableExtension exercises code paths where a server sends a caBLEv2
 // extension in a get() request.
 class WebAuthnCableExtension : public WebAuthnBrowserTest {
diff --git a/chrome/browser/win/conflicts/installed_applications_unittest.cc b/chrome/browser/win/conflicts/installed_applications_unittest.cc
index ae494e1..f1b6d7c 100644
--- a/chrome/browser/win/conflicts/installed_applications_unittest.cc
+++ b/chrome/browser/win/conflicts/installed_applications_unittest.cc
@@ -4,9 +4,9 @@
 
 #include "chrome/browser/win/conflicts/installed_applications.h"
 
+#include <algorithm>
 #include <map>
 
-#include "base/ranges/algorithm.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/test_reg_util_win.h"
 #include "base/win/registry.h"
@@ -397,10 +397,13 @@
   auto applications = installed_applications().applications_;
   std::sort(std::begin(applications), std::end(applications));
   EXPECT_EQ(std::end(applications),
-            base::ranges::adjacent_find(
-                applications, std::equal<>(), [](const auto& app) {
-                  return std::tie(app.name, app.registry_root,
-                                  app.registry_key_path,
-                                  app.registry_wow64_access);
-                }));
+            std::adjacent_find(std::begin(applications), std::end(applications),
+                               [](const auto& lhs, const auto& rhs) {
+                                 return std::tie(lhs.name, lhs.registry_root,
+                                                 lhs.registry_key_path,
+                                                 lhs.registry_wow64_access) ==
+                                        std::tie(rhs.name, rhs.registry_root,
+                                                 rhs.registry_key_path,
+                                                 rhs.registry_wow64_access);
+                               }));
 }
diff --git a/chrome/browser/win/conflicts/module_blocklist_cache_util_unittest.cc b/chrome/browser/win/conflicts/module_blocklist_cache_util_unittest.cc
index 2db9d15..0af2cdd09 100644
--- a/chrome/browser/win/conflicts/module_blocklist_cache_util_unittest.cc
+++ b/chrome/browser/win/conflicts/module_blocklist_cache_util_unittest.cc
@@ -52,8 +52,8 @@
 
   // Sort the entries and make sure each module is unique.
   std::sort(entries.begin(), entries.end(), internal::ModuleLess());
-  CHECK(base::ranges::adjacent_find(entries, internal::ModuleEqual()) ==
-        entries.end());
+  CHECK(std::adjacent_find(entries.begin(), entries.end(),
+                           internal::ModuleEqual()) == entries.end());
 
   return entries;
 }
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 883d8a80..05dad11 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1665770398-17d4f7bea9fcc7d745ba4f47c559f834a80ec320.profdata
+chrome-linux-main-1665791948-e3778de99e281f8d497aab292bd2b3edfe866c10.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 324fd7d..37e1d7f0 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1665770398-c2ca4d7140879c329be52f41540880b88c5e6fea.profdata
+chrome-mac-arm-main-1665791948-da8d820eb239ddbf7cce7167780fef3b1a569374.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index b704373..2d34063 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1665770398-e5491636ea29ef2e4a2a0d4123cb4c9b39977e3f.profdata
+chrome-win32-main-1665780980-268de8ccceb67cb8a6186d4a4e093edcc58b176b.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index c0e6f11..b24b1809 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1665770398-60ea39db78e2e9990263a15c0195420700de507e.profdata
+chrome-win64-main-1665780980-352bf7bac339e5f02e7759c28ffa6e66ed59b582.profdata
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 4d0f889..fd48499 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2616,7 +2616,6 @@
         "../browser/ui/test/test_browser_dialog_mac.h",
         "../browser/ui/test/test_browser_dialog_mac.mm",
         "../browser/ui/views/frame/browser_non_client_frame_view_mac_browsertest.mm",
-        "../browser/webauthn/chrome_webauthn_mac_browsertest.mm",
         "../browser/webshare/mac/sharing_service_operation_browsertest.cc",
         "../common/mac/app_mode_chrome_locator_browsertest.mm",
         "../common/profiler/thread_profiler_browsertest.cc",
@@ -9491,6 +9490,7 @@
       "../browser/ui/webui/settings/settings_interactive_uitest.cc",
       "../browser/webapps/web_app_offline_browsertest.cc",
       "../browser/webauth_interactive_uitest.cc",
+      "../browser/webauthn/chrome_webauthn_autofill_interactive_uitest.cc",
       "//ui/base/clipboard/clipboard_unittest.cc",
       "base/interactive_ui_tests_main.cc",
     ]
@@ -9963,6 +9963,7 @@
         "../browser/ui/cocoa/status_bubble_mac_interactive_uitest.mm",
         "../browser/ui/cocoa/tab_contents/web_contents_view_mac_interactive_uitest.mm",
         "../browser/ui/find_bar/find_bar_platform_helper_mac_interactive_uitest.mm",
+        "../browser/webauthn/chrome_webauthn_autofill_mac_interactive_uitest.mm",
       ]
 
       sources -= [
diff --git a/chrome/test/data/webui/chromeos/internet_detail_dialog_test.js b/chrome/test/data/webui/chromeos/internet_detail_dialog_test.js
index 1f7e39c4..bf1cf33 100644
--- a/chrome/test/data/webui/chromeos/internet_detail_dialog_test.js
+++ b/chrome/test/data/webui/chromeos/internet_detail_dialog_test.js
@@ -8,7 +8,7 @@
 import {MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
 import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
 import {CrosNetworkConfigRemote, InhibitReason} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
-import {ConnectionStateType, DeviceStateType, NetworkType, OncSource} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
+import {ConnectionStateType, DeviceStateType, NetworkType, OncSource, PortalState} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {FakeNetworkConfig} from 'chrome://test/chromeos/fake_network_config_mojom.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
@@ -18,7 +18,8 @@
   constructor() {
     super([
       'getDialogArguments',
-      'dialogClose',
+      'closeDialog',
+      'showPortalSignin',
     ]);
   }
 
@@ -28,7 +29,10 @@
   }
 
   /** @override */
-  dialogClose() {}
+  closeDialog() {}
+
+  /** @override */
+  showPortalSignin() {}
 }
 
 suite('internet-detail-dialog', () => {
@@ -104,6 +108,116 @@
     return element;
   }
 
+  suite('captive portal ui updates', () => {
+    function getButton(buttonId) {
+      const button =
+          internetDetailDialog.shadowRoot.querySelector(`#${buttonId}`);
+      assertTrue(!!button);
+      return button;
+    }
+
+    test('WiFi in a portal portalState', function() {
+      mojoApi_.setNetworkTypeEnabledState(NetworkType.kWiFi, true);
+      const wifiNetwork = getManagedProperties(NetworkType.kWiFi, 'wifi_user');
+      wifiNetwork.source = OncSource.kUser;
+      wifiNetwork.connectable = true;
+      wifiNetwork.connectionState = ConnectionStateType.kPortal;
+      wifiNetwork.portalState = PortalState.kPortal;
+
+      mojoApi_.setManagedPropertiesForTest(wifiNetwork);
+      init();
+      internetDetailDialog.isCaptivePortalUI2022Enabled_ = true;
+      return flushAsync().then(() => {
+        const networkStateText =
+            internetDetailDialog.shadowRoot.querySelector(`#networkState`);
+        assertTrue(networkStateText.hasAttribute('warning'));
+        assertEquals(
+            networkStateText.textContent.trim(),
+            internetDetailDialog.i18n('networkListItemSignIn'));
+        const signinButton = getButton('signinButton');
+        assertTrue(!!signinButton);
+        assertFalse(signinButton.hasAttribute('hidden'));
+        assertFalse(signinButton.disabled);
+      });
+    });
+
+    test('WiFi in a no internet portalState', function() {
+      mojoApi_.setNetworkTypeEnabledState(NetworkType.kWiFi, true);
+      const wifiNetwork = getManagedProperties(NetworkType.kWiFi, 'wifi_user');
+      wifiNetwork.source = OncSource.kUser;
+      wifiNetwork.connectable = true;
+      wifiNetwork.connectionState = ConnectionStateType.kPortal;
+      wifiNetwork.portalState = PortalState.kNoInternet;
+
+      mojoApi_.setManagedPropertiesForTest(wifiNetwork);
+      init();
+      internetDetailDialog.isCaptivePortalUI2022Enabled_ = true;
+      return flushAsync().then(() => {
+        const networkStateText =
+            internetDetailDialog.shadowRoot.querySelector(`#networkState`);
+        assertTrue(networkStateText.hasAttribute('warning'));
+        assertEquals(
+            networkStateText.textContent.trim(),
+            internetDetailDialog.i18n(
+                'networkListItemConnectedNoConnectivity'));
+        const signinButton = getButton('signinButton');
+        assertTrue(!!signinButton);
+        assertTrue(signinButton.hasAttribute('hidden'));
+        assertTrue(signinButton.disabled);
+      });
+    });
+
+    test('WiFi in a proxy-auth portalState', function() {
+      mojoApi_.setNetworkTypeEnabledState(NetworkType.kWiFi, true);
+      const wifiNetwork = getManagedProperties(NetworkType.kWiFi, 'wifi_user');
+      wifiNetwork.source = OncSource.kUser;
+      wifiNetwork.connectable = true;
+      wifiNetwork.connectionState = ConnectionStateType.kPortal;
+      wifiNetwork.portalState = PortalState.kProxyAuthRequired;
+
+      mojoApi_.setManagedPropertiesForTest(wifiNetwork);
+      init();
+      internetDetailDialog.isCaptivePortalUI2022Enabled_ = true;
+      return flushAsync().then(() => {
+        const networkStateText =
+            internetDetailDialog.shadowRoot.querySelector(`#networkState`);
+        assertTrue(networkStateText.hasAttribute('warning'));
+        assertEquals(
+            networkStateText.textContent.trim(),
+            internetDetailDialog.i18n('networkListItemSignIn'));
+        const signinButton = getButton('signinButton');
+        assertTrue(!!signinButton);
+        assertFalse(signinButton.hasAttribute('hidden'));
+        assertFalse(signinButton.disabled);
+      });
+    });
+
+    test('WiFi in a portal portalState and feature flag disabled', function() {
+      mojoApi_.setNetworkTypeEnabledState(NetworkType.kWiFi, true);
+      const wifiNetwork = getManagedProperties(NetworkType.kWiFi, 'wifi_user');
+      wifiNetwork.source = OncSource.kUser;
+      wifiNetwork.connectable = true;
+      wifiNetwork.connectionState = ConnectionStateType.kPortal;
+      wifiNetwork.portalState = PortalState.kPortal;
+
+      mojoApi_.setManagedPropertiesForTest(wifiNetwork);
+      init();
+      internetDetailDialog.isCaptivePortalUI2022Enabled_ = false;
+      return flushAsync().then(() => {
+        const networkStateText =
+            internetDetailDialog.shadowRoot.querySelector(`#networkState`);
+        assertTrue(networkStateText.hasAttribute('connected'));
+        assertEquals(
+            networkStateText.textContent.trim(),
+            internetDetailDialog.i18n('OncConnected'));
+        const signinButton =
+            internetDetailDialog.shadowRoot.querySelector(`#signinButton`);
+        // Button does not exist because feature flag is disabled.
+        assertTrue(!signinButton);
+      });
+    });
+  });
+
   test('Network not on active sim, hide configurations', async () => {
     await setupCellularNetwork(/*isPrimary=*/ false, /*isInhibited=*/ false);
 
diff --git a/chrome/test/data/webui/new_tab_page/lens_form_test.ts b/chrome/test/data/webui/new_tab_page/lens_form_test.ts
index 0f4e135b3..1b1e0b8 100644
--- a/chrome/test/data/webui/new_tab_page/lens_form_test.ts
+++ b/chrome/test/data/webui/new_tab_page/lens_form_test.ts
@@ -12,6 +12,7 @@
   let lensForm: LensFormElement;
 
   let fileFormSubmitted = false;
+  let urlFormSubmitted = false;
   let lastError: LensErrorType|null = null;
   let loading = false;
 
@@ -23,6 +24,10 @@
       fileFormSubmitted = true;
     };
 
+    lensForm.$.urlForm.submit = () => {
+      urlFormSubmitted = true;
+    };
+
     lensForm.addEventListener('error', (e: Event) => {
       const event = e as CustomEvent<LensErrorType>;
       lastError = event.detail;
@@ -35,6 +40,7 @@
 
   teardown(() => {
     fileFormSubmitted = false;
+    urlFormSubmitted = false;
     lastError = null;
     loading = false;
   });
@@ -99,6 +105,85 @@
         assertFalse(loading);
       });
 
+  test('submit url with valid http should submit', async () => {
+    // Arrange.
+    const url = 'http://www.example.com/dog.jpg';
+
+    // Act.
+    lensForm.submitUrl(url);
+
+    // Assert.
+    assertTrue(urlFormSubmitted);
+    assertTrue(loading);
+  });
+
+  test('submit url with valid https should submit', async () => {
+    // Arrange.
+    const url = 'https://www.example.com/dog.jpg';
+
+    // Act.
+    lensForm.submitUrl(url);
+
+    // Assert.
+    assertTrue(urlFormSubmitted);
+    assertTrue(loading);
+  });
+
+  test(
+      'submit url with empty scheme should fail with invalid scheme error',
+      async () => {
+        // Arrange.
+        const url = 'www.example.com/dog.jpg';
+
+        // Act.
+        lensForm.submitUrl(url);
+
+        // Assert.
+        assertFalse(urlFormSubmitted);
+        assertEquals(LensErrorType.INVALID_SCHEME, lastError);
+      });
+
+  test(
+      'submit url with invalid scheme should fail with invalid scheme error',
+      async () => {
+        // Arrange.
+        const url = 'file://www.example.com/dog.jpg';
+
+        // Act.
+        lensForm.submitUrl(url);
+
+        // Assert.
+        assertFalse(urlFormSubmitted);
+        assertEquals(LensErrorType.INVALID_SCHEME, lastError);
+      });
+
+  test('submit invalid url should fail with invalid url error', async () => {
+    // Arrange.
+    const url = 'http://www.example.com/\uD800.jpg';
+
+    // Act.
+    lensForm.submitUrl(url);
+
+    // Assert.
+    assertFalse(urlFormSubmitted);
+    assertEquals(LensErrorType.INVALID_URL, lastError);
+  });
+
+  test('submit long url should fail with length too great error', async () => {
+    // Arrange.
+    let longString = 'http://www.example.com/dog.jpg?a=';
+    for (let i = 0; i < 2000; i++) {
+      longString += 'x';
+    }
+
+    // Act.
+    lensForm.submitUrl(longString);
+
+    // Assert.
+    assertFalse(urlFormSubmitted);
+    assertEquals(LensErrorType.LENGTH_TOO_GREAT, lastError);
+  });
+
   function dispatchFileInputChangeWithDataTransfer(dataTransfer: DataTransfer) {
     lensForm.$.fileInput.files = dataTransfer.files;
     lensForm.$.fileInput.dispatchEvent(new Event('change'));
diff --git a/chrome/test/data/webui/new_tab_page/lens_upload_dialog_test.ts b/chrome/test/data/webui/new_tab_page/lens_upload_dialog_test.ts
index 054ff35d..cff6413 100644
--- a/chrome/test/data/webui/new_tab_page/lens_upload_dialog_test.ts
+++ b/chrome/test/data/webui/new_tab_page/lens_upload_dialog_test.ts
@@ -7,7 +7,7 @@
 
 import {LensUploadDialogElement} from 'chrome://new-tab-page/lazy_load.js';
 import {WindowProxy} from 'chrome://new-tab-page/new_tab_page.js';
-import {assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 
@@ -19,6 +19,9 @@
   let outsideClickTarget: HTMLDivElement;
   let windowProxy: TestBrowserProxy;
 
+  let submitUrlCalled = false;
+  let submittedUrl: string|null = null;
+
   setup(() => {
     document.body.innerHTML = '';
     windowProxy = installMock(WindowProxy);
@@ -42,6 +45,16 @@
 
     uploadDialog = document.createElement('ntp-lens-upload-dialog');
     wrapperElement.appendChild(uploadDialog);
+
+    uploadDialog.$.lensForm.submitUrl = (url: string) => {
+      submitUrlCalled = true;
+      submittedUrl = url;
+    };
+  });
+
+  teardown(() => {
+    submitUrlCalled = false;
+    submittedUrl = null;
   });
 
   test('hidden be default', () => {
@@ -121,4 +134,63 @@
         // Assert.
         assertFalse(uploadDialog.hasAttribute('is-offline_'));
       });
+
+  test('submit url does not submit with empty url', async () => {
+    // Arrange.
+    uploadDialog.openDialog();
+    await waitAfterNextRender(uploadDialog);
+
+    // Act.
+    clickInputSubmit();
+
+    // Assert.
+    assertFalse(submitUrlCalled);
+  });
+
+  test(
+      'submit valid url by clicking submit button should submit ', async () => {
+        // Arrange.
+        const url = 'http://google.com/image.png';
+        uploadDialog.openDialog();
+        await waitAfterNextRender(uploadDialog);
+
+        // Act.
+        setInputBoxValue(url);
+        clickInputSubmit();
+
+        // Assert.
+        assertTrue(submitUrlCalled);
+        assertEquals(url, submittedUrl);
+      });
+
+  test('submit valid url by typing enter should submit ', async () => {
+    // Arrange.
+    const url = 'http://google.com/image.png';
+    uploadDialog.openDialog();
+    await waitAfterNextRender(uploadDialog);
+
+    // Act.
+    setInputBoxValue(url);
+    getInputBox().dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
+
+    // Assert.
+    assertTrue(submitUrlCalled);
+    assertEquals(url, submittedUrl);
+  });
+
+  function getInputBox(): HTMLInputElement {
+    return uploadDialog.shadowRoot!.querySelector('#inputBox')!;
+  }
+
+  function setInputBoxValue(value: string) {
+    const inputBox = getInputBox();
+    inputBox.value = value;
+    inputBox.dispatchEvent(new InputEvent('input'));
+  }
+
+  function clickInputSubmit() {
+    const inputSubmit =
+        uploadDialog.shadowRoot!.querySelector('#inputSubmit') as HTMLElement;
+    inputSubmit.click();
+  }
 });
diff --git a/chrome/test/data/webui/settings/chromeos/device_page_tests.js b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
index 240d22a..81d52bc 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
@@ -1269,9 +1269,22 @@
             displayDiv.click();
             assertEquals(
                 displayPage.displays[1].id, displayPage.selectedDisplay.id);
+            flush();
 
-            displayPage.updatePrimaryDisplay_({target: {value: '0'}});
-            displayPage.onOrientationChange_({target: {value: '90'}});
+            const primaryDisplaySelect =
+                displayPage.shadowRoot.getElementById('primaryDisplaySelect');
+            assertTrue(!!primaryDisplaySelect);
+            primaryDisplaySelect.value = '0';
+            primaryDisplaySelect.dispatchEvent(new CustomEvent('change'));
+            flush();
+
+            const orientationSelect =
+                displayPage.shadowRoot.getElementById('orientationSelect');
+            assertTrue(!!orientationSelect);
+            orientationSelect.value = '90';
+            orientationSelect.dispatchEvent(new CustomEvent('change'));
+            flush();
+
             fakeSystemDisplay.onDisplayChanged.callListeners();
 
             return Promise.all([
@@ -1294,7 +1307,12 @@
             assertEquals(90, displayPage.displays[1].rotation);
 
             // Mirror the displays.
-            displayPage.onMirroredTap_({target: {blur: function() {}}});
+            const displayMirrorCheckbox =
+                displayPage.shadowRoot.getElementById('displayMirrorCheckbox');
+            assertTrue(!!displayMirrorCheckbox);
+            displayMirrorCheckbox.click();
+            flush();
+
             fakeSystemDisplay.onDisplayChanged.callListeners();
 
             return Promise.all([
diff --git a/chrome/test/data/webui/settings/cr_settings_browsertest.js b/chrome/test/data/webui/settings/cr_settings_browsertest.js
index 0e07062..65efc07 100644
--- a/chrome/test/data/webui/settings/cr_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_browsertest.js
@@ -757,7 +757,6 @@
   mocha.run();
 });
 
-
 var CrSettingsReviewNotificationPermissionsTest =
     class extends CrSettingsBrowserTest {
   /** @override */
@@ -775,9 +774,12 @@
   }
 };
 
-TEST_F('CrSettingsReviewNotificationPermissionsTest', 'All', function() {
-  mocha.run();
-});
+// Failing on buildbots. http://crbug.com/1374908
+TEST_F(
+    'CrSettingsReviewNotificationPermissionsTest',
+    'DISABLED_CrSettingsReviewNotificationPermissionsTest', function() {
+      mocha.run();
+    });
 
 [['AppearanceFontsPage', 'appearance_fonts_page_test.js'],
  [
diff --git a/chromeos/ash/components/audio/BUILD.gn b/chromeos/ash/components/audio/BUILD.gn
index 2f66848..2fec89d 100644
--- a/chromeos/ash/components/audio/BUILD.gn
+++ b/chromeos/ash/components/audio/BUILD.gn
@@ -77,6 +77,7 @@
     "audio_device_selection_generated_unittest.cc",
     "audio_device_selection_test_base.cc",
     "audio_device_selection_test_base.h",
+    "audio_device_selection_unittest.cc",
     "audio_devices_pref_handler_impl_unittest.cc",
     "cras_audio_handler_unittest.cc",
     "cros_audio_config_impl_unittest.cc",
diff --git a/chromeos/ash/components/audio/audio_device.h b/chromeos/ash/components/audio/audio_device.h
index 3fe3bceb..8b585e46 100644
--- a/chromeos/ash/components/audio/audio_device.h
+++ b/chromeos/ash/components/audio/audio_device.h
@@ -38,9 +38,9 @@
 };
 
 // Default value of user priority preference.
-const uint32_t kUserPriorityNone = 0;
+const int kUserPriorityNone = 0;
 // Min value of user priority preference.
-const uint32_t kUserPriorityMin = 1;
+const int kUserPriorityMin = 1;
 
 struct COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_AUDIO) AudioDevice {
   AudioDevice();
diff --git a/chromeos/ash/components/audio/audio_device_selection_unittest.cc b/chromeos/ash/components/audio/audio_device_selection_unittest.cc
new file mode 100644
index 0000000..1dec1d7
--- /dev/null
+++ b/chromeos/ash/components/audio/audio_device_selection_unittest.cc
@@ -0,0 +1,103 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/ash/components/audio/audio_device_selection_test_base.h"
+
+#include "base/test/metrics/user_action_tester.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+const char* kInputSwitched = "StatusArea_Audio_SwitchInputDevice";
+const char* kOutputSwitched = "StatusArea_Audio_SwitchOutputDevice";
+const char* kInputOverridden = "StatusArea_Audio_AutoInputSelectionOverridden";
+const char* kOutputOverridden =
+    "StatusArea_Audio_AutoOutputSelectionOverridden";
+
+namespace ash {
+namespace {
+
+class AudioDeviceSelectionTest : public AudioDeviceSelectionTestBase {};
+
+TEST_F(AudioDeviceSelectionTest, PlugUnplugMetricAction) {
+  AudioNode input1 = NewInputNode("USB");
+  AudioNode input2 = NewInputNode("USB");
+  AudioNode output3 = NewOutputNode("USB");
+  AudioNode output4 = NewOutputNode("USB");
+
+  {
+    base::UserActionTester actions;
+    Plug(input1);
+    Plug(output3);
+    ASSERT_EQ(ActiveInputNodeId(), input1.id);
+    ASSERT_EQ(ActiveOutputNodeId(), output3.id);
+    Plug(input2);
+    Plug(output4);
+    ASSERT_EQ(ActiveInputNodeId(), input2.id);
+    ASSERT_EQ(ActiveOutputNodeId(), output4.id);
+    // Automatic switches should not generate events.
+    EXPECT_EQ(actions.GetActionCount(kInputSwitched), 0);
+    EXPECT_EQ(actions.GetActionCount(kOutputSwitched), 0);
+    EXPECT_EQ(actions.GetActionCount(kInputOverridden), 0);
+    EXPECT_EQ(actions.GetActionCount(kOutputOverridden), 0);
+  }
+
+  {
+    base::UserActionTester actions;
+    Select(input1);
+    ASSERT_EQ(ActiveInputNodeId(), input1.id);
+    ASSERT_EQ(ActiveOutputNodeId(), output4.id);
+    EXPECT_EQ(actions.GetActionCount(kInputSwitched), 1);
+    EXPECT_EQ(actions.GetActionCount(kOutputSwitched), 0);
+    EXPECT_EQ(actions.GetActionCount(kInputOverridden), 1);
+    EXPECT_EQ(actions.GetActionCount(kOutputOverridden), 0);
+  }
+
+  {
+    base::UserActionTester actions;
+    Select(output3);
+    ASSERT_EQ(ActiveInputNodeId(), input1.id);
+    ASSERT_EQ(ActiveOutputNodeId(), output3.id);
+    EXPECT_EQ(actions.GetActionCount(kInputSwitched), 0);
+    EXPECT_EQ(actions.GetActionCount(kOutputSwitched), 1);
+    EXPECT_EQ(actions.GetActionCount(kInputOverridden), 0);
+    EXPECT_EQ(actions.GetActionCount(kOutputOverridden), 1);
+  }
+
+  {
+    base::UserActionTester actions;
+    Select(input2);
+    Select(output4);
+    ASSERT_EQ(ActiveInputNodeId(), input2.id);
+    ASSERT_EQ(ActiveOutputNodeId(), output4.id);
+    EXPECT_EQ(actions.GetActionCount(kInputSwitched), 1);
+    EXPECT_EQ(actions.GetActionCount(kOutputSwitched), 1);
+    // Switching back and forth should not be counted.
+    EXPECT_EQ(actions.GetActionCount(kInputOverridden), 0);
+    EXPECT_EQ(actions.GetActionCount(kOutputOverridden), 0);
+  }
+
+  {
+    base::UserActionTester actions;
+    Unplug(input1);
+    Plug(input1);
+    ASSERT_EQ(ActiveInputNodeId(), input2.id);
+    Select(input1);
+    EXPECT_EQ(actions.GetActionCount(kInputSwitched), 1);
+    // Switching after the system decides to do nothing, should be counted.
+    EXPECT_EQ(actions.GetActionCount(kInputOverridden), 1);
+  }
+
+  {
+    base::UserActionTester actions;
+    Unplug(output3);
+    Plug(output3);
+    ASSERT_EQ(ActiveOutputNodeId(), output4.id);
+    Select(output3);
+    EXPECT_EQ(actions.GetActionCount(kOutputSwitched), 1);
+    // Switching after the system decides to do nothing, should be counted.
+    EXPECT_EQ(actions.GetActionCount(kOutputOverridden), 1);
+  }
+}
+
+}  // namespace
+}  // namespace ash
diff --git a/chromeos/ash/components/audio/audio_devices_pref_handler.h b/chromeos/ash/components/audio/audio_devices_pref_handler.h
index 6d8e86e..8e0e619 100644
--- a/chromeos/ash/components/audio/audio_devices_pref_handler.h
+++ b/chromeos/ash/components/audio/audio_devices_pref_handler.h
@@ -67,7 +67,7 @@
   virtual void SetUserPriorityHigherThan(const AudioDevice& target,
                                          const AudioDevice& base) = 0;
   // Reads the user priority from prefs.
-  virtual int32_t GetUserPriority(const AudioDevice& device) = 0;
+  virtual int GetUserPriority(const AudioDevice& device) = 0;
 
   // Reads the audio output allowed value from prefs.
   virtual bool GetAudioOutputAllowedValue() const = 0;
diff --git a/chromeos/ash/components/audio/audio_devices_pref_handler_impl.cc b/chromeos/ash/components/audio/audio_devices_pref_handler_impl.cc
index 6b9927b..9b050dc1 100644
--- a/chromeos/ash/components/audio/audio_devices_pref_handler_impl.cc
+++ b/chromeos/ash/components/audio/audio_devices_pref_handler_impl.cc
@@ -266,7 +266,7 @@
   }
 }
 
-int32_t AudioDevicesPrefHandlerImpl::GetUserPriority(
+int AudioDevicesPrefHandlerImpl::GetUserPriority(
     const AudioDevice& device) {
   if (device.is_input) {
     return input_device_user_priority_settings_
diff --git a/chromeos/ash/components/audio/audio_devices_pref_handler_impl.h b/chromeos/ash/components/audio/audio_devices_pref_handler_impl.h
index 8ada10b..e410ffa 100644
--- a/chromeos/ash/components/audio/audio_devices_pref_handler_impl.h
+++ b/chromeos/ash/components/audio/audio_devices_pref_handler_impl.h
@@ -48,7 +48,7 @@
 
   void SetUserPriorityHigherThan(const AudioDevice& target,
                                  const AudioDevice& base) override;
-  int32_t GetUserPriority(const AudioDevice& device) override;
+  int GetUserPriority(const AudioDevice& device) override;
 
   bool GetNoiseCancellationState() override;
   void SetNoiseCancellationState(bool noise_cancellation_state) override;
diff --git a/chromeos/ash/components/audio/audio_devices_pref_handler_impl_unittest.cc b/chromeos/ash/components/audio/audio_devices_pref_handler_impl_unittest.cc
index 8d866a5..7f3d8b0 100644
--- a/chromeos/ash/components/audio/audio_devices_pref_handler_impl_unittest.cc
+++ b/chromeos/ash/components/audio/audio_devices_pref_handler_impl_unittest.cc
@@ -179,7 +179,7 @@
                          : audio_pref_handler_->GetOutputVolumeValue(&device);
   }
 
-  double GetUserPriority(const AudioDevice& device) {
+  int GetUserPriority(const AudioDevice& device) {
     return audio_pref_handler_->GetUserPriority(device);
   }
 
diff --git a/chromeos/ash/components/audio/audio_devices_pref_handler_stub.cc b/chromeos/ash/components/audio/audio_devices_pref_handler_stub.cc
index e139b3a7..fe64a02 100644
--- a/chromeos/ash/components/audio/audio_devices_pref_handler_stub.cc
+++ b/chromeos/ash/components/audio/audio_devices_pref_handler_stub.cc
@@ -96,7 +96,7 @@
   }
 }
 
-int32_t AudioDevicesPrefHandlerStub::GetUserPriority(
+int AudioDevicesPrefHandlerStub::GetUserPriority(
     const AudioDevice& device) {
   if (user_priority_map_.find(device.stable_device_id) ==
       user_priority_map_.end())
diff --git a/chromeos/ash/components/audio/cras_audio_handler.cc b/chromeos/ash/components/audio/cras_audio_handler.cc
index cd4c36a..75ce55f2 100644
--- a/chromeos/ash/components/audio/cras_audio_handler.cc
+++ b/chromeos/ash/components/audio/cras_audio_handler.cc
@@ -786,11 +786,28 @@
     if (active_device.is_input) {
       base::RecordAction(
           base::UserMetricsAction("StatusArea_Audio_SwitchInputDevice"));
+      if (!input_device_selected_by_user_) {
+        base::RecordAction(base::UserMetricsAction(
+            "StatusArea_Audio_AutoInputSelectionOverridden"));
+      }
     } else {
       base::RecordAction(
           base::UserMetricsAction("StatusArea_Audio_SwitchOutputDevice"));
+      if (!output_device_selected_by_user_) {
+        base::RecordAction(base::UserMetricsAction(
+            "StatusArea_Audio_AutoOutputSelectionOverridden"));
+      }
     }
   }
+
+  // Update *_selected_by_user_.
+  // Including to unset it when selected by priority or by camera.
+  if (active_device.is_input) {
+    input_device_selected_by_user_ = activate_by == ACTIVATE_BY_USER;
+  } else {
+    output_device_selected_by_user_ = activate_by == ACTIVATE_BY_USER;
+  }
+
   if (active_device.is_input)
     CrasAudioClient::Get()->SetActiveInputNode(active_device.id);
   else
@@ -1535,7 +1552,7 @@
 
 void CrasAudioHandler::HandleNonHotplugNodesChange(
     bool is_input,
-    const AudioDevicePriorityQueue& hotplug_nodes,
+    const AudioDevicePriorityQueue& hotplug_devices,
     bool has_device_change,
     bool has_device_removed,
     bool active_device_removed) {
@@ -1546,7 +1563,7 @@
   if (!has_device_change && has_current_active_node)
     return;
 
-  if (hotplug_nodes.empty()) {
+  if (hotplug_devices.empty()) {
     if (has_device_removed) {
       if (!active_device_removed && has_current_active_node) {
         // Removed a non-active device, keep the current active device.
@@ -1746,22 +1763,27 @@
 
 void CrasAudioHandler::UpdateDevicesAndSwitchActive(
     const AudioNodeList& nodes) {
-  AudioDevicePriorityQueue hotplug_output_nodes;
-  AudioDevicePriorityQueue hotplug_input_nodes;
+  AudioDevicePriorityQueue hotplug_output_devices;
+  AudioDevicePriorityQueue hotplug_input_devices;
   bool has_output_removed = false;
   bool has_input_removed = false;
   bool active_output_removed = false;
   bool active_input_removed = false;
   bool output_devices_changed =
-      HasDeviceChange(nodes, false, &hotplug_output_nodes, &has_output_removed,
-                      &active_output_removed);
+      HasDeviceChange(nodes, false, &hotplug_output_devices,
+                      &has_output_removed, &active_output_removed);
   bool input_devices_changed =
-      HasDeviceChange(nodes, true, &hotplug_input_nodes, &has_input_removed,
+      HasDeviceChange(nodes, true, &hotplug_input_devices, &has_input_removed,
                       &active_input_removed);
 
+  std::vector<AudioDevice> devices;
+  devices.reserve(nodes.size());
+  for (AudioNode node : nodes) {
+    devices.push_back(ConvertAudioNodeWithModifiedPriority(node));
+  }
+
   // Updates the display_rotation to the internal speaker when it's added.
-  for (auto node : nodes) {
-    AudioDevice device = ConvertAudioNodeWithModifiedPriority(node);
+  for (AudioDevice device : devices) {
     DeviceStatus status = CheckDeviceStatus(device);
     if (status == NEW_DEVICE &&
         device.type == AudioDeviceType::kInternalSpeaker) {
@@ -1778,8 +1800,7 @@
   while (!output_devices_pq_.empty())
     output_devices_pq_.pop();
 
-  for (size_t i = 0; i < nodes.size(); ++i) {
-    AudioDevice device = ConvertAudioNodeWithModifiedPriority(nodes[i]);
+  for (AudioDevice device : devices) {
     audio_devices_[device.id] = device;
     if (!has_alternative_input_ && device.is_input &&
         device.IsExternalDevice()) {
@@ -1797,12 +1818,12 @@
   }
 
   // Handle output device changes.
-  HandleAudioDeviceChange(false, output_devices_pq_, hotplug_output_nodes,
+  HandleAudioDeviceChange(false, output_devices_pq_, hotplug_output_devices,
                           output_devices_changed, has_output_removed,
                           active_output_removed);
 
   // Handle input device changes.
-  HandleAudioDeviceChange(true, input_devices_pq_, hotplug_input_nodes,
+  HandleAudioDeviceChange(true, input_devices_pq_, hotplug_input_devices,
                           input_devices_changed, has_input_removed,
                           active_input_removed);
 
@@ -1820,13 +1841,24 @@
 void CrasAudioHandler::HandleAudioDeviceChange(
     bool is_input,
     const AudioDevicePriorityQueue& devices_pq,
-    const AudioDevicePriorityQueue& hotplug_nodes,
+    const AudioDevicePriorityQueue& hotplug_devices,
     bool has_device_change,
     bool has_device_removed,
     bool active_device_removed) {
   uint64_t& active_node_id =
       is_input ? active_input_node_id_ : active_output_node_id_;
 
+  if (has_device_change) {
+    // Mark device selected by the system, including when the algorithm
+    // does nothing ultimately. We still treat not switching the device
+    // as a decision of the algorithm.
+    if (is_input) {
+      input_device_selected_by_user_ = false;
+    } else {
+      output_device_selected_by_user_ = false;
+    }
+  }
+
   // No audio devices found.
   if (devices_pq.empty()) {
     VLOG(1) << "No " << (is_input ? "input" : "output") << " devices found";
@@ -1842,12 +1874,13 @@
   if (!active_device || !active_device->active)
     active_node_id = 0;
 
-  if (!active_node_id || hotplug_nodes.empty() || hotplug_nodes.size() > 1) {
-    HandleNonHotplugNodesChange(is_input, hotplug_nodes, has_device_change,
+  if (!active_node_id || hotplug_devices.empty() ||
+      hotplug_devices.size() > 1) {
+    HandleNonHotplugNodesChange(is_input, hotplug_devices, has_device_change,
                                 has_device_removed, active_device_removed);
   } else {
     // Typical user hotplug case.
-    HandleHotPlugDevice(hotplug_nodes.top(), devices_pq);
+    HandleHotPlugDevice(hotplug_devices.top(), devices_pq);
   }
 }
 
diff --git a/chromeos/ash/components/audio/cras_audio_handler.h b/chromeos/ash/components/audio/cras_audio_handler.h
index cb4cad5..c7d9379 100644
--- a/chromeos/ash/components/audio/cras_audio_handler.h
+++ b/chromeos/ash/components/audio/cras_audio_handler.h
@@ -814,6 +814,10 @@
   // In this case, input mute changes will be disabled.
   bool input_muted_by_microphone_mute_switch_ = false;
 
+  // Whether the audio device was selected by user, to track user overrides
+  bool input_device_selected_by_user_ = false;
+  bool output_device_selected_by_user_ = false;
+
   // Task runner of browser main thread. All member variables should be accessed
   // on this thread.
   scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
diff --git a/chromeos/components/cdm_factory_daemon/cdm_factory_daemon_proxy_lacros.cc b/chromeos/components/cdm_factory_daemon/cdm_factory_daemon_proxy_lacros.cc
index ee5538c..84df9f42 100644
--- a/chromeos/components/cdm_factory_daemon/cdm_factory_daemon_proxy_lacros.cc
+++ b/chromeos/components/cdm_factory_daemon/cdm_factory_daemon_proxy_lacros.cc
@@ -94,13 +94,14 @@
     return;
   }
 
-  if (!LacrosService::Get()->IsBrowserCdmFactoryAvailable()) {
+  auto* service = LacrosService::Get();
+  if (!service || !service->IsBrowserCdmFactoryAvailable()) {
     std::move(callback).Run();
     return;
   }
   // For Lacros, we connect to the ash-chrome browser process which will proxy
   // the connection to the daemon.
-  LacrosService::Get()->BindBrowserCdmFactory(
+  service->BindBrowserCdmFactory(
       mojo::GenericPendingReceiver(ash_remote_.BindNewPipeAndPassReceiver()));
   std::move(callback).Run();
   return;
diff --git a/chromeos/services/machine_learning/cpp/lacros/service_connection_lacros.cc b/chromeos/services/machine_learning/cpp/lacros/service_connection_lacros.cc
index 8b6858f55..49f01111 100644
--- a/chromeos/services/machine_learning/cpp/lacros/service_connection_lacros.cc
+++ b/chromeos/services/machine_learning/cpp/lacros/service_connection_lacros.cc
@@ -43,19 +43,24 @@
 
 chromeos::machine_learning::mojom::MachineLearningService&
 ServiceConnectionLacros::GetMachineLearningService() {
+  // TODO(crbug.com/1374564): Determine whether it is safe to assume
+  // LacrosService is always available here.
+  auto* service = chromeos::LacrosService::Get();
+  DCHECK(service);
   mojo::Remote<chromeos::machine_learning::mojom::MachineLearningService>&
-      machine_learning_service_remote =
-          chromeos::LacrosService::Get()
-              ->GetRemote<
-                  chromeos::machine_learning::mojom::MachineLearningService>();
+      machine_learning_service_remote = service->GetRemote<
+          chromeos::machine_learning::mojom::MachineLearningService>();
   return *machine_learning_service_remote.get();
 }
 
 void ServiceConnectionLacros::BindMachineLearningService(
     mojo::PendingReceiver<
         chromeos::machine_learning::mojom::MachineLearningService> receiver) {
-  chromeos::LacrosService::Get()->BindMachineLearningService(
-      std::move(receiver));
+  // TODO(crbug.com/1374564): Determine whether it is safe to assume
+  // LacrosService is always available here.
+  auto* service = chromeos::LacrosService::Get();
+  DCHECK(service);
+  service->BindMachineLearningService(std::move(receiver));
 }
 
 void ServiceConnectionLacros::Initialize() {}
diff --git a/chromeos/tast_control.gni b/chromeos/tast_control.gni
index edd4e1f6..0dac43fb 100644
--- a/chromeos/tast_control.gni
+++ b/chromeos/tast_control.gni
@@ -294,6 +294,21 @@
   # b/249879125
   "launcher.SearchBuiltInApps.tablet_mode",
 
+  # http://b/250566486
+  "lacros.Basic",
+  "lacros.ShelfLaunch",
+  "lacros.ShelfLaunch.primary",
+  "lacros.AppLauncherLaunch",
+  "lacros.AudioPinnedStream.play",
+  "mlservice.WebHandwritingRecognitionNotSupported.lacros",
+  "platform.PerfettoChromeConsumer.lacros",
+  "power.SmartDim.lacros",
+
+  # https://crbug.com/1371407 and http://b/250566486.
+  "lacros.AudioPinnedStream.record",
+  "lacros.AudioPlay",
+  "lacros.AudioRecord",
+
   # https://crbug.com/1356506
   "inputs.PhysicalKeyboardCantoneseTyping",
   "inputs.PhysicalKeyboardCantoneseTyping.lacros",
diff --git a/components/feed/core/v2/feed_stream.cc b/components/feed/core/v2/feed_stream.cc
index 38e882f..b475e65 100644
--- a/components/feed/core/v2/feed_stream.cc
+++ b/components/feed/core/v2/feed_stream.cc
@@ -1399,6 +1399,12 @@
   info_card_tracker_.ResetState(info_card_type);
 }
 
+void FeedStream::ReportContentSliceVisibleTimeForGoodVisits(
+    base::TimeDelta elapsed) {
+  metrics_reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      elapsed);
+}
+
 void FeedStream::SetContentOrder(const StreamType& stream_type,
                                  ContentOrder content_order) {
   if (!stream_type.IsWebFeed()) {
diff --git a/components/feed/core/v2/feed_stream.h b/components/feed/core/v2/feed_stream.h
index 6c4cbcbf..3d78ecb 100644
--- a/components/feed/core/v2/feed_stream.h
+++ b/components/feed/core/v2/feed_stream.h
@@ -178,6 +178,8 @@
                                          int info_card_type) override;
   void ResetInfoCardStates(const StreamType& stream_type,
                            int info_card_type) override;
+  void ReportContentSliceVisibleTimeForGoodVisits(
+      base::TimeDelta elapsed) override;
   base::Time GetLastFetchTime(const StreamType& stream_type) override;
   void SetContentOrder(const StreamType& stream_type,
                        ContentOrder content_order) override;
diff --git a/components/feed/core/v2/feedstore_util.cc b/components/feed/core/v2/feedstore_util.cc
index 59acdf7d..c5f2c30a 100644
--- a/components/feed/core/v2/feedstore_util.cc
+++ b/components/feed/core/v2/feedstore_util.cc
@@ -24,17 +24,22 @@
   DCHECK(stream_type.IsChannelFeed());
   std::string encoding;
   base::Base64Encode(stream_type.GetWebFeedId(), &encoding);
-  return encoding;
+  return std::string(kChannelStreamKeyPrefix) + encoding;
 }
 
-StreamType StreamTypeFromKey(std::string key) {
+StreamType StreamTypeFromKey(base::StringPiece key) {
   if (key == kForYouStreamKey)
     return StreamType(feed::StreamKind::kForYou);
   if (key == kFollowStreamKey)
     return StreamType(feed::StreamKind::kFollowing);
-  std::string channel_key;
-  if (base::Base64Decode(key, &channel_key))
-    return StreamType(feed::StreamKind::kChannel, channel_key);
+  if (base::StartsWith(key, kChannelStreamKeyPrefix,
+                       base::CompareCase::SENSITIVE)) {
+    std::string channel_key;
+    if (base::Base64Decode(key.substr(kChannelStreamKeyPrefix.size()),
+                           &channel_key)) {
+      return StreamType(feed::StreamKind::kChannel, channel_key);
+    }
+  }
   return {};
 }
 
diff --git a/components/feed/core/v2/feedstore_util.h b/components/feed/core/v2/feedstore_util.h
index 7c61b4c..0a987ade 100644
--- a/components/feed/core/v2/feedstore_util.h
+++ b/components/feed/core/v2/feedstore_util.h
@@ -22,9 +22,10 @@
 
 const char kForYouStreamKey[] = "i";
 const char kFollowStreamKey[] = "w";
+constexpr base::StringPiece kChannelStreamKeyPrefix = "c";
 
 std::string StreamKey(const feed::StreamType& stream_type);
-feed::StreamType StreamTypeFromKey(std::string key);
+feed::StreamType StreamTypeFromKey(base::StringPiece key);
 
 ///////////////////////////////////////////////////
 // Functions that operate on feedstore proto types.
diff --git a/components/feed/core/v2/feedstore_util_unittest.cc b/components/feed/core/v2/feedstore_util_unittest.cc
index 35707055..119dcc8 100644
--- a/components/feed/core/v2/feedstore_util_unittest.cc
+++ b/components/feed/core/v2/feedstore_util_unittest.cc
@@ -88,6 +88,7 @@
 
   EXPECT_TRUE(StreamTypeFromKey(StreamKey(following)).IsWebFeed());
   EXPECT_TRUE(StreamTypeFromKey(StreamKey(for_you)).IsForYou());
+  EXPECT_EQ(StreamTypeFromKey("z"), StreamType());
 }
 
 }  // namespace
diff --git a/components/feed/core/v2/metrics_reporter.cc b/components/feed/core/v2/metrics_reporter.cc
index 941d52e2..a31c99db 100644
--- a/components/feed/core/v2/metrics_reporter.cc
+++ b/components/feed/core/v2/metrics_reporter.cc
@@ -738,7 +738,7 @@
   }
 }
 
-void MetricsReporter::ReportStableContentSliceVisibilityTime(
+void MetricsReporter::ReportStableContentSliceVisibilityTimeForGoodVisits(
     base::TimeDelta delta) {
   if (good_visit_state_)
     good_visit_state_->AddTimeInFeed(delta);
diff --git a/components/feed/core/v2/metrics_reporter.h b/components/feed/core/v2/metrics_reporter.h
index 0a0d17a..6713013 100644
--- a/components/feed/core/v2/metrics_reporter.h
+++ b/components/feed/core/v2/metrics_reporter.h
@@ -77,10 +77,10 @@
   void PageLoaded();
   void OtherUserAction(const StreamType& stream_type,
                        FeedUserActionType action_type);
-  // Report a period of time during which at least one content slice was >50%
-  // visible and covered >25% of the viewport.
-  // TODO(iwells): Call this.
-  void ReportStableContentSliceVisibilityTime(base::TimeDelta delta);
+  // Report a period of time during which at least one content slice was visible
+  // enough or covering enough of the viewport.
+  void ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::TimeDelta delta);
 
   // Indicates the user scrolled the feed by |distance_dp| and then stopped
   // scrolling.
diff --git a/components/feed/core/v2/metrics_reporter_unittest.cc b/components/feed/core/v2/metrics_reporter_unittest.cc
index 24a583f..c3633d8 100644
--- a/components/feed/core/v2/metrics_reporter_unittest.cc
+++ b/components/feed/core/v2/metrics_reporter_unittest.cc
@@ -1370,22 +1370,26 @@
 
 TEST_F(MetricsReporterTest, GoodVisit_Scroll_GoodTimeSpentInFeed) {
   reporter_->StreamScrolled(StreamType(StreamKind::kForYou), 1);
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(30));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(30));
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 0);
 
   // Passing a minute in the feed should log a Good Visit since a scroll
   // happened.
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(30));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(30));
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 1);
 }
 
 TEST_F(MetricsReporterTest, GoodVisit_GoodTimeSpentInFeed_Scroll) {
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(30));
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(30));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(30));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(30));
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 0);
@@ -1400,22 +1404,25 @@
 
 TEST_F(MetricsReporterTest, GoodVisit_SmallTimesDroppped) {
   // Reach 59.9 seconds and a scroll.
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(30));
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(29) +
-                                                    base::Milliseconds(900));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(30));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(29) + base::Milliseconds(900));
   reporter_->StreamScrolled(StreamType(StreamKind::kForYou), 1);
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 0);
 
   // Ignore less than half a second.
-  reporter_->ReportStableContentSliceVisibilityTime(base::Milliseconds(200));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Milliseconds(200));
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 0);
 
   // More than half a second counts.
-  reporter_->ReportStableContentSliceVisibilityTime(base::Milliseconds(501));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Milliseconds(501));
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 1);
@@ -1424,14 +1431,17 @@
 TEST_F(MetricsReporterTest, GoodVisit_LargeTimesCapped) {
   reporter_->StreamScrolled(StreamType(StreamKind::kForYou), 1);
   // Capped to 30 seconds.
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(61));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(61));
   // 59 seconds so far.
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(29));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(29));
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 0);
 
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(2));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(2));
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 1);
@@ -1443,20 +1453,23 @@
       kClientGoodVisits,
       {{"min_stable_content_slice_visibility_time", "200ms"}});
   // Reach 59.9 seconds and a scroll.
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(30));
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(29) +
-                                                    base::Milliseconds(900));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(30));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(29) + base::Milliseconds(900));
   reporter_->StreamScrolled(StreamType(StreamKind::kForYou), 1);
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 0);
 
-  reporter_->ReportStableContentSliceVisibilityTime(base::Milliseconds(150));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Milliseconds(150));
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 0);
 
-  reporter_->ReportStableContentSliceVisibilityTime(base::Milliseconds(201));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Milliseconds(201));
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 1);
@@ -1468,14 +1481,17 @@
       kClientGoodVisits, {{"max_stable_content_slice_visibility_time", "40s"}});
   reporter_->StreamScrolled(StreamType(StreamKind::kForYou), 1);
   // Capped to 40 seconds.
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(61));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(61));
   // 59 seconds so far.
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(19));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(19));
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 0);
 
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(2));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(2));
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 1);
@@ -1563,11 +1579,13 @@
       kClientGoodVisits, {{"good_time_in_feed", "45s"}});
 
   reporter_->StreamScrolled(StreamType(StreamKind::kForYou), 1);
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(30));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(30));
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 0);
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(15));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(15));
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 1);
@@ -1638,8 +1656,10 @@
   task_environment_.FastForwardBy(base::Minutes(5));
 
   reporter_->StreamScrolled(StreamType(StreamKind::kForYou), 1);
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(30));
-  reporter_->ReportStableContentSliceVisibilityTime(base::Seconds(30));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(30));
+  reporter_->ReportStableContentSliceVisibilityTimeForGoodVisits(
+      base::Seconds(30));
   histogram_.ExpectBucketCount(
       "ContentSuggestions.Feed.AllFeeds.EngagementType",
       FeedEngagementType::kGoodVisit, 0);
diff --git a/components/feed/core/v2/public/feed_api.h b/components/feed/core/v2/public/feed_api.h
index 0a0c32f..e77af81 100644
--- a/components/feed/core/v2/public/feed_api.h
+++ b/components/feed/core/v2/public/feed_api.h
@@ -207,6 +207,12 @@
   // Resets all the states of the info card.
   virtual void ResetInfoCardStates(const StreamType& stream_type,
                                    int info_card_type) = 0;
+  // Report a period of time for which at least one content slice is visible
+  // enough or at least one content slice covers enough of the viewport. See the
+  // slice_exposure_threshold and slice_coverage_threshold feature params for
+  // what counts as visible enough and covering enough.
+  virtual void ReportContentSliceVisibleTimeForGoodVisits(
+      base::TimeDelta elapsed) = 0;
 
   // The following methods are used for the internals page.
 
diff --git a/components/feed/core/v2/public/test/stub_feed_api.h b/components/feed/core/v2/public/test/stub_feed_api.h
index 75ec307..102961c 100644
--- a/components/feed/core/v2/public/test/stub_feed_api.h
+++ b/components/feed/core/v2/public/test/stub_feed_api.h
@@ -99,6 +99,8 @@
                                          int info_card_type) override {}
   void ResetInfoCardStates(const StreamType& stream_type,
                            int info_card_type) override {}
+  void ReportContentSliceVisibleTimeForGoodVisits(
+      base::TimeDelta elapsed) override {}
   DebugStreamData GetDebugStreamData() override;
   void ForceRefreshForDebugging(const StreamType& stream_type) override {}
   std::string DumpStateForDebugging() override;
diff --git a/components/feed/feed_feature_list.cc b/components/feed/feed_feature_list.cc
index 527974c..b40d02ba 100644
--- a/components/feed/feed_feature_list.cc
+++ b/components/feed/feed_feature_list.cc
@@ -190,4 +190,10 @@
     &kClientGoodVisits, "max_stable_content_slice_visibility_time",
     base::Seconds(30)};
 
+const base::FeatureParam<double> kSliceVisibleExposureThreshold{
+    &kClientGoodVisits, "slice_exposure_threshold", 0.5f};
+
+const base::FeatureParam<double> kSliceVisibleCoverageThreshold{
+    &kClientGoodVisits, "slice_coverage_threshold", 0.25f};
+
 }  // namespace feed
diff --git a/components/feed/feed_feature_list.h b/components/feed/feed_feature_list.h
index edce9f13..a3be964 100644
--- a/components/feed/feed_feature_list.h
+++ b/components/feed/feed_feature_list.h
@@ -164,6 +164,11 @@
 // viewport-stable feed viewing to this time.
 extern const base::FeatureParam<base::TimeDelta>
     kMaxStableContentSliceVisibilityTime;
+// Minimum slice exposure needed for counting time in feed for good visits.
+extern const base::FeatureParam<double> kSliceVisibleExposureThreshold;
+// Minimum slice coverage of viewport needed for counting time in feed for good
+// visits.
+extern const base::FeatureParam<double> kSliceVisibleCoverageThreshold;
 
 }  // namespace feed
 
diff --git a/components/history/core/browser/expire_history_backend.cc b/components/history/core/browser/expire_history_backend.cc
index 6a286cad..785d36e 100644
--- a/components/history/core/browser/expire_history_backend.cc
+++ b/components/history/core/browser/expire_history_backend.cc
@@ -6,6 +6,7 @@
 
 #include <stddef.h>
 
+#include <algorithm>
 #include <functional>
 #include <limits>
 #include <memory>
@@ -20,7 +21,6 @@
 #include "base/location.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/safe_conversions.h"
-#include "base/ranges/algorithm.h"
 #include "base/task/sequenced_task_runner.h"
 #include "build/build_config.h"
 #include "components/favicon/core/favicon_database.h"
@@ -262,8 +262,10 @@
   // `times` must be in reverse chronological order and have no
   // duplicates, i.e. each member must be earlier than the one before
   // it.
-  DCHECK(base::ranges::adjacent_find(times, std::less_equal<base::Time>()) ==
-         times.end());
+  DCHECK(
+      std::adjacent_find(
+          times.begin(), times.end(), std::less_equal<base::Time>()) ==
+      times.end());
 
   if (!main_db_)
     return;
diff --git a/components/omnibox/browser/autocomplete_controller.cc b/components/omnibox/browser/autocomplete_controller.cc
index 16f9333..4c4ff67a 100644
--- a/components/omnibox/browser/autocomplete_controller.cc
+++ b/components/omnibox/browser/autocomplete_controller.cc
@@ -912,8 +912,6 @@
     }
   }
 
-  const auto last_result_for_logging = result_.GetMatchDedupComparators();
-
   if (regenerate_result)
     result_.Reset();
 
@@ -971,13 +969,6 @@
   result_.Validate();
 #endif  // DCHECK_IS_ON()
 
-  // Will log metrics for how many matches changed. Will also log timing metrics
-  // for the current request if it's complete; otherwise, will just update
-  // timestamps of when the last update changing any or the default suggestion
-  // occurred.
-  metrics_.OnUpdateResult(last_result_for_logging,
-                          result_.GetMatchDedupComparators());
-
   // Below are all annotations after the match list is ready.
 
   // Only produce Pedals for the default focus case (not on focus or on delete).
@@ -1220,10 +1211,19 @@
 }
 
 void AutocompleteController::NotifyChanged() {
-  // `CopyFrom()` does a vector copy, and `NotifyChanged()` is called a lot, so
-  // guard the copy to measure performance regressions.
+  // Will log metrics for how many matches changed. Will also log timing metrics
+  // for the current request if it's complete; otherwise, will just update
+  // timestamps of when the last update changed any or the default suggestion.
+  metrics_.OnNotifyChanged(last_result_for_logging_,
+                           result_.GetMatchDedupComparators());
+
+  // `NotifyChanged()` is called a lot, so guard the copies so performance
+  // differences between them are also measured.
   if (DebouncingEnabled())
     published_result_.CopyFrom(result_);
+
+  last_result_for_logging_ = result_.GetMatchDedupComparators();
+
   for (Observer& obs : observers_)
     obs.OnResultChanged(this, notify_changed_default_match_);
   notify_changed_debouncer_.CancelRequest();
diff --git a/components/omnibox/browser/autocomplete_controller.h b/components/omnibox/browser/autocomplete_controller.h
index 1336ae72..2879e61 100644
--- a/components/omnibox/browser/autocomplete_controller.h
+++ b/components/omnibox/browser/autocomplete_controller.h
@@ -379,6 +379,10 @@
   // empty and unused.
   AutocompleteResult published_result_;
 
+  // Used for logging the changes between updates.
+  std::vector<AutocompleteResult::MatchDedupComparator>
+      last_result_for_logging_;
+
   // The most recent time the default match (inline match) changed.  This may
   // be earlier than the most recent keystroke if the recent keystrokes didn't
   // change the suggested match in the omnibox.  (For instance, if
diff --git a/components/omnibox/browser/autocomplete_controller_metrics.cc b/components/omnibox/browser/autocomplete_controller_metrics.cc
index 9509ef3..534c917 100644
--- a/components/omnibox/browser/autocomplete_controller_metrics.cc
+++ b/components/omnibox/browser/autocomplete_controller_metrics.cc
@@ -25,9 +25,13 @@
   last_default_change_time_ = start_time_;
 }
 
-void AutocompleteControllerMetrics::OnUpdateResult(
+void AutocompleteControllerMetrics::OnNotifyChanged(
     std::vector<AutocompleteResult::MatchDedupComparator> last_result,
     std::vector<AutocompleteResult::MatchDedupComparator> new_result) {
+  // Only log metrics for async requests.
+  if (controller_.input().omit_asynchronous_matches())
+    return;
+
   // If results are empty then the omnibox is likely closed, and clearing old
   // results won't be user visible. E.g., this occurs when opening a new tab
   // while the popup was open.
@@ -48,14 +52,11 @@
   LogSuggestionChangeInAnyPositionMetrics(any_match_changed_or_removed);
 
   // Log suggestion finalization times.
+  // This handles logging as soon as the final update occurs, while `OnStop()`
+  // handles the case where the final update never occurs because of
+  // interruptions.
 
-  // Only log suggestion finalization metrics for async requests. This handles
-  // logging as soon as the final update occurs, while `OnStop()` handles the
-  // case where the final update never occurs because of interruptions.
-  // TODO(manukh): Consider adding this filter to the above metrics as well.
-  if (controller_.input().omit_asynchronous_matches())
-    return;
-  // E.g., suggestion deletion can call `OnUpdateResult()` after the controller
+  // E.g., suggestion deletion can call `OnNotifyChanged()` after the controller
   // is done and finalization metrics have been logged. They shouldn't be
   // re-logged.
   if (logged_finalization_metrics_)
@@ -80,6 +81,13 @@
 
 void AutocompleteControllerMetrics::OnProviderUpdate(
     const AutocompleteProvider& provider) const {
+  // Only log metrics for async requests. This will likely never happen, since
+  // `OnProviderUpdate()` is only called by async providers (but not necessarily
+  // async'ly, see the comments in
+  // `AutocompleteController::OnProviderUpdate()`).
+  if (controller_.input().omit_asynchronous_matches())
+    return;
+
   // Some async providers may produce multiple updates. Only log the final async
   // update.
   if (provider.done())
@@ -87,6 +95,10 @@
 }
 
 void AutocompleteControllerMetrics::OnStop() {
+  // Only log metrics for async requests.
+  if (controller_.input().omit_asynchronous_matches())
+    return;
+
   // Done providers should already be logged by `OnProviderUpdate()`.
   for (const auto& provider : controller_.providers()) {
     if (!provider->done()) {
@@ -95,7 +107,7 @@
     }
   }
 
-  // If the controller is done, `OnUpdateResult()` should have already logged
+  // If the controller is done, `OnNotifyChanged()` should have already logged
   // finalization metrics. This case, i.e. `OnStop()` invoked even though the
   // controller is done, is possible because 1) `OnStart()` calls `OnStop()`
   // and 2) `AutocompleteController::stop_timer_` may fire after the controller
@@ -133,7 +145,7 @@
     const std::string& name,
     bool completed,
     const base::TimeTicks end_time) const {
-  const auto name_prefix = "Omnibox.AsyncAutocompletionTime." + name;
+  const auto name_prefix = "Omnibox.AsyncAutocompletionTime2." + name;
   const auto elapsed_time = end_time - start_time_;
   // These metrics are logged up to about 40 times per omnibox keystroke. But
   // use UMA functions as the names are dynamic.
@@ -146,7 +158,7 @@
 
 void AutocompleteControllerMetrics::LogSuggestionChangeIndexMetrics(
     size_t change_index) const {
-  std::string name = "Omnibox.MatchStability.MatchChangeIndex";
+  std::string name = "Omnibox.MatchStability2.MatchChangeIndex";
   size_t max = AutocompleteResult::kMaxAutocompletePositionValue;
   // These metrics are logged up to about 50 times per omnibox keystroke, so use
   // UMA macros for efficiency.
@@ -159,7 +171,7 @@
 
 void AutocompleteControllerMetrics::LogSuggestionChangeInAnyPositionMetrics(
     bool changed) const {
-  std::string name = "Omnibox.MatchStability.MatchChangeInAnyPosition";
+  std::string name = "Omnibox.MatchStability2.MatchChangeInAnyPosition";
   // These metrics are logged up to about 5 times per omnibox keystroke, so
   // use UMA macros for efficiency.
   if (controller_.in_start())
diff --git a/components/omnibox/browser/autocomplete_controller_metrics.h b/components/omnibox/browser/autocomplete_controller_metrics.h
index 57030668..642c26ef 100644
--- a/components/omnibox/browser/autocomplete_controller_metrics.h
+++ b/components/omnibox/browser/autocomplete_controller_metrics.h
@@ -48,12 +48,12 @@
   // updated for the new request.
   void OnStart();
 
-  // Called when `AutocompleteController::UpdateResult()` is called. Will log
+  // Called when `AutocompleteController::NotifyChanged()` is called. Will log
   // metrics on how many suggestions changed with this update. If the controller
   // is done, will also log suggestion finalization metrics; otherwise, future
   // calls to `OnProviderUpdate()`, `OnStop()`, or `OnStart()` will log
   // suggestion finalization metrics.
-  void OnUpdateResult(
+  void OnNotifyChanged(
       std::vector<AutocompleteResult::MatchDedupComparator> last_result,
       std::vector<AutocompleteResult::MatchDedupComparator> new_result);
 
diff --git a/components/omnibox/browser/autocomplete_controller_metrics_unittest.cc b/components/omnibox/browser/autocomplete_controller_metrics_unittest.cc
index 441c9d05..5872e77 100644
--- a/components/omnibox/browser/autocomplete_controller_metrics_unittest.cc
+++ b/components/omnibox/browser/autocomplete_controller_metrics_unittest.cc
@@ -57,7 +57,7 @@
     controller_.in_start_ = true;
     controller_.done_ = sync_results_only;
     task_environment_.FastForwardBy(base::Milliseconds(sync_milliseconds));
-    metrics_.OnUpdateResult(old_results, sync_results);
+    metrics_.OnNotifyChanged(old_results, sync_results);
     controller_.in_start_ = false;
   }
 
@@ -82,7 +82,7 @@
   // Convenience method to check the buckets of a single metric.
   void ExpectMetrics(const std::string metric_name,
                      std::vector<base::Bucket> expected_buckets) {
-    const std::string prefix = "Omnibox.AsyncAutocompletionTime.";
+    const std::string prefix = "Omnibox.AsyncAutocompletionTime2.";
     EXPECT_THAT(
         histogram_tester_->GetAllSamples(prefix + metric_name),
         ElementsAreArray(expected_buckets.data(), expected_buckets.size()))
@@ -182,14 +182,14 @@
   SimulateStart(false, 1, {}, {{}});
   // 1st async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({}, {{}});
+  metrics_.OnNotifyChanged({}, {{}});
   // 2nd async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({}, {{}});
+  metrics_.OnNotifyChanged({}, {{}});
   // Last async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
   controller_.done_ = true;
-  metrics_.OnUpdateResult({}, {{}});
+  metrics_.OnNotifyChanged({}, {{}});
   ExpectSingleCountSuggestionFinalizationMetrics(4, 4, 4, true);
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
@@ -200,14 +200,14 @@
   SimulateStart(false, 1, {{}}, {{}});
   // 1st async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   // 2nd async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   // Last async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
   controller_.done_ = true;
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   ExpectSingleCountSuggestionFinalizationMetrics(4, 0, 0, true);
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
@@ -219,14 +219,14 @@
   SimulateStart(false, 1, {{}}, {{}});
   // 1st async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   // 2nd async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   // Last async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
   controller_.done_ = true;
-  metrics_.OnUpdateResult({}, {{}});
+  metrics_.OnNotifyChanged({}, {{}});
   ExpectSingleCountSuggestionFinalizationMetrics(4, 4, 4, true);
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
@@ -238,14 +238,14 @@
   SimulateStart(false, 1, {{}}, {{}});
   // 1st async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({}, {{}});
+  metrics_.OnNotifyChanged({}, {{}});
   // 2nd async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   // Last async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
   controller_.done_ = true;
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   ExpectSingleCountSuggestionFinalizationMetrics(4, 2, 2, true);
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
@@ -256,14 +256,14 @@
   SimulateStart(false, 1, {}, {{}});
   // 1st async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   // 2nd async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   // Last async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
   controller_.done_ = true;
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   ExpectSingleCountSuggestionFinalizationMetrics(4, 1, 1, true);
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
@@ -277,7 +277,7 @@
   SimulateStart(false, 1, {}, {{}});
   // 1st async update.
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   // Stop timer.
   task_environment_.FastForwardBy(base::Milliseconds(1));
   metrics_.OnStop();
@@ -291,7 +291,7 @@
   SimulateStart(false, 1, {}, {{}});
   // 1 async update for 1st input.
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({}, {{}});
+  metrics_.OnNotifyChanged({}, {{}});
   ExpectNoSuggestionFinalizationMetrics();
   // Interrupted by 2nd input. The log should include the time until
   // interruption.
@@ -306,12 +306,12 @@
   // 1 async update for 3rd input. Controller completes with the 2nd update.
   ResetHistogramTester();
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({}, {{}});
+  metrics_.OnNotifyChanged({}, {{}});
   ExpectNoSuggestionFinalizationMetrics();
   // 2nd and last async update for 3rd input.
   controller_.done_ = true;
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({}, {{}});
+  metrics_.OnNotifyChanged({}, {{}});
   ExpectSingleCountSuggestionFinalizationMetrics(3, 3, 3, true);
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
@@ -322,11 +322,11 @@
   SimulateStart(false, 1, {}, {{}});
   // 1st async update with non-default match changed.
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({{}}, {{}, {}});
+  metrics_.OnNotifyChanged({{}}, {{}, {}});
   // 2nd async update with no matches changed.
   task_environment_.FastForwardBy(base::Milliseconds(1));
   controller_.done_ = true;
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   ExpectSingleCountSuggestionFinalizationMetrics(3, 2, 1, true);
   StopAndExpectNoSuggestionFinalizationMetrics();
 }
@@ -346,7 +346,7 @@
   metrics_.OnProviderUpdate(*async_provider_done_sync);
   ExpectProviderMetrics(async_provider_done_sync->GetName(), 1, true);
   controller_.done_ = false;
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   controller_.in_start_ = false;
   ExpectNoSuggestionFinalizationMetrics();
   ResetHistogramTester();
@@ -355,7 +355,7 @@
   task_environment_.FastForwardBy(base::Milliseconds(1));
   metrics_.OnProviderUpdate(*async_provider_done_not_last);
   ExpectProviderMetrics(async_provider_done_not_last->GetName(), 2, true);
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   ExpectNoSuggestionFinalizationMetrics();
   ResetHistogramTester();
 
@@ -364,7 +364,7 @@
   metrics_.OnProviderUpdate(*async_provider_done_last);
   controller_.done_ = true;
   ExpectProviderMetrics(async_provider_done_last->GetName(), 3, true);
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   ExpectSingleCountSuggestionFinalizationMetrics(3, 0, 0, true);
 
   StopAndExpectNoSuggestionFinalizationMetrics();
@@ -385,20 +385,20 @@
   metrics_.OnProviderUpdate(*provider);
   controller_.done_ = false;
   task_environment_.FastForwardBy(base::Milliseconds(1));
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
   controller_.in_start_ = false;
 
   // 1st async update without completion.
   task_environment_.FastForwardBy(base::Milliseconds(1));
   metrics_.OnProviderUpdate(*provider);
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
 
   // Last async update with completion.
   task_environment_.FastForwardBy(base::Milliseconds(1));
   provider->done_ = true;
   controller_.done_ = true;
   metrics_.OnProviderUpdate(*provider);
-  metrics_.OnUpdateResult({{}}, {{}});
+  metrics_.OnNotifyChanged({{}}, {{}});
 
   ExpectProviderMetrics(provider->GetName(), 3, true);
   ExpectSingleCountSuggestionFinalizationMetrics(3, 0, 0, true);
@@ -451,90 +451,93 @@
 
   // Verify logging to the Async* histograms.
   controller_.in_start_ = false;
-  metrics_.OnUpdateResult(first_result, second_result);
+  metrics_.OnNotifyChanged(first_result, second_result);
   // Expect the default match, third match, and last two matches to be logged
   // as changed, and nothing else.
   EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeIndex.Async"),
+                  "Omnibox.MatchStability2.MatchChangeIndex.Async"),
               testing::ElementsAre(base::Bucket(0, 1), base::Bucket(2, 1),
                                    base::Bucket(3, 1), base::Bucket(4, 1)));
   // Expect that we log that at least one of the matches has changed.
   EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeInAnyPosition.Async"),
+                  "Omnibox.MatchStability2.MatchChangeInAnyPosition.Async"),
               testing::ElementsAre(base::Bucket(1, 1)));
   // Expect that we don't log async updates to the sync histograms.
   EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeIndex.CrossInput"),
+                  "Omnibox.MatchStability2.MatchChangeIndex.CrossInput"),
               testing::ElementsAre());
-  EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeInAnyPosition.CrossInput"),
-              testing::ElementsAre());
+  EXPECT_THAT(
+      histogram_tester_->GetAllSamples(
+          "Omnibox.MatchStability2.MatchChangeInAnyPosition.CrossInput"),
+      testing::ElementsAre());
   // Verify the unsliced histograms.
   EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeIndex"),
+                  "Omnibox.MatchStability2.MatchChangeIndex"),
               testing::ElementsAre(base::Bucket(0, 1), base::Bucket(2, 1),
                                    base::Bucket(3, 1), base::Bucket(4, 1)));
   EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeInAnyPosition"),
+                  "Omnibox.MatchStability2.MatchChangeInAnyPosition"),
               testing::ElementsAre(base::Bucket(1, 1)));
   ResetHistogramTester();
 
   // Verify logging to the CrossInput* histograms.
   controller_.in_start_ = true;
-  metrics_.OnUpdateResult(first_result, second_result);
+  metrics_.OnNotifyChanged(first_result, second_result);
   // Expect the default match, third match, and last two matches to be logged
   // as changed, and nothing else.
   EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeIndex.CrossInput"),
+                  "Omnibox.MatchStability2.MatchChangeIndex.CrossInput"),
               testing::ElementsAre(base::Bucket(0, 1), base::Bucket(2, 1),
                                    base::Bucket(3, 1), base::Bucket(4, 1)));
   // Expect that we log that at least one of the matches has changed.
-  EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeInAnyPosition.CrossInput"),
-              testing::ElementsAre(base::Bucket(1, 1)));
+  EXPECT_THAT(
+      histogram_tester_->GetAllSamples(
+          "Omnibox.MatchStability2.MatchChangeInAnyPosition.CrossInput"),
+      testing::ElementsAre(base::Bucket(1, 1)));
   // Expect that we don't log sync updates to the async histograms.
   EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeIndex.Async"),
+                  "Omnibox.MatchStability2.MatchChangeIndex.Async"),
               testing::ElementsAre());
   EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeInAnyPosition.Async"),
+                  "Omnibox.MatchStability2.MatchChangeInAnyPosition.Async"),
               testing::ElementsAre());
   // Verify the unsliced histograms.
   EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeIndex"),
+                  "Omnibox.MatchStability2.MatchChangeIndex"),
               testing::ElementsAre(base::Bucket(0, 1), base::Bucket(2, 1),
                                    base::Bucket(3, 1), base::Bucket(4, 1)));
   // Expect that we log that at least one of the matches has changed.
   EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeInAnyPosition"),
+                  "Omnibox.MatchStability2.MatchChangeInAnyPosition"),
               testing::ElementsAre(base::Bucket(1, 1)));
   ResetHistogramTester();
 
   // Verify no logging when appending matches.
   controller_.in_start_ = false;
-  metrics_.OnUpdateResult(second_result, third_result);
+  metrics_.OnNotifyChanged(second_result, third_result);
   controller_.in_start_ = true;
-  metrics_.OnUpdateResult(second_result, third_result);
+  metrics_.OnNotifyChanged(second_result, third_result);
   // Expect no changes logged; expect 1 false logged to
   // *MatchChangedInAnyPosition.
   EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeIndex.Async"),
+                  "Omnibox.MatchStability2.MatchChangeIndex.Async"),
               testing::ElementsAre());
   EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeInAnyPosition.Async"),
+                  "Omnibox.MatchStability2.MatchChangeInAnyPosition.Async"),
               testing::ElementsAre(base::Bucket(0, 1)));
   EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeIndex.CrossInput"),
+                  "Omnibox.MatchStability2.MatchChangeIndex.CrossInput"),
               testing::ElementsAre());
-  EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeInAnyPosition.CrossInput"),
-              testing::ElementsAre(base::Bucket(0, 1)));
+  EXPECT_THAT(
+      histogram_tester_->GetAllSamples(
+          "Omnibox.MatchStability2.MatchChangeInAnyPosition.CrossInput"),
+      testing::ElementsAre(base::Bucket(0, 1)));
   // Verify the unsliced histograms.
   EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeIndex"),
+                  "Omnibox.MatchStability2.MatchChangeIndex"),
               testing::ElementsAre());
   EXPECT_THAT(histogram_tester_->GetAllSamples(
-                  "Omnibox.MatchStability.MatchChangeInAnyPosition"),
+                  "Omnibox.MatchStability2.MatchChangeInAnyPosition"),
               testing::ElementsAre(base::Bucket(0, 2)));
   ResetHistogramTester();
 }
diff --git a/components/omnibox/browser/in_memory_url_index_unittest.cc b/components/omnibox/browser/in_memory_url_index_unittest.cc
index 6347206..f7cdcbd 100644
--- a/components/omnibox/browser/in_memory_url_index_unittest.cc
+++ b/components/omnibox/browser/in_memory_url_index_unittest.cc
@@ -7,6 +7,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <algorithm>
 #include <fstream>
 #include <memory>
 #include <numeric>
@@ -20,7 +21,6 @@
 #include "base/i18n/case_conversion.h"
 #include "base/memory/raw_ptr.h"
 #include "base/path_service.h"
-#include "base/ranges/algorithm.h"
 #include "base/run_loop.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
@@ -723,8 +723,9 @@
 
   // Each next group should fill almost everything, while the previous group
   // should occupy what's left.
-  auto* error_position = base::ranges::adjacent_find(
-      item_groups, [&](const ItemGroup& previous, const ItemGroup& current) {
+  auto* error_position = std::adjacent_find(
+      std::begin(item_groups), std::end(item_groups),
+      [&](const ItemGroup& previous, const ItemGroup& current) {
         auto ids = GetHistoryIdsUpTo(current.max_id);
         EXPECT_TRUE(GetPrivateData()->TrimHistoryIdsPool(&ids));
 
diff --git a/components/omnibox/browser/omnibox_field_trial.cc b/components/omnibox/browser/omnibox_field_trial.cc
index f52bf17..8900cdb 100644
--- a/components/omnibox/browser/omnibox_field_trial.cc
+++ b/components/omnibox/browser/omnibox_field_trial.cc
@@ -218,20 +218,6 @@
   return base::Milliseconds(1500);
 }
 
-base::Time OmniboxFieldTrial::GetLocalHistoryZeroSuggestAgeThreshold() {
-  std::string param_value = base::GetFieldTrialParamValueByFeature(
-      omnibox::kOmniboxLocalZeroSuggestAgeThreshold,
-      OmniboxFieldTrial::kOmniboxLocalZeroSuggestAgeThresholdParam);
-
-  // If the field trial param is not found or cannot be parsed to an unsigned
-  // integer, return the default value.
-  unsigned int param_value_as_int = 0;
-  if (!base::StringToUint(param_value, &param_value_as_int)) {
-    param_value_as_int = 60;
-  }
-  return (base::Time::Now() - base::Days(param_value_as_int));
-}
-
 bool OmniboxFieldTrial::ShortcutsScoringMaxRelevance(
     OmniboxEventProto::PageClassification current_page_classification,
     int* max_relevance) {
@@ -717,9 +703,6 @@
     OmniboxFieldTrial::kMaxNumHQPUrlsIndexedAtStartupOnNonLowEndDevicesParam[] =
         "MaxNumHQPUrlsIndexedAtStartupOnNonLowEndDevices";
 
-const char OmniboxFieldTrial::kOmniboxLocalZeroSuggestAgeThresholdParam[] =
-    "OmniboxLocalZeroSuggestAgeThreshold";
-
 const char OmniboxFieldTrial::kMaxZeroSuggestMatchesParam[] =
     "MaxZeroSuggestMatches";
 const char OmniboxFieldTrial::kOmniboxMaxURLMatchesParam[] =
@@ -822,6 +805,21 @@
   }
 }
 
+// Determines the age threshold in days for local zero-prefix suggestions.
+const base::FeatureParam<int> kOmniboxLocalZeroSuggestAgeThresholdParam(
+    &omnibox::kOmniboxLocalZeroSuggestAgeThreshold,
+    "OmniboxLocalZeroSuggestAgeThreshold",
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+    60);
+#else
+    90);
+#endif
+
+base::Time GetLocalHistoryZeroSuggestAgeThreshold() {
+  return (base::Time::Now() -
+          base::Days(kOmniboxLocalZeroSuggestAgeThresholdParam.Get()));
+}
+
 const base::FeatureParam<bool> kZeroSuggestIgnoreDuplicateVisits(
     &omnibox::kLocalHistorySuggestRevamp,
     "ZeroSuggestIgnoreDuplicateVisits",
@@ -829,7 +827,11 @@
 const base::FeatureParam<bool> kPrefixSuggestIgnoreDuplicateVisits(
     &omnibox::kLocalHistorySuggestRevamp,
     "PrefixSuggestIgnoreDuplicateVisits",
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
     false);
+#else
+    true);
+#endif
 
 // Short bookmarks.
 
diff --git a/components/omnibox/browser/omnibox_field_trial.h b/components/omnibox/browser/omnibox_field_trial.h
index 530fb59..5eb9e0f 100644
--- a/components/omnibox/browser/omnibox_field_trial.h
+++ b/components/omnibox/browser/omnibox_field_trial.h
@@ -149,13 +149,6 @@
 base::TimeDelta StopTimerFieldTrialDuration();
 
 // ---------------------------------------------------------
-// For the OmniboxLocalZeroSuggestAgeThreshold field trial.
-
-// Returns the age threshold since the last visit in order to consider a
-// normalized keyword search term as a zero-prefix suggestion.
-base::Time GetLocalHistoryZeroSuggestAgeThreshold();
-
-// ---------------------------------------------------------
 // For the ShortcutsScoringMaxRelevance experiment that's part of the
 // bundled omnibox field trial.
 
@@ -444,11 +437,6 @@
 extern const char kMaxNumHQPUrlsIndexedAtStartupOnLowEndDevicesParam[];
 extern const char kMaxNumHQPUrlsIndexedAtStartupOnNonLowEndDevicesParam[];
 
-// Parameter name determining the age threshold for local zero-prefix
-// suggestions. The value of this parameter should be parsable as an unsigned
-// integer, which will be used to specify the age threshold in days.
-extern const char kOmniboxLocalZeroSuggestAgeThresholdParam[];
-
 // Parameter names used by num suggestion experiments.
 extern const char kMaxZeroSuggestMatchesParam[];
 extern const char kOmniboxMaxURLMatchesParam[];
@@ -540,6 +528,13 @@
 bool IsZeroSuggestPrefetchingEnabledInContext(
     metrics::OmniboxEventProto::PageClassification page_classification);
 
+// Determines the age threshold in days for local zero-prefix suggestions.
+extern const base::FeatureParam<int> kOmniboxLocalZeroSuggestAgeThresholdParam;
+
+// Returns the age threshold since the last visit in order to consider a
+// normalized keyword search term as a zero-prefix suggestion.
+base::Time GetLocalHistoryZeroSuggestAgeThreshold();
+
 // Whether duplicative visits should be ignored for local history zero-suggest.
 // A duplicative visit is a visit to the same search term in an interval smaller
 // than kAutocompleteDuplicateVisitIntervalThreshold.
diff --git a/components/omnibox/browser/omnibox_field_trial_unittest.cc b/components/omnibox/browser/omnibox_field_trial_unittest.cc
index 71fdf1d..8dd7f0b 100644
--- a/components/omnibox/browser/omnibox_field_trial_unittest.cc
+++ b/components/omnibox/browser/omnibox_field_trial_unittest.cc
@@ -357,24 +357,24 @@
 TEST_F(OmniboxFieldTrialTest, LocalZeroSuggestAgeThreshold) {
   base::test::ScopedFeatureList scoped_feature_list_;
 
+  // Verify the default value.
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+  const int expected_age_threshold_days = 60;
+#else
+  const int expected_age_threshold_days = 90;
+#endif
+  base::Time age_threshold =
+      OmniboxFieldTrial::GetLocalHistoryZeroSuggestAgeThreshold();
+  EXPECT_EQ(expected_age_threshold_days,
+            base::TimeDelta(base::Time::Now() - age_threshold).InDays());
+
   // The default value can be overridden.
   scoped_feature_list_.InitAndEnableFeatureWithParameters(
       omnibox::kOmniboxLocalZeroSuggestAgeThreshold,
-      {{OmniboxFieldTrial::kOmniboxLocalZeroSuggestAgeThresholdParam, "3"}});
-  base::Time age_threshold =
-      OmniboxFieldTrial::GetLocalHistoryZeroSuggestAgeThreshold();
-  EXPECT_EQ(3, base::TimeDelta(base::Time::Now() - age_threshold).InDays());
-
-  // If the age threshold is not parsable to an unsigned integer, the default
-  // value is used.
-  scoped_feature_list_.Reset();
-  scoped_feature_list_.InitAndEnableFeatureWithParameters(
-      omnibox::kOmniboxLocalZeroSuggestAgeThreshold,
-      {{OmniboxFieldTrial::kOmniboxLocalZeroSuggestAgeThresholdParam, "j"}});
-  const int expected_age_threshold_days = 60;
+      {{OmniboxFieldTrial::kOmniboxLocalZeroSuggestAgeThresholdParam.name,
+        "3"}});
   age_threshold = OmniboxFieldTrial::GetLocalHistoryZeroSuggestAgeThreshold();
-  EXPECT_EQ(expected_age_threshold_days,
-            base::TimeDelta(base::Time::Now() - age_threshold).InDays());
+  EXPECT_EQ(3, base::TimeDelta(base::Time::Now() - age_threshold).InDays());
 }
 
 TEST_F(OmniboxFieldTrialTest, HUPNewScoringFieldTrial) {
diff --git a/components/omnibox/browser/scored_history_match.cc b/components/omnibox/browser/scored_history_match.cc
index bb2c625f..f853e91d 100644
--- a/components/omnibox/browser/scored_history_match.cc
+++ b/components/omnibox/browser/scored_history_match.cc
@@ -6,13 +6,13 @@
 
 #include <math.h>
 
+#include <algorithm>
 #include <utility>
 #include <vector>
 
 #include "base/check_op.h"
 #include "base/no_destructor.h"
 #include "base/numerics/safe_conversions.h"
-#include "base/ranges/algorithm.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
@@ -634,8 +634,11 @@
   auto visits_end =
       visits.begin() + std::min(visits.size(), max_visits_to_score_);
   // Visits should be in newest to oldest order.
-  DCHECK(base::ranges::adjacent_find(visits.begin(), visits_end, std::less<>(),
-                                     &history::VisitInfo::first) == visits_end);
+  DCHECK(std::adjacent_find(
+             visits.begin(), visits_end,
+             [](const history::VisitInfo& a, const history::VisitInfo& b) {
+               return a.first < b.first;
+             }) == visits_end);
   for (auto i = visits.begin(); i != visits_end; ++i) {
     const bool is_page_transition_typed =
         ui::PageTransitionCoreTypeIs(i->second, ui::PAGE_TRANSITION_TYPED);
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index 422e38c9..7195553 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -187,7 +187,7 @@
 // zero-prefix and prefix suggestions.
 BASE_FEATURE(kLocalHistorySuggestRevamp,
              "LocalHistorySuggestRevamp",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             enabled_by_default_desktop_only);
 
 // Enables local history zero-prefix suggestions in every context in which the
 // remote zero-prefix suggestions are enabled.
@@ -197,12 +197,12 @@
 
 // Used to adjust the age threshold since the last visit in order to consider a
 // normalized keyword search term as a zero-prefix suggestion. If disabled, the
-// default value of 60 days for Desktop and 7 days for Android and iOS is used.
+// default value of 90 days for Desktop and 60 days for Android and iOS is used.
 // If enabled, the age threshold is determined by this feature's companion
 // parameter, OmniboxFieldTrial::kOmniboxLocalZeroSuggestAgeThresholdParam.
 BASE_FEATURE(kOmniboxLocalZeroSuggestAgeThreshold,
              "OmniboxLocalZeroSuggestAgeThreshold",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             enabled_by_default_desktop_only);
 
 // Mainly used to enable sending INTERACTION_CLOBBER focus type for zero-prefix
 // requests with an empty input on Web/SRP on Mobile. Enabled by default on
diff --git a/components/password_manager/core/browser/generation/password_generator.cc b/components/password_manager/core/browser/generation/password_generator.cc
index d6544e0f..5aec52d 100644
--- a/components/password_manager/core/browser/generation/password_generator.cc
+++ b/components/password_manager/core/browser/generation/password_generator.cc
@@ -4,6 +4,7 @@
 
 #include "components/password_manager/core/browser/generation/password_generator.h"
 
+#include <algorithm>
 #include <limits>
 #include <map>
 #include <utility>
@@ -11,7 +12,6 @@
 
 #include "base/check.h"
 #include "base/rand_util.h"
-#include "base/ranges/algorithm.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/autofill/core/browser/proto/password_requirements.pb.h"
 
@@ -80,9 +80,10 @@
 // sequences of '-' or '_' that are joined into long strokes on the screen
 // in many fonts.
 bool IsDifficultToRead(const std::u16string& password) {
-  return base::ranges::adjacent_find(password, [](auto a, auto b) {
-           return a == b && (a == '-' || a == '_');
-         }) != password.end();
+  return std::adjacent_find(password.begin(), password.end(),
+                            [](auto a, auto b) {
+                              return a == b && (a == '-' || a == '_');
+                            }) != password.end();
 }
 
 // Generates a password according to |spec| and tries to maximze the entropy
diff --git a/components/performance_manager/v8_memory/v8_context_tracker_internal.cc b/components/performance_manager/v8_memory/v8_context_tracker_internal.cc
index 043b8af4..f4d7acd 100644
--- a/components/performance_manager/v8_memory/v8_context_tracker_internal.cc
+++ b/components/performance_manager/v8_memory/v8_context_tracker_internal.cc
@@ -135,7 +135,7 @@
             static_cast<bool>(description.execution_context_token));
   if (execution_context_data) {
     DCHECK_EQ(execution_context_data->GetToken(),
-              description.execution_context_token.value());
+              *description.execution_context_token);
 
     // These must be same process.
     DCHECK_EQ(process_data, execution_context_data->process_data());
diff --git a/components/translate/core/browser/translate_language_list.cc b/components/translate/core/browser/translate_language_list.cc
index 1e1753e..088cc236 100644
--- a/components/translate/core/browser/translate_language_list.cc
+++ b/components/translate/core/browser/translate_language_list.cc
@@ -6,6 +6,7 @@
 
 #include <stddef.h>
 
+#include <algorithm>
 #include <iterator>
 
 #include "base/bind.h"
@@ -13,7 +14,6 @@
 #include "base/json/json_reader.h"
 #include "base/lazy_instance.h"
 #include "base/notreached.h"
-#include "base/ranges/algorithm.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/values.h"
@@ -185,7 +185,8 @@
   DCHECK(
       std::is_sorted(supported_languages_.begin(), supported_languages_.end()));
   DCHECK(supported_languages_.end() ==
-         base::ranges::adjacent_find(supported_languages_));
+         std::adjacent_find(supported_languages_.begin(),
+                            supported_languages_.end()));
 
   if (update_is_disabled)
     return;
@@ -361,7 +362,8 @@
   DCHECK(
       std::is_sorted(supported_languages_.begin(), supported_languages_.end()));
   DCHECK(supported_languages_.end() ==
-         base::ranges::adjacent_find(supported_languages_));
+         std::adjacent_find(supported_languages_.begin(),
+                            supported_languages_.end()));
 
   NotifyEvent(__LINE__, base::JoinString(supported_languages_, ", "));
   return true;
diff --git a/components/url_rewrite/common/url_loader_throttle.cc b/components/url_rewrite/common/url_loader_throttle.cc
index 24889d46..09aff972 100644
--- a/components/url_rewrite/common/url_loader_throttle.cc
+++ b/components/url_rewrite/common/url_loader_throttle.cc
@@ -200,19 +200,12 @@
 
 void URLLoaderThrottle::ApplyRule(network::ResourceRequest* request,
                                   const mojom::UrlRequestRulePtr& rule) {
-  if (!RuleFiltersMatchUrl(request->url, rule))
+  // Prevent applying rules on redirect navigations.
+  if (request->navigation_redirect_chain.size() > 1u)
     return;
 
-  // Prevent applying rules more than once when redirected.
-  for (const auto& url : request->navigation_redirect_chain) {
-    // Last element in redirect chain is the current navigation.
-    if (&url == &request->navigation_redirect_chain.back()) {
-      continue;
-    }
-    if (RuleFiltersMatchUrl(url, rule)) {
-      return;
-    }
-  }
+  if (!RuleFiltersMatchUrl(request->url, rule))
+    return;
 
   for (const auto& rewrite : rule->actions)
     ApplyRewrite(request, rewrite);
diff --git a/components/url_rewrite/common/url_loader_throttle_unittest.cc b/components/url_rewrite/common/url_loader_throttle_unittest.cc
index 9e677fbc..d1d706d 100644
--- a/components/url_rewrite/common/url_loader_throttle_unittest.cc
+++ b/components/url_rewrite/common/url_loader_throttle_unittest.cc
@@ -35,7 +35,6 @@
       std::vector<std::string> cors_exempt_headers) {
     return base::BindLambdaForTesting(
         [cors_exempt_headers](base::StringPiece header) {
-          LOG(INFO) << "HEADER: " << header;
           for (const auto& exempt_header : cors_exempt_headers) {
             if (base::EqualsCaseInsensitiveASCII(header, exempt_header)) {
               return true;
@@ -212,12 +211,11 @@
   EXPECT_EQ(request.url, GURL(kUrlWithQueryString));
 }
 
-// Tests URL replacement rules applies when redirecting from a different host.
+// Tests URL replacement rules do not apply when redirecting.
 TEST_F(URLLoaderThrottleTest, RedirectsFromDifferentHost) {
   constexpr char kAppendQueryString[] = "foo=1&bar=2";
   constexpr char kBaseUrl1[] = "http://a.com";
   constexpr char kBaseUrl2[] = "http://b.com";
-  constexpr char kUrl2WithQueryString[] = "http://b.com?foo=1&bar=2";
 
   mojom::UrlRequestRewriteAppendToQueryPtr append_query =
       mojom::UrlRequestRewriteAppendToQuery::New(kAppendQueryString);
@@ -246,7 +244,7 @@
   request.url = GURL(kBaseUrl2);
   request.navigation_redirect_chain = {GURL(kBaseUrl1), GURL(kBaseUrl2)};
   throttle.WillStartRequest(&request, &defer);
-  EXPECT_EQ(request.url, GURL(kUrl2WithQueryString));
+  EXPECT_EQ(request.url, GURL(kBaseUrl2));
 }
 
 // Tests URL replacement rules do not apply more than once when redirecting to a
@@ -278,7 +276,6 @@
 
   network::ResourceRequest request;
   request.url = GURL(kBaseUrl1);
-  request.navigation_redirect_chain = {GURL(kBaseUrl1)};
   throttle.WillStartRequest(&request, &defer);
   EXPECT_EQ(request.url, GURL(kUrl1WithQueryString));
 
@@ -288,50 +285,6 @@
   EXPECT_EQ(request.url, GURL(kBaseUrl2));
 }
 
-// Tests URL replacement rules do not apply more than once when redirecting to a
-// different host then back to the same host.
-TEST_F(URLLoaderThrottleTest, RedirectsToDifferentHostThenBack) {
-  constexpr char kAppendQueryString[] = "foo=1&bar=2";
-  constexpr char kBaseUrl1[] = "http://a.com";
-  constexpr char kBaseUrl2[] = "http://b.com";
-  constexpr char kUrl1WithQueryString[] = "http://a.com?foo=1&bar=2";
-
-  mojom::UrlRequestRewriteAppendToQueryPtr append_query =
-      mojom::UrlRequestRewriteAppendToQuery::New(kAppendQueryString);
-  std::vector<mojom::UrlRequestActionPtr> actions;
-  actions.push_back(
-      mojom::UrlRequestAction::NewAppendToQuery(std::move(append_query)));
-
-  mojom::UrlRequestRulePtr rule = mojom::UrlRequestRule::New();
-  rule->hosts_filter = absl::optional<std::vector<std::string>>({"*.a.com"});
-  rule->actions = std::move(actions);
-
-  mojom::UrlRequestRewriteRulesPtr rules = mojom::UrlRequestRewriteRules::New();
-  rules->rules.push_back(std::move(rule));
-
-  URLLoaderThrottle throttle(
-      base::MakeRefCounted<UrlRequestRewriteRules>(std::move(rules)),
-      CreateCorsExemptHeadersCallback({}));
-  bool defer = false;
-
-  network::ResourceRequest request;
-  request.url = GURL(kBaseUrl1);
-  request.navigation_redirect_chain = {GURL(kBaseUrl1)};
-  throttle.WillStartRequest(&request, &defer);
-  EXPECT_EQ(request.url, GURL(kUrl1WithQueryString));
-
-  request.url = GURL(kBaseUrl2);
-  request.navigation_redirect_chain = {GURL(kBaseUrl1), GURL(kBaseUrl2)};
-  throttle.WillStartRequest(&request, &defer);
-  EXPECT_EQ(request.url, GURL(kBaseUrl2));
-
-  request.url = GURL(kBaseUrl1);
-  request.navigation_redirect_chain = {GURL(kBaseUrl1), GURL(kBaseUrl2),
-                                       GURL(kBaseUrl1)};
-  throttle.WillStartRequest(&request, &defer);
-  EXPECT_EQ(request.url, GURL(kBaseUrl1));
-}
-
 class TestThrottleDelegate : public blink::URLLoaderThrottle::Delegate {
  public:
   TestThrottleDelegate() = default;
diff --git a/components/viz/common/quads/render_pass_io.cc b/components/viz/common/quads/render_pass_io.cc
index c0d34bf..7e58782 100644
--- a/components/viz/common/quads/render_pass_io.cc
+++ b/components/viz/common/quads/render_pass_io.cc
@@ -242,21 +242,19 @@
   return list;
 }
 
-bool FloatArrayFromList(const base::Value& list,
+bool FloatArrayFromList(const base::Value::List& list,
                         size_t expected_count,
                         float* data) {
   DCHECK(data);
   DCHECK_LT(0u, expected_count);
-  if (!list.is_list())
-    return false;
-  size_t count = list.GetList().size();
+  size_t count = list.size();
   if (count != expected_count)
     return false;
   std::vector<double> double_data(count);
   for (size_t ii = 0; ii < count; ++ii) {
-    if (!list.GetList()[ii].is_double())
+    if (!list[ii].is_double())
       return false;
-    double_data[ii] = list.GetList()[ii].GetDouble();
+    double_data[ii] = list[ii].GetDouble();
   }
   for (size_t ii = 0; ii < count; ++ii)
     data[ii] = static_cast<float>(double_data[ii]);
@@ -604,7 +602,7 @@
   const base::Value* drop_shadow_offset =
       dict.FindDictKey("drop_shadow_offset");
   const std::string* image_filter = dict.FindStringKey("image_filter");
-  const base::Value* matrix = dict.FindListKey("matrix");
+  const base::Value::List* matrix = dict.GetDict().FindList("matrix");
   absl::optional<int> zoom_inset = dict.FindIntKey("zoom_inset");
   const base::Value* shape = dict.FindListKey("shape");
   absl::optional<int> blur_tile_mode = dict.FindIntKey("blur_tile_mode");
@@ -868,7 +866,7 @@
   return FloatArrayToList(data);
 }
 
-bool Matrix3x3FromList(const base::Value& list, skcms_Matrix3x3* mat) {
+bool Matrix3x3FromList(const base::Value::List& list, skcms_Matrix3x3* mat) {
   DCHECK(mat);
   return FloatArrayFromList(list, 9u, reinterpret_cast<float*>(mat->vals));
 }
@@ -885,7 +883,7 @@
   return FloatArrayToList(data);
 }
 
-bool TransferFunctionFromList(const base::Value& list,
+bool TransferFunctionFromList(const base::Value::List& list,
                               skcms_TransferFunction* fn) {
   DCHECK(fn);
   float data[7];
@@ -945,8 +943,8 @@
   bool uses_custom_primary_matrix =
       primary_id == static_cast<uint8_t>(gfx::ColorSpace::PrimaryID::CUSTOM);
   if (uses_custom_primary_matrix) {
-    const base::Value* custom_primary_matrix =
-        dict.FindListKey("custom_primary_matrix");
+    const base::Value::List* custom_primary_matrix =
+        dict.GetDict().FindList("custom_primary_matrix");
     if (!custom_primary_matrix ||
         !Matrix3x3FromList(*custom_primary_matrix, &t_custom_primary_matrix)) {
       return false;
@@ -959,8 +957,8 @@
       transfer_id ==
           static_cast<uint8_t>(gfx::ColorSpace::TransferID::CUSTOM_HDR);
   if (uses_custom_transfer_params) {
-    const base::Value* custom_transfer_params =
-        dict.FindListKey("custom_transfer_params");
+    const base::Value::List* custom_transfer_params =
+        dict.GetDict().FindList("custom_transfer_params");
     if (!custom_transfer_params ||
         !TransferFunctionFromList(*custom_transfer_params,
                                   &t_custom_transfer_params)) {
@@ -1514,7 +1512,7 @@
       dict.FindBool("premultiplied_alpha");
   const base::Value::Dict* uv_top_left = dict.FindDict("uv_top_left");
   const base::Value::Dict* uv_bottom_right = dict.FindDict("uv_bottom_right");
-  const base::Value* vertex_opacity = dict_value.FindListKey("vertex_opacity");
+  const base::Value::List* vertex_opacity = dict.FindList("vertex_opacity");
   const base::Value::Dict* damage_rect = dict.FindDict("damage_rect");
   absl::optional<bool> y_flipped = dict.FindBool("y_flipped");
   absl::optional<bool> nearest_neighbor = dict.FindBool("nearest_neighbor");
diff --git a/components/viz/service/display_embedder/output_presenter_gl.cc b/components/viz/service/display_embedder/output_presenter_gl.cc
index 5da24c2..cfda8221 100644
--- a/components/viz/service/display_embedder/output_presenter_gl.cc
+++ b/components/viz/service/display_embedder/output_presenter_gl.cc
@@ -60,7 +60,7 @@
   int GetPresentCount() const final;
   void OnContextLost() final;
 
-  gl::GLImage* GetGLImage(std::unique_ptr<gfx::GpuFence>* fence);
+  gl::OverlayImage GetOverlayImage(std::unique_ptr<gfx::GpuFence>* fence);
 
   const gfx::ColorSpace& color_space() {
     DCHECK(overlay_representation_);
@@ -102,13 +102,17 @@
     overlay_representation_->OnContextLost();
 }
 
-gl::GLImage* PresenterImageGL::GetGLImage(
+gl::OverlayImage PresenterImageGL::GetOverlayImage(
     std::unique_ptr<gfx::GpuFence>* fence) {
   DCHECK(scoped_overlay_read_access_);
   if (fence) {
     *fence = TakeGpuFence(scoped_overlay_read_access_->TakeAcquireFence());
   }
+#if defined(USE_OZONE)
+  return scoped_overlay_read_access_->GetNativePixmap();
+#else
   return scoped_overlay_read_access_->gl_image();
+#endif
 }
 
 }  // namespace
@@ -319,7 +323,7 @@
   std::unique_ptr<gfx::GpuFence> fence;
   auto* presenter_image = static_cast<PresenterImageGL*>(image);
   // If the submitted_image() is being scheduled, we don't new a new fence.
-  auto* gl_image = presenter_image->GetGLImage(
+  gl::OverlayImage overlay_image = presenter_image->GetOverlayImage(
       (is_submitted || !gl_surface_->SupportsPlaneGpuFences()) ? nullptr
                                                                : &fence);
 
@@ -330,7 +334,7 @@
   // overlays, damage should be added to OutputSurfaceOverlayPlane and passed in
   // here.
   gl_surface_->ScheduleOverlayPlane(
-      gl_image, std::move(fence),
+      std::move(overlay_image), std::move(fence),
       gfx::OverlayPlaneData(
           kPlaneZOrder, plane.transform, plane.display_rect, plane.uv_rect,
           plane.enable_blending,
@@ -359,12 +363,17 @@
     const OutputPresenter::OverlayPlaneCandidate& overlay_plane_candidate,
     ScopedOverlayAccess* access,
     std::unique_ptr<gfx::GpuFence> acquire_fence) {
-#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_APPLE) || defined(USE_OZONE)
   // Note that |overlay_plane_candidate| has different types on different
   // platforms. On Android and Ozone it is an OverlayCandidate, on Windows it is
   // a DCLayerOverlay, and on macOS it is a CALayeroverlay.
-  auto* gl_image = access ? access->gl_image() : nullptr;
 #if BUILDFLAG(IS_ANDROID) || defined(USE_OZONE)
+#if defined(USE_OZONE)
+  // TODO(crbug.com/1366808): Add ScopedOverlayAccess::GetOverlayImage() that
+  // works on all platforms.
+  gl::OverlayImage overlay_image = access ? access->GetNativePixmap() : nullptr;
+#else
+  auto* overlay_image = access ? access->gl_image() : nullptr;
+#endif
   // TODO(msisov): Once shared image factory allows creating a non backed
   // images and ScheduleOverlayPlane does not rely on GLImage, remove the if
   // condition that checks if this is a solid color overlay plane.
@@ -374,7 +383,7 @@
   // may have a protocol that asks Wayland compositor to create a solid color
   // buffer for a client. OverlayProcessorDelegated decides if a solid color
   // overlay is an overlay candidate and should be scheduled.
-  if (gl_image || overlay_plane_candidate.is_solid_color) {
+  if (overlay_image || overlay_plane_candidate.is_solid_color) {
 #if DCHECK_IS_ON()
     if (overlay_plane_candidate.is_solid_color) {
       LOG_IF(FATAL, !overlay_plane_candidate.color.has_value())
@@ -400,7 +409,7 @@
     }
 
     gl_surface_->ScheduleOverlayPlane(
-        gl_image, std::move(acquire_fence),
+        std::move(overlay_image), std::move(acquire_fence),
         gfx::OverlayPlaneData(
             overlay_plane_candidate.plane_z_order,
             absl::get<gfx::OverlayTransform>(overlay_plane_candidate.transform),
@@ -416,6 +425,7 @@
             overlay_plane_candidate.clip_rect));
   }
 #elif BUILDFLAG(IS_APPLE)
+  auto* gl_image = access ? access->gl_image() : nullptr;
   gl_surface_->ScheduleCALayer(ui::CARendererLayerParams(
       overlay_plane_candidate.shared_state->is_clipped,
       gfx::ToEnclosingRect(overlay_plane_candidate.shared_state->clip_rect),
@@ -429,7 +439,6 @@
       overlay_plane_candidate.filter, overlay_plane_candidate.hdr_metadata,
       overlay_plane_candidate.protected_video_type));
 #endif
-#endif  //  BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_APPLE) || defined(USE_OZONE)
 }
 
 #if BUILDFLAG(IS_MAC)
diff --git a/components/viz/service/display_embedder/skia_output_device_buffer_queue.cc b/components/viz/service/display_embedder/skia_output_device_buffer_queue.cc
index 3bdebb50..9842b32 100644
--- a/components/viz/service/display_embedder/skia_output_device_buffer_queue.cc
+++ b/components/viz/service/display_embedder/skia_output_device_buffer_queue.cc
@@ -87,12 +87,16 @@
   }
 
   bool IsInUseByWindowServer() const {
+#if BUILDFLAG(IS_MAC)
     if (!scoped_read_access_)
       return false;
     auto* gl_image = scoped_read_access_->gl_image();
     if (!gl_image)
       return false;
     return gl_image->IsInUseByWindowServer();
+#else
+    return false;
+#endif
   }
 
   void Ref() { ++ref_; }
@@ -331,12 +335,11 @@
     return nullptr;
   }
 
-  // Fuchsia does not provide a GLImage overlay.
-#if BUILDFLAG(IS_FUCHSIA)
+#if defined(IS_OZONE)
   const bool needs_gl_image = false;
 #else
   const bool needs_gl_image = true;
-#endif  // BUILDFLAG(IS_FUCHSIA)
+#endif  // defined(IS_OZONE)
 
   // TODO(penghuang): do not depend on GLImage.
   auto shared_image_access =
diff --git a/components/viz/service/display_embedder/skia_output_device_buffer_queue_unittest.cc b/components/viz/service/display_embedder/skia_output_device_buffer_queue_unittest.cc
index 7e55967..886e9cb 100644
--- a/components/viz/service/display_embedder/skia_output_device_buffer_queue_unittest.cc
+++ b/components/viz/service/display_embedder/skia_output_device_buffer_queue_unittest.cc
@@ -219,7 +219,7 @@
   }
 
   bool ScheduleOverlayPlane(
-      gl::GLImage* image,
+      gl::OverlayImage image,
       std::unique_ptr<gfx::GpuFence> gpu_fence,
       const gfx::OverlayPlaneData& overlay_plane_data) override {
     return true;
diff --git a/components/zucchini/imposed_ensemble_matcher.cc b/components/zucchini/imposed_ensemble_matcher.cc
index 09c09f0..36afad6 100644
--- a/components/zucchini/imposed_ensemble_matcher.cc
+++ b/components/zucchini/imposed_ensemble_matcher.cc
@@ -4,12 +4,12 @@
 
 #include "components/zucchini/imposed_ensemble_matcher.h"
 
+#include <algorithm>
 #include <sstream>
 #include <utility>
 
 #include "base/bind.h"
 #include "base/logging.h"
-#include "base/ranges/algorithm.h"
 #include "components/zucchini/io_utils.h"
 
 namespace zucchini {
@@ -67,8 +67,9 @@
             });
 
   // Check for overlaps in "new" file.
-  if (base::ranges::adjacent_find(
-          matches_, [](const ElementMatch& match1, const ElementMatch& match2) {
+  if (std::adjacent_find(
+          matches_.begin(), matches_.end(),
+          [](const ElementMatch& match1, const ElementMatch& match2) {
             return match1.new_element.hi() > match2.new_element.lo();
           }) != matches_.end()) {
     return kOverlapInNew;
diff --git a/content/browser/android/navigation_handle_proxy.cc b/content/browser/android/navigation_handle_proxy.cc
index 4cdec4a3..60cfc74 100644
--- a/content/browser/android/navigation_handle_proxy.cc
+++ b/content/browser/android/navigation_handle_proxy.cc
@@ -104,20 +104,4 @@
   Java_NavigationHandle_release(env, java_navigation_handle_);
 }
 
-// Called from Java.
-void NavigationHandleProxy::SetRequestHeader(
-    JNIEnv* env,
-    const JavaParamRef<jstring>& name,
-    const JavaParamRef<jstring>& value) {
-  cpp_navigation_handle_->SetRequestHeader(ConvertJavaStringToUTF8(name),
-                                           ConvertJavaStringToUTF8(value));
-}
-
-// Called from Java.
-void NavigationHandleProxy::RemoveRequestHeader(
-    JNIEnv* env,
-    const JavaParamRef<jstring>& name) {
-  cpp_navigation_handle_->RemoveRequestHeader(ConvertJavaStringToUTF8(name));
-}
-
 }  // namespace content
diff --git a/content/browser/android/navigation_handle_proxy.h b/content/browser/android/navigation_handle_proxy.h
index 1d0894e..ad8cd4e 100644
--- a/content/browser/android/navigation_handle_proxy.h
+++ b/content/browser/android/navigation_handle_proxy.h
@@ -35,15 +35,6 @@
   void DidRedirect();
   void DidFinish();
 
-  // Called from Java.
-  void SetRequestHeader(JNIEnv* env,
-                        const base::android::JavaParamRef<jstring>& name,
-                        const base::android::JavaParamRef<jstring>& value);
-
-  // Called from Java.
-  void RemoveRequestHeader(JNIEnv* env,
-                           const base::android::JavaParamRef<jstring>& name);
-
  private:
   base::android::ScopedJavaGlobalRef<jobject> java_navigation_handle_;
   raw_ptr<NavigationHandle> cpp_navigation_handle_ = nullptr;
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index d8cd60b..860e005 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -18,7 +18,6 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
 #include "base/path_service.h"
-#include "base/ranges/algorithm.h"
 #include "base/run_loop.h"
 #include "base/strings/pattern.h"
 #include "base/strings/stringprintf.h"
@@ -1005,8 +1004,8 @@
 
   const std::vector<double>& progresses = delegate->progresses;
   // All updates should be in order ...
-  if (base::ranges::adjacent_find(progresses, std::greater<>()) !=
-      progresses.end()) {
+  if (std::adjacent_find(progresses.begin(), progresses.end(),
+                         std::greater<>()) != progresses.end()) {
     ADD_FAILURE() << "Progress values should be in order: "
                   << ::testing::PrintToString(progresses);
   }
@@ -1026,8 +1025,8 @@
 
   const std::vector<double>& progresses = delegate->progresses;
   // All updates should be in order ...
-  if (base::ranges::adjacent_find(progresses, std::greater<>()) !=
-      progresses.end()) {
+  if (std::adjacent_find(progresses.begin(), progresses.end(),
+                         std::greater<>()) != progresses.end()) {
     ADD_FAILURE() << "Progress values should be in order: "
                   << ::testing::PrintToString(progresses);
   }
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 0ab9f47..42e9767f 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -300,6 +300,10 @@
       url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS));
 }
 
+std::string FormatOriginForDisplay(const url::Origin& origin) {
+  return FormatUrlForDisplay(origin.GetURL());
+}
+
 bool ShouldFailIfNotSignedInWithIdp(
     const GURL& idp_url,
     FederatedIdentitySharingPermissionContextDelegate*
@@ -837,8 +841,7 @@
   if (!pending_idps_.empty())
     return;
 
-  std::string rp_url_for_display =
-      FormatUrlForDisplay(rp_web_contents->GetLastCommittedURL());
+  std::string rp_url_for_display = FormatOriginForDisplay(GetEmbeddingOrigin());
 
   std::vector<IdentityProviderData> idp_data_for_display;
   for (const auto& idp : idp_order_) {
@@ -848,8 +851,7 @@
 
   absl::optional<std::string> iframe_url_for_display = absl::nullopt;
   if (IsFedCmIframeSupportEnabled() && show_iframe_requester_) {
-    iframe_url_for_display =
-        FormatUrlForDisplay(render_frame_host().GetLastCommittedURL());
+    iframe_url_for_display = FormatOriginForDisplay(origin());
   }
 
   request_dialog_controller_->ShowAccountsDialog(
@@ -892,10 +894,8 @@
   DCHECK(render_frame_host().GetMainFrame()->IsInPrimaryMainFrame());
 
   request_dialog_controller_->ShowFailureDialog(
-      rp_web_contents,
-      FormatUrlForDisplay(rp_web_contents->GetLastCommittedURL()),
-      FormatUrlForDisplay(idp_url),
-      FormatUrlForDisplay(render_frame_host().GetLastCommittedURL()),
+      rp_web_contents, FormatOriginForDisplay(GetEmbeddingOrigin()),
+      FormatUrlForDisplay(idp_url), FormatOriginForDisplay(origin()),
       base::BindOnce(
           &FederatedAuthRequestImpl::OnDismissFailureDialog,
           weak_ptr_factory_.GetWeakPtr(), FederatedAuthRequestResult::kError,
diff --git a/content/browser/webui/shared_resources_data_source.cc b/content/browser/webui/shared_resources_data_source.cc
index 3bdad44..e98656f7 100644
--- a/content/browser/webui/shared_resources_data_source.cc
+++ b/content/browser/webui/shared_resources_data_source.cc
@@ -32,22 +32,16 @@
 namespace {
 
 const std::set<int> GetContentResourceIds() {
-  return std::set<int>{
-      IDR_GEOMETRY_MOJOM_WEBUI_JS,
-      IDR_IMAGE_MOJOM_WEBUI_JS,
-      IDR_ORIGIN_MOJO_WEBUI_JS,
-      IDR_RANGE_MOJOM_WEBUI_JS,
-      IDR_TOKEN_MOJO_WEBUI_JS,
-      IDR_UI_WINDOW_OPEN_DISPOSITION_MOJO_WEBUI_JS,
-      IDR_URL_MOJOM_WEBUI_JS,
-      IDR_VULKAN_INFO_MOJO_JS,
-      IDR_VULKAN_TYPES_MOJO_JS,
+  return std::set<int> {
+    IDR_GEOMETRY_MOJOM_WEBUI_JS, IDR_IMAGE_MOJOM_WEBUI_JS,
+        IDR_ORIGIN_MOJO_WEBUI_JS, IDR_RANGE_MOJOM_WEBUI_JS,
+        IDR_TOKEN_MOJO_WEBUI_JS, IDR_UI_WINDOW_OPEN_DISPOSITION_MOJO_WEBUI_JS,
+        IDR_URL_MOJOM_WEBUI_JS, IDR_VULKAN_INFO_MOJO_JS,
+        IDR_VULKAN_TYPES_MOJO_JS,
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-      IDR_ORIGIN_MOJO_JS,
-      IDR_UI_WINDOW_OPEN_DISPOSITION_MOJO_JS,
-      IDR_UNGUESSABLE_TOKEN_MOJO_JS,
-      IDR_URL_MOJO_JS,
+        IDR_ORIGIN_MOJO_JS, IDR_UI_WINDOW_OPEN_DISPOSITION_MOJO_JS,
+        IDR_UNGUESSABLE_TOKEN_MOJO_JS, IDR_URL_MOJO_JS,
 #endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
   };
 }
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandle.java b/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandle.java
index d4e4394..34c29460 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandle.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/NavigationHandle.java
@@ -8,7 +8,6 @@
 
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
 import org.chromium.net.NetError;
 import org.chromium.ui.base.PageTransition;
 import org.chromium.url.GURL;
@@ -244,25 +243,6 @@
     }
 
     /**
-     * Set request's header. If the header is already present, its value is overwritten. When
-     * modified during a navigation start, the headers will be applied to the initial network
-     * request. When modified during a redirect, the headers will be applied to the redirected
-     * request.
-     */
-    public void setRequestHeader(String headerName, String headerValue) {
-        NavigationHandleJni.get().setRequestHeader(
-                mNativeNavigationHandleProxy, headerName, headerValue);
-    }
-
-    /**
-     * Remove a request's header. If the header is not present, it has no effect. Must be called
-     * during a redirect.
-     */
-    public void removeRequestHeader(String headerName) {
-        NavigationHandleJni.get().removeRequestHeader(mNativeNavigationHandleProxy, headerName);
-    }
-
-    /**
      * Get the Origin that initiated this navigation. May be null in the case of navigations
      * originating from the browser.
      */
@@ -311,11 +291,4 @@
     public boolean isReload() {
         return mIsReload;
     }
-
-    @NativeMethods
-    interface Natives {
-        void setRequestHeader(
-                long nativeNavigationHandleProxy, String headerName, String headerValue);
-        void removeRequestHeader(long nativeNavigationHandleProxy, String headerName);
-    }
 }
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 0ca55f1..e8a63ee 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -1226,12 +1226,6 @@
              "WebAssemblyLazyCompilation",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Enable WebAssembly SIMD.
-// https://github.com/WebAssembly/Simd
-BASE_FEATURE(kWebAssemblySimd,
-             "WebAssemblySimd",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Enable WebAssembly tiering (Liftoff -> TurboFan).
 BASE_FEATURE(kWebAssemblyTiering,
              "WebAssemblyTiering",
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index e10cdf16..844f254 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -262,7 +262,6 @@
     kEnableExperimentalWebAssemblyStackSwitching);
 #endif  // defined(ARCH_CPU_X86_64)
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebAssemblyLazyCompilation);
-CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebAssemblySimd);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebAssemblyTiering);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebAssemblyTrapHandler);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebAuthConditionalUI);
diff --git a/content/renderer/media/renderer_webmediaplayer_delegate.cc b/content/renderer/media/renderer_webmediaplayer_delegate.cc
index 6e1d4b4..66ae4755 100644
--- a/content/renderer/media/renderer_webmediaplayer_delegate.cc
+++ b/content/renderer/media/renderer_webmediaplayer_delegate.cc
@@ -191,7 +191,7 @@
   const bool is_shown =
       visibility_state == blink::mojom::PageVisibilityState::kVisible ||
       visibility_state == blink::mojom::PageVisibilityState::kHiddenButPainting;
-  if (is_shown_ == is_shown)
+  if (is_shown_.has_value() && *is_shown_ == is_shown)
     return;
   is_shown_ = is_shown;
 
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 2e135b98..ff94305 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -4,6 +4,7 @@
 
 #include "content/renderer/render_frame_impl.h"
 
+#include <algorithm>
 #include <map>
 #include <memory>
 #include <string>
@@ -33,7 +34,6 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/observer_list.h"
 #include "base/process/process.h"
-#include "base/ranges/algorithm.h"
 #include "base/run_loop.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_piece.h"
@@ -726,7 +726,8 @@
     DCHECK(std::is_sorted(params_.digests_of_uris_to_skip.begin(),
                           params_.digests_of_uris_to_skip.end()));
     // URLs are not duplicated.
-    DCHECK(base::ranges::adjacent_find(params_.digests_of_uris_to_skip) ==
+    DCHECK(std::adjacent_find(params_.digests_of_uris_to_skip.begin(),
+                              params_.digests_of_uris_to_skip.end()) ==
            params_.digests_of_uris_to_skip.end());
   }
 
diff --git a/content/renderer/render_process_impl.cc b/content/renderer/render_process_impl.cc
index f2bdea3..f3b4c9c 100644
--- a/content/renderer/render_process_impl.cc
+++ b/content/renderer/render_process_impl.cc
@@ -174,10 +174,6 @@
   SetV8FlagIfNotFeature(features::kWebAssemblyLazyCompilation,
                         "--no-wasm-lazy-compilation");
 
-  SetV8FlagIfFeature(features::kWebAssemblySimd, "--experimental-wasm-simd");
-  SetV8FlagIfNotFeature(features::kWebAssemblySimd,
-                        "--no-experimental-wasm-simd");
-
   constexpr char kImportAssertionsFlag[] = "--harmony-import-assertions";
   v8::V8::SetFlagsFromString(kImportAssertionsFlag,
                              sizeof(kImportAssertionsFlag));
diff --git a/content/test/gpu/gpu_tests/trace_integration_test.py b/content/test/gpu/gpu_tests/trace_integration_test.py
index c5ff251..9edab82 100644
--- a/content/test/gpu/gpu_tests/trace_integration_test.py
+++ b/content/test/gpu/gpu_tests/trace_integration_test.py
@@ -2,6 +2,10 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+# TODO(dawn:549) Move WebGPU caching tests to a separate module to trim file.
+# pylint: disable=too-many-lines
+
+from enum import Enum
 import logging
 import os
 import posixpath
@@ -132,6 +136,20 @@
 _PROFILE_TYPE_KEY = 'profile_type'
 
 
+class _TraceTestOrigin(Enum):
+  """Enum type for different origin types when navigating to URLs.
+
+  The default enum DEFAULT resolves URLs using the explicit localhost IP,
+  i.e. 127.0.0.1. LOCALHOST resolves URLs using 'localhost' instead. This is
+  useful when we want the navigations to hit the same resource but appear to
+  be from a different origin.
+
+  As an implementation detail, the values of the enums correspond to the name of
+  the SeriallyExecutedBrowserTestCase instance functions to get the URLs."""
+  DEFAULT = 'UrlOfStaticFilePath'
+  LOCALHOST = 'LocalhostUrlOfStaticFilePath'
+
+
 class _TraceTestArguments():
   """Struct-like object for passing trace test arguments instead of dicts."""
 
@@ -143,7 +161,8 @@
       finish_js_condition: str,
       success_eval_func: str,
       other_args: dict,
-      restart_browser: bool = True):
+      restart_browser: bool = True,
+      origin: _TraceTestOrigin = _TraceTestOrigin.DEFAULT):
     self.browser_args = browser_args
     self.category = category
     self.test_harness_script = test_harness_script
@@ -151,25 +170,53 @@
     self.success_eval_func = success_eval_func
     self.other_args = other_args
     self.restart_browser = restart_browser
+    self.origin = origin
 
 
 class _CacheTraceTestArguments():
   """Struct-like object for passing persistent cache trace test arguments.
 
   Cache trace tests consist of a series of normal trace test invocations that
-  are necessary in order to verify the expected caching behaviors."""
+  are necessary in order to verify the expected caching behaviors. The tests
+  start with a first load page which is generally used to populate cache
+  entries. If |test_renavigation| is true, the same browser that opened the
+  first load page is navigated to each cache page in |cache_pages| and the cache
+  conditions are verified. Then, regardless of the value of |test_renavigation|,
+  we iterate across the |cache_pages| again and restart the browser for each
+  page using a new clean user data directory that is seeded with the contents
+  from the first load page, and verify the cache conditions. The seeding just
+  copies all files in the user data directory from the first load over, see
+  *BrowserFinder classes for more details on the seeding:
+    //third_party/catapult/telemetry/telemetry/internal/backends/chrome/
+
+  The renavigation tests are suitable when we are verifying for cache hits since
+  no new entries should be generated in the |cache_pages|. However, they are not
+  suitable for cache miss cases because each |cache_page| may cause entries to
+  be written to the cache, thereby causing subsequent |cache_pages| to see cache
+  hits when we actually expect them to be misses. Note this is not a problem
+  for the restarted browser case because each browser restart seeds a new
+  temporary directory with only the contents after the first load page."""
 
   def __init__(  # pylint: disable=too-many-arguments
-      self, browser_args: List[str], category: str, test_harness_script: str,
-      finish_js_condition: str, first_load_eval_func: str,
-      cache_hit_eval_func: str, cache_hit_pages: List[str]):
+      self,
+      browser_args: List[str],
+      category: str,
+      test_harness_script: str,
+      finish_js_condition: str,
+      first_load_eval_func: str,
+      cache_eval_func: str,
+      cache_pages: List[str],
+      cache_page_origin: _TraceTestOrigin = _TraceTestOrigin.DEFAULT,
+      test_renavigation: bool = True):
     self.browser_args = browser_args
     self.category = category
     self.test_harness_script = test_harness_script
     self.finish_js_condition = finish_js_condition
     self.first_load_eval_func = first_load_eval_func
-    self.cache_hit_eval_func = cache_hit_eval_func
-    self.cache_hit_pages = cache_hit_pages
+    self.cache_eval_func = cache_eval_func
+    self.cache_pages = cache_pages
+    self.cache_page_origin = cache_page_origin
+    self.test_renavigation = test_renavigation
 
   def GenerateFirstLoadTest(self) -> _TraceTestArguments:
     """Returns the trace test arguments for the first load cache test."""
@@ -186,27 +233,30 @@
   ) -> Generator[Tuple[str, _TraceTestArguments], None, None]:
     """Returns a generator for all cache hit trace tests.
 
-    First pass of tests just do a re-navigation, second pass should restarts
-    with a seeded profile directory.
+    First pass of tests just do a re-navigation, second pass restarts with a
+    seeded profile directory.
     """
-    for cache_hit_page in self.cache_hit_pages:
+    if self.test_renavigation:
+      for cache_hit_page in self.cache_pages:
+        yield (posixpath.join(gpu_data_relative_path, cache_hit_page),
+               _TraceTestArguments(browser_args=self.browser_args,
+                                   category=self.category,
+                                   test_harness_script=self.test_harness_script,
+                                   finish_js_condition=self.finish_js_condition,
+                                   success_eval_func=self.cache_eval_func,
+                                   other_args=cache_args,
+                                   restart_browser=False,
+                                   origin=self.cache_page_origin))
+    for cache_hit_page in self.cache_pages:
       yield (posixpath.join(gpu_data_relative_path, cache_hit_page),
              _TraceTestArguments(browser_args=self.browser_args,
                                  category=self.category,
                                  test_harness_script=self.test_harness_script,
                                  finish_js_condition=self.finish_js_condition,
-                                 success_eval_func=self.cache_hit_eval_func,
+                                 success_eval_func=self.cache_eval_func,
                                  other_args=cache_args,
-                                 restart_browser=False))
-    for cache_hit_page in self.cache_hit_pages:
-      yield (posixpath.join(gpu_data_relative_path, cache_hit_page),
-             _TraceTestArguments(browser_args=self.browser_args,
-                                 category=self.category,
-                                 test_harness_script=self.test_harness_script,
-                                 finish_js_condition=self.finish_js_condition,
-                                 success_eval_func=self.cache_hit_eval_func,
-                                 other_args=cache_args,
-                                 restart_browser=True))
+                                 restart_browser=True,
+                                 origin=self.cache_page_origin))
 
 
 class TraceIntegrationTest(gpu_integration_test.GpuIntegrationTest):
@@ -292,14 +342,14 @@
     #
     # The following tests are caching tests that do not render to canvas and so
     # are not a part of the pixel tests suite. Each tuple represents:
-    #   (test_name, first_load_url, cache_hit_pages)
+    #   (test_name, first_load_url, cache_pages)
     #
     # test_name: Name of the test.
     # first_load_url: The first URL that is loaded and when cache entries should
     #   be written. This URL determines the number of expected cache entries to
     #   expect in following loads.
-    # cache_hit_pages: List of URLs that should be both re-navigated to, and
-    #   reloaded in a restarted browser to expect cache hits from disk.
+    # cache_pages: List of URLs that should be both re-navigated and/or
+    #   reloaded in a restarted browser to expect some cache condition.
     webgpu_cache_test_browser_args = [
         cba.ENABLE_UNSAFE_WEBGPU,
         cba.ENABLE_EXPERIMENTAL_WEB_PLATFORM_FEATURES,
@@ -311,7 +361,7 @@
     # WebGPU load and reload caching tests.
     #   These tests load the |first_load_url|, records the number of cache
     #   entries written, then both re-navigates and restarts the browser for
-    #   each subsequence |cache_hit_pages| and verifies that the number of cache
+    #   each subsequence |cache_pages| and verifies that the number of cache
     #   hits is at least equal to the number of cache entries written before.
     webgpu_caching_tests: List[Tuple[str, str, List[str]]] = [
         ('RenderPipelineMainThread', 'webgpu-caching.html?testId=render-test', [
@@ -370,7 +420,7 @@
              'webgpu-caching.html?testId=compute-test-async&worker=true'
          ]),
     ]
-    for (name, first_load_page, cache_hit_pages) in webgpu_caching_tests:
+    for (name, first_load_page, cache_pages) in webgpu_caching_tests:
       yield ('WebGPUCachingTraceTest_' + name,
              posixpath.join(gpu_data_relative_path, first_load_page), [
                  _CacheTraceTestArguments(
@@ -379,15 +429,15 @@
                      test_harness_script=basic_test_harness_script,
                      finish_js_condition='domAutomationController._finished',
                      first_load_eval_func='CheckWebGPUFirstLoadCache',
-                     cache_hit_eval_func='CheckWebGPUCacheHits',
-                     cache_hit_pages=cache_hit_pages)
+                     cache_eval_func='CheckWebGPUCacheHits',
+                     cache_pages=cache_pages)
              ])
 
     # WebGPU incognito mode caching tests
     #   These tests load the |first_load_url| (which runs the same WebGPU code
     #   multiple times) in incognito mode, verifies that the pages had some
     #   in-memory cache hits, then both re-navigates and restarts the browser
-    #   for each subsequence |cache_hit_pages| and verifies that the number of
+    #   for each subsequence |cache_pages| and verifies that the number of
     #   cache hits is 0 since the in-memory cache should be purged.
     webgpu_incognito_caching_tests: List[Tuple[str, str, List[str]]] = [
         ('RenderPipelineIncognito',
@@ -405,8 +455,7 @@
              'webgpu-caching.html?testId=compute-test-async&worker=true'
          ]),
     ]
-    for (name, first_load_page,
-         cache_hit_pages) in webgpu_incognito_caching_tests:
+    for (name, first_load_page, cache_pages) in webgpu_incognito_caching_tests:
       yield ('WebGPUCachingTraceTest_' + name,
              posixpath.join(gpu_data_relative_path, first_load_page), [
                  _CacheTraceTestArguments(
@@ -416,8 +465,44 @@
                      test_harness_script=basic_test_harness_script,
                      finish_js_condition='domAutomationController._finished',
                      first_load_eval_func='CheckWebGPUCacheHits',
-                     cache_hit_eval_func='CheckNoWebGPUCacheHits',
-                     cache_hit_pages=cache_hit_pages)
+                     cache_eval_func='CheckNoWebGPUCacheHits',
+                     cache_pages=cache_pages)
+             ])
+
+    # WebGPU different origin caching tests
+    #   These tests load the |first_load_url| on the default origin, making sure
+    #   that the load populates on-disk entries. The tests then restart the
+    #   browser for subsequent |cache_pages| on localhost origin and
+    #   verifies that there are no cache hits.
+    webgpu_origin_caching_tests: List[Tuple[str, str, List[str]]] = [
+        ('RenderPipelineDifferentOrigins',
+         'webgpu-caching.html?testId=render-test', [
+             'webgpu-caching.html?testId=render-test',
+             'webgpu-caching.html?testId=render-test-async',
+             'webgpu-caching.html?testId=render-test&worker=true',
+             'webgpu-caching.html?testId=render-test-async&worker=true'
+         ]),
+        ('ComputePipelineDifferentOrigins',
+         'webgpu-caching.html?testId=compute-test', [
+             'webgpu-caching.html?testId=compute-test',
+             'webgpu-caching.html?testId=compute-test-async',
+             'webgpu-caching.html?testId=compute-test&worker=true',
+             'webgpu-caching.html?testId=compute-test-async&worker=true'
+         ]),
+    ]
+    for (name, first_load_page, cache_pages) in webgpu_origin_caching_tests:
+      yield ('WebGPUCachingTraceTest_' + name,
+             posixpath.join(gpu_data_relative_path, first_load_page), [
+                 _CacheTraceTestArguments(
+                     browser_args=webgpu_cache_test_browser_args,
+                     category='gpu',
+                     test_harness_script=basic_test_harness_script,
+                     finish_js_condition='domAutomationController._finished',
+                     first_load_eval_func='CheckWebGPUFirstLoadCache',
+                     cache_eval_func='CheckNoWebGPUCacheHits',
+                     cache_pages=cache_pages,
+                     cache_page_origin=_TraceTestOrigin.LOCALHOST,
+                     test_renavigation=False)
              ])
 
   def _RunActualGpuTraceTest(self,
@@ -443,7 +528,7 @@
     tab.browser.platform.tracing_controller.StartTracing(config, 60)
 
     # Perform page navigation.
-    url = self.UrlOfStaticFilePath(test_path)
+    url = getattr(self, args.origin.value)(test_path)
     tab.Navigate(url, script_to_evaluate_on_commit=args.test_harness_script)
 
     try:
@@ -485,7 +570,7 @@
                                     profile_dir=cache_profile_dir.name,
                                     profile_type='exact')
 
-      # Generate and run the cache hit tests using the seeding cache dir.
+      # Generate and run the cache hit tests using the seeded cache dir.
       for (hit_path, trace_params) in params.GenerateCacheHitTests(results):
         self._RunActualGpuTraceTest(hit_path,
                                     trace_params,
diff --git a/device/bluetooth/floss/bluetooth_adapter_floss.cc b/device/bluetooth/floss/bluetooth_adapter_floss.cc
index fc92c83..e05cf56 100644
--- a/device/bluetooth/floss/bluetooth_adapter_floss.cc
+++ b/device/bluetooth/floss/bluetooth_adapter_floss.cc
@@ -579,6 +579,8 @@
   DCHECK(FlossDBusManager::Get());
   DCHECK(IsPresent());
 
+  BLUETOOTH_LOG(EVENT) << __func__ << device_found;
+
   auto device_floss = base::WrapUnique(new BluetoothDeviceFloss(
       this, device_found, ui_task_runner_, socket_thread_));
 
@@ -603,9 +605,27 @@
         base::BindOnce(&BluetoothAdapterFloss::OnGetConnectionState,
                        weak_ptr_factory_.GetWeakPtr(), device_found),
         device_found);
+    return;
   }
 
-  BLUETOOTH_LOG(EVENT) << __func__ << device_found;
+  BluetoothDeviceFloss* device =
+      static_cast<BluetoothDeviceFloss*>(devices_[canonical_address].get());
+  if (UpdateDevice(device, device_floss.get())) {
+    for (auto& observer : observers_)
+      observer.DeviceChanged(this, device);
+  }
+}
+
+bool BluetoothAdapterFloss::UpdateDevice(BluetoothDeviceFloss* device,
+                                         BluetoothDeviceFloss* new_device) {
+  bool updated = false;
+
+  if (new_device->GetName() && device->GetName() != new_device->GetName()) {
+    device->SetName(new_device->GetName().value_or(""));
+    updated = true;
+  }
+
+  return updated;
 }
 
 void BluetoothAdapterFloss::AdapterClearedDevice(
diff --git a/device/bluetooth/floss/bluetooth_adapter_floss.h b/device/bluetooth/floss/bluetooth_adapter_floss.h
index 3beb2fd0..2880e8e 100644
--- a/device/bluetooth/floss/bluetooth_adapter_floss.h
+++ b/device/bluetooth/floss/bluetooth_adapter_floss.h
@@ -195,6 +195,8 @@
 
   void PopulateInitialDevices();
   void ClearAllDevices();
+  bool UpdateDevice(BluetoothDeviceFloss* device,
+                    BluetoothDeviceFloss* new_device);
 
   // floss::FlossAdapterClient::Observer override.
   void DiscoverableChanged(bool discoverable) override;
diff --git a/device/bluetooth/floss/bluetooth_floss_unittest.cc b/device/bluetooth/floss/bluetooth_floss_unittest.cc
index bab13efa6..dee7eea3 100644
--- a/device/bluetooth/floss/bluetooth_floss_unittest.cc
+++ b/device/bluetooth/floss/bluetooth_floss_unittest.cc
@@ -556,6 +556,16 @@
   EXPECT_TRUE(same_bonded_device != nullptr);
 }
 
+TEST_F(BluetoothFlossTest, UpdatesDeviceName) {
+  InitializeAdapter();
+  DiscoverDevices();
+
+  BluetoothDevice* device =
+      adapter_->GetDevice(FakeFlossAdapterClient::kClassicAddress);
+  ASSERT_TRUE(device != nullptr);
+  EXPECT_EQ(device->GetName(), FakeFlossAdapterClient::kClassicName);
+}
+
 #if BUILDFLAG(IS_CHROMEOS)
 TEST_F(BluetoothFlossTest, StartLowEnergyScanSessions) {
   InitializeAdapter();
diff --git a/device/bluetooth/floss/fake_floss_adapter_client.cc b/device/bluetooth/floss/fake_floss_adapter_client.cc
index db875a18..34b205b 100644
--- a/device/bluetooth/floss/fake_floss_adapter_client.cc
+++ b/device/bluetooth/floss/fake_floss_adapter_client.cc
@@ -29,6 +29,8 @@
 const char FakeFlossAdapterClient::kKeyboardAddress[] = "aa:aa:aa:aa:aa:aa";
 const char FakeFlossAdapterClient::kPhoneAddress[] = "bb:bb:bb:bb:bb:bb";
 const char FakeFlossAdapterClient::kOldDeviceAddress[] = "cc:cc:cc:cc:cc:cc";
+const char FakeFlossAdapterClient::kClassicAddress[] = "dd:dd:dd:dd:dd:dd";
+const char FakeFlossAdapterClient::kClassicName[] = "Classic Device";
 const uint32_t FakeFlossAdapterClient::kPasskey = 123456;
 const uint32_t FakeFlossAdapterClient::kHeadsetClassOfDevice = 2360344;
 
@@ -53,6 +55,9 @@
     observer.AdapterFoundDevice(FlossDeviceId({kKeyboardAddress, ""}));
     observer.AdapterFoundDevice(FlossDeviceId({kPhoneAddress, ""}));
     observer.AdapterFoundDevice(FlossDeviceId({kOldDeviceAddress, ""}));
+    // Simulate a device which sends its name later
+    observer.AdapterFoundDevice(FlossDeviceId({kClassicAddress, ""}));
+    observer.AdapterFoundDevice(FlossDeviceId({kClassicAddress, kClassicName}));
   }
 
   PostDelayedTask(base::BindOnce(std::move(callback), Void{}));
diff --git a/device/bluetooth/floss/fake_floss_adapter_client.h b/device/bluetooth/floss/fake_floss_adapter_client.h
index 47610f6c..371b4ed 100644
--- a/device/bluetooth/floss/fake_floss_adapter_client.h
+++ b/device/bluetooth/floss/fake_floss_adapter_client.h
@@ -27,6 +27,8 @@
   static const char kKeyboardAddress[];
   static const char kPhoneAddress[];
   static const char kOldDeviceAddress[];
+  static const char kClassicAddress[];
+  static const char kClassicName[];
   static const uint32_t kPasskey;
   static const uint32_t kHeadsetClassOfDevice;
 
diff --git a/docs/updater/dev_manual.md b/docs/updater/dev_manual.md
index 1472c017..ee8fb28a 100644
--- a/docs/updater/dev_manual.md
+++ b/docs/updater/dev_manual.md
@@ -56,6 +56,15 @@
   ```
   .\tools\mb\mb.bat run --swarmed --no-default-dimensions -d pool chromium.win.uac -d os Windows-10 .\out\Default updater_tests_system -- --gtest_filter=*Install*
   ```
+* `mb` can schedule tests in the pools managed by different swarming servers.
+  The default server is
+  [chromium-swarm.appspot.com](https://chromium-swarm.appspot.com/botlist?k=pool).
+  To schedule tests to pools managed by
+  [chrome-swarming.appspot.com](https://chrome-swarming.appspot.com/botlist?k=pool),
+  for example `chrome.tests`, add `--internal` flag in the command line:
+  ```
+    tools/mb/mb run -v --swarmed --internal --no-default-dimensions -d pool chrome.tests -d os Windows-10 out/WinDefault updater_tests
+  ```
 * If your test introduces dependency on a new app on macOS, you need to let
  `mb` tool know so it can correctly figure out the dependency. Example:
   https://crrev.com/c/3470143.
diff --git a/extensions/browser/process_manager.cc b/extensions/browser/process_manager.cc
index ae89ae7..f5ea02d4 100644
--- a/extensions/browser/process_manager.cc
+++ b/extensions/browser/process_manager.cc
@@ -425,8 +425,11 @@
   return nullptr;
 }
 
-ExtensionHost* ProcessManager::GetExtensionHostForRenderFrameHost(
+ExtensionHost* ProcessManager::GetBackgroundHostForRenderFrameHost(
     content::RenderFrameHost* render_frame_host) {
+  if (!render_frame_host->IsInPrimaryMainFrame())
+    return nullptr;
+
   content::WebContents* web_contents =
       content::WebContents::FromRenderFrameHost(render_frame_host);
   for (ExtensionHost* extension_host : background_hosts_) {
diff --git a/extensions/browser/process_manager.h b/extensions/browser/process_manager.h
index 7528a5b7..14aa6af 100644
--- a/extensions/browser/process_manager.h
+++ b/extensions/browser/process_manager.h
@@ -120,12 +120,9 @@
   ExtensionHost* GetBackgroundHostForExtension(const std::string& extension_id);
 
   // Returns the background page ExtensionHost for the given
-  // |render_frame_host|, if |render_frame_host| is within the extension's
-  // background. Note that this will return the background page host for
-  // iframes embedded in the background page, even if they are not extension
-  // frames.
-  // TODO(https://crbug.com/1340001): Make these "gotchas" less subtle.
-  ExtensionHost* GetExtensionHostForRenderFrameHost(
+  // |render_frame_host|, if |render_frame_host| is in primary main frame and
+  // within the extension's background.
+  ExtensionHost* GetBackgroundHostForRenderFrameHost(
       content::RenderFrameHost* render_frame_host);
 
   // Returns true if the (lazy) background host for the given extension has
diff --git a/gpu/command_buffer/service/shared_image/external_vk_image_backing.cc b/gpu/command_buffer/service/shared_image/external_vk_image_backing.cc
index 00cb394..435bb3b 100644
--- a/gpu/command_buffer/service/shared_image/external_vk_image_backing.cc
+++ b/gpu/command_buffer/service/shared_image/external_vk_image_backing.cc
@@ -313,6 +313,7 @@
       backend_texture_(size.width(),
                        size.height(),
                        CreateGrVkImageInfo(image_.get())),
+      promise_texture_(SkPromiseImageTexture::Make(backend_texture_)),
       command_pool_(command_pool),
       use_separate_gl_texture_(use_separate_gl_texture) {}
 
diff --git a/gpu/command_buffer/service/shared_image/external_vk_image_backing.h b/gpu/command_buffer/service/shared_image/external_vk_image_backing.h
index 7ab3065..f7a7d5e 100644
--- a/gpu/command_buffer/service/shared_image/external_vk_image_backing.h
+++ b/gpu/command_buffer/service/shared_image/external_vk_image_backing.h
@@ -21,6 +21,7 @@
 #include "gpu/command_buffer/service/shared_memory_region_wrapper.h"
 #include "gpu/command_buffer/service/texture_manager.h"
 #include "gpu/vulkan/vulkan_device_queue.h"
+#include "third_party/skia/include/core/SkPromiseImageTexture.h"
 #include "ui/gfx/gpu_memory_buffer.h"
 
 namespace gpu {
@@ -76,6 +77,9 @@
 
   SharedContextState* context_state() const { return context_state_.get(); }
   const GrBackendTexture& backend_texture() const { return backend_texture_; }
+  sk_sp<SkPromiseImageTexture> promise_texture() const {
+    return promise_texture_;
+  }
   VulkanImage* image() const { return image_.get(); }
   const scoped_refptr<gles2::TexturePassthrough>& GetTexturePassthrough()
       const {
@@ -189,6 +193,7 @@
   scoped_refptr<SharedContextState> context_state_;
   std::unique_ptr<VulkanImage> image_;
   GrBackendTexture backend_texture_;
+  sk_sp<SkPromiseImageTexture> promise_texture_;
   const raw_ptr<VulkanCommandPool> command_pool_;
   const bool use_separate_gl_texture_;
 
diff --git a/gpu/command_buffer/service/shared_image/external_vk_image_skia_representation.cc b/gpu/command_buffer/service/shared_image/external_vk_image_skia_representation.cc
index fa58b9bf..bcbbed0 100644
--- a/gpu/command_buffer/service/shared_image/external_vk_image_skia_representation.cc
+++ b/gpu/command_buffer/service/shared_image/external_vk_image_skia_representation.cc
@@ -27,7 +27,8 @@
     ~ExternalVkImageSkiaImageRepresentation() {
   DCHECK_EQ(access_mode_, kNone) << "Previous access hasn't end yet.";
   DCHECK(!end_access_semaphore_);
-  backing_impl()->context_state()->EraseCachedSkSurface(this);
+  backing_impl()->context_state()->EraseCachedSkSurface(
+      backing_impl()->promise_texture().get());
 }
 
 sk_sp<SkSurface> ExternalVkImageSkiaImageRepresentation::BeginWriteAccess(
@@ -54,7 +55,8 @@
     return nullptr;
   }
 
-  auto surface = backing_impl()->context_state()->GetCachedSkSurface(this);
+  auto surface = backing_impl()->context_state()->GetCachedSkSurface(
+      promise_texture.get());
 
   // If surface properties are different from the last access, then we cannot
   // reuse the cached SkSurface.
@@ -68,11 +70,13 @@
         backing_impl()->color_space().ToSkColorSpace(), &surface_props);
     if (!surface) {
       LOG(ERROR) << "MakeFromBackendTexture() failed.";
-      backing_impl()->context_state()->EraseCachedSkSurface(this);
+      backing_impl()->context_state()->EraseCachedSkSurface(
+          promise_texture.get());
       return nullptr;
     }
     surface_msaa_count_ = final_msaa_count;
-    backing_impl()->context_state()->CacheSkSurface(this, surface);
+    backing_impl()->context_state()->CacheSkSurface(promise_texture.get(),
+                                                    surface);
   }
 
   [[maybe_unused]] int count = surface->getCanvas()->save();
@@ -130,7 +134,8 @@
   if (surface) {
     surface->getCanvas()->restoreToCount(1);
     surface = nullptr;
-    DCHECK(backing_impl()->context_state()->CachedSkSurfaceIsUnique(this));
+    DCHECK(backing_impl()->context_state()->CachedSkSurfaceIsUnique(
+        backing_impl()->promise_texture().get()));
   }
   EndAccess(false /* readonly */);
   access_mode_ = kNone;
@@ -209,7 +214,7 @@
     end_semaphores->back().initVulkan(end_access_semaphore_.GetVkSemaphore());
   }
 
-  return SkPromiseImageTexture::Make(backing_impl()->backend_texture());
+  return backing_impl()->promise_texture();
 }
 
 void ExternalVkImageSkiaImageRepresentation::EndAccess(bool readonly) {
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc
index e7794567..23c9265 100644
--- a/gpu/command_buffer/service/webgpu_decoder_impl.cc
+++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
@@ -1992,14 +1992,13 @@
       base::UnguessableToken::Deserialize(high, low);
   blink::WebGPUExecutionContextToken execution_context_token;
   switch (type) {
-    case blink::WebGPUExecutionContextToken::Base::template TypeIndex<
-        blink::DocumentToken>::kValue: {
+    case blink::WebGPUExecutionContextToken::IndexOf<blink::DocumentToken>(): {
       execution_context_token = blink::WebGPUExecutionContextToken(
           blink::DocumentToken(unguessable_token));
       break;
     }
-    case blink::WebGPUExecutionContextToken::Base::template TypeIndex<
-        blink::DedicatedWorkerToken>::kValue: {
+    case blink::WebGPUExecutionContextToken::IndexOf<
+        blink::DedicatedWorkerToken>(): {
       execution_context_token = blink::WebGPUExecutionContextToken(
           blink::DedicatedWorkerToken(unguessable_token));
       break;
diff --git a/gpu/config/software_rendering_list.json b/gpu/config/software_rendering_list.json
index 20ea3b55..e7d662d 100644
--- a/gpu/config/software_rendering_list.json
+++ b/gpu/config/software_rendering_list.json
@@ -1689,6 +1689,22 @@
       "features": [
         "all"
       ]
+    },
+    {
+      "id": 178,
+      "description": "Intel IronLake only shows black in hardware decoded H264 [b/202962575, b/233244020]",
+      "os": {
+        "type": "chromeos"
+      },
+      "intel_gpu_generation": {
+        "op": "=",
+        "value": "5"
+      },
+      "driver_vendor": "Mesa",
+      "gl_vendor": "Intel.*",
+      "features": [
+        "accelerated_video_decode"
+      ]
     }
   ]
 }
diff --git a/gpu/ipc/service/image_transport_surface_overlay_mac.h b/gpu/ipc/service/image_transport_surface_overlay_mac.h
index 8d1b4e5..54df9cd 100644
--- a/gpu/ipc/service/image_transport_surface_overlay_mac.h
+++ b/gpu/ipc/service/image_transport_surface_overlay_mac.h
@@ -92,7 +92,7 @@
   gl::GLSurfaceFormat GetFormat() override;
   bool OnMakeCurrent(gl::GLContext* context) override;
   bool ScheduleOverlayPlane(
-      gl::GLImage* image,
+      gl::OverlayImage image,
       std::unique_ptr<gfx::GpuFence> gpu_fence,
       const gfx::OverlayPlaneData& overlay_plane_data) override;
   bool ScheduleCALayer(const ui::CARendererLayerParams& params) override;
@@ -187,7 +187,7 @@
   gl::GLSurfaceFormat GetFormat() override;
   bool OnMakeCurrent(gl::GLContext* context) override;
   bool ScheduleOverlayPlane(
-      gl::GLImage* image,
+      gl::OverlayImage image,
       std::unique_ptr<gfx::GpuFence> gpu_fence,
       const gfx::OverlayPlaneData& overlay_plane_data) override;
   bool ScheduleCALayer(const ui::CARendererLayerParams& params) override;
diff --git a/gpu/ipc/service/image_transport_surface_overlay_mac.mm b/gpu/ipc/service/image_transport_surface_overlay_mac.mm
index 622ae164..6944ef9d 100644
--- a/gpu/ipc/service/image_transport_surface_overlay_mac.mm
+++ b/gpu/ipc/service/image_transport_surface_overlay_mac.mm
@@ -270,7 +270,7 @@
 }
 
 bool ImageTransportSurfaceOverlayMac::ScheduleOverlayPlane(
-    gl::GLImage* image,
+    gl::OverlayImage image,
     std::unique_ptr<gfx::GpuFence> gpu_fence,
     const gfx::OverlayPlaneData& overlay_plane_data) {
   if (overlay_plane_data.plane_transform != gfx::OVERLAY_TRANSFORM_NONE) {
@@ -607,7 +607,7 @@
 }
 
 bool ImageTransportSurfaceOverlayMacEGL::ScheduleOverlayPlane(
-    gl::GLImage* image,
+    gl::OverlayImage image,
     std::unique_ptr<gfx::GpuFence> gpu_fence,
     const gfx::OverlayPlaneData& overlay_plane_data) {
   if (overlay_plane_data.plane_transform != gfx::OVERLAY_TRANSFORM_NONE) {
diff --git a/infra/config/generated/builders/reclient/ios-simulator reclient staging untrusted/properties.json b/infra/config/generated/builders/reclient/ios-simulator reclient staging untrusted/properties.json
index 2045994..777bbb3 100644
--- a/infra/config/generated/builders/reclient/ios-simulator reclient staging untrusted/properties.json
+++ b/infra/config/generated/builders/reclient/ios-simulator reclient staging untrusted/properties.json
@@ -45,6 +45,9 @@
     }
   },
   "$build/reclient": {
+    "bootstrap_env": {
+      "GLOG_vmodule": "bridge*=2"
+    },
     "instance": "rbe-chromium-untrusted",
     "metrics_project": "chromium-reclient-metrics"
   },
diff --git a/infra/config/generated/builders/reclient/ios-simulator reclient staging/properties.json b/infra/config/generated/builders/reclient/ios-simulator reclient staging/properties.json
index 4e570ca..c9df000 100644
--- a/infra/config/generated/builders/reclient/ios-simulator reclient staging/properties.json
+++ b/infra/config/generated/builders/reclient/ios-simulator reclient staging/properties.json
@@ -45,6 +45,9 @@
     }
   },
   "$build/reclient": {
+    "bootstrap_env": {
+      "GLOG_vmodule": "bridge*=2"
+    },
     "instance": "rbe-chromium-trusted",
     "metrics_project": "chromium-reclient-metrics"
   },
diff --git a/infra/config/generated/builders/reclient/ios-simulator reclient test untrusted/properties.json b/infra/config/generated/builders/reclient/ios-simulator reclient test untrusted/properties.json
index 334ae5e..ce61745 100644
--- a/infra/config/generated/builders/reclient/ios-simulator reclient test untrusted/properties.json
+++ b/infra/config/generated/builders/reclient/ios-simulator reclient test untrusted/properties.json
@@ -45,6 +45,10 @@
     }
   },
   "$build/reclient": {
+    "bootstrap_env": {
+      "GLOG_vmodule": "bridge*=2",
+      "RBE_ip_timeout": "-1s"
+    },
     "instance": "rbe-chromium-untrusted-test",
     "metrics_project": "chromium-reclient-metrics"
   },
diff --git a/infra/config/generated/builders/reclient/ios-simulator reclient test/properties.json b/infra/config/generated/builders/reclient/ios-simulator reclient test/properties.json
index 177dd52..03a24d3f 100644
--- a/infra/config/generated/builders/reclient/ios-simulator reclient test/properties.json
+++ b/infra/config/generated/builders/reclient/ios-simulator reclient test/properties.json
@@ -45,6 +45,10 @@
     }
   },
   "$build/reclient": {
+    "bootstrap_env": {
+      "GLOG_vmodule": "bridge*=2",
+      "RBE_ip_timeout": "-1s"
+    },
     "instance": "rbe-chromium-trusted-test",
     "metrics_project": "chromium-reclient-metrics"
   },
diff --git a/infra/config/generated/builders/reclient/mac-arm64-rel reclient staging untrusted/properties.json b/infra/config/generated/builders/reclient/mac-arm64-rel reclient staging untrusted/properties.json
index 052d39a..a3eaca6b 100644
--- a/infra/config/generated/builders/reclient/mac-arm64-rel reclient staging untrusted/properties.json
+++ b/infra/config/generated/builders/reclient/mac-arm64-rel reclient staging untrusted/properties.json
@@ -44,6 +44,9 @@
     }
   },
   "$build/reclient": {
+    "bootstrap_env": {
+      "GLOG_vmodule": "bridge*=2"
+    },
     "instance": "rbe-chromium-untrusted",
     "metrics_project": "chromium-reclient-metrics"
   },
diff --git a/infra/config/generated/builders/reclient/mac-arm64-rel reclient staging/properties.json b/infra/config/generated/builders/reclient/mac-arm64-rel reclient staging/properties.json
index 059d42c3..62488d6 100644
--- a/infra/config/generated/builders/reclient/mac-arm64-rel reclient staging/properties.json
+++ b/infra/config/generated/builders/reclient/mac-arm64-rel reclient staging/properties.json
@@ -44,6 +44,9 @@
     }
   },
   "$build/reclient": {
+    "bootstrap_env": {
+      "GLOG_vmodule": "bridge*=2"
+    },
     "instance": "rbe-chromium-trusted",
     "metrics_project": "chromium-reclient-metrics"
   },
diff --git a/infra/config/generated/builders/reclient/mac-arm64-rel reclient test untrusted/properties.json b/infra/config/generated/builders/reclient/mac-arm64-rel reclient test untrusted/properties.json
index b94ed01..1698692 100644
--- a/infra/config/generated/builders/reclient/mac-arm64-rel reclient test untrusted/properties.json
+++ b/infra/config/generated/builders/reclient/mac-arm64-rel reclient test untrusted/properties.json
@@ -44,6 +44,10 @@
     }
   },
   "$build/reclient": {
+    "bootstrap_env": {
+      "GLOG_vmodule": "bridge*=2",
+      "RBE_ip_timeout": "-1s"
+    },
     "instance": "rbe-chromium-untrusted-test",
     "metrics_project": "chromium-reclient-metrics"
   },
diff --git a/infra/config/generated/builders/reclient/mac-arm64-rel reclient test/properties.json b/infra/config/generated/builders/reclient/mac-arm64-rel reclient test/properties.json
index f97086c..5c5eac4c 100644
--- a/infra/config/generated/builders/reclient/mac-arm64-rel reclient test/properties.json
+++ b/infra/config/generated/builders/reclient/mac-arm64-rel reclient test/properties.json
@@ -44,6 +44,10 @@
     }
   },
   "$build/reclient": {
+    "bootstrap_env": {
+      "GLOG_vmodule": "bridge*=2",
+      "RBE_ip_timeout": "-1s"
+    },
     "instance": "rbe-chromium-trusted-test",
     "metrics_project": "chromium-reclient-metrics"
   },
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index d471cf29..97a5f79 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -3872,6 +3872,9 @@
         '    "use_luci_auth": true'
         '  },'
         '  "$build/reclient": {'
+        '    "bootstrap_env": {'
+        '      "RBE_experimental_goma_deps_cache": "true"'
+        '    },'
         '    "cache_silo": "Comparison Android CQ - cache siloed",'
         '    "instance": "rbe-chromium-untrusted-test",'
         '    "jobs": 300,'
@@ -3962,6 +3965,9 @@
         '    "use_luci_auth": true'
         '  },'
         '  "$build/reclient": {'
+        '    "bootstrap_env": {'
+        '      "RBE_experimental_goma_deps_cache": "true"'
+        '    },'
         '    "cache_silo": "Comparison Linux - cache siloed",'
         '    "instance": "rbe-chromium-trusted",'
         '    "jobs": 250,'
@@ -4051,6 +4057,9 @@
         '    "use_luci_auth": true'
         '  },'
         '  "$build/reclient": {'
+        '    "bootstrap_env": {'
+        '      "RBE_experimental_goma_deps_cache": "true"'
+        '    },'
         '    "cache_silo": "Comparison Linux CQ - cache siloed",'
         '    "instance": "rbe-chromium-untrusted-test",'
         '    "jobs": 150,'
@@ -4139,7 +4148,9 @@
         '  },'
         '  "$build/reclient": {'
         '    "bootstrap_env": {'
-        '      "GLOG_vmodule": "bridge*=2"'
+        '      "GLOG_vmodule": "bridge*=2",'
+        '      "RBE_experimental_goma_deps_cache": "true",'
+        '      "RBE_ip_timeout": "-1s"'
         '    },'
         '    "cache_silo": "Comparison Mac - cache siloed",'
         '    "instance": "rbe-chromium-trusted-test",'
@@ -4229,7 +4240,8 @@
         '  },'
         '  "$build/reclient": {'
         '    "bootstrap_env": {'
-        '      "GLOG_vmodule": "bridge*=2"'
+        '      "GLOG_vmodule": "bridge*=2",'
+        '      "RBE_experimental_goma_deps_cache": "true"'
         '    },'
         '    "cache_silo": "Comparison Mac CQ - cache siloed",'
         '    "instance": "rbe-chromium-untrusted-test",'
@@ -4319,7 +4331,8 @@
         '  },'
         '  "$build/reclient": {'
         '    "bootstrap_env": {'
-        '      "GLOG_vmodule": "bridge*=2"'
+        '      "GLOG_vmodule": "bridge*=2",'
+        '      "RBE_experimental_goma_deps_cache": "true"'
         '    },'
         '    "cache_silo": "Comparison Mac - cache siloed",'
         '    "instance": "rbe-chromium-trusted-test",'
@@ -4408,7 +4421,8 @@
         '  },'
         '  "$build/reclient": {'
         '    "bootstrap_env": {'
-        '      "GLOG_vmodule": "bridge*=2"'
+        '      "GLOG_vmodule": "bridge*=2",'
+        '      "RBE_experimental_goma_deps_cache": "true"'
         '    },'
         '    "cache_silo": "Comparison Mac - cache siloed",'
         '    "instance": "rbe-chromium-trusted-test",'
@@ -4588,6 +4602,9 @@
         '    "use_luci_auth": true'
         '  },'
         '  "$build/reclient": {'
+        '    "bootstrap_env": {'
+        '      "RBE_experimental_goma_deps_cache": "true"'
+        '    },'
         '    "cache_silo": "Comparison Simple Chrome CQ - cache siloed",'
         '    "instance": "rbe-chromium-untrusted-test",'
         '    "jobs": 300,'
@@ -4678,6 +4695,9 @@
         '    "use_luci_auth": true'
         '  },'
         '  "$build/reclient": {'
+        '    "bootstrap_env": {'
+        '      "RBE_experimental_goma_deps_cache": "true"'
+        '    },'
         '    "cache_silo": "Comparison Windows 8 cores - cache siloed",'
         '    "instance": "rbe-chromium-trusted",'
         '    "jobs": 80,'
@@ -4767,99 +4787,10 @@
         '    "use_luci_auth": true'
         '  },'
         '  "$build/reclient": {'
-        '    "cache_silo": "Comparison Windows - cache siloed",'
-        '    "instance": "rbe-chromium-trusted",'
-        '    "jobs": 250,'
-        '    "metrics_project": "chromium-reclient-metrics"'
-        '  },'
-        '  "$recipe_engine/resultdb/test_presentation": {'
-        '    "column_keys": [],'
-        '    "grouping_keys": ['
-        '      "status",'
-        '      "v.test_suite"'
-        '    ]'
-        '  },'
-        '  "builder_group": "chromium.fyi",'
-        '  "recipe": "reclient_goma_comparison"'
-        '}'
-      priority: 35
-      execution_timeout_secs: 21600
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "chromium_swarming.expose_merge_script_failures"
-        value: 10
-      }
-      experiments {
-        key: "luci.buildbucket.omit_python2"
-        value: 100
-      }
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "ci_test_results"
-          test_results {}
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "gpu_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
-            }
-          }
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "blink_web_tests_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*blink_wpt_tests/.+)"
-            }
-          }
-        }
-        history_options {
-          use_invocation_timestamp: true
-        }
-      }
-    }
-    builders {
-      name: "Comparison Windows (reclient) (reproxy cache)"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
-      dimensions: "cores:32"
-      dimensions: "cpu:x86-64"
-      dimensions: "free_space:high"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.chromium.ci"
-      dimensions: "ssd:0"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$build/goma": {'
-        '    "enable_ats": true,'
-        '    "jobs": 250,'
-        '    "rpc_extra_params": "?prod",'
-        '    "server_host": "goma.chromium.org",'
-        '    "use_luci_auth": true'
-        '  },'
-        '  "$build/reclient": {'
         '    "bootstrap_env": {'
         '      "RBE_experimental_goma_deps_cache": "true"'
         '    },'
-        '    "cache_silo": "Comparison Windows (reproxy cache) - cache siloed",'
+        '    "cache_silo": "Comparison Windows - cache siloed",'
         '    "instance": "rbe-chromium-trusted",'
         '    "jobs": 250,'
         '    "metrics_project": "chromium-reclient-metrics"'
@@ -4948,6 +4879,9 @@
         '    "use_luci_auth": true'
         '  },'
         '  "$build/reclient": {'
+        '    "bootstrap_env": {'
+        '      "RBE_experimental_goma_deps_cache": "true"'
+        '    },'
         '    "cache_silo": "Comparison Windows CQ - cache siloed",'
         '    "instance": "rbe-chromium-untrusted-test",'
         '    "jobs": 300,'
@@ -5035,6 +4969,9 @@
         '    "use_luci_auth": true'
         '  },'
         '  "$build/reclient": {'
+        '    "bootstrap_env": {'
+        '      "RBE_experimental_goma_deps_cache": "true"'
+        '    },'
         '    "cache_silo": "Comparison ios - cache siloed",'
         '    "instance": "rbe-chromium-trusted-test",'
         '    "jobs": 250,'
@@ -5127,6 +5064,9 @@
         '    "use_luci_auth": true'
         '  },'
         '  "$build/reclient": {'
+        '    "bootstrap_env": {'
+        '      "RBE_experimental_goma_deps_cache": "true"'
+        '    },'
         '    "cache_silo": "Comparison ios CQ - cache siloed",'
         '    "instance": "rbe-chromium-untrusted-test",'
         '    "jobs": 150,'
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index 13a148a..d684fb0 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -8687,11 +8687,6 @@
     category: "win|cq"
     short_name: "re"
   }
-  builders {
-    name: "buildbucket/luci.chromium.ci/Comparison Windows (reclient) (reproxy cache)"
-    category: "win|expcache"
-    short_name: "re"
-  }
   header {
     oncalls {
       name: "Chromium"
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg
index bff57767..76bd5c89 100644
--- a/infra/config/generated/luci/luci-scheduler.cfg
+++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -839,16 +839,6 @@
   }
 }
 job {
-  id: "Comparison Windows (reclient) (reproxy cache)"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "ci"
-    builder: "Comparison Windows (reclient) (reproxy cache)"
-  }
-}
-job {
   id: "Comparison Windows (reclient)(CQ)"
   realm: "ci"
   acl_sets: "ci"
@@ -7274,7 +7264,6 @@
   triggers: "Comparison Simple Chrome (reclient)(CQ)"
   triggers: "Comparison Windows (8 cores) (reclient)"
   triggers: "Comparison Windows (reclient)"
-  triggers: "Comparison Windows (reclient) (reproxy cache)"
   triggers: "Comparison Windows (reclient)(CQ)"
   triggers: "Comparison ios (reclient)"
   triggers: "Comparison ios (reclient)(CQ)"
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index ed4a4fe..0c2bfed 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -1098,6 +1098,9 @@
     reclient_instance = reclient.instance.DEFAULT_TRUSTED,
     reclient_jobs = 250,
     os = os.LINUX_DEFAULT,
+    reclient_bootstrap_env = {
+        "RBE_experimental_goma_deps_cache": "true",
+    },
 )
 
 ci.builder(
@@ -1116,7 +1119,9 @@
     os = os.MAC_DEFAULT,
     cores = None,
     reclient_bootstrap_env = {
+        "RBE_ip_timeout": "-1s",
         "GLOG_vmodule": "bridge*=2",
+        "RBE_experimental_goma_deps_cache": "true",
     },
 )
 
@@ -1137,6 +1142,7 @@
     cores = None,
     reclient_bootstrap_env = {
         "GLOG_vmodule": "bridge*=2",
+        "RBE_experimental_goma_deps_cache": "true",
     },
 )
 
@@ -1158,6 +1164,7 @@
     cpu = cpu.ARM64,
     reclient_bootstrap_env = {
         "GLOG_vmodule": "bridge*=2",
+        "RBE_experimental_goma_deps_cache": "true",
     },
 )
 
@@ -1176,6 +1183,9 @@
     reclient_jobs = 80,
     os = os.WINDOWS_DEFAULT,
     free_space = builders.free_space.high,
+    reclient_bootstrap_env = {
+        "RBE_experimental_goma_deps_cache": "true",
+    },
 )
 
 ci.builder(
@@ -1194,27 +1204,9 @@
     reclient_jobs = 250,
     os = os.WINDOWS_DEFAULT,
     free_space = builders.free_space.high,
-)
-
-ci.builder(
-    name = "Comparison Windows (reclient) (reproxy cache)",
-    builderless = True,
-    console_view_entry = consoles.console_view_entry(
-        category = "win|expcache",
-        short_name = "re",
-    ),
-    cores = 32,
-    goma_jobs = 250,
-    executable = "recipe:reclient_goma_comparison",
-    execution_timeout = 6 * time.hour,
-    reclient_cache_silo = "Comparison Windows (reproxy cache) - cache siloed",
-    reclient_instance = reclient.instance.DEFAULT_TRUSTED,
-    reclient_jobs = 250,
     reclient_bootstrap_env = {
         "RBE_experimental_goma_deps_cache": "true",
     },
-    os = os.WINDOWS_DEFAULT,
-    free_space = builders.free_space.high,
 )
 
 ci.builder(
@@ -1249,6 +1241,9 @@
     os = os.MAC_DEFAULT,
     cores = None,
     xcode = xcode.x14main,
+    reclient_bootstrap_env = {
+        "RBE_experimental_goma_deps_cache": "true",
+    },
 )
 
 ci.builder(
@@ -1270,6 +1265,9 @@
     os = os.LINUX_DEFAULT,
     cores = 32,
     ssd = True,
+    reclient_bootstrap_env = {
+        "RBE_experimental_goma_deps_cache": "true",
+    },
 )
 
 ci.builder(
@@ -1291,6 +1289,9 @@
     os = os.LINUX_DEFAULT,
     cores = 16,
     ssd = True,
+    reclient_bootstrap_env = {
+        "RBE_experimental_goma_deps_cache": "true",
+    },
 )
 
 ci.builder(
@@ -1315,6 +1316,7 @@
     cores = None,
     reclient_bootstrap_env = {
         "GLOG_vmodule": "bridge*=2",
+        "RBE_experimental_goma_deps_cache": "true",
     },
 )
 
@@ -1339,6 +1341,9 @@
     os = os.WINDOWS_DEFAULT,
     ssd = True,
     cores = 32,
+    reclient_bootstrap_env = {
+        "RBE_experimental_goma_deps_cache": "true",
+    },
 )
 
 ci.builder(
@@ -1361,6 +1366,9 @@
     os = os.LINUX_DEFAULT,
     cores = 32,
     ssd = True,
+    reclient_bootstrap_env = {
+        "RBE_experimental_goma_deps_cache": "true",
+    },
 )
 
 ci.builder(
@@ -1384,6 +1392,9 @@
     cores = None,
     ssd = True,
     xcode = xcode.x14main,
+    reclient_bootstrap_env = {
+        "RBE_experimental_goma_deps_cache": "true",
+    },
 )
 
 # Build Perf builders use CQ reclient instance and high reclient jobs/cores and
diff --git a/infra/config/subprojects/reclient/reclient.star b/infra/config/subprojects/reclient/reclient.star
index 579856a..e1b9cdd 100644
--- a/infra/config/subprojects/reclient/reclient.star
+++ b/infra/config/subprojects/reclient/reclient.star
@@ -308,6 +308,10 @@
     cores = None,
     xcode = xcode.x13main,
     priority = 35,
+    reclient_bootstrap_env = {
+        "RBE_ip_timeout": "-1s",
+        "GLOG_vmodule": "bridge*=2",
+    },
 )
 
 fyi_reclient_staging_builder(
@@ -332,6 +336,9 @@
     cores = None,
     xcode = xcode.x13main,
     priority = 35,
+    reclient_bootstrap_env = {
+        "GLOG_vmodule": "bridge*=2",
+    },
 )
 
 fyi_reclient_staging_builder(
@@ -355,6 +362,9 @@
     builderless = True,
     cores = None,
     priority = 35,
+    reclient_bootstrap_env = {
+        "GLOG_vmodule": "bridge*=2",
+    },
 )
 
 fyi_reclient_test_builder(
@@ -378,4 +388,8 @@
     builderless = True,
     cores = None,
     priority = 35,
+    reclient_bootstrap_env = {
+        "RBE_ip_timeout": "-1s",
+        "GLOG_vmodule": "bridge*=2",
+    },
 )
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index f778419..3661a0e 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -1272,6 +1272,11 @@
      flag_descriptions::kEnableRefineDataSourceReloadReportingDescription,
      flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kEnableRefineDataSourceReloadReporting)},
+    {"enable-compromised-passwords-muting",
+     flag_descriptions::kEnableCompromisedPasswordsMutingName,
+     flag_descriptions::kEnableCompromisedPasswordsMutingDescription,
+     flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(password_manager::features::kMuteCompromisedPasswords)},
 };
 
 bool SkipConditionalFeatureEntry(const flags_ui::FeatureEntry& entry) {
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index 8aea288..b56636e 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -259,6 +259,12 @@
 const char kEnableCheckVisibilityOnAttentionLogStartDescription[] =
     "Enable checking feed visibility on attention log start.";
 
+const char kEnableCompromisedPasswordsMutingName[] =
+    "Enable the muting of compromised passwords in the Password Manager";
+const char kEnableCompromisedPasswordsMutingDescription[] =
+    "Enable the compromised password alert mutings in Password Manager to be "
+    "respected in the app.";
+
 const char kEnableDiscoverFeedDiscoFeedEndpointName[] =
     "Enable discover feed discofeed";
 const char kEnableDiscoverFeedDiscoFeedEndpointDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index b0afa820..46dd7ed 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -198,6 +198,11 @@
 extern const char kEnableCheckVisibilityOnAttentionLogStartName[];
 extern const char kEnableCheckVisibilityOnAttentionLogStartDescription[];
 
+// Title and description for the flag to enable the muting of compromised
+// passwords in the Password Manager.
+extern const char kEnableCompromisedPasswordsMutingName[];
+extern const char kEnableCompromisedPasswordsMutingDescription[];
+
 // Title and description for the flag to enable the sync promotion on top of the
 // discover feed.
 extern const char kEnableDiscoverFeedTopSyncPromoName[];
diff --git a/ios/chrome/browser/follow/BUILD.gn b/ios/chrome/browser/follow/BUILD.gn
index a7ffbfc..d4f6e66c 100644
--- a/ios/chrome/browser/follow/BUILD.gn
+++ b/ios/chrome/browser/follow/BUILD.gn
@@ -67,6 +67,8 @@
     "//ios/chrome/browser/follow:enums",
     "//ios/chrome/browser/follow:utils",
     "//ios/chrome/browser/history",
+    "//ios/chrome/browser/ntp:features",
+    "//ios/chrome/browser/signin",
     "//ios/chrome/browser/url",
     "//ios/web/public",
     "//ios/web/public/js_messaging",
diff --git a/ios/chrome/browser/follow/follow_tab_helper.mm b/ios/chrome/browser/follow/follow_tab_helper.mm
index 69e61e1..ff397922 100644
--- a/ios/chrome/browser/follow/follow_tab_helper.mm
+++ b/ios/chrome/browser/follow/follow_tab_helper.mm
@@ -27,6 +27,9 @@
 #import "ios/chrome/browser/follow/follow_service_factory.h"
 #import "ios/chrome/browser/follow/follow_util.h"
 #import "ios/chrome/browser/history/history_service_factory.h"
+#import "ios/chrome/browser/ntp/features.h"
+#import "ios/chrome/browser/signin/authentication_service.h"
+#import "ios/chrome/browser/signin/authentication_service_factory.h"
 #import "ios/chrome/browser/url/url_util.h"
 #import "ios/chrome/grit/ios_strings.h"
 #import "ios/web/public/js_messaging/web_frame.h"
@@ -104,9 +107,23 @@
 void FollowTabHelper::PageLoaded(
     web::WebState* web_state,
     web::PageLoadCompletionStatus load_completion_status) {
-  // TODO(crbug.com/1340154): move the checking to `follow_iph_presenter_`
-  // (FollowIPHCoordinator), so this class won't need to access browser_state
-  // anymore, which brings convinience to testing.
+  // Do not show follow IPH if the user is not signed in.
+  ChromeBrowserState* browserState =
+      ChromeBrowserState::FromBrowserState(web_state->GetBrowserState());
+  AuthenticationService* authenticationService =
+      AuthenticationServiceFactory::GetForBrowserState(browserState);
+  if (!authenticationService || !authenticationService->GetPrimaryIdentity(
+                                    signin::ConsentLevel::kSignin)) {
+    return;
+  }
+
+  // Do not show Follow IPH if it is disabled.
+  if (!base::FeatureList::IsEnabled(
+          feature_engagement::kIPHFollowWhileBrowsingFeature)) {
+    return;
+  }
+
+  DCHECK(IsWebChannelsEnabled());
 
   // Record when the page was successfully loaded. Computing whether the
   // IPH needs to be displayed is done asynchronously and the time used
@@ -114,8 +131,8 @@
   // displayed.
   const base::Time page_load_time = base::Time::Now();
 
-  // Do not update follow menu option and do not show IPH when browsing non
-  // http,https URLs and Chrome URLs, such as NTP, flags, version, sad tab, etc.
+  // Do not show IPH when browsing non http, https URLs and Chrome URLs, such as
+  // NTP, flags, version, sad tab, etc.
   const GURL& url = web_state->GetVisibleURL();
   if (UrlHasChromeScheme(url) || !url.SchemeIsHTTPOrHTTPS()) {
     return;
@@ -146,13 +163,6 @@
                                            WebPageURLs* web_page_urls) {
   DCHECK(web_state_);
 
-  // Update follow menu option if needed.
-  if (follow_menu_updater_ && should_update_follow_item_) {
-    UpdateFollowMenuItemWithURL(web_page_urls);
-  }
-
-  // Show follow in-product help (IPH) if eligible.
-
   // Don't show follow in-product help (IPH) if there's no presenter. Ex.
   // follow_iph_presenter_ is nil when link preview page is loaded.
   if (!follow_iph_presenter_) {
diff --git a/ios/chrome/browser/ui/autofill/form_input_accessory/branding_view_controller.mm b/ios/chrome/browser/ui/autofill/form_input_accessory/branding_view_controller.mm
index 7c28937..fa215f52 100644
--- a/ios/chrome/browser/ui/autofill/form_input_accessory/branding_view_controller.mm
+++ b/ios/chrome/browser/ui/autofill/form_input_accessory/branding_view_controller.mm
@@ -57,8 +57,6 @@
       NOTREACHED();
       break;
   }
-  UIImage* logo = [[UIImage imageNamed:logoName]
-      imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
   UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];
   if (@available(iOS 15.0, *)) {
     UIButtonConfiguration* buttonConfig =
@@ -69,13 +67,11 @@
   } else {
     button.imageEdgeInsets = UIEdgeInsetsMake(0, kLeadingInset, 0, 0);
   }
-  [button setImage:logo forState:UIControlStateNormal];
-  [button setImage:logo forState:UIControlStateHighlighted];
   button.accessibilityIdentifier = kBrandingButtonAXId;
-  button.imageView.contentMode = UIViewContentModeScaleAspectFit;
   button.isAccessibilityElement = NO;  // Prevents VoiceOver users from tap.
   button.translatesAutoresizingMaskIntoConstraints = NO;
   self.view = button;
+  [self configureBrandingWithImageName:logoName];
 
   // Adds keyboard popup listener to show animation when keyboard is fully
   // settled.
@@ -86,6 +82,22 @@
            object:nil];
 }
 
+#pragma mark - UITraitEnvironment
+
+// UIImages with rendering mode UIImageRenderingModeAlwaysOriginal do not
+// automatically respond when the user interface mode changes. To fix this, the
+// view controller should get notified on these changes and update the image
+// accordingly. Similar issue and fix as crbug.com/998090.
+- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
+  [super traitCollectionDidChange:previousTraitCollection];
+  if (self.traitCollection.userInterfaceStyle !=
+          previousTraitCollection.userInterfaceStyle &&
+      autofill::features::GetAutofillBrandingType() ==
+          autofill::features::AutofillBrandingType::kMonotone) {
+    [self configureBrandingWithImageName:@"monotone_branding_icon"];
+  }
+}
+
 #pragma mark - Accessors
 
 - (BOOL)shouldAnimate {
@@ -109,6 +121,17 @@
 
 #pragma mark - Private
 
+// Add the branding image with the correct size, and make sure it persists
+// across different button states.
+- (void)configureBrandingWithImageName:(NSString*)name {
+  UIImage* logo = [[UIImage imageNamed:name]
+      imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
+  UIButton* button = (UIButton*)self.view;
+  [button setImage:logo forState:UIControlStateNormal];
+  [button setImage:logo forState:UIControlStateHighlighted];
+  button.imageView.contentMode = UIViewContentModeScaleAspectFit;
+}
+
 // Check if the branding icon is visible and should perform an animation, and do
 // so if it should.
 - (void)onKeyboardAnimationComplete {
diff --git a/mojo/core/ipcz_driver/transport.cc b/mojo/core/ipcz_driver/transport.cc
index ebf2e9a1..a2227ea 100644
--- a/mojo/core/ipcz_driver/transport.cc
+++ b/mojo/core/ipcz_driver/transport.cc
@@ -101,8 +101,10 @@
 #if BUILDFLAG(IS_WIN)
 // Encodes a Windows HANDLE value for transmission within a serialized driver
 // object payload. See documentation on HandleOwner above for general notes
-// about how handles are communicated over IPC on Windows.
-void EncodeHandle(PlatformHandle& handle,
+// about how handles are communicated over IPC on Windows. Returns true on
+// success, with the encoded handle value in `out_handle`. Returns false if
+// handle duplication failed.
+bool EncodeHandle(PlatformHandle& handle,
                   const base::Process& remote_process,
                   HandleOwner handle_owner,
                   HANDLE& out_handle) {
@@ -112,7 +114,7 @@
     // be sufficiently privileged and equipped to duplicate such handles to
     // itself.
     out_handle = handle.ReleaseHandle();
-    return;
+    return true;
   }
 
   // To encode a handle that already belongs to the recipient, we must first
@@ -121,10 +123,9 @@
   // handle to the remote process.
   DCHECK_EQ(handle_owner, HandleOwner::kRecipient);
   DCHECK(remote_process.IsValid());
-  BOOL result = ::DuplicateHandle(
-      ::GetCurrentProcess(), handle.ReleaseHandle(), remote_process.Handle(),
-      &out_handle, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
-  DCHECK(result);
+  return ::DuplicateHandle(::GetCurrentProcess(), handle.ReleaseHandle(),
+                           remote_process.Handle(), &out_handle, 0, FALSE,
+                           DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
 }
 
 // Decodes a Windows HANDLE value from a transmission containing a serialized
@@ -398,17 +399,18 @@
     return IPCZ_RESULT_INVALID_ARGUMENT;
   }
 
+  bool ok = true;
   for (size_t i = 0; i < object_num_handles; ++i) {
 #if BUILDFLAG(IS_WIN)
-    EncodeHandle(platform_handles[i], remote_process_, handle_owner,
-                 handle_data[i]);
+    ok &= EncodeHandle(platform_handles[i], remote_process_, handle_owner,
+                       handle_data[i]);
 #else
     handles[i] = TransmissiblePlatformHandle::ReleaseAsHandle(
         base::MakeRefCounted<TransmissiblePlatformHandle>(
             std::move(platform_handles[i])));
 #endif
   }
-  return IPCZ_RESULT_OK;
+  return ok ? IPCZ_RESULT_OK : IPCZ_RESULT_INVALID_ARGUMENT;
 }
 
 IpczResult Transport::DeserializeObject(
diff --git a/net/url_request/url_request.cc b/net/url_request/url_request.cc
index 2446869d..b794d96e 100644
--- a/net/url_request/url_request.cc
+++ b/net/url_request/url_request.cc
@@ -12,7 +12,6 @@
 #include "base/compiler_specific.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/rand_util.h"
-#include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/synchronization/lock.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -47,9 +46,6 @@
 #include "url/gurl.h"
 #include "url/origin.h"
 
-using base::Time;
-using std::string;
-
 namespace net {
 
 namespace {
@@ -225,8 +221,8 @@
   return upload_data_stream_.get() != nullptr;
 }
 
-void URLRequest::SetExtraRequestHeaderByName(const string& name,
-                                             const string& value,
+void URLRequest::SetExtraRequestHeaderByName(base::StringPiece name,
+                                             base::StringPiece value,
                                              bool overwrite) {
   DCHECK(!is_pending_ || is_redirecting_);
   if (overwrite) {
@@ -236,7 +232,7 @@
   }
 }
 
-void URLRequest::RemoveRequestHeaderByName(const string& name) {
+void URLRequest::RemoveRequestHeaderByName(base::StringPiece name) {
   DCHECK(!is_pending_ || is_redirecting_);
   extra_request_headers_.RemoveHeader(name);
 }
@@ -319,9 +315,8 @@
   return base::Value(std::move(dict));
 }
 
-void URLRequest::LogBlockedBy(const char* blocked_by) {
-  DCHECK(blocked_by);
-  DCHECK_GT(strlen(blocked_by), 0u);
+void URLRequest::LogBlockedBy(base::StringPiece blocked_by) {
+  DCHECK(!blocked_by.empty());
 
   // Only log information to NetLog during startup and certain deferring calls
   // to delegates.  For all reads but the first, do nothing.
@@ -329,14 +324,14 @@
     return;
 
   LogUnblocked();
-  blocked_by_ = blocked_by;
+  blocked_by_ = std::string(blocked_by);
   use_blocked_by_as_load_param_ = false;
 
   net_log_.BeginEventWithStringParams(NetLogEventType::DELEGATE_INFO,
                                       "delegate_blocked_by", blocked_by_);
 }
 
-void URLRequest::LogAndReportBlockedBy(const char* source) {
+void URLRequest::LogAndReportBlockedBy(base::StringPiece source) {
   LogBlockedBy(source);
   use_blocked_by_as_load_param_ = true;
 }
@@ -368,8 +363,8 @@
   return UploadProgress();
 }
 
-void URLRequest::GetResponseHeaderByName(const string& name,
-                                         string* value) const {
+void URLRequest::GetResponseHeaderByName(base::StringPiece name,
+                                         std::string* value) const {
   DCHECK(value);
   if (response_info_.headers.get()) {
     response_info_.headers->GetNormalizedHeader(name, value);
@@ -409,12 +404,12 @@
   return job_->GetTransactionRemoteEndpoint(endpoint);
 }
 
-void URLRequest::GetMimeType(string* mime_type) const {
+void URLRequest::GetMimeType(std::string* mime_type) const {
   DCHECK(job_.get());
   job_->GetMimeType(mime_type);
 }
 
-void URLRequest::GetCharset(string* charset) const {
+void URLRequest::GetCharset(std::string* charset) const {
   DCHECK(job_.get());
   job_->GetCharset(charset);
 }
@@ -512,13 +507,13 @@
 }
 #endif
 
-void URLRequest::SetReferrer(const std::string& referrer) {
+void URLRequest::SetReferrer(base::StringPiece referrer) {
   DCHECK(!is_pending_);
   GURL referrer_url(referrer);
   if (referrer_url.is_valid()) {
     referrer_ = referrer_url.GetAsReferrer().spec();
   } else {
-    referrer_ = referrer;
+    referrer_ = std::string(referrer);
   }
 }
 
diff --git a/net/url_request/url_request.h b/net/url_request/url_request.h
index 778a965..8d820e7 100644
--- a/net/url_request/url_request.h
+++ b/net/url_request/url_request.h
@@ -14,7 +14,7 @@
 #include "base/containers/flat_set.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
-#include "base/strings/string_piece_forward.h"
+#include "base/strings/string_piece.h"
 #include "base/supports_user_data.h"
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
@@ -375,7 +375,7 @@
   // the request is started. The referrer URL may be suppressed or changed
   // during the course of the request, for example because of a referrer policy
   // set with set_referrer_policy().
-  void SetReferrer(const std::string& referrer);
+  void SetReferrer(base::StringPiece referrer);
 
   // The referrer policy to apply when updating the referrer during redirects.
   // The referrer policy may only be changed before Start() is called. Any
@@ -406,10 +406,10 @@
   // Set or remove a extra request header.  These methods may only be called
   // before Start() is called, or between receiving a redirect and trying to
   // follow it.
-  void SetExtraRequestHeaderByName(const std::string& name,
-                                   const std::string& value,
+  void SetExtraRequestHeaderByName(base::StringPiece name,
+                                   base::StringPiece value,
                                    bool overwrite);
-  void RemoveRequestHeaderByName(const std::string& name);
+  void RemoveRequestHeaderByName(base::StringPiece name);
 
   // Sets all extra request headers.  Any extra request headers set by other
   // methods are overwritten by this method.  This method may only be called
@@ -450,15 +450,13 @@
   // Logs information about the what external object currently blocking the
   // request.  LogUnblocked must be called before resuming the request.  This
   // can be called multiple times in a row either with or without calling
-  // LogUnblocked between calls.  |blocked_by| must not be NULL or have length
-  // 0.
-  void LogBlockedBy(const char* blocked_by);
+  // LogUnblocked between calls.  |blocked_by| must not be empty.
+  void LogBlockedBy(base::StringPiece blocked_by);
 
   // Just like LogBlockedBy, but also makes GetLoadState return source as the
   // |param| in the value returned by GetLoadState.  Calling LogUnblocked or
-  // LogBlockedBy will clear the load param.  |blocked_by| must not be NULL or
-  // have length 0.
-  void LogAndReportBlockedBy(const char* blocked_by);
+  // LogBlockedBy will clear the load param.  |blocked_by| must not be empty.
+  void LogAndReportBlockedBy(base::StringPiece blocked_by);
 
   // Logs that the request is no longer blocked by the last caller to
   // LogBlockedBy.
@@ -473,7 +471,7 @@
   // that appear more than once in the response are coalesced, with values
   // separated by commas (per RFC 2616). This will not work with cookies since
   // comma can be used in cookie values.
-  void GetResponseHeaderByName(const std::string& name,
+  void GetResponseHeaderByName(base::StringPiece name,
                                std::string* value) const;
 
   // The time when |this| was constructed.
@@ -811,8 +809,8 @@
     return expected_response_checksum_;
   }
 
-  void set_expected_response_checksum(const std::string& checksum) {
-    expected_response_checksum_ = checksum;
+  void set_expected_response_checksum(base::StringPiece checksum) {
+    expected_response_checksum_ = std::string(checksum);
   }
 
   static bool DefaultCanUseCookies();
diff --git a/services/device/generic_sensor/platform_sensor_fusion.cc b/services/device/generic_sensor/platform_sensor_fusion.cc
index d08aa3b..c8f2560a 100644
--- a/services/device/generic_sensor/platform_sensor_fusion.cc
+++ b/services/device/generic_sensor/platform_sensor_fusion.cc
@@ -4,6 +4,8 @@
 
 #include "services/device/generic_sensor/platform_sensor_fusion.h"
 
+#include <algorithm>
+
 #include "base/bind.h"
 #include "base/check.h"
 #include "base/memory/raw_ptr.h"
@@ -45,7 +47,7 @@
     const auto& types = fusion_algorithm_->source_types();
     DCHECK(!types.empty());
     // Make sure there are no dups.
-    DCHECK(base::ranges::adjacent_find(types) == types.end());
+    DCHECK(std::adjacent_find(types.begin(), types.end()) == types.end());
     DCHECK(result_callback_);
     DCHECK(reading_buffer_);
     DCHECK(provider_);
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 18bebcc..07957d2 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -28470,6 +28470,77 @@
       },
       {
         "args": [
+          "--use-persistent-shell",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-config=../../tools/android/avd/proto/generic_android24.textpb",
+          "--git-revision=${got_revision}"
+        ],
+        "ci_only": true,
+        "experiment_percentage": 100,
+        "isolate_profile_data": true,
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "chrome_public_persistent_shell_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "chrome_public_persistent_shell_test_apk",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "machine_type": "n1-standard-4|e2-standard-4",
+              "os": "Ubuntu-18.04",
+              "pool": "chromium.tests.avd"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "generic_android25",
+              "path": ".android_emulator/generic_android25"
+            }
+          ],
+          "optional_dimensions": {
+            "60": [
+              {
+                "caches": "generic_android25"
+              }
+            ]
+          },
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 25
+        },
+        "test": "chrome_public_test_apk",
+        "test_id_prefix": "ninja://chrome/android:chrome_public_test_apk/"
+      },
+      {
+        "args": [
           "--gtest_filter=org.chromium.chrome.browser.contextualsearch.ContextualSearchManagerTest.test*ExternalNavigationWithUserGesture*:org.chromium.shape_detection.*",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -35445,6 +35516,61 @@
       },
       {
         "args": [
+          "--use-persistent-shell",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--git-revision=${got_revision}"
+        ],
+        "ci_only": true,
+        "experiment_percentage": 100,
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "chrome_public_persistent_shell_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "chrome_public_persistent_shell_test_apk",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 19
+        },
+        "test": "chrome_public_test_apk",
+        "test_id_prefix": "ninja://chrome/android:chrome_public_test_apk/"
+      },
+      {
+        "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
           "--git-revision=${got_revision}"
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index d56e89c..598e595 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -1234,6 +1234,16 @@
     },
   },
   # TODO(crbug/1239300): Remove when experiment is done.
+  'chrome_public_persistent_shell_test_apk': {
+    'modifications': {
+      'android-nougat-x86-rel': {
+        'swarming': {
+          'shards': 25,
+        },
+      },
+    },
+  },
+  # TODO(crbug/1239300): Remove when experiment is done.
   'chrome_public_persistent_shell_unit_test_apk': {
     'modifications': {
       'android-nougat-x86-rel': {
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index d1e2793..4de501c 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -461,19 +461,33 @@
 
     # TODO(crbug/1239300): Remove when persistent_shell experiment is over.
     'chrome_persistent_shell_tests': {
-       'chrome_public_persistent_shell_unit_test_apk': {
-         'args': [
-           '--use-persistent-shell',
-         ],
-         'ci_only': True,
-         'test': 'chrome_public_unit_test_apk',
-         'experiment_percentage': 100,
-         'swarming': {
-           'shards': 2,
-         },
-         'mixins': [
-           'skia_gold_test',
-         ],
+      'chrome_public_persistent_shell_test_apk': {
+        'args': [
+          '--use-persistent-shell',
+        ],
+        'ci_only': True,
+        'test': 'chrome_public_test_apk',
+        'experiment_percentage': 100,
+        'swarming': {
+          'shards': 19,
+        },
+        'mixins': [
+          'skia_gold_test',
+        ],
+      },
+      'chrome_public_persistent_shell_unit_test_apk': {
+        'args': [
+          '--use-persistent-shell',
+        ],
+        'ci_only': True,
+        'test': 'chrome_public_unit_test_apk',
+        'experiment_percentage': 100,
+        'swarming': {
+          'shards': 2,
+        },
+        'mixins': [
+          'skia_gold_test',
+        ],
       }
     },
 
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index f7c1742..2ff6d15 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -7305,20 +7305,15 @@
                     "params": {
                         "EntitySuggestionsReduceLatencyDecoderTimeout": "405",
                         "MaxZeroSuggestMatches": "10",
-                        "OmniboxLocalZeroSuggestAgeThreshold": "90",
                         "OmniboxMaxURLMatches": "7",
-                        "PrefixSuggestIgnoreDuplicateVisits": "true",
-                        "UIMaxAutocompleteMatches": "8",
-                        "ZeroSuggestIgnoreDuplicateVisits": "false"
+                        "UIMaxAutocompleteMatches": "8"
                     },
                     "enable_features": [
-                        "LocalHistorySuggestRevamp",
                         "OmniboxBlurWithEscape",
                         "OmniboxClosePopupWithEscape",
                         "OmniboxDocumentProviderAso",
                         "OmniboxEntitySuggestionsReduceLatency",
                         "OmniboxKeywordSearchButton",
-                        "OmniboxLocalZeroSuggestAgeThreshold",
                         "OmniboxMaxURLMatches",
                         "OmniboxMaxZeroSuggestMatches",
                         "OmniboxPreserveLongerShortcutsText",
diff --git a/third_party/blink/common/tokens/multi_token_unittest.cc b/third_party/blink/common/tokens/multi_token_unittest.cc
index ec02723..294afbc 100644
--- a/third_party/blink/common/tokens/multi_token_unittest.cc
+++ b/third_party/blink/common/tokens/multi_token_unittest.cc
@@ -4,8 +4,6 @@
 
 #include "third_party/blink/public/common/tokens/multi_token.h"
 
-#include <algorithm>
-
 #include "base/types/token_type.h"
 #include "base/unguessable_token.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -16,59 +14,12 @@
 using BarToken = base::TokenType<class BarTokenTag>;
 using BazToken = base::TokenType<class BazTokenTag>;
 
-// Test MultiTokenVariantCount.
-static_assert(internal::MultiTokenVariantCount<FooToken, BarToken>::kValue == 2,
-              "unexpected count");
-static_assert(
-    internal::MultiTokenVariantCount<FooToken, BarToken, BazToken>::kValue == 3,
-    "unexpected count");
+static_assert(internal::IsBaseTokenTypeV<FooToken>);
+static_assert(!internal::IsBaseTokenTypeV<int>);
 
-// Test MultiTokenTypeRepeated.
-static_assert(!internal::MultiTokenTypeRepeated<FooToken>::kValue,
-              "unexpected repeated value");
-static_assert(!internal::MultiTokenTypeRepeated<FooToken, FooToken>::kValue,
-              "unexpected repeated value");
-static_assert(
-    !internal::MultiTokenTypeRepeated<FooToken, FooToken, BarToken>::kValue,
-    "unexpected repeated value");
-static_assert(
-    internal::MultiTokenTypeRepeated<FooToken, FooToken, BarToken, FooToken>::
-        kValue,
-    "unexpected repeated value");
-static_assert(
-    internal::MultiTokenTypeRepeated<FooToken, BarToken, FooToken, FooToken>::
-        kValue,
-    "unexpected repeated value");
-
-// Test MultiTokenAnyTypeRepeated.
-static_assert(!internal::MultiTokenAnyTypeRepeated<FooToken>::kValue,
-              "unexpected any repeated value");
-static_assert(!internal::MultiTokenAnyTypeRepeated<FooToken, BarToken>::kValue,
-              "unexpected any repeated value");
-static_assert(
-    !internal::MultiTokenAnyTypeRepeated<FooToken, BarToken, BazToken>::kValue,
-    "unexpected any repeated value");
-static_assert(
-    internal::MultiTokenAnyTypeRepeated<FooToken, BarToken, FooToken>::kValue,
-    "unexpected any repeated value");
-static_assert(
-    internal::MultiTokenAnyTypeRepeated<FooToken, BarToken, BarToken>::kValue,
-    "unexpected any repeated value");
-
-// Test MultiTokenVariantIsTokenType.
-static_assert(internal::MultiTokenVariantIsTokenType<FooToken>::kValue,
-              "unexpected is token type value");
-static_assert(!internal::MultiTokenVariantIsTokenType<int>::kValue,
-              "unexpected is token type value");
-
-// Test MultiTokenAllVariantsAreTokenType.
-static_assert(
-    internal::MultiTokenAllVariantsAreTokenType<FooToken, BarToken>::kValue,
-    "unexpected all variants are token type value");
-static_assert(!internal::MultiTokenAllVariantsAreTokenType<FooToken,
-                                                           BarToken,
-                                                           int>::kValue,
-              "unexpected all variants are token type value");
+static_assert(internal::AreAllUnique<int>);
+static_assert(!internal::AreAllUnique<int, int>);
+static_assert(!internal::AreAllUnique<int, char, int>);
 
 using FooBarToken = MultiToken<FooToken, BarToken>;
 using FooBarBazToken = MultiToken<FooToken, BarToken, BazToken>;
@@ -121,4 +72,10 @@
   EXPECT_EQ(token2.GetAs<BarToken>(), token3.GetAs<BarToken>());
 }
 
+TEST(MultiTokenTest, IndexOf) {
+  static_assert(FooBarBazToken::IndexOf<FooToken>() == 0);
+  static_assert(FooBarBazToken::IndexOf<BarToken>() == 1);
+  static_assert(FooBarBazToken::IndexOf<BazToken>() == 2);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/public/common/tokens/multi_token.h b/third_party/blink/public/common/tokens/multi_token.h
index 857f648..d1e7c71 100644
--- a/third_party/blink/public/common/tokens/multi_token.h
+++ b/third_party/blink/public/common/tokens/multi_token.h
@@ -13,143 +13,111 @@
 #include <type_traits>
 
 #include "base/unguessable_token.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
 #include "third_party/blink/public/common/tokens/multi_token_internal.h"
 
 namespace blink {
 
-// Defines MultiToken, which is effectively a variant over 2 or more
-// instances of base::TokenType.
+// `MultiToken<Tokens...>` is a variant of 2 or more token types. Each token
+// type must be an instantiation of `base::TokenType`, and each token type must
+// be unique within `Tokens...`. Unlike `base::UnguessableToken`, a `MultiToken`
+// is always valid: there is no null state. Default constructing a `MultiToken`
+// will create a `MultiToken` containing an instance of the first token type in
+// `Tokens`.
 //
-// A MultiToken<..> emulates a token like interface. When default constructed
-// it will construct itself as an instance of |TokenVariant0|. Additionally it
-// offers the following functions allowing casting and querying token types at
-// runtime:
+// Usage:
 //
-//   // Determines whether this token stores an instance of a TokenType.
-//   bool Is<TokenType>() const;
+// using CowToken = base::TokenType<class CowTokenTag>;
+// using GoatToken = base::TokenType<class GoatTokenTag>;
+// using UngulateToken = blink::MultiToken<CowToken, GoatToken>;
 //
-//   // Extracts the stored token in its original type. The stored token must
-//   // be of the provided type otherwise this will explode at runtime.
-//   const TokenType& GetAs<TokenType>() const;
+// void TeleportCow(const CowToken&);
+// void TeleportGoat(const GoatToken&);
 //
-// A variant must have at least 2 valid input types, but can have arbitrarily
-// many. They must all be distinct, and they must all be instances of
-// base::TokenType.
-template <typename TokenVariant0,
-          typename TokenVariant1,
-          typename... TokenVariants>
-class MultiToken : public internal::MultiTokenBase<TokenVariant0,
-                                                   TokenVariant1,
-                                                   TokenVariants...> {
+// void TeleportUngulate(const UngulateToken& token) {
+//   if (token.Is<CowToken>()) {
+//     TeleportCow(token.Get<CowToken>());
+//   } else if (token.Is<GoatToken>()) {
+//     TeleportGoat(token.Get<GoatToken>());
+//   }
+//   CHECK(false);  // Not reachable.
+// }
+template <typename... Tokens>
+class MultiToken {
+  static_assert(sizeof...(Tokens) > 1);
+  static_assert(std::conjunction_v<internal::IsBaseTokenType<Tokens>...>);
+  static_assert(internal::AreAllUnique<Tokens...>);
+
+  template <typename T>
+  using EnableIfIsSupportedToken =
+      internal::EnableIfIsSupportedToken<T, Tokens...>;
+
  public:
-  using Base =
-      internal::MultiTokenBase<TokenVariant0, TokenVariant1, TokenVariants...>;
+  using Storage = absl::variant<Tokens...>;
 
-  // The total number of types.
-  static const uint32_t kVariantCount = Base::VariantCount::kValue;
-
-  // Default constructor. The resulting token will be a valid token of type
-  // TokenVariant0.
+  // A default constructed token will hold a default-constructed instance (i.e.
+  // randomly initialised) of the first token type in `Tokens...`.
   MultiToken() = default;
 
-  // Copy constructors.
-  MultiToken(const MultiToken& other) = default;
-  template <typename InputTokenType,
-            typename = typename std::enable_if<
-                Base::template ValidType<InputTokenType>::kValue>::type>
+  template <typename T, EnableIfIsSupportedToken<T> = 0>
   // NOLINTNEXTLINE(google-explicit-constructor)
-  MultiToken(const InputTokenType& input_token)
-      : value_(input_token.value()),
-        variant_index_(Base::template TypeIndex<InputTokenType>::kValue) {}
+  MultiToken(const T& token) : storage_(token) {}
+  MultiToken(const MultiToken&) = default;
+
+  template <typename T, EnableIfIsSupportedToken<T> = 0>
+  MultiToken& operator=(const T& token) {
+    storage_ = token;
+    return *this;
+  }
+  MultiToken& operator=(const MultiToken&) = default;
 
   ~MultiToken() = default;
 
-  // Assignment operators.
-  MultiToken& operator=(const MultiToken& other) = default;
-  template <typename InputTokenType,
-            typename = typename std::enable_if<
-                Base::template ValidType<InputTokenType>::kValue>::type>
-  MultiToken& operator=(const InputTokenType& input_token) {
-    value_ = input_token.value();
-    variant_index_ = Base::template TypeIndex<InputTokenType>::kValue;
-    return *this;
-  }
-
-  const base::UnguessableToken& value() const { return value_; }
-  uint32_t variant_index() const { return variant_index_; }
-  std::string ToString() const { return value().ToString(); }
-
-  // Type checking.
-  template <typename InputTokenType,
-            typename = typename std::enable_if<
-                Base::template ValidType<InputTokenType>::kValue>::type>
+  // Returns true iff `this` currently holds a token of type `T`.
+  template <typename T, EnableIfIsSupportedToken<T> = 0>
   bool Is() const {
-    return variant_index_ == Base::template TypeIndex<InputTokenType>::kValue;
+    return absl::holds_alternative<T>(storage_);
   }
 
-  // Type conversion. Allows extracting the underlying token type. This should
-  // only be called for the actual type that is stored in this token. This can
-  // be checked by calling "Is<>" first.
-  template <typename InputTokenType,
-            typename = typename std::enable_if<
-                Base::template ValidType<InputTokenType>::kValue>::type>
-  InputTokenType GetAs() const {
-    CHECK(Is<InputTokenType>()) << "invalid token type cast";
-    // Type-punning via casting is undefined behaviour, so we return by value.
-    return InputTokenType(value_);
+  // Returns `T` if `this` currently holds a token of type `T`; otherwise,
+  // crashes.
+  template <typename T, EnableIfIsSupportedToken<T> = 0>
+  const T& GetAs() const {
+    return absl::get<T>(storage_);
   }
 
-  // Comparison with untyped tokens. Only compares the token value, ignoring the
-  // type.
-  int Compare(const base::UnguessableToken& other) const {
-    return Base::CompareImpl(value_, other);
-  }
-  bool operator<(const base::UnguessableToken& other) const {
-    return Compare(other) == -1;
-  }
-  bool operator==(const base::UnguessableToken& other) const {
-    return Compare(other) == 0;
-  }
-  bool operator!=(const base::UnguessableToken& other) const {
-    return Compare(other) != 0;
+  // Comparison operators
+  friend bool operator<(const MultiToken& lhs, const MultiToken& rhs) {
+    return lhs.storage_ < rhs.storage_;
   }
 
-  // Comparison with other MultiTokens. Compares by token, then type.
-  int Compare(const MultiToken& other) const {
-    return Base::CompareImpl(std::tie(value_, variant_index_),
-                             std::tie(other.value_, other.variant_index_));
+  friend bool operator==(const MultiToken& lhs, const MultiToken& rhs) {
+    return lhs.storage_ == rhs.storage_;
   }
-  bool operator<(const MultiToken& other) const { return Compare(other) == -1; }
-  bool operator==(const MultiToken& other) const { return Compare(other) == 0; }
-  bool operator!=(const MultiToken& other) const { return Compare(other) != 0; }
 
-  // Comparison with individual typed tokens. Compares by token, then type.
-  template <typename InputTokenType,
-            typename = typename std::enable_if<
-                Base::template ValidType<InputTokenType>::kValue>::type>
-  int Compare(const InputTokenType& other) const {
-    static constexpr uint32_t kInputTokenTypeIndex =
-        Base::template TypeIndex<InputTokenType>::kValue;
-    return Base::CompareImpl(std::tie(value_, variant_index_),
-                             std::tie(other.value(), kInputTokenTypeIndex));
+  friend bool operator!=(const MultiToken& lhs, const MultiToken& rhs) {
+    return !(lhs == rhs);
   }
-  template <typename InputTokenType,
-            typename = typename std::enable_if<
-                Base::template ValidType<InputTokenType>::kValue>::type>
-  bool operator<(const InputTokenType& other) const {
-    return Compare(other) == -1;
+
+  template <typename T, EnableIfIsSupportedToken<T> = 0>
+  friend bool operator==(const MultiToken& lhs, const T& rhs) {
+    return absl::holds_alternative<T>(lhs.storage_) &&
+           absl::get<T>(lhs.storage_) == rhs;
   }
-  template <typename InputTokenType,
-            typename = typename std::enable_if<
-                Base::template ValidType<InputTokenType>::kValue>::type>
-  bool operator==(const InputTokenType& other) const {
-    return Compare(other) == 0;
+
+  template <typename T, EnableIfIsSupportedToken<T> = 0>
+  friend bool operator==(const T& lhs, const MultiToken& rhs) {
+    return rhs == lhs;
   }
-  template <typename InputTokenType,
-            typename = typename std::enable_if<
-                Base::template ValidType<InputTokenType>::kValue>::type>
-  bool operator!=(const InputTokenType& other) const {
-    return Compare(other) != 0;
+
+  template <typename T, EnableIfIsSupportedToken<T> = 0>
+  friend bool operator!=(const MultiToken& lhs, const T& rhs) {
+    return !(lhs == rhs);
+  }
+
+  template <typename T, EnableIfIsSupportedToken<T> = 0>
+  friend bool operator!=(const T& lhs, const MultiToken& rhs) {
+    return !(lhs == rhs);
   }
 
   // Hash functor for use in unordered containers.
@@ -157,18 +125,57 @@
     using argument_type = MultiToken;
     using result_type = size_t;
     result_type operator()(const MultiToken& token) const {
-      return base::UnguessableTokenHash()(token.value_);
+      return base::UnguessableTokenHash()(token.value());
     }
   };
 
- private:
-  // The underlying untyped token value. This will *never* be null initialized.
-  base::UnguessableToken value_ = base::UnguessableToken::Create();
+  // Prefer the above helpers where possible. These methods are primarily useful
+  // for serialization/deserialization.
 
-  // The index of the variant type that is currently stored in this token.
-  uint32_t variant_index_ = 0;
+  // Returns the underlying `base::UnguessableToken` of the currently held
+  // token.
+  const base::UnguessableToken& value() const;
+
+  // 0-based index of the currently held token's type, based on its position in
+  // `Tokens...`.
+  uint32_t variant_index() const {
+    return static_cast<uint32_t>(storage_.index());
+  }
+
+  // Returns the 0-based index that a token of type `T` would have if it were
+  // currently held.
+  template <typename T, EnableIfIsSupportedToken<T> = 0>
+  static constexpr size_t IndexOf() {
+    return absl::variant<Tag<Tokens>...>(Tag<T>()).index();
+  }
+
+  // Equivalent to `value().ToString()`.
+  std::string ToString() const;
+
+ private:
+  // Helper struct for IndexOf(); a `base::TokenType` is never usable as a
+  // literal type but a Tag<base::TokenType> is.
+  template <typename T>
+  struct Tag {};
+
+  Storage storage_;
 };
 
+template <typename... Tokens>
+const base::UnguessableToken& MultiToken<Tokens...>::value() const {
+  return absl::visit(
+      [](const auto& token) -> const base::UnguessableToken& {
+        return token.value();
+      },
+      storage_);
+}
+
+template <typename... Tokens>
+std::string MultiToken<Tokens...>::ToString() const {
+  return absl::visit([](const auto& token) { return token.ToString(); },
+                     storage_);
+}
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_PUBLIC_COMMON_TOKENS_MULTI_TOKEN_H_
diff --git a/third_party/blink/public/common/tokens/multi_token_internal.h b/third_party/blink/public/common/tokens/multi_token_internal.h
index 65f05364..8a1a06c 100644
--- a/third_party/blink/public/common/tokens/multi_token_internal.h
+++ b/third_party/blink/public/common/tokens/multi_token_internal.h
@@ -8,223 +8,33 @@
 #ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_TOKENS_MULTI_TOKEN_INTERNAL_H_
 #define THIRD_PARTY_BLINK_PUBLIC_COMMON_TOKENS_MULTI_TOKEN_INTERNAL_H_
 
-#include <algorithm>
-#include <cstring>
 #include <type_traits>
 
 #include "base/types/token_type.h"
-#include "base/unguessable_token.h"
 
-namespace blink {
+namespace blink::internal {
 
-namespace internal {
+template <typename T>
+struct IsBaseTokenType : std::false_type {};
 
-////////////////////////////////////////////////////////////////////////////////
-// MultiTokenVariantCount
-//
-// Counts the number of token types.
+template <typename T>
+struct IsBaseTokenType<base::TokenType<T>> : std::true_type {};
 
-template <typename... VariantTypes>
-struct MultiTokenVariantCount;
+template <typename T>
+inline constexpr bool IsBaseTokenTypeV = IsBaseTokenType<T>::value;
 
-// Recursive case.
-template <typename FirstVariantType, typename... OtherVariantTypes>
-struct MultiTokenVariantCount<FirstVariantType, OtherVariantTypes...> {
-  // Deliberately use uint32_t here so as not to incur an extra 4 bytes of
-  // overhead on 64-bit systems, as this is the same type used by the
-  // |variant_index_|.
-  static constexpr uint32_t kValue =
-      1 + MultiTokenVariantCount<OtherVariantTypes...>::kValue;
-};
-
-// Base case.
+template <typename... Types>
+bool AreAllUnique;
 template <>
-struct MultiTokenVariantCount<> {
-  static constexpr uint32_t kValue = 0;
-};
+inline constexpr bool AreAllUnique<> = true;
+template <typename T, typename... Ts>
+inline constexpr bool AreAllUnique<T, Ts...> =
+    (!std::is_same_v<T, Ts> && ...) && AreAllUnique<Ts...>;
 
-////////////////////////////////////////////////////////////////////////////////
-// MultiTokenVariantIsTokenType
-//
-// Ensures if a QueryType is a a base::TokenType<>.
+template <typename T, typename... Types>
+using EnableIfIsSupportedToken =
+    std::enable_if_t<(std::is_same_v<T, Types> || ...), int>;
 
-// Default case.
-template <typename QueryType>
-struct MultiTokenVariantIsTokenType {
-  static constexpr bool kValue = false;
-};
-
-// Specialization for base::TokenType<>.
-template <typename TokenTypeTag>
-struct MultiTokenVariantIsTokenType<::base::TokenType<TokenTypeTag>> {
-  static constexpr bool kValue = true;
-
-  // We expect an identical layout, which allows us to reinterpret_cast between
-  // types. The spec does not guarantee this, but sane compilers do. Thankfully
-  // we can check whether or not the compiler is sane (and if the behaviour is
-  // safe) at compile-time.
-  static_assert(
-      sizeof(::base::TokenType<TokenTypeTag>) ==
-          sizeof(::base::UnguessableToken),
-      "base::TokenType must have the same sizeof as base::UnguessableToken");
-  static_assert(
-      alignof(::base::TokenType<TokenTypeTag>) ==
-          alignof(::base::UnguessableToken),
-      "base::TokenType must have the same alignof as base::UnguessableToken");
-};
-
-////////////////////////////////////////////////////////////////////////////////
-// MultiTokenAllVariantsAreTokenType
-//
-// Ensures that all variants are of type base::TokenType.
-
-template <typename... VariantTypes>
-struct MultiTokenAllVariantsAreTokenType;
-
-// Recursive case.
-template <typename FirstVariantType, typename... OtherVariantTypes>
-struct MultiTokenAllVariantsAreTokenType<FirstVariantType,
-                                         OtherVariantTypes...> {
-  static constexpr bool kValue =
-      MultiTokenVariantIsTokenType<FirstVariantType>::kValue &&
-      MultiTokenAllVariantsAreTokenType<OtherVariantTypes...>::kValue;
-};
-
-// Base case.
-template <>
-struct MultiTokenAllVariantsAreTokenType<> {
-  static constexpr bool kValue = true;
-};
-
-////////////////////////////////////////////////////////////////////////////////
-// MultiTokenTypeRepeated
-//
-// Determines if a QueryType is repeated in a variadic list of types.
-
-template <typename QueryType, typename... VariantTypes>
-struct MultiTokenTypeRepeated;
-
-// Recursive case.
-template <typename QueryType,
-          typename FirstVariantType,
-          typename... OtherVariantTypes>
-struct MultiTokenTypeRepeated<QueryType,
-                              FirstVariantType,
-                              OtherVariantTypes...> {
-  static constexpr size_t kCount =
-      (std::is_same<QueryType, FirstVariantType>::value ? 1 : 0) +
-      MultiTokenTypeRepeated<QueryType, OtherVariantTypes...>::kCount;
-  static constexpr bool kValue = kCount > 1;
-};
-
-// Base case.
-template <typename QueryType>
-struct MultiTokenTypeRepeated<QueryType> {
-  static constexpr size_t kCount = 0;
-  static constexpr bool kValue = false;
-};
-
-////////////////////////////////////////////////////////////////////////////////
-// MultiTokenAnyTypeRepeated
-//
-// Determines if any type is repeated in a variadic list of types.
-
-template <typename... VariantTypes>
-struct MultiTokenAnyTypeRepeated;
-
-// Recursive case.
-template <typename FirstVariantType, typename... OtherVariantTypes>
-struct MultiTokenAnyTypeRepeated<FirstVariantType, OtherVariantTypes...> {
-  static constexpr bool kValue =
-      MultiTokenTypeRepeated<FirstVariantType,
-                             FirstVariantType,
-                             OtherVariantTypes...>::kValue ||
-      MultiTokenAnyTypeRepeated<OtherVariantTypes...>::kValue;
-};
-
-// Base case.
-template <>
-struct MultiTokenAnyTypeRepeated<> {
-  static constexpr bool kValue = false;
-};
-
-////////////////////////////////////////////////////////////////////////////////
-// MultiTokenTypeIndex
-//
-// Returns the index of a QueryType from a variadic list of N types, or N if the
-// QueryType is not found in the list.
-
-template <typename QueryType, typename... VariantTypes>
-struct MultiTokenTypeIndex;
-
-// Recursive case.
-template <typename QueryType,
-          typename FirstVariantType,
-          typename... OtherVariantTypes>
-struct MultiTokenTypeIndex<QueryType, FirstVariantType, OtherVariantTypes...> {
-  static constexpr size_t kValue =
-      (std::is_same<QueryType, FirstVariantType>::value
-           ? 0
-           : (1 +
-              MultiTokenTypeIndex<QueryType, OtherVariantTypes...>::kValue));
-};
-
-// Base case.
-template <typename QueryType>
-struct MultiTokenTypeIndex<QueryType> {
-  static constexpr size_t kValue = 0;
-};
-
-////////////////////////////////////////////////////////////////////////////////
-// MultiTokenBase
-//
-// Base class that brings helper structs into a single namespace for
-// convenience.
-template <typename... TokenVariants>
-class MultiTokenBase {
- public:
-  // Ensures that no types are repeated, as that's non-sensical.
-  using AnyRepeated = internal::MultiTokenAnyTypeRepeated<TokenVariants...>;
-  static_assert(!AnyRepeated::kValue, "input types must not be repeated");
-
-  // Ensures that all variants are instances of base::TokenType.
-  using AllVariantsAreTokenType =
-      internal::MultiTokenAllVariantsAreTokenType<TokenVariants...>;
-  static_assert(AllVariantsAreTokenType::kValue,
-                "input types must be instances of base::TokenType");
-
-  // Counts the number of variants.
-  using VariantCount = internal::MultiTokenVariantCount<TokenVariants...>;
-
-  // For determining the index of a type. Used to assign an integer ID to a
-  // type, as a kind of untyped enum.
-  template <typename QueryType>
-  struct TypeIndex
-      : public internal::MultiTokenTypeIndex<QueryType, TokenVariants...> {};
-
-  // For determining if a type is valid for this variant. Useful in enable_if
-  // statements.
-  template <typename QueryType>
-  struct ValidType {
-    static constexpr bool kValue =
-        TypeIndex<QueryType>::kValue != VariantCount::kValue;
-  };
-
-  // Helper comparator. Compares underlying types using only < and == to
-  // return -1, 0, or 1 depending on their relative values.
-  template <typename InputType>
-  static int CompareImpl(const InputType& lhs, const InputType& rhs) {
-    if (lhs < rhs)
-      return -1;
-    if (lhs == rhs)
-      return 0;
-    DCHECK(rhs < lhs);
-    return 1;
-  }
-};
-
-}  // namespace internal
-
-}  // namespace blink
+}  // namespace blink::internal
 
 #endif  // THIRD_PARTY_BLINK_PUBLIC_COMMON_TOKENS_MULTI_TOKEN_INTERNAL_H_
diff --git a/third_party/blink/public/platform/web_navigation_body_loader.h b/third_party/blink/public/platform/web_navigation_body_loader.h
index 5db2104..68628252 100644
--- a/third_party/blink/public/platform/web_navigation_body_loader.h
+++ b/third_party/blink/public/platform/web_navigation_body_loader.h
@@ -14,6 +14,7 @@
 #include "third_party/blink/public/platform/web_common.h"
 #include "third_party/blink/public/platform/web_loader_freeze_mode.h"
 #include "third_party/blink/public/platform/web_url_error.h"
+#include "third_party/blink/renderer/platform/wtf/functional.h"
 
 namespace blink {
 
@@ -54,6 +55,16 @@
         int64_t total_decoded_body_length,
         bool should_report_corb_blocking,
         const absl::optional<WebURLError>& error) = 0;
+
+    // The client can return a ProcessBackgroundDataCallback which will be
+    // called on a background thread with the decoded data. The returned
+    // callback will be called on a background thread with the same decoded data
+    // which will be given to DecodedBodyDataReceived().
+    using ProcessBackgroundDataCallback =
+        WTF::CrossThreadRepeatingFunction<void(const WebString&)>;
+    virtual ProcessBackgroundDataCallback TakeProcessBackgroundDataCallback() {
+      return ProcessBackgroundDataCallback();
+    }
   };
 
   // This method fills navigation params related to the navigation request,
diff --git a/third_party/blink/renderer/core/animation/css/css_scroll_timeline.cc b/third_party/blink/renderer/core/animation/css/css_scroll_timeline.cc
index 633f946..26fd2ec 100644
--- a/third_party/blink/renderer/core/animation/css/css_scroll_timeline.cc
+++ b/third_party/blink/renderer/core/animation/css/css_scroll_timeline.cc
@@ -45,7 +45,9 @@
                      options.reference_element_.value_or(
                          document->ScrollingElementNoLayout()),
                      options.direction_),
-      name_(options.name_) {}
+      name_(options.name_) {
+  SnapshotState();
+}
 
 bool CSSScrollTimeline::Matches(const Options& options) const {
   return (GetReferenceType() == options.reference_type_) &&
diff --git a/third_party/blink/renderer/core/animation/css/css_scroll_timeline_test.cc b/third_party/blink/renderer/core/animation/css/css_scroll_timeline_test.cc
index 32bc954..6e03f60 100644
--- a/third_party/blink/renderer/core/animation/css/css_scroll_timeline_test.cc
+++ b/third_party/blink/renderer/core/animation/css/css_scroll_timeline_test.cc
@@ -214,4 +214,27 @@
             GetDocumentAnimations().GetUnvalidatedTimelinesForTesting().size());
 }
 
+TEST_F(CSSScrollTimelineTest, DocumentScrollerInQuirksMode) {
+  GetDocument().SetCompatibilityMode(Document::kQuirksMode);
+
+  SetBodyInnerHTML(R"HTML(
+    <style>
+    @keyframes anim {
+      from { z-index: 100; }
+      to { z-index: 100; }
+    }
+    #element {
+      animation: anim 10s forwards scroll(root);
+    }
+    </style>
+    <div id=element></div>
+  )HTML");
+
+  Element* element = GetDocument().getElementById("element");
+  ASSERT_TRUE(element);
+
+  EXPECT_EQ(100, element->GetComputedStyle()->ZIndex());
+  // Don't crash.
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/css/css_view_timeline.cc b/third_party/blink/renderer/core/animation/css/css_view_timeline.cc
index 8eb78d67..a4ff48b 100644
--- a/third_party/blink/renderer/core/animation/css/css_view_timeline.cc
+++ b/third_party/blink/renderer/core/animation/css/css_view_timeline.cc
@@ -20,7 +20,9 @@
     : ViewTimeline(document,
                    options.subject_,
                    options.direction_,
-                   options.inset_) {}
+                   options.inset_) {
+  SnapshotState();
+}
 
 bool CSSViewTimeline::Matches(const Options& options) const {
   return (subject() == options.subject_) &&
diff --git a/third_party/blink/renderer/core/dom/document_encoding_data.h b/third_party/blink/renderer/core/dom/document_encoding_data.h
index cebf5a9d..f7608637 100644
--- a/third_party/blink/renderer/core/dom/document_encoding_data.h
+++ b/third_party/blink/renderer/core/dom/document_encoding_data.h
@@ -31,6 +31,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DOM_DOCUMENT_ENCODING_DATA_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_DOCUMENT_ENCODING_DATA_H_
 
+#include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_encoding.h"
 
@@ -38,7 +39,7 @@
 class TextResourceDecoder;
 struct WebEncodingData;
 
-class DocumentEncodingData {
+class CORE_EXPORT DocumentEncodingData {
   DISALLOW_NEW();
 
  public:
diff --git a/third_party/blink/renderer/core/dom/document_parser.h b/third_party/blink/renderer/core/dom/document_parser.h
index 3745b27..6aec948 100644
--- a/third_party/blink/renderer/core/dom/document_parser.h
+++ b/third_party/blink/renderer/core/dom/document_parser.h
@@ -25,12 +25,14 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_DOCUMENT_PARSER_H_
 
 #include <memory>
+#include "base/callback.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/document_encoding_data.h"
 #include "third_party/blink/renderer/platform/bindings/name_client.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
+#include "third_party/blink/renderer/platform/wtf/functional.h"
 
 namespace blink {
 
@@ -63,6 +65,11 @@
   virtual void SetHasAppendedData() {}
   virtual void AppendDecodedData(const String& data,
                                  const DocumentEncodingData& encoding_data) {}
+  using BackgroundScanCallback =
+      WTF::CrossThreadRepeatingFunction<void(const String&)>;
+  virtual BackgroundScanCallback TakeBackgroundScanCallback() {
+    return BackgroundScanCallback();
+  }
 
   // FIXME: append() should be private, but DocumentLoader and DOMPatchSupport
   // uses it for now.
diff --git a/third_party/blink/renderer/core/dom/scriptable_document_parser.cc b/third_party/blink/renderer/core/dom/scriptable_document_parser.cc
index 850bc1b..8c8ac80 100644
--- a/third_party/blink/renderer/core/dom/scriptable_document_parser.cc
+++ b/third_party/blink/renderer/core/dom/scriptable_document_parser.cc
@@ -67,6 +67,12 @@
   return nullptr;
 }
 
+bool ScriptableDocumentParser::HasInlineScriptStreamerForTesting(
+    const String& source) {
+  base::AutoLock lock(streamers_lock_);
+  return inline_script_streamers_.Contains(source);
+}
+
 void ScriptableDocumentParser::AddCSSTokenizer(
     const String& source,
     std::unique_ptr<CachedCSSTokenizer> tokenizer) {
diff --git a/third_party/blink/renderer/core/dom/scriptable_document_parser.h b/third_party/blink/renderer/core/dom/scriptable_document_parser.h
index 5353377..0f90ee5c 100644
--- a/third_party/blink/renderer/core/dom/scriptable_document_parser.h
+++ b/third_party/blink/renderer/core/dom/scriptable_document_parser.h
@@ -78,6 +78,7 @@
   // The returned streamer is guaranteed to be correct for script text that
   // matches the passed in |source|.
   InlineScriptStreamer* TakeInlineScriptStreamer(const String& source);
+  bool HasInlineScriptStreamerForTesting(const String& source);
 
   // Adds a tokenizer for |source| which can be later retrieved with
   // TakeCSSTokenizer(). This may be called on any thread.
diff --git a/third_party/blink/renderer/core/dom/tree_ordered_map.cc b/third_party/blink/renderer/core/dom/tree_ordered_map.cc
index 09ff639..70a6a1f 100644
--- a/third_party/blink/renderer/core/dom/tree_ordered_map.cc
+++ b/third_party/blink/renderer/core/dom/tree_ordered_map.cc
@@ -62,7 +62,8 @@
 
 inline bool KeyMatchesMapName(const AtomicString& key, const Element& element) {
   auto* html_map_element = DynamicTo<HTMLMapElement>(element);
-  return html_map_element && html_map_element->GetName() == key;
+  return html_map_element && (html_map_element->GetName() == key ||
+                              html_map_element->GetIdAttribute() == key);
 }
 
 inline bool KeyMatchesSlotName(const AtomicString& key,
diff --git a/third_party/blink/renderer/core/dom/tree_scope.cc b/third_party/blink/renderer/core/dom/tree_scope.cc
index 216ff6cae..d367536 100644
--- a/third_party/blink/renderer/core/dom/tree_scope.cc
+++ b/third_party/blink/renderer/core/dom/tree_scope.cc
@@ -178,20 +178,24 @@
 
 void TreeScope::AddImageMap(HTMLMapElement& image_map) {
   const AtomicString& name = image_map.GetName();
-  if (!name)
+  const AtomicString& id = image_map.GetIdAttribute();
+  if (!name && !id)
     return;
   if (!image_maps_by_name_)
     image_maps_by_name_ = MakeGarbageCollected<TreeOrderedMap>();
-  image_maps_by_name_->Add(name, image_map);
+  if (name)
+    image_maps_by_name_->Add(name, image_map);
+  if (id)
+    image_maps_by_name_->Add(id, image_map);
 }
 
 void TreeScope::RemoveImageMap(HTMLMapElement& image_map) {
   if (!image_maps_by_name_)
     return;
-  const AtomicString& name = image_map.GetName();
-  if (!name)
-    return;
-  image_maps_by_name_->Remove(name, image_map);
+  if (const AtomicString& name = image_map.GetName())
+    image_maps_by_name_->Remove(name, image_map);
+  if (const AtomicString& id = image_map.GetIdAttribute())
+    image_maps_by_name_->Remove(id, image_map);
 }
 
 HTMLMapElement* TreeScope::GetImageMap(const String& url) const {
@@ -203,6 +207,8 @@
   if (hash_pos == kNotFound)
     return nullptr;
   String name = url.Substring(hash_pos + 1);
+  if (name.empty())
+    return nullptr;
   return To<HTMLMapElement>(
       image_maps_by_name_->GetElementByMapName(AtomicString(name), *this));
 }
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index c9d4e71..b3e530f 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -336,6 +336,7 @@
   visitor->Trace(lifecycle_observers_);
   visitor->Trace(fullscreen_video_elements_);
   visitor->Trace(pending_transform_updates_);
+  visitor->Trace(pending_opacity_updates_);
 }
 
 void LocalFrameView::ForAllChildViewsAndPlugins(
@@ -5012,17 +5013,52 @@
   return true;
 }
 
-void LocalFrameView::UpdateAllPendingTransforms() {
+bool LocalFrameView::UpdateAllPendingTransforms() {
   DCHECK(GetFrame().IsLocalRoot() || !IsAttached());
-  ForAllNonThrottledLocalFrameViews([](LocalFrameView& frame_view) {
+  bool updated = false;
+  ForAllNonThrottledLocalFrameViews([&updated](LocalFrameView& frame_view) {
     if (frame_view.pending_transform_updates_) {
       for (const LayoutObject* object :
            *frame_view.pending_transform_updates_) {
         PaintPropertyTreeBuilder::DirectlyUpdateTransformMatrix(*object);
+        updated = true;
       }
       frame_view.pending_transform_updates_->clear();
     }
   });
+  return updated;
 }
 
+void LocalFrameView::AddPendingOpacityUpdate(LayoutObject& object) {
+  if (!pending_opacity_updates_) {
+    pending_opacity_updates_ =
+        MakeGarbageCollected<HeapHashSet<Member<LayoutObject>>>();
+  }
+  pending_opacity_updates_->insert(&object);
+}
+
+bool LocalFrameView::RemovePendingOpacityUpdate(const LayoutObject& object) {
+  if (!pending_opacity_updates_)
+    return false;
+  auto it = pending_opacity_updates_->find(const_cast<LayoutObject*>(&object));
+  if (it == pending_opacity_updates_->end())
+    return false;
+  pending_opacity_updates_->erase(it);
+  return true;
+}
+
+bool LocalFrameView::UpdateAllPendingOpacityUpdates() {
+  DCHECK(GetFrame().IsLocalRoot() || !IsAttached());
+  bool updated = false;
+  ForAllNonThrottledLocalFrameViews([&updated](LocalFrameView& frame_view) {
+    if (frame_view.pending_opacity_updates_) {
+      for (const LayoutObject* object : *frame_view.pending_opacity_updates_) {
+        PaintPropertyTreeBuilder::DirectlyUpdateOpacityValue(*object);
+        updated = true;
+      }
+      frame_view.pending_opacity_updates_->clear();
+    }
+  });
+  return updated;
+}
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index bb71479a..4846c14f58 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -769,7 +769,11 @@
 
   void AddPendingTransformUpdate(LayoutObject& object);
   bool RemovePendingTransformUpdate(const LayoutObject& object);
-  void UpdateAllPendingTransforms();
+  bool UpdateAllPendingTransforms();
+
+  void AddPendingOpacityUpdate(LayoutObject& object);
+  bool RemovePendingOpacityUpdate(const LayoutObject& object);
+  bool UpdateAllPendingOpacityUpdates();
 
  protected:
   void FrameRectsChanged(const gfx::Rect&) override;
@@ -1176,7 +1180,9 @@
   // possible, avoids needing to walk the tree to update them. See:
   // https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/core/paint/README.md#Transform-update-optimization
   // for more on the fast path
+  // TODO(yotha): unify these into one HeapHashMap.
   Member<HeapHashSet<Member<LayoutObject>>> pending_transform_updates_;
+  Member<HeapHashSet<Member<LayoutObject>>> pending_opacity_updates_;
 
   // TODO(1370937): Currently we don't yet know how to handle soft navigation
   // UKM reporting. This flag indicates that First Contentful Paint was reported
diff --git a/third_party/blink/renderer/core/html/html_map_element.cc b/third_party/blink/renderer/core/html/html_map_element.cc
index 6f3d1200..ccfe1d9 100644
--- a/third_party/blink/renderer/core/html/html_map_element.cc
+++ b/third_party/blink/renderer/core/html/html_map_element.cc
@@ -67,7 +67,8 @@
         image_element.FastGetAttribute(html_names::kUsemapAttr)
             .GetString()
             .Substring(1);
-    if (use_map_name == name_)
+    if (!use_map_name.empty() &&
+        (use_map_name == name_ || use_map_name == GetIdAttribute()))
       return &image_element;
   }
 
@@ -75,24 +76,23 @@
 }
 
 void HTMLMapElement::ParseAttribute(const AttributeModificationParams& params) {
-  // FIXME: This logic seems wrong for XML documents.
-  // Either the id or name will be used depending on the order the attributes
-  // are parsed.
-
+  // To return the first image that matches usemap on name or id attributes, we
+  // need to track their values in the TreeScope.
+  // https://html.spec.whatwg.org/multipage/#image-map-processing-model
   if (params.name == html_names::kIdAttr ||
       params.name == html_names::kNameAttr) {
     if (params.name == html_names::kIdAttr) {
       // Call base class so that hasID bit gets set.
       HTMLElement::ParseAttribute(params);
-      if (IsA<HTMLDocument>(GetDocument()))
-        return;
     }
     if (isConnected())
       GetTreeScope().RemoveImageMap(*this);
     String map_name = params.new_value;
     if (map_name[0] == '#')
       map_name = map_name.Substring(1);
-    name_ = AtomicString(map_name);
+    // name_ is the parsed name attribute value that is not empty.
+    if (!map_name.empty() && params.name == html_names::kNameAttr)
+      name_ = AtomicString(map_name);
     if (isConnected())
       GetTreeScope().AddImageMap(*this);
 
diff --git a/third_party/blink/renderer/core/html/parser/background_html_scanner_test.cc b/third_party/blink/renderer/core/html/parser/background_html_scanner_test.cc
index 183873c..56c25fe 100644
--- a/third_party/blink/renderer/core/html/parser/background_html_scanner_test.cc
+++ b/third_party/blink/renderer/core/html/parser/background_html_scanner_test.cc
@@ -114,10 +114,10 @@
               .task_runner = task_runner_, .min_size = 0u, .enabled = true},
           /*pretokenize_css_params=*/
           OptimizationParams{
-              .task_runner = task_runner_, .min_size = 0u, .enabled = true}));
-  preload_scanner.ScanInBackground(
-      "<script>foo</script>", GetDocument().ValidBaseElementURL(),
+              .task_runner = task_runner_, .min_size = 0u, .enabled = true}),
       CrossThreadBindRepeating([](std::unique_ptr<PendingPreloadData>) {}));
+  preload_scanner.ScanInBackground("<script>foo</script>",
+                                   GetDocument().ValidBaseElementURL());
   FlushTaskRunner();
   EXPECT_NE(parser->TakeInlineScriptStreamer("foo"), nullptr);
 }
diff --git a/third_party/blink/renderer/core/html/parser/html_document_parser.cc b/third_party/blink/renderer/core/html/parser/html_document_parser.cc
index 9afebbe..4a4265b 100644
--- a/third_party/blink/renderer/core/html/parser/html_document_parser.cc
+++ b/third_party/blink/renderer/core/html/parser/html_document_parser.cc
@@ -90,11 +90,19 @@
 class ShouldCompleteScope;
 class AttemptToEndForbiddenScope;
 
-bool ThreadedPreloadScannerEnabled() {
+enum class FeatureResetMode {
+  kUseCached,
+  kResetForTesting,
+};
+
+bool ThreadedPreloadScannerEnabled(
+    FeatureResetMode reset_mode = FeatureResetMode::kUseCached) {
   // Cache the feature value since checking for each parser regresses some micro
   // benchmarks.
-  static const bool kEnabled =
+  static bool kEnabled =
       base::FeatureList::IsEnabled(features::kThreadedPreloadScanner);
+  if (reset_mode == FeatureResetMode::kResetForTesting)
+    kEnabled = base::FeatureList::IsEnabled(features::kThreadedPreloadScanner);
   return kEnabled;
 }
 
@@ -106,11 +114,14 @@
   return kEnabled;
 }
 
-bool PrecompileInlineScriptsEnabled() {
+bool PrecompileInlineScriptsEnabled(
+    FeatureResetMode reset_mode = FeatureResetMode::kUseCached) {
   // Cache the feature value since checking for each parser regresses some micro
   // benchmarks.
-  static const bool kEnabled =
+  static bool kEnabled =
       base::FeatureList::IsEnabled(features::kPrecompileInlineScripts);
+  if (reset_mode == FeatureResetMode::kResetForTesting)
+    kEnabled = base::FeatureList::IsEnabled(features::kPrecompileInlineScripts);
   return kEnabled;
 }
 
@@ -627,7 +638,7 @@
   preload_scanner_.reset();
   insertion_preload_scanner_.reset();
   background_script_scanner_.Reset();
-  background_scanner_.Reset();
+  background_scanner_.reset();
   // Oilpan: HTMLTokenProducer may allocate a fair amount of memory. Destroy
   // it to ensure that memory is released.
   token_producer_.reset();
@@ -1364,6 +1375,20 @@
     AttemptToRunDeferredScriptsAndEnd();
 }
 
+// static
+void HTMLDocumentParser::ResetCachedFeaturesForTesting() {
+  ThreadedPreloadScannerEnabled(FeatureResetMode::kResetForTesting);
+  PrecompileInlineScriptsEnabled(FeatureResetMode::kResetForTesting);
+}
+
+// static
+void HTMLDocumentParser::FlushPreloadScannerThreadForTesting() {
+  base::RunLoop run_loop;
+  GetPreloadScannerThread()->GetTaskRunner()->PostTask(FROM_HERE,
+                                                       run_loop.QuitClosure());
+  run_loop.Run();
+}
+
 void HTMLDocumentParser::ExecuteScriptsWaitingForResources() {
   TRACE_EVENT0("blink",
                "HTMLDocumentParser::ExecuteScriptsWaitingForResources");
@@ -1567,6 +1592,13 @@
                        have_seen_first_byte ? ".NonInitial" : ".Initial"});
 }
 
+DocumentParser::BackgroundScanCallback
+HTMLDocumentParser::TakeBackgroundScanCallback() {
+  if (!background_scan_fn_)
+    return BackgroundScanCallback();
+  return CrossThreadBindRepeating(std::move(background_scan_fn_), KURL());
+}
+
 void HTMLDocumentParser::ScanInBackground(const String& source) {
   if (task_runner_state_->IsSynchronous() || !GetDocument()->Url().IsValid())
     return;
@@ -1580,17 +1612,29 @@
     // is already available.
     DCHECK(!preload_scanner_);
     if (!background_scanner_) {
+      // See comment on NavigationBodyLoader::StartLoadingBodyInBackground() for
+      // details on how the preload scanner flow works when the body data is
+      // being loaded in the background.
       background_scanner_ = HTMLPreloadScanner::CreateBackground(
-          this, options_, GetPreloadScannerThread()->GetTaskRunner());
+          this, options_, GetPreloadScannerThread()->GetTaskRunner(),
+          CrossThreadBindRepeating(
+              &HTMLDocumentParser::AddPreloadDataOnBackgroundThread,
+              WrapCrossThreadWeakPersistent(this),
+              GetDocument()->GetTaskRunner(TaskType::kInternalLoading)));
+
+      background_scan_fn_ = CrossThreadBindRepeating(
+          [](base::WeakPtr<HTMLPreloadScanner> scanner, const KURL& url,
+             const String& data) {
+            PostCrossThreadTask(
+                *GetPreloadScannerThread()->GetTaskRunner(), FROM_HERE,
+                CrossThreadBindOnce(&HTMLPreloadScanner::ScanInBackground,
+                                    std::move(scanner), data, url));
+          },
+          background_scanner_->AsWeakPtr());
     }
 
-    background_scanner_.AsyncCall(&HTMLPreloadScanner::ScanInBackground)
-        .WithArgs(
-            source, GetDocument()->ValidBaseElementURL(),
-            CrossThreadBindRepeating(
-                &HTMLDocumentParser::AddPreloadDataOnBackgroundThread,
-                WrapCrossThreadPersistent(this),
-                GetDocument()->GetTaskRunner(TaskType::kInternalLoading)));
+    if (background_scan_fn_)
+      background_scan_fn_.Run(GetDocument()->ValidBaseElementURL(), source);
     return;
   }
 
diff --git a/third_party/blink/renderer/core/html/parser/html_document_parser.h b/third_party/blink/renderer/core/html/parser/html_document_parser.h
index 3eb88c5e..f00f7ee 100644
--- a/third_party/blink/renderer/core/html/parser/html_document_parser.h
+++ b/third_party/blink/renderer/core/html/parser/html_document_parser.h
@@ -126,6 +126,9 @@
   void SetDecoder(std::unique_ptr<TextResourceDecoder>) final;
   void NotifyNoRemainingAsyncScripts() final;
 
+  static void ResetCachedFeaturesForTesting();
+  static void FlushPreloadScannerThreadForTesting();
+
  protected:
   HTMLDocumentParser(HTMLDocument&,
                      ParserSynchronizationPolicy,
@@ -167,6 +170,7 @@
   void DocumentElementAvailable() override;
   void CommitPreloadedData() override;
   void FlushPendingPreloads() override;
+  BackgroundScanCallback TakeBackgroundScanCallback() override;
 
   // HTMLParserScriptRunnerHost
   void NotifyScriptLoaded() final;
@@ -250,7 +254,10 @@
   // A scanner used only for input provided to the insert() method.
   std::unique_ptr<HTMLPreloadScanner> insertion_preload_scanner_;
   WTF::SequenceBound<BackgroundHTMLScanner> background_script_scanner_;
-  WTF::SequenceBound<HTMLPreloadScanner> background_scanner_;
+  HTMLPreloadScanner::BackgroundPtr background_scanner_;
+  using BackgroundScanFn =
+      WTF::CrossThreadRepeatingFunction<void(const KURL&, const String&)>;
+  BackgroundScanFn background_scan_fn_;
 
   scoped_refptr<base::SingleThreadTaskRunner> loading_task_runner_;
 
diff --git a/third_party/blink/renderer/core/html/parser/html_document_parser_test.cc b/third_party/blink/renderer/core/html/parser/html_document_parser_test.cc
index e626e1a..f1f6f11 100644
--- a/third_party/blink/renderer/core/html/parser/html_document_parser_test.cc
+++ b/third_party/blink/renderer/core/html/parser/html_document_parser_test.cc
@@ -189,6 +189,62 @@
   static_cast<DocumentParser*>(parser)->StopParsing();
 }
 
+class HTMLDocumentParserThreadedPreloadScannerTest : public PageTestBase {
+ protected:
+  HTMLDocumentParserThreadedPreloadScannerTest() {
+    scoped_feature_list_.InitWithFeatures(
+        {features::kThreadedPreloadScanner, features::kPrecompileInlineScripts},
+        {});
+    HTMLDocumentParser::ResetCachedFeaturesForTesting();
+  }
+
+  ~HTMLDocumentParserThreadedPreloadScannerTest() override {
+    scoped_feature_list_.Reset();
+    HTMLDocumentParser::ResetCachedFeaturesForTesting();
+  }
+
+  void SetUp() override {
+    PageTestBase::SetUp();
+    GetDocument().SetURL(KURL("https://example.test"));
+  }
+
+  HTMLDocumentParser* CreateParser(HTMLDocument& document) {
+    return MakeGarbageCollected<HTMLDocumentParser>(document,
+                                                    kAllowDeferredParsing);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(HTMLDocumentParserThreadedPreloadScannerTest,
+       TakeBackgroundScanCallback) {
+  auto& document = To<HTMLDocument>(GetDocument());
+  HTMLDocumentParser* parser = CreateParser(document);
+  ScopedParserDetacher detacher(parser);
+
+  // First append "foo" script which should be passed through to the scanner.
+  parser->AppendDecodedData("<script>foo</script>", DocumentEncodingData());
+  HTMLDocumentParser::FlushPreloadScannerThreadForTesting();
+  EXPECT_TRUE(parser->HasInlineScriptStreamerForTesting("foo"));
+
+  // Now take the callback.
+  auto callback =
+      static_cast<DocumentParser*>(parser)->TakeBackgroundScanCallback();
+
+  // Append "bar" script which should not be passed to the scanner.
+  parser->AppendDecodedData("<script>bar</script>", DocumentEncodingData());
+  HTMLDocumentParser::FlushPreloadScannerThreadForTesting();
+  EXPECT_FALSE(parser->HasInlineScriptStreamerForTesting("bar"));
+
+  // Append "baz" script to the callback which should be passed to the scanner.
+  callback.Run("<script>baz</script>");
+  HTMLDocumentParser::FlushPreloadScannerThreadForTesting();
+  EXPECT_TRUE(parser->HasInlineScriptStreamerForTesting("baz"));
+
+  static_cast<DocumentParser*>(parser)->StopParsing();
+}
+
 class HTMLDocumentParserProcessImmediatelyTest : public PageTestBase {
  protected:
   void SetUp() override {
diff --git a/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc b/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc
index dcc7a2f..04722bd 100644
--- a/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc
+++ b/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc
@@ -1069,18 +1069,22 @@
 }
 
 // static
-WTF::SequenceBound<HTMLPreloadScanner> HTMLPreloadScanner::CreateBackground(
+HTMLPreloadScanner::BackgroundPtr HTMLPreloadScanner::CreateBackground(
     HTMLDocumentParser* parser,
     HTMLParserOptions options,
-    scoped_refptr<base::SequencedTaskRunner> task_runner) {
+    scoped_refptr<base::SequencedTaskRunner> task_runner,
+    TakePreloadFn take_preload) {
   auto* document = parser->GetDocument();
-  return WTF::SequenceBound<HTMLPreloadScanner>(
-      std::move(task_runner), std::make_unique<HTMLTokenizer>(options),
-      options.priority_hints_origin_trial_enabled, document->Url(),
-      std::make_unique<CachedDocumentParameters>(document),
-      MediaValuesCached::MediaValuesCachedData(*document),
-      TokenPreloadScanner::ScannerType::kMainDocument,
-      BackgroundHTMLScanner::ScriptTokenScanner::Create(parser));
+  return BackgroundPtr(
+      new HTMLPreloadScanner(
+          std::make_unique<HTMLTokenizer>(options),
+          options.priority_hints_origin_trial_enabled, document->Url(),
+          std::make_unique<CachedDocumentParameters>(document),
+          MediaValuesCached::MediaValuesCachedData(*document),
+          TokenPreloadScanner::ScannerType::kMainDocument,
+          BackgroundHTMLScanner::ScriptTokenScanner::Create(parser),
+          std::move(take_preload)),
+      Deleter{task_runner});
 }
 
 HTMLPreloadScanner::HTMLPreloadScanner(
@@ -1091,14 +1095,16 @@
     const MediaValuesCached::MediaValuesCachedData& media_values_cached_data,
     const TokenPreloadScanner::ScannerType scanner_type,
     std::unique_ptr<BackgroundHTMLScanner::ScriptTokenScanner>
-        script_token_scanner)
+        script_token_scanner,
+    TakePreloadFn take_preload)
     : scanner_(document_url,
                std::move(document_parameters),
                media_values_cached_data,
                scanner_type,
                priority_hints_origin_trial_enabled),
       tokenizer_(std::move(tokenizer)),
-      script_token_scanner_(std::move(script_token_scanner)) {}
+      script_token_scanner_(std::move(script_token_scanner)),
+      take_preload_(std::move(take_preload)) {}
 
 HTMLPreloadScanner::~HTMLPreloadScanner() = default;
 
@@ -1107,8 +1113,7 @@
 }
 
 std::unique_ptr<PendingPreloadData> HTMLPreloadScanner::Scan(
-    const KURL& starting_base_element_url,
-    const TakePreloadFn& take_preload) {
+    const KURL& starting_base_element_url) {
   auto pending_data = std::make_unique<PendingPreloadData>();
 
   TRACE_EVENT1("blink", "HTMLPreloadScanner::scan", "source_length",
@@ -1147,19 +1152,19 @@
       return pending_data;
     }
     // Incrementally add preloads when scanning in the background.
-    if (take_preload && !pending_data->requests.empty()) {
-      take_preload.Run(std::move(pending_data));
+    if (take_preload_ && !pending_data->requests.empty()) {
+      take_preload_.Run(std::move(pending_data));
       pending_data = std::make_unique<PendingPreloadData>();
     }
   }
   return pending_data;
 }
 
-void HTMLPreloadScanner::ScanInBackground(const String& source,
-                                          const KURL& document_base_element_url,
-                                          const TakePreloadFn& take_preload) {
+void HTMLPreloadScanner::ScanInBackground(
+    const String& source,
+    const KURL& document_base_element_url) {
   source_.Append(source);
-  take_preload.Run(Scan(document_base_element_url, take_preload));
+  take_preload_.Run(Scan(document_base_element_url));
 }
 
 CachedDocumentParameters::CachedDocumentParameters(Document* document) {
diff --git a/third_party/blink/renderer/core/html/parser/html_preload_scanner.h b/third_party/blink/renderer/core/html/parser/html_preload_scanner.h
index 35a3d54..816d888a 100644
--- a/third_party/blink/renderer/core/html/parser/html_preload_scanner.h
+++ b/third_party/blink/renderer/core/html/parser/html_preload_scanner.h
@@ -31,6 +31,7 @@
 #include <utility>
 
 #include "base/memory/ptr_util.h"
+#include "base/memory/weak_ptr.h"
 #include "services/network/public/cpp/client_hints.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/renderer/core/core_export.h"
@@ -157,7 +158,7 @@
   PictureData picture_data_;
   size_t template_count_;
   std::unique_ptr<CachedDocumentParameters> document_parameters_;
-  Persistent<MediaValuesCached> media_values_;
+  CrossThreadPersistent<MediaValuesCached> media_values_;
   ScannerType scanner_type_;
   // TODO(domfarolino): Remove this once Priority Hints is no longer in Origin
   // Trial (see https://crbug.com/821464). This member exists because
@@ -167,7 +168,8 @@
   bool priority_hints_origin_trial_enabled_;
 };
 
-class CORE_EXPORT HTMLPreloadScanner {
+class CORE_EXPORT HTMLPreloadScanner
+    : public base::SupportsWeakPtr<HTMLPreloadScanner> {
   USING_FAST_MALLOC(HTMLPreloadScanner);
 
  public:
@@ -177,11 +179,23 @@
       HTMLParserOptions options,
       TokenPreloadScanner::ScannerType scanner_type);
 
+  using TakePreloadFn = WTF::CrossThreadRepeatingFunction<void(
+      std::unique_ptr<PendingPreloadData>)>;
+
   // Creates a HTMLPreloadScanner which will be bound to |task_runner|.
-  static WTF::SequenceBound<HTMLPreloadScanner> CreateBackground(
+  struct Deleter {
+    void operator()(const HTMLPreloadScanner* ptr) {
+      if (ptr)
+        task_runner_->DeleteSoon(FROM_HERE, ptr);
+    }
+    scoped_refptr<base::SequencedTaskRunner> task_runner_;
+  };
+  using BackgroundPtr = std::unique_ptr<HTMLPreloadScanner, Deleter>;
+  static BackgroundPtr CreateBackground(
       HTMLDocumentParser* parser,
       HTMLParserOptions options,
-      scoped_refptr<base::SequencedTaskRunner> task_runner);
+      scoped_refptr<base::SequencedTaskRunner> task_runner,
+      TakePreloadFn take_preload);
 
   HTMLPreloadScanner(std::unique_ptr<HTMLTokenizer>,
                      bool priority_hints_origin_trial_enabled,
@@ -190,23 +204,20 @@
                      const MediaValuesCached::MediaValuesCachedData&,
                      const TokenPreloadScanner::ScannerType,
                      std::unique_ptr<BackgroundHTMLScanner::ScriptTokenScanner>
-                         script_token_scanner);
+                         script_token_scanner,
+                     TakePreloadFn take_preload = TakePreloadFn());
   HTMLPreloadScanner(const HTMLPreloadScanner&) = delete;
   HTMLPreloadScanner& operator=(const HTMLPreloadScanner&) = delete;
   ~HTMLPreloadScanner();
 
   void AppendToEnd(const SegmentedString&);
 
-  using TakePreloadFn = WTF::CrossThreadRepeatingFunction<void(
-      std::unique_ptr<PendingPreloadData>)>;
   std::unique_ptr<PendingPreloadData> Scan(
-      const KURL& document_base_element_url,
-      const TakePreloadFn& take_preload = TakePreloadFn());
+      const KURL& document_base_element_url);
 
   // Scans |source| and calls |take_preload| with the generated preload data.
   void ScanInBackground(const String& source,
-                        const KURL& document_base_element_url,
-                        const TakePreloadFn& take_preload);
+                        const KURL& document_base_element_url);
 
  private:
   TokenPreloadScanner scanner_;
@@ -215,6 +226,7 @@
   std::unique_ptr<HTMLTokenizer> tokenizer_;
   std::unique_ptr<BackgroundHTMLScanner::ScriptTokenScanner>
       script_token_scanner_;
+  TakePreloadFn take_preload_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 420886e..c76e4f18 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -3680,6 +3680,7 @@
 
   if (GetFrameView()) {
     GetFrameView()->RemovePendingTransformUpdate(*this);
+    GetFrameView()->RemovePendingOpacityUpdate(*this);
     SetIsBackgroundAttachmentFixedObject(false);
   }
 }
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 11a93e18..f93fcdb 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -147,6 +147,7 @@
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
 #include "third_party/blink/renderer/platform/weborigin/security_policy.h"
+#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
@@ -1029,6 +1030,17 @@
   BodyDataReceivedImpl(body_data);
 }
 
+DocumentLoader::ProcessBackgroundDataCallback
+DocumentLoader::TakeProcessBackgroundDataCallback() {
+  auto callback = parser_->TakeBackgroundScanCallback();
+  if (!callback)
+    return ProcessBackgroundDataCallback();
+  return CrossThreadBindRepeating(
+      [](const DocumentParser::BackgroundScanCallback& callback,
+         const WebString& data) { callback.Run(data); },
+      std::move(callback));
+}
+
 void DocumentLoader::BodyDataReceivedImpl(BodyData& data) {
   TRACE_EVENT0("loading", "DocumentLoader::BodyDataReceived");
   base::span<const char> encoded_data = data.EncodedData();
diff --git a/third_party/blink/renderer/core/loader/document_loader.h b/third_party/blink/renderer/core/loader/document_loader.h
index 1e9fc28..26ad99b 100644
--- a/third_party/blink/renderer/core/loader/document_loader.h
+++ b/third_party/blink/renderer/core/loader/document_loader.h
@@ -548,6 +548,7 @@
                            int64_t total_decoded_body_length,
                            bool should_report_corb_blocking,
                            const absl::optional<WebURLError>& error) override;
+  ProcessBackgroundDataCallback TakeProcessBackgroundDataCallback() override;
 
   void ApplyClientHintsConfig(
       const WebVector<network::mojom::WebClientHintsType>&
diff --git a/third_party/blink/renderer/core/paint/README.md b/third_party/blink/renderer/core/paint/README.md
index c13720a..af393b2 100644
--- a/third_party/blink/renderer/core/paint/README.md
+++ b/third_party/blink/renderer/core/paint/README.md
@@ -462,9 +462,9 @@
 from its containing self-painting layer to this layer, assuming that this layer
 needs all paint phases that its container self-painting layer needs.
 
-### Transform update optimization
+### Property tree update optimization
 
-In specific cases of a transform update, we can directly update the property
+In some specific cases of style updates, we can directly update the property
 tree without needing to run the property tree builder (Which requires a layout
 tree walk). During `PaintLayer::StyleDidChange` we check if this update meets
 the requirements for a quick update, and if so we add it to a list of pending
@@ -472,9 +472,13 @@
 changes can't be detected correctly).
 
 The updates are executed later in `PrePaintTreeWalk::WalkTree` using the
-`LocalFrameView::UpdateAllPendingTransforms`. If at some point during pre-paint
-we reach a node that has a pending update, we mark that node as needs full
-update, and remove the pending update from the list.
+the designated functions (For example -
+`LocalFrameView::UpdateAllPendingTransforms`). If at some point during
+pre-paint we reach a node that has a pending update, we mark that node as needs
+full update, and remove the pending update from the list.
+
+Current updates that are checked for an optimized update are transform updates
+and opacity updates.
 
 ### Hit test information recording
 
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_test.cc b/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
index 56b5a04..c6e446e2 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
@@ -981,6 +981,75 @@
   EXPECT_FALSE(transform_node->transform_changed);
 }
 
+// Same as the test above but for opacity changes
+TEST_P(CompositingSimTest, FastPathOpacityUpdateFromStyle) {
+  InitializeWithHTML(R"HTML(
+      <!DOCTYPE html>
+      <style>
+        @keyframes animation {
+          0% { opacity: 0.2; }
+          100% { opacity: 0.8; }
+        }
+        #div {
+          opacity: 0.1;
+          width: 100px;
+          height: 100px;
+          /*
+            This causes the opacity to have an active animation, but because
+            the delay is so large, it will not have an effect for the duration
+            of this unit test.
+          */
+          animation-name: animation;
+          animation-duration: 999s;
+          animation-delay: 999s;
+        }
+      </style>
+      <div id='div'></div>
+  )HTML");
+
+  Compositor().BeginFrame();
+
+  // Check the initial state of the blink effect node.
+  auto* div = GetElementById("div");
+  auto* div_properties =
+      div->GetLayoutObject()->FirstFragment().PaintProperties();
+  ASSERT_TRUE(div_properties);
+  EXPECT_NEAR(0.1, div_properties->Effect()->Opacity(), 0.001);
+  EXPECT_TRUE(div_properties->Effect()->HasActiveOpacityAnimation());
+  EXPECT_FALSE(div->GetLayoutObject()->NeedsPaintPropertyUpdate());
+
+  // Check the initial state of the cc effect node.
+  auto* div_cc_layer = CcLayerByDOMElementId("div");
+  auto effect_tree_index = div_cc_layer->effect_tree_index();
+  const auto* effect_node =
+      GetPropertyTrees()->effect_tree().Node(effect_tree_index);
+  EXPECT_FALSE(effect_node->effect_changed);
+  EXPECT_FALSE(paint_artifact_compositor()->NeedsUpdate());
+  EXPECT_NEAR(0.1, effect_node->opacity, 0.001);
+
+  // Change the effect style and ensure the blink and cc effect nodes are
+  // not marked for a full update.
+  div->setAttribute(html_names::kStyleAttr, "opacity: 0.15");
+  GetDocument().View()->UpdateLifecycleToLayoutClean(
+      DocumentUpdateReason::kTest);
+  EXPECT_FALSE(div->GetLayoutObject()->NeedsPaintPropertyUpdate());
+  EXPECT_FALSE(paint_artifact_compositor()->NeedsUpdate());
+
+  // Continue to run the lifecycle to paint and ensure that updates are
+  // performed.
+  UpdateAllLifecyclePhasesExceptPaint();
+  EXPECT_NEAR(0.15, div_properties->Effect()->Opacity(), 0.001);
+  EXPECT_NEAR(0.15, effect_node->opacity, 0.001);
+  EXPECT_TRUE(effect_node->effect_changed);
+  EXPECT_FALSE(div->GetLayoutObject()->NeedsPaintPropertyUpdate());
+  EXPECT_FALSE(paint_artifact_compositor()->NeedsUpdate());
+  EXPECT_TRUE(effect_node->effect_changed);
+
+  // After a frame the |opacity_changed| value should be reset.
+  Compositor().BeginFrame();
+  EXPECT_FALSE(effect_node->effect_changed);
+}
+
 TEST_P(CompositingSimTest, DirectSVGTransformPropertyUpdate) {
   InitializeWithHTML(R"HTML(
     <!doctype html>
diff --git a/third_party/blink/renderer/core/paint/object_paint_properties.h b/third_party/blink/renderer/core/paint/object_paint_properties.h
index d417496..7bc1c0b6 100644
--- a/third_party/blink/renderer/core/paint/object_paint_properties.h
+++ b/third_party/blink/renderer/core/paint/object_paint_properties.h
@@ -321,6 +321,18 @@
         std::move(transform_and_origin), animation_state);
   }
 
+  PaintPropertyChangeType DirectlyUpdateOpacity(
+      float opacity,
+      const EffectPaintPropertyNode::AnimationState& animation_state) {
+    // TODO(yotha): Remove this check once we make sure crbug.com/1370268 is
+    // fixed
+    DCHECK(effect_ != nullptr);
+    if (effect_ == nullptr) {
+      return PaintPropertyChangeType::kNodeAddedOrRemoved;
+    }
+    return effect_->DirectlyUpdateOpacity(opacity, animation_state);
+  }
+
  private:
   // Return true if the property tree structure changes (an existing node was
   // deleted), and false otherwise. See the class-level comment ("update & clear
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index 631eddb..3ebecf5 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -2457,9 +2457,18 @@
     }
   }
 
+  bool needs_full_opacity_update = diff.OpacityChanged();
+  if (needs_full_opacity_update) {
+    if (PaintPropertyTreeBuilder::ScheduleDeferredOpacityNodeUpdate(
+            GetLayoutObject())) {
+      needs_full_opacity_update = false;
+      SetNeedsDescendantDependentFlagsUpdate();
+    }
+  }
+
   // See also |LayoutObject::SetStyle| which handles these invalidations if a
   // PaintLayer is not present.
-  if (needs_full_transform_update || diff.OpacityChanged() ||
+  if (needs_full_transform_update || needs_full_opacity_update ||
       diff.ZIndexChanged() || diff.FilterChanged() || diff.CssClipChanged() ||
       diff.BlendModeChanged() || diff.MaskChanged() ||
       diff.CompositingReasonsChanged()) {
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 1fae5b6..8379c36 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -851,6 +851,24 @@
   }
 }
 
+static void DirectlyUpdateCcOpacity(const LayoutObject& object,
+                                    ObjectPaintProperties& properties,
+                                    PaintPropertyChangeType& change_type) {
+  if (change_type == PaintPropertyChangeType::kChangedOnlySimpleValues &&
+      properties.Effect()->HasDirectCompositingReasons()) {
+    if (auto* paint_artifact_compositor =
+            object.GetFrameView()->GetPaintArtifactCompositor()) {
+      bool updated =
+          paint_artifact_compositor->DirectlyUpdateCompositedOpacityValue(
+              *properties.Effect());
+      if (updated) {
+        change_type = PaintPropertyChangeType::kChangedOnlyCompositedValues;
+        properties.Effect()->CompositorSimpleValuesUpdated();
+      }
+    }
+  }
+}
+
 // TODO(dbaron): Remove this function when we can remove the
 // BackfaceVisibilityInteropEnabled() check, and have the caller use
 // CompositingReason::kDirectReasonsForTransformProperty directly.
@@ -1446,6 +1464,9 @@
 
 void FragmentPaintPropertyTreeBuilder::UpdateEffect() {
   DCHECK(properties_);
+  // Since we're doing a full update, clear list of objects waiting for a
+  // deferred update
+  object_.GetFrameView()->RemovePendingOpacityUpdate(object_);
   const ComputedStyle& style = object_.StyleRef();
 
   if (NeedsPaintPropertyUpdate()) {
@@ -1557,25 +1578,7 @@
       // If we have simple value change, which means opacity, we should try to
       // directly update it on the PaintArtifactCompositor in order to avoid
       // doing a full rebuild.
-      if (effective_change_type ==
-              PaintPropertyChangeType::kChangedOnlySimpleValues &&
-          properties_->Effect()->HasDirectCompositingReasons() &&
-          // TODO(crbug.com/1253797): Due to the bug, we may create multiple
-          // cc effect nodes for one blink effect node of a fragmented object,
-          // and direct update would be incomplete in the case.
-          !object_.FirstFragment().NextFragment()) {
-        if (auto* paint_artifact_compositor =
-                object_.GetFrameView()->GetPaintArtifactCompositor()) {
-          bool updated =
-              paint_artifact_compositor->DirectlyUpdateCompositedOpacityValue(
-                  *properties_->Effect());
-          if (updated) {
-            effective_change_type =
-                PaintPropertyChangeType::kChangedOnlyCompositedValues;
-            properties_->Effect()->CompositorSimpleValuesUpdated();
-          }
-        }
-      }
+      DirectlyUpdateCcOpacity(object_, *properties_, effective_change_type);
       OnUpdateEffect(effective_change_type);
 
       auto mask_direct_compositing_reasons =
@@ -2957,6 +2960,8 @@
   // optimized transform updates
   if (object_.GetFrameView()->RemovePendingTransformUpdate(object_))
     object_.GetMutableForPainting().SetOnlyThisNeedsPaintPropertyUpdate();
+  if (object_.GetFrameView()->RemovePendingOpacityUpdate(object_))
+    object_.GetMutableForPainting().SetOnlyThisNeedsPaintPropertyUpdate();
 
   if (box.Size() == box.PreviousSize())
     return;
@@ -4222,6 +4227,17 @@
   return false;
 }
 
+bool PaintPropertyTreeBuilder::ScheduleDeferredOpacityNodeUpdate(
+    LayoutObject& object) {
+  if (!base::FeatureList::IsEnabled(features::kFastPathPaintPropertyUpdates))
+    return false;
+  if (CanDoDeferredOpacityNodeUpdate(object)) {
+    object.GetFrameView()->AddPendingOpacityUpdate(object);
+    return true;
+  }
+  return false;
+}
+
 // Fast-path for directly updating transforms. Returns true if successful. This
 // is similar to |FragmentPaintPropertyTreeBuilder::UpdateIndividualTransform|.
 void PaintPropertyTreeBuilder::DirectlyUpdateTransformMatrix(
@@ -4280,6 +4296,34 @@
   }
 }
 
+void PaintPropertyTreeBuilder::DirectlyUpdateOpacityValue(
+    const LayoutObject& object) {
+  DCHECK(CanDoDeferredOpacityNodeUpdate(object));
+  const ComputedStyle& style = object.StyleRef();
+
+  EffectPaintPropertyNode::AnimationState animation_state;
+  animation_state.is_running_opacity_animation_on_compositor =
+      style.IsRunningOpacityAnimationOnCompositor();
+  animation_state.is_running_backdrop_filter_animation_on_compositor =
+      style.IsRunningBackdropFilterAnimationOnCompositor();
+
+  FragmentData* fragment_data = &object.GetMutableForPainting().FirstFragment();
+  auto* properties = fragment_data->PaintProperties();
+  auto effective_change_type =
+      properties->DirectlyUpdateOpacity(style.Opacity(), animation_state);
+  // If we have simple value change, which means opacity, we should try to
+  // directly update it on the PaintArtifactCompositor in order to avoid
+  // needing to run the property tree builder at all.
+  DirectlyUpdateCcOpacity(object, *properties, effective_change_type);
+
+  if (effective_change_type >=
+      PaintPropertyChangeType::kChangedOnlySimpleValues) {
+    object.GetFrameView()->SetPaintArtifactCompositorNeedsUpdate(
+        PaintArtifactCompositorUpdateReason::
+            kPaintPropertyTreeBuilderPaintPropertyChanged);
+  }
+}
+
 void PaintPropertyTreeBuilder::IssueInvalidationsAfterUpdate() {
   // We need to update property tree states of paint chunks.
   auto max_change = properties_changed_.Max();
@@ -4387,4 +4431,38 @@
   return true;
 }
 
+bool PaintPropertyTreeBuilder::CanDoDeferredOpacityNodeUpdate(
+    const LayoutObject& object) {
+  // If we already need a full update, do not do the direct update.
+  if (object.NeedsPaintPropertyUpdate() ||
+      object.DescendantNeedsPaintPropertyUpdate()) {
+    return false;
+  }
+
+  // In some cases where we need to remove the update, objects that are not
+  // boxes can cause a bug. (See SetNeedsPaintPropertyUpdateIfNeeded)
+  if (!object.IsBox())
+    return false;
+
+  // This fast path does not support iterating over each fragment, so do not
+  // run the fast path in the presence of fragmentation.
+  if (object.FirstFragment().NextFragment())
+    return false;
+
+  auto* properties = object.FirstFragment().PaintProperties();
+  // Cannot directly update properties if they have not been created yet.
+  if (!properties || !properties->Effect())
+    return false;
+
+  // Descendant state depends on opacity being zero, so we can't do a direct
+  // update if it changes
+  bool old_opacity_is_zero = properties->Effect()->Opacity() == 0;
+  bool new_opacity_is_zero = object.Style()->Opacity() == 0;
+  if (old_opacity_is_zero != new_opacity_is_zero) {
+    return false;
+  }
+
+  return true;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.h b/third_party/blink/renderer/core/paint/paint_property_tree_builder.h
index 0bcd685..66f00d2 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.h
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.h
@@ -371,8 +371,10 @@
   }
 
   static void DirectlyUpdateTransformMatrix(const LayoutObject& object);
+  static void DirectlyUpdateOpacityValue(const LayoutObject& object);
 
   static bool ScheduleDeferredTransformNodeUpdate(LayoutObject& object);
+  static bool ScheduleDeferredOpacityNodeUpdate(LayoutObject& object);
 
  private:
   ALWAYS_INLINE void InitFragmentPaintProperties(
@@ -410,6 +412,7 @@
 
   bool IsInNGFragmentTraversal() const { return pre_paint_info_; }
   static bool CanDoDeferredTransformNodeUpdate(const LayoutObject& object);
+  static bool CanDoDeferredOpacityNodeUpdate(const LayoutObject& object);
 
   const LayoutObject& object_;
   NGPrePaintInfo* pre_paint_info_;
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc b/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
index 8642c9e..edd44c9 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_update_tests.cc
@@ -2000,4 +2000,25 @@
   EXPECT_TRUE(GetPaintLayerByElementId("masked")->SelfNeedsRepaint());
 }
 
+TEST_P(PaintPropertyTreeUpdateTest,
+       DirectOpacityUpdateSkipsPropertyTreeBuilder) {
+  SetBodyInnerHTML(R"HTML(
+      <div id='div' style="opacity:0.5"></div>
+  )HTML");
+
+  auto* div_properties = PaintPropertiesForElement("div");
+  ASSERT_TRUE(div_properties);
+  EXPECT_EQ(0.5, div_properties->Effect()->Opacity());
+  auto* div = GetDocument().getElementById("div");
+  EXPECT_FALSE(div->GetLayoutObject()->NeedsPaintPropertyUpdate());
+
+  div->setAttribute(html_names::kStyleAttr, "opacity:0.8");
+  GetDocument().View()->UpdateLifecycleToLayoutClean(
+      DocumentUpdateReason::kTest);
+  EXPECT_FALSE(div->GetLayoutObject()->NeedsPaintPropertyUpdate());
+
+  UpdateAllLifecyclePhasesExceptPaint();
+  EXPECT_NEAR(0.8, div_properties->Effect()->Opacity(), 0.001);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
index a6a0a23..2e1ad9a1 100644
--- a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
+++ b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
@@ -78,6 +78,10 @@
     ShowAllPropertyTrees(root_frame_view);
 #endif
 
+  if (root_frame_view.UpdateAllPendingTransforms() ||
+      root_frame_view.UpdateAllPendingOpacityUpdates())
+    needs_invalidate_chrome_client_ = true;
+
   // If the page has anything changed, we need to inform the chrome client
   // so that the client will initiate repaint of the contents if needed (e.g.
   // when this page is embedded as a non-composited content of another page).
@@ -85,7 +89,6 @@
     if (auto* client = root_frame_view.GetChromeClient())
       client->InvalidateContainer();
   }
-  root_frame_view.UpdateAllPendingTransforms();
 }
 
 void PrePaintTreeWalk::Walk(LocalFrameView& frame_view,
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer.cc b/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer.cc
index 69a153a9..32df292 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer.cc
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result_bloberizer.cc
@@ -7,7 +7,6 @@
 #include <hb.h>
 
 #include "base/logging.h"
-#include "base/ranges/algorithm.h"
 #include "third_party/blink/renderer/platform/fonts/font.h"
 #include "third_party/blink/renderer/platform/fonts/shaping/caching_word_shaper.h"
 #include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h"
@@ -412,8 +411,9 @@
 
   void Finish(unsigned from, unsigned to) {
     std::sort(cluster_starts_.begin(), cluster_starts_.end());
-    DCHECK_EQ(base::ranges::adjacent_find(cluster_starts_),
-              cluster_starts_.end());
+    DCHECK_EQ(
+        std::adjacent_find(cluster_starts_.begin(), cluster_starts_.end()),
+        cluster_starts_.end());
     DVLOG(4) << "  Cluster starts: " << base::make_span(cluster_starts_);
     if (!cluster_starts_.empty()) {
       // 'from' may point inside a cluster; the least seen index may be larger.
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
index c9fcebb1..55358e65 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
@@ -399,16 +399,12 @@
       EnsureCompositorTransformNode(transform_node.Parent()->Unalias());
   id = transform_tree_.Insert(cc::TransformNode(), parent_id);
 
-  // ScrollUnification creates the entire scroll tree and will already have done
-  // this.
-  if (!base::FeatureList::IsEnabled(features::kScrollUnification)) {
-    if (auto* scroll_translation_for_fixed =
-            transform_node.ScrollTranslationForFixed()) {
-      // Fixed-position can cause different topologies of the transform tree and
-      // the scroll tree. This ensures the ancestor scroll nodes of the scroll
-      // node for a descendant transform node below is created.
-      EnsureCompositorTransformNode(*scroll_translation_for_fixed);
-    }
+  if (auto* scroll_translation_for_fixed =
+          transform_node.ScrollTranslationForFixed()) {
+    // Fixed-position can cause different topologies of the transform tree and
+    // the scroll tree. This ensures the ancestor scroll nodes of the scroll
+    // node for a descendant transform node below is created.
+    EnsureCompositorTransformNode(*scroll_translation_for_fixed);
   }
 
   cc::TransformNode& compositor_node = *transform_tree_.Node(id);
diff --git a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc
index 28a479bf..b23bab2 100644
--- a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.cc
@@ -42,18 +42,8 @@
   }
   bool opacity_changed = opacity != other.opacity;
   bool opacity_change_is_simple =
-      opacity_changed &&
-      // Opacity change is simple if
-      // - opacity doesn't change from or to 1, or
-      // - there was and is active opacity animation, or
-      // TODO(crbug.com/1285498): Optimize for will-change: opacity.
-      // The rule is because whether opacity is 1 affects whether the effect
-      // should create a render surface if there is no active opacity animation.
-      ((opacity != 1.f && other.opacity != 1.f) ||
-       ((direct_compositing_reasons &
-         CompositingReason::kActiveOpacityAnimation) &&
-        (other.direct_compositing_reasons &
-         CompositingReason::kActiveOpacityAnimation)));
+      IsOpacityChangeSimple(opacity, other.opacity, direct_compositing_reasons,
+                            other.direct_compositing_reasons);
   if (opacity_changed && !opacity_change_is_simple) {
     DCHECK(!animation_state.is_running_opacity_animation_on_compositor);
     return PaintPropertyChangeType::kChangedOnlyValues;
@@ -94,6 +84,19 @@
   return PaintPropertyChangeType::kUnchanged;
 }
 
+bool EffectPaintPropertyNode::State::IsOpacityChangeSimple(
+    float opacity,
+    float new_opacity,
+    CompositingReasons direct_compositing_reasons,
+    CompositingReasons new_direct_compositing_reasons) {
+  bool opacity_changed = opacity != new_opacity;
+  return opacity_changed && ((opacity != 1.f && new_opacity != 1.f) ||
+                             ((direct_compositing_reasons &
+                               CompositingReason::kActiveOpacityAnimation) &&
+                              (new_direct_compositing_reasons &
+                               CompositingReason::kActiveOpacityAnimation)));
+}
+
 const EffectPaintPropertyNode& EffectPaintPropertyNode::Root() {
   DEFINE_STATIC_REF(EffectPaintPropertyNode, root,
                     base::AdoptRef(new EffectPaintPropertyNode(
@@ -149,6 +152,40 @@
   }
 }
 
+PaintPropertyChangeType EffectPaintPropertyNode::State::ComputeOpacityChange(
+    float new_opacity,
+    const AnimationState& animation_state) const {
+  bool opacity_changed = opacity != new_opacity;
+  bool opacity_change_is_simple = State::IsOpacityChangeSimple(
+      opacity, new_opacity, direct_compositing_reasons,
+      direct_compositing_reasons);
+  if (opacity_changed && !opacity_change_is_simple) {
+    DCHECK(!animation_state.is_running_opacity_animation_on_compositor);
+    return PaintPropertyChangeType::kChangedOnlyValues;
+  }
+
+  bool simple_values_changed =
+      opacity_change_is_simple &&
+      !animation_state.is_running_opacity_animation_on_compositor;
+  if (simple_values_changed) {
+    return PaintPropertyChangeType::kChangedOnlySimpleValues;
+  }
+  if (opacity_changed) {
+    return PaintPropertyChangeType::kChangedOnlyCompositedValues;
+  }
+  return PaintPropertyChangeType::kUnchanged;
+}
+
+PaintPropertyChangeType EffectPaintPropertyNode::DirectlyUpdateOpacity(
+    float opacity,
+    const AnimationState& animation_state) {
+  auto change = state_.ComputeOpacityChange(opacity, animation_state);
+  state_.opacity = opacity;
+  if (change != PaintPropertyChangeType::kUnchanged)
+    AddChanged(change);
+  return change;
+}
+
 gfx::RectF EffectPaintPropertyNode::MapRect(const gfx::RectF& rect) const {
   if (state_.filter.IsEmpty())
     return rect;
diff --git a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
index e40a762..e98e719 100644
--- a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
@@ -132,6 +132,22 @@
     PaintPropertyChangeType ComputeChange(
         const State& other,
         const AnimationState& animation_state) const;
+
+    PaintPropertyChangeType ComputeOpacityChange(
+        float opacity,
+        const AnimationState& animation_state) const;
+
+    // Opacity change is simple if
+    // - opacity doesn't change from or to 1, or
+    // - there was and is active opacity animation, or
+    // TODO(crbug.com/1285498): Optimize for will-change: opacity.
+    // The rule is because whether opacity is 1 affects whether the effect
+    // should create a render surface if there is no active opacity animation.
+    static bool IsOpacityChangeSimple(
+        float opacity,
+        float new_opacity,
+        CompositingReasons direct_compositing_reasons,
+        CompositingReasons new_direct_compositing_reasons);
   };
 
   // This node is really a sentinel, and does not represent a real effect.
@@ -157,6 +173,10 @@
     return std::max(parent_changed, state_changed);
   }
 
+  PaintPropertyChangeType DirectlyUpdateOpacity(
+      float opacity,
+      const AnimationState& animation_state);
+
   const EffectPaintPropertyNode& Unalias() const = delete;
   bool IsParentAlias() const = delete;
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.cc
index a94e202..82f2357 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.cc
@@ -8,6 +8,7 @@
 #include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/run_loop.h"
 #include "base/strings/strcat.h"
 #include "base/trace_event/trace_event.h"
 #include "services/network/public/cpp/features.h"
@@ -34,6 +35,12 @@
 namespace blink {
 namespace {
 
+bool ShouldSendDirectlyToPreloadScanner() {
+  static const base::FeatureParam<bool> kSendToScannerParam{
+      &features::kThreadedBodyLoader, "send-to-scanner", true};
+  return kSendToScannerParam.Get();
+}
+
 // A chunk of data read by the OffThreadBodyReader. This will be created on a
 // background thread and processed on the main thread.
 struct DataChunk {
@@ -130,11 +137,36 @@
     return std::move(data_chunks_);
   }
 
+  void StoreProcessBackgroundDataCallback(Client* client) {
+    DCHECK(IsMainThread());
+    if (background_callback_set_)
+      return;
+
+    auto callback = client->TakeProcessBackgroundDataCallback();
+    if (!callback)
+      return;
+
+    background_callback_set_ = true;
+
+    base::AutoLock lock(lock_);
+    process_background_data_callback_ = std::move(callback);
+
+    // Process any existing data to make sure we don't miss any.
+    for (const auto& chunk : data_chunks_)
+      process_background_data_callback_.Run(chunk.decoded_data);
+  }
+
   void Delete() const {
     DCHECK(IsMainThread());
     reader_task_runner_->DeleteSoon(FROM_HERE, this);
   }
 
+  void FlushForTesting() {
+    base::RunLoop run_loop;
+    reader_task_runner_->PostTask(FROM_HERE, run_loop.QuitClosure());
+    run_loop.Run();
+  }
+
  private:
   // BodyReader:
   bool ShouldContinueReading() override {
@@ -185,6 +217,9 @@
     bool post_task;
     {
       base::AutoLock lock(lock_);
+      if (decoded_data && process_background_data_callback_)
+        process_background_data_callback_.Run(decoded_data);
+
       // If |data_chunks_| is not empty, there is already a task posted which
       // will consume the data, so no need to post another one.
       post_task = data_chunks_.empty();
@@ -214,6 +249,11 @@
   bool has_seen_end_of_data_ = false;
 
   base::Lock lock_;
+  // This bool is used on the main thread to avoid locking when the callback has
+  // already been set.
+  bool background_callback_set_ = false;
+  Client::ProcessBackgroundDataCallback process_background_data_callback_
+      GUARDED_BY(lock_);
   std::vector<DataChunk> data_chunks_ GUARDED_BY(lock_);
 };
 
@@ -269,7 +309,9 @@
                       task_runner_),
       resource_load_info_notifier_wrapper_(
           std::move(resource_load_info_notifier_wrapper)),
-      original_url_(original_url) {}
+      original_url_(original_url),
+      should_send_directly_to_preload_scanner_(
+          ShouldSendDirectlyToPreloadScanner()) {}
 
 NavigationBodyLoader::~NavigationBodyLoader() {
   if (!has_received_completion_ || !has_seen_end_of_data_) {
@@ -360,6 +402,12 @@
       should_keep_encoded_data));
 }
 
+void NavigationBodyLoader::FlushOffThreadBodyReaderForTesting() {
+  if (!off_thread_body_reader_)
+    return;
+  off_thread_body_reader_->FlushForTesting();
+}
+
 void NavigationBodyLoader::BindURLLoaderAndContinue() {
   url_loader_.Bind(std::move(endpoints_->url_loader), task_runner_);
   url_loader_client_receiver_.Bind(std::move(endpoints_->url_loader_client),
@@ -419,6 +467,9 @@
     }
   }
   NotifyCompletionIfAppropriate();
+
+  if (weak_self && should_send_directly_to_preload_scanner_)
+    off_thread_body_reader_->StoreProcessBackgroundDataCallback(client_);
 }
 
 void NavigationBodyLoader::ReadFromDataPipe() {
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.h b/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.h
index 72ec6573..a100fce 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.h
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.h
@@ -62,9 +62,25 @@
   // callbacks will not be called until StartLoadingBody() is called. If
   // |should_keep_encoded_data| is true, the original data will be copied from
   // the background thread and passed to DecodedBodyDataReceived().
+  //
+  // If the preload scanner is being used in the parser, the flow of data to the
+  // scanner will go like this:
+  //   1. NavigationBodyLoader calls DecodedBodyDataReceived(), which will
+  //      end up getting forwarded to HTMLDocumentParser::Append().
+  //   2. HTMLDocumentParser::Append() will cause the background preload
+  //      scanner to be created and scan the initial data passed to Append().
+  //   3. NavigationBodyLoader calls TakeProcessBackgroundDataCallback()
+  //      which tells HTMLDocumentParser to stop sending data to the
+  //      preload scanner in Append().
+  //   4. NavigationBodyLoader will pass data directly to the callback
+  //      taken from TakeProcessBackgroundDataCallback(), which avoids
+  //      hitting the main thread at all. HTMLDocumentParser will still
+  //      receive data through Append() calls.
   void StartLoadingBodyInBackground(std::unique_ptr<BodyTextDecoder> decoder,
                                     bool should_keep_encoded_data);
 
+  void FlushOffThreadBodyReaderForTesting();
+
  private:
   // The loading flow is outlined below. NavigationBodyLoader can be safely
   // deleted at any moment, and it will record cancelation stats, but will not
@@ -176,6 +192,7 @@
   using OffThreadBodyReaderPtr =
       std::unique_ptr<OffThreadBodyReader, OffThreadBodyReaderDeleter>;
   OffThreadBodyReaderPtr off_thread_body_reader_;
+  bool should_send_directly_to_preload_scanner_ = false;
 
   base::WeakPtrFactory<NavigationBodyLoader> weak_factory_{this};
 };
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader_unittest.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader_unittest.cc
index 75c6d1b2..e18fde5 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader_unittest.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader_unittest.cc
@@ -27,6 +27,7 @@
 #include "third_party/blink/renderer/platform/loader/fetch/code_cache_host.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
 #include "third_party/blink/renderer/platform/weborigin/referrer.h"
+#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
 
 namespace blink {
 
@@ -143,6 +144,10 @@
       run_loop_->Quit();
   }
 
+  ProcessBackgroundDataCallback TakeProcessBackgroundDataCallback() override {
+    return std::move(process_background_data_callback_);
+  }
+
   void TakeActions() {
     if (!buffer_to_write_.empty()) {
       std::string buffer = buffer_to_write_;
@@ -227,6 +232,7 @@
   bool destroy_loader_ = false;
   std::string data_received_;
   absl::optional<WebURLError> error_;
+  ProcessBackgroundDataCallback process_background_data_callback_;
 };
 
 TEST_F(NavigationBodyLoaderTest, SetDefersBeforeStart) {
@@ -246,6 +252,41 @@
   EXPECT_EQ("HELLO", TakeDataReceived());
 }
 
+TEST_F(NavigationBodyLoaderTest, ProcessBackgroundData) {
+  CreateBodyLoader();
+  StartLoadingInBackground();
+  // First flush data to the off thread reader. The background data callback
+  // should not see this since it is not set yet.
+  Write("hello");
+  To<NavigationBodyLoader>(loader_.get())->FlushOffThreadBodyReaderForTesting();
+
+  String background_data = "";
+  process_background_data_callback_ = CrossThreadBindRepeating(
+      [](String* background_data, const WebString& data) {
+        *background_data = *background_data + String(data);
+      },
+      CrossThreadUnretained(&background_data));
+
+  ExpectDecodedDataReceived();
+  StartLoading();
+  Wait();
+  EXPECT_EQ("HELLO", TakeDataReceived());
+  EXPECT_EQ("", background_data);
+
+  // Now write more data with the background data callback set.
+  ExpectDecodedDataReceived();
+  Write("hello2");
+  Wait();
+  EXPECT_EQ("HELLO2", TakeDataReceived());
+  EXPECT_EQ("HELLO2", background_data);
+
+  ExpectDecodedDataReceived();
+  Write("hello3");
+  Wait();
+  EXPECT_EQ("HELLO3", TakeDataReceived());
+  EXPECT_EQ("HELLO2HELLO3", background_data);
+}
+
 TEST_F(NavigationBodyLoaderTest, DataReceived) {
   CreateBodyLoader();
   StartLoading();
diff --git a/third_party/blink/renderer/platform/media/web_media_player_impl.cc b/third_party/blink/renderer/platform/media/web_media_player_impl.cc
index 8287f504..791da6c 100644
--- a/third_party/blink/renderer/platform/media/web_media_player_impl.cc
+++ b/third_party/blink/renderer/platform/media/web_media_player_impl.cc
@@ -2610,6 +2610,7 @@
   DCHECK(main_task_runner_->BelongsToCurrentThread());
 
   was_suspended_for_frame_closed_ = true;
+  UpdateBackgroundVideoOptimizationState();
   UpdatePlayState();
 }
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index ee01a0c..d2cd2cf 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -7239,6 +7239,8 @@
 # Disable top flakes trigger step failure on builders
 crbug.com/1371949 [ Mac12 ] virtual/low-priority-script-loading/http/tests/devtools/network/resource-priority.js [ Failure Pass ]
 crbug.com/1371949 [ Mac11 ] virtual/low-priority-script-loading/http/tests/devtools/network/resource-priority.js [ Failure Pass ]
+crbug.com/1374204 [ Linux ] inspector-protocol/debugger/updateCallFrameScopes.js [ Failure Pass ]
+crbug.com/1374204 [ Mac ] inspector-protocol/debugger/updateCallFrameScopes.js [ Failure Pass ]
 
 # Sheriff 2022-10-07
 crbug.com/1372556 [ Linux ] external/wpt/css/css-text/text-transform/text-transform-capitalize-* [ Failure Pass ]
@@ -7276,8 +7278,6 @@
 crbug.com/1374264 [ Mac ] external/wpt/screen-capture/permissions-policy-audio.https.sub.html [ Failure ]
 crbug.com/1374264 [ Mac ] external/wpt/screen-capture/permissions-policy-video.https.sub.html [ Failure ]
 
-crbug.com/1372181 external/wpt/scroll-animations/css/scroll-timeline-default-iframe-print.html [ Failure ]
-
 # Sheriff 2022-10-04
 crbug.com/1145491 [ Mac ] virtual/controls-refresh-hc/fast/forms/color-scheme/media/video-overlay-menu.html [ Skip ]
 
@@ -7286,3 +7286,392 @@
 crbug.com/1373684 external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child.tentative.sub.window.html [ Failure Pass Timeout ]
 crbug.com/1373684 external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-child-special-cases.tentative.sub.window.html [ Failure Pass Timeout ]
 crbug.com/1373684 external/wpt/html/semantics/embedded-content/the-iframe-element/sandbox-top-navigation-grandchild.tentative.sub.window.html [ Failure Pass Timeout ]
+
+#Temporarily disable tests until debugged and fixed  2022-10-05
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/alpha-video-playback.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/color-profile-video-seek-object-fit.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/color-profile-video-seek.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/color-profile-video.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/controls-after-reload.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/controls-strict.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/controls-styling-strict.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/controls-styling.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/controls-without-preload.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-aspect-ratio.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-canvas-alpha.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-colorspace-yuv420.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-colorspace-yuv422.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-controls-rendering.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-display-toggle.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-object-fit-change.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-object-fit.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-replaces-poster.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-zoom-controls.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/controls/overflow-menu-focus.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/controls/video-overlay-cast-dark-rendering.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/controls/video-overlay-cast-light-rendering.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/track/track-cue-rendering-horizontal.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/media-controls-overflow-hidden.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/media-element-play-after-eos.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-canvas.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-seek-past-end-paused.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/W3C/video/networkState/networkState_during_progress.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/external/wpt/media-source/mediasource-changetype-play-implicit.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/external/wpt/media-source/mediasource-changetype-play.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/external/wpt/media-source/mediasource-config-change-webm-v-framesize.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/external/wpt/media-source/mediasource-duration.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/external/wpt/media-source/mediasource-seek-beyond-duration.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/external/wpt/media-source/dedicated-worker/mediasource-worker-play-terminate-worker.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-duration-known-after-eos.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-loop.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-no-audio.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-seek-to-duration-with-playbackrate-zero.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/controls/controls-video-keynav.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/controls/paint-controls-webkit-appearance-none-custom-bg.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/controls/paint-controls-webkit-appearance-none.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/track/track-cue-rendering-rtl.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-currentTime-delay.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-layer-crash.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-persistence.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-size.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-source-error-no-candidate.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-source-type.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-src-skip-suspend-after-have-metadata.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/video-zoom.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/controls/captions-menu-always-visible.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/controls/overflow-menu-always-visible.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/controls/video-controls-with-cast-rendering.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/controls/volumechange-muted-attribute.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/track/track-css-matching-timestamps.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/track/track-css-matching.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/track/track-cue-rendering-position-auto-rtl.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/track/track-cue-rendering-position-auto.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/track/track-cue-rendering-ruby.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-frameserver/media/track/track-cue-rendering-snap-to-lines-not-set.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/alpha-video-playback.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/color-profile-video-seek-object-fit.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/color-profile-video-seek.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/color-profile-video.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls-after-reload.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls-strict.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls-styling-strict.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls-styling.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls-without-preload.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-aspect-ratio.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-canvas-alpha.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-colorspace-yuv420.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-colorspace-yuv422.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-controls-rendering.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-display-toggle.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-object-fit-change.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-object-fit.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-replaces-poster.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-zoom-controls.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/video-overlay-cast-dark-rendering.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/video-overlay-cast-light-rendering.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-horizontal.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-element-play-after-eos.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-canvas.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-seek-past-end-paused.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/networkState/networkState_during_progress.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-changetype-play-implicit.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-changetype-play.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-config-change-mp4-av-audio-bitrate.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-config-change-mp4-av-framesize.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-config-change-mp4-av-video-bitrate.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-config-change-mp4-v-framesize.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-config-change-webm-av-audio-bitrate.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-config-change-webm-av-framesize.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-config-change-webm-av-video-bitrate.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-config-change-webm-v-bitrate.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-config-change-webm-v-framerate.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-config-change-webm-v-framesize.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-duration.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-play-then-seek-back.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-play.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-redundant-seek.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-replay.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-seek-beyond-duration.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/dedicated-worker/mediasource-worker-handle-transfer.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/dedicated-worker/mediasource-worker-play-terminate-worker.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/dedicated-worker/mediasource-worker-play.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mse-for-webcodecs/tentative/mediasource-webcodecs-appendencodedchunks-play.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/event-attributes.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-currentTime-delay.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-currentTime-set.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-duration-known-after-eos.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-loop.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-no-audio.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-not-paused-while-looping.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-playbackrate.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-poster-after-playing.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-seek-to-duration-with-playbackrate-zero.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-seeking.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/controls-video-keynav.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/doubletap-to-jump-backwards-at-start.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/doubletap-to-jump-backwards.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/doubletap-to-jump-forwards-too-short.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/paint-controls-webkit-appearance-none-custom-bg.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/paint-controls-webkit-appearance-none.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/scrubbing-touch.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/scrubbing.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/time-update-after-unload.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/toggle-class-with-poster-frame.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/toggle-class-with-poster-image.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/toggle-class-with-state-fullscreen.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0005.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0006.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0009.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0011.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0014.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0024.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0035.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0036.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0037.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0038.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0039.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0051.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0052.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0053.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0054.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0055.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0059.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0072.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0078.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0079.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0085.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0086.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0087.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0088.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0089.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0090.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0091.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-css-cue-lifetime.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-css-matching-timestamps.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-css-matching.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-mutable-text.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-nothing-to-render.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-rtl.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-snap-to-lines-not-set.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-word-breaking.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/regions-webvtt/vtt-region-display.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/encrypted-media/encrypted-media-lifetime-reload.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/encrypted-media/encrypted-media-playback-before-setmediakeys.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/encrypted-media/encrypted-media-playback-encrypted-and-clear-sources.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/encrypted-media/encrypted-media-playback-multiple-sessions.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/encrypted-media/encrypted-media-playback-setmediakeys-after-src.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/encrypted-media/encrypted-media-playback-setmediakeys-before-src.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/encrypted-media/encrypted-media-playback-two-videos.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/encrypted-media/encrypted-media-setmediakeys-again-after-playback.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/encrypted-media/encrypted-media-setmediakeys-again-after-resetting-src.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/encrypted-media/encrypted-media-setmediakeys-at-same-time.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/encrypted-media/encrypted-media-waiting-for-a-key.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-seek-past-end-playing.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/video-controls-with-cast-rendering.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0004.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0017.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0080.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-fragments/TC0081.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-ruby.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-activesourcebuffers.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-append-buffer.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-appendbuffer-quota-exceeded.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-config-change-webm-a-bitrate.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-duration-boundaryconditions.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-errors.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-h264-play-starved.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-seek-during-pending-seek.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-sourcebuffer-mode.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/external/wpt/media-source/mediasource-timestamp-offset.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/audio-concurrent-supported.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/audio-constructor-preload.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/audio-constructor-src.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/audio-constructor.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/audio-controls-do-not-fade-out.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/audio-data-url.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/audio-delete-while-slider-thumb-clicked.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/audio-garbage-collect.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/audio-play-event.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/audio-repaint.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/audio-src-suspend-after-have-metadata.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/auto-play-in-sandbox-with-allow-scripts.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/autoplay-clears-autoplaying-flag.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/autoplay-document-move.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/autoplay-muted-conditions.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/autoplay-muted.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/autoplay-never-visible.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/autoplay-unmute-offscreen.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/autoplay-webapp-scope.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/autoplay-when-visible-multiple-times.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/autoplay-when-visible.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/autoplay-with-preload-none.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/autoplay.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/autoplay/document-user-activation.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/autoplay/muted-change-src.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/autoplay/muted-playsinline.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/autoplay/muted-volume-0.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls-drag-timebar.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls-right-click-on-timebar.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/accessibility-caption-button.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/accessibility-timeline-element.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/accessibility-timeline.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/captions-menu-always-visible.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/click-anywhere-to-play-pause.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/closed-captions-dynamic-update.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/closed-captions-on-off.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/closed-captions-single-track.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/closed-captions-switch-track.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/controls-cast-do-not-fade-out.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/controls-cast-overlay-slow-fade.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/controls-layout-in-different-size.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/doubletap-on-play-button.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/doubletap-to-toggle-fullscreen.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/immersive-doubletap.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/overflow-button-disabled-no-source.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/overflow-menu-always-visible.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/overflow-menu-hide-on-click-item.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/overflow-menu-hide-on-click-outside.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/overflow-menu-hide-on-click-panel.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/overflow-menu-hide-on-scroll.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/overflow-menu-keyboard-navigation.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/overflow-menu-pointer-selection.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/overflow-menu-toggle-class-for-animation.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/overflow-menu-visibility.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/overlay-play-button-tap-to-hide.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/playback-speed-menu.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/progress-bar-repaint-on-size-change.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/show-controls-on-touch.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/singletouch-on-play-button.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/slow-doubletap.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/tab-into-controls-after-touch.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/tap-to-hide-controls.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/text-track-menu-pointer-selection.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/text-track-menu-keyboard-navigation.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/toggle-class-with-state-source-reset.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/video-enter-exit-fullscreen-while-hovering-shows-controls.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/video-enter-exit-fullscreen-without-hovering-doesnt-show-controls.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/controls/volumechange-muted-attribute.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/gc-while-playing.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/gc-while-seeking.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-continues-playing-after-replace-source.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-controls-invalid-url.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-controls-overflow-hidden.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-controls-tap-show-controls-without-activating.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-document-audio-repaint.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-document-audio-size.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-ended.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-play-promise.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-src-suspend-after-have-enough-data.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-src-suspend-after-have-future-data.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-src-suspend-after-have-metadata.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-src-suspend-before-have-metadata.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/media-state.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/picture-in-picture/clear-after-pip.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/picture-in-picture/clear-after-request.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/picture-in-picture/clear-player-during-pip.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/picture-in-picture/controls/picture-in-picture-button.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/picture-in-picture/picture-in-picture-interstitial.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/picture-in-picture/picture-in-picture-interstitial-sizing.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/remove-from-document.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/css-cue-for-video-in-shadow-2.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/css-cue-for-video-in-shadow.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/cue-style-invalidation.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/regions-webvtt/vtt-region-dom-layout.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/text-track-selection-menu-add-track.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-css-all-cues.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-css-author-settings-override-user-settings.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-css-important-user-settings-override-author-settings.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-css-is-pseudo.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-css-matching-default.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-css-matching-lang.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-css-property-allowlist.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-css-user-settings-override-internal-settings.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-container-rendering-position.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-gc-wrapper.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-dynamic-controls.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-dynamic-style.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-font-with-zoom.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-mode-changed.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-negative-line-numbers.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-no-space.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-on-resize.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-overscan.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-position-auto-rtl.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-position-auto.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-tree-is-removed-properly.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-vertical.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-wider-than-controls.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-with-padding.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/track/track-cue-rendering-zero-dimension-crash.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-autoplay.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-black-bg-in-media-document.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-buffered-unknown-duration.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-controls-auto-hide-after-play-by-touch.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-controls-dont-show-on-focus-when-disabled.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-controls-hide-after-touch-on-control.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-controls-hide-on-move-outside-controls.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-controls-in-media-document.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-controls-labels.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-controls-overflow-menu-fullscreen-button.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-controls-show-on-focus.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-controls-toggling.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-controls-track-selection-menu.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-controls-visibility-multimodal-mouse-after-touch.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-controls-visibility-multimodal-touch-after-mouse.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-controls-zoomed.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-currentTime-before-have-metadata-media-fragment-uri.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-currentTime-before-have-metadata.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-dom-autoplay.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-double-seek-currentTime.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-layer-crash.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-pause-immediately.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-persistence.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-play-empty-events.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-positive-start-time-seek-after-start-time.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-positive-start-time-seek-before-start-time.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-poster-load-after-playing.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-preload.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-remove-insert-repaints.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-scales-in-media-document.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-seek-by-small-increment.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-size.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-source.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-src-skip-suspend-after-have-metadata.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-src-suspend-after-have-metadata.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-srcobject-mediastream-src-file.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-timeupdate-during-playback.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/video-zoom.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/viewport-in-standalone-media-document.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/audio/events/event_canplay.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/audio/events/event_canplaythrough.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/audio/events/event_loadeddata.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/audio/events/event_order_canplay_canplaythrough.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/audio/events/event_order_canplay_playing.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/audio/events/event_order_loadedmetadata_loadeddata.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/audio/events/event_play.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/audio/events/event_playing_manual.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/audio/events/event_playing.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/audio/events/event_timeupdate.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/audio/paused/paused_false_during_play.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/audio/readyState/readyState_during_canplay.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/audio/readyState/readyState_during_canplaythrough.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/audio/readyState/readyState_during_loadeddata.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/audio/readyState/readyState_during_playing.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/events/event_canplay.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/events/event_canplaythrough.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/events/event_loadeddata.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/events/event_order_canplay_canplaythrough.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/events/event_order_canplay_playing.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/events/event_order_loadedmetadata_loadeddata.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/events/event_order_loadstart_progress.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/events/event_play.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/events/event_playing_manual.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/events/event_playing.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/events/event_progress.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/events/event_timeupdate.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/paused/paused_false_during_play.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/readyState/readyState_during_canplay.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/readyState/readyState_during_canplaythrough.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/readyState/readyState_during_loadeddata.html [ Skip ]
+crbug.com/1362106 [ Win ] virtual/media-foundation-for-clear-dcomp/media/W3C/video/readyState/readyState_during_playing.html [ Skip ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 267642c..4f46713 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -178,6 +178,23 @@
              "--disable-composited-antialiasing"]
   },
   {
+    "prefix": "media-foundation-for-clear-dcomp",
+    "platforms": ["Win"],
+    "bases": ["external/wpt/media-source",
+              "media"],
+    "args": ["--use-gpu-in-tests",
+             "--enable-features=MediaFoundationClearPlayback,MediaFoundationClearRendering:strategy/direct-composition"]
+  },
+  {
+    "prefix": "media-foundation-for-clear-frameserver",
+    "platforms": ["Win"],
+    "bases": ["external/wpt/media-source",
+              "media"],
+    "args": ["--use-gpu-in-tests",
+             "--force-mfmediaengine-renderer",
+             "--enable-features=MediaFoundationClearRendering:strategy/frame-server"]
+  },
+  {
     "prefix": "media-gpu-accelerated",
     "platforms": ["Linux", "Mac", "Win", "Fuchsia"],
     "bases": ["external/wpt/media-source",
diff --git a/third_party/blink/web_tests/external/wpt/.well-known/web-identity b/third_party/blink/web_tests/external/wpt/.well-known/web-identity
index f3a99e2..836d87b7 100644
--- a/third_party/blink/web_tests/external/wpt/.well-known/web-identity
+++ b/third_party/blink/web_tests/external/wpt/.well-known/web-identity
@@ -1,6 +1,13 @@
 def main(request, response):
-  if not b"sec-fetch-dest" in request.headers or request.headers[b"sec-fetch-dest"] != b"webidentity":
-    return (500, [], "Missing Sec-Fetch-Dest header")
+  if len(request.cookies) > 0:
+    return (530, [], "Cookie should not be sent to manifest list endpoint")
+  if request.headers.get(b"Accept") != b"application/json":
+    return (531, [], "Wrong Accept")
+  if request.headers.get(b"Sec-Fetch-Dest") != b"webidentity":
+    return (532, [], "Wrong Sec-Fetch-Dest header")
+  if request.headers.get(b"Referer"):
+    return (533, [], "Should not have Referer")
+
   config = request.server.config
   host = config.browser_host + ":" + str(config.ports["https"][0])
   return """
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/fedcm-logout-rps.https.html b/third_party/blink/web_tests/external/wpt/credential-management/fedcm-logout-rps.https.html
index 9a96777..51b1230 100644
--- a/third_party/blink/web_tests/external/wpt/credential-management/fedcm-logout-rps.https.html
+++ b/third_party/blink/web_tests/external/wpt/credential-management/fedcm-logout-rps.https.html
@@ -7,9 +7,9 @@
 <script src="/resources/testharnessreport.js"></script>
 
 <script type="module">
-  import {fedcm_test} from './support/fedcm-mojojs-helper.js';
+  import {fedcm_mojo_mock_test} from './support/fedcm-mojojs-helper.js';
 
-  fedcm_test(async (t, mock) => {
+  fedcm_mojo_mock_test(async (t, mock) => {
     mock.logoutRpsReturn("kError");
     return promise_rejects_dom(t, "NetworkError",
       IdentityCredential.logoutRPs([{
@@ -19,7 +19,7 @@
     );
   }, "IdentityCredential.logoutRPs() error.");
 
-  fedcm_test(async (t, mock) => {
+  fedcm_mojo_mock_test(async (t, mock) => {
     mock.logoutRpsReturn("kSuccess");
     await IdentityCredential.logoutRPs([{
       accountId: "1234",
@@ -27,7 +27,7 @@
     }]);
   }, "IdentityCredential.logoutRPs() success.");
 
-  fedcm_test(async (t, mock) => {
+  fedcm_mojo_mock_test(async (t, mock) => {
     return promise_rejects_dom(t, "NetworkError",
       IdentityCredential.logoutRPs([{
         accountId: "1234",
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/fedcm-network-requests.sub.https.html b/third_party/blink/web_tests/external/wpt/credential-management/fedcm-network-requests.sub.https.html
index 9c21ccec..12a3813a 100644
--- a/third_party/blink/web_tests/external/wpt/credential-management/fedcm-network-requests.sub.https.html
+++ b/third_party/blink/web_tests/external/wpt/credential-management/fedcm-network-requests.sub.https.html
@@ -20,14 +20,20 @@
   }
 };
 
-promise_test(async t => {
-  await set_fedcm_cookie();
+// Test wrapper which does FedCM-specific setup.
+function fedcm_test(test_func) {
+  promise_test(async t => {
+    await set_fedcm_cookie();
+    await test_func(t);
+  });
+}
+
+fedcm_test(async t => {
   const cred = await navigator.credentials.get(test_options);
   assert_equals(cred.token, "token");
 }, "Successfully obtaining token should resolve the promise.");
 
-promise_test(async t => {
-  await set_fedcm_cookie();
+fedcm_test(async t => {
   const first = navigator.credentials.get(test_options);
   const second = navigator.credentials.get(test_options);
 
@@ -43,7 +49,7 @@
 },
 "When there's a pending request, a second `get` call should be rejected. ");
 
-promise_test(async t => {
+fedcm_test(async t => {
   const cred = navigator.credentials.get({
   identity: {
     providers: []
@@ -52,7 +58,7 @@
   return promise_rejects_js(t, TypeError, cred);
 }, "Reject when provider list is empty");
 
-promise_test(async t => {
+fedcm_test(async t => {
   const cred = navigator.credentials.get({
   identity: {
     providers: [{
@@ -64,7 +70,7 @@
   return promise_rejects_js(t, TypeError, cred);
 }, "Reject when configURL is missing" );
 
-promise_test(async t => {
+fedcm_test(async t => {
   const cred = navigator.credentials.get({
   identity: {
     providers: [{
@@ -76,7 +82,7 @@
   return promise_rejects_dom(t, "InvalidStateError", cred);
 }, "Reject when configURL is invalid");
 
-promise_test(async t => {
+fedcm_test(async t => {
   const cred = navigator.credentials.get({
   identity: {
     providers: [{
@@ -88,7 +94,7 @@
   return promise_rejects_dom(t, "InvalidStateError", cred);
 }, "Reject when clientId is empty");
 
-promise_test(async t => {
+fedcm_test(async t => {
   const cred = await navigator.credentials.get({
     identity: {
       providers: [{
@@ -100,7 +106,7 @@
   assert_equals(cred.token, "token");
 }, "nonce is not required in FederatedIdentityProvider.");
 
-promise_test(async t => {
+fedcm_test(async t => {
   const cred = navigator.credentials.get({
   identity: {
     providers: [{
@@ -111,7 +117,7 @@
   return promise_rejects_js(t, TypeError, cred);
 }, "Reject when clientId is missing" );
 
-promise_test(async t => {
+fedcm_test(async t => {
   let controller = new AbortController();
   const cred = navigator.credentials.get({
     identity: test_options.identity,
@@ -121,9 +127,7 @@
   return promise_rejects_dom(t, 'AbortError', cred);
 }, "Test the abort signal");
 
-promise_test(async t => {
-  await set_fedcm_cookie();
-
+fedcm_test(async t => {
   let controller = new AbortController();
   const first_cred = navigator.credentials.get({
     identity: test_options.identity,
@@ -136,7 +140,7 @@
   assert_equals(second_cred.token, "token");
 }, "Get after abort should work");
 
-promise_test(async t => {
+fedcm_test(async t => {
   const cred = navigator.credentials.get({
     identity: {
       providers: [{
@@ -148,14 +152,12 @@
   return promise_rejects_dom(t, "NetworkError", cred);
 }, "Provider configURL should honor Content-Security-Policy.");
 
-promise_test(async t => {
-  await set_fedcm_cookie();
+fedcm_test(async t => {
   const cred = await navigator.credentials.get(test_options);
   assert_equals(cred.token, 'token');
 }, 'Test that COEP policy do not apply to FedCM requests');
 
-promise_test(async t => {
-  await set_fedcm_cookie();
+fedcm_test(async t => {
   const cred = navigator.credentials.get({
     identity: {
       providers: [{
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/support/README.md b/third_party/blink/web_tests/external/wpt/credential-management/support/README.md
index 902a7ca..6fb064c 100644
--- a/third_party/blink/web_tests/external/wpt/credential-management/support/README.md
+++ b/third_party/blink/web_tests/external/wpt/credential-management/support/README.md
@@ -34,7 +34,7 @@
 
 ## FedCM Testing
 
-`fedcm-mojojs-helper.js` exposes `fedcm_test` which is a specialized
+`fedcm-mojojs-helper.js` exposes `fedcm_mojo_mock_test` which is a specialized
 `promise_test` which comes pre-setup with the appropriate mocking infrastructure
 to emulate platform federated auth backend. The mock is passed to the test
 function as the second parameter.
@@ -42,9 +42,9 @@
 Example usage:
 ```
 <script type="module">
-  import {fedcm_test} from './support/fedcm-mojojs-helper.js';
+  import {fedcm_mojo_mock_test} from './support/fedcm-mojojs-helper.js';
 
-  fedcm_test(async (t, mock) => {
+  fedcm_mojo_mock_test(async (t, mock) => {
     mock.returnToken("a_token");
     assert_equals("a_token", await navigator.credentials.get(options));
   }, "Successfully obtaining a token using mock.");
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/support/accounts.py b/third_party/blink/web_tests/external/wpt/credential-management/support/accounts.py
index e9828cf..f645dfda 100644
--- a/third_party/blink/web_tests/external/wpt/credential-management/support/accounts.py
+++ b/third_party/blink/web_tests/external/wpt/credential-management/support/accounts.py
@@ -1,8 +1,13 @@
 def main(request, response):
-  if not b"sec-fetch-dest" in request.headers or request.headers[b"sec-fetch-dest"] != b"webidentity":
-    return (500, [], "Missing Sec-Fetch-Dest header")
-  if not b"cookie" in request.cookies or request.cookies[b"cookie"].value != b"1":
-    return (500, [], "Missing cookie")
+  if request.cookies.get(b"cookie") != b"1":
+    return (530, [], "Missing cookie")
+  if request.headers.get(b"Accept") != b"application/json":
+    return (531, [], "Wrong Accept")
+  if request.headers.get(b"Sec-Fetch-Dest") != b"webidentity":
+    return (532, [], "Wrong Sec-Fetch-Dest header")
+  if request.headers.get(b"Referer"):
+    return (533, [], "Should not have Referer")
+
   return """
 {
  "accounts": [{
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/support/client_metadata.py b/third_party/blink/web_tests/external/wpt/credential-management/support/client_metadata.py
index e9ae722..f08b2852 100644
--- a/third_party/blink/web_tests/external/wpt/credential-management/support/client_metadata.py
+++ b/third_party/blink/web_tests/external/wpt/credential-management/support/client_metadata.py
@@ -1,8 +1,13 @@
 def main(request, response):
-  if not b"sec-fetch-dest" in request.headers or request.headers[b"sec-fetch-dest"] != b"webidentity":
-    return (500, [], "Missing Sec-Fetch-Dest header")
-  if b"cookie" in request.cookies:
-    return (500, [], "Cookie should not be sent to this endpoint")
+  if len(request.cookies) > 0:
+    return (530, [], "Cookie should not be sent to this endpoint")
+  if request.headers.get(b"Accept") != b"application/json":
+    return (531, [], "Wrong Accept")
+  if request.headers.get(b"Sec-Fetch-Dest") != b"webidentity":
+    return (532, [], "Wrong Sec-Fetch-Dest header")
+  if not request.headers.get(b"Referer"):
+    return (533, [], "Missing Referer")
+
   return """
 {
   "privacy_policy_url": "https://privacypolicy.com"
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/support/fedcm-mojojs-helper.js b/third_party/blink/web_tests/external/wpt/credential-management/support/fedcm-mojojs-helper.js
index 428a2fc..40ab729 100644
--- a/third_party/blink/web_tests/external/wpt/credential-management/support/fedcm-mojojs-helper.js
+++ b/third_party/blink/web_tests/external/wpt/credential-management/support/fedcm-mojojs-helper.js
@@ -5,7 +5,7 @@
 
 import { MockFederatedAuthRequest } from './fedcm-mock.js';
 
-export function fedcm_test(test_func, name, exception, properties) {
+export function fedcm_mojo_mock_test(test_func, name, exception, properties) {
   promise_test(async (t) => {
     assert_implements(navigator.credentials, 'missing navigator.credentials');
     const mock = new MockFederatedAuthRequest();
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/support/token.py b/third_party/blink/web_tests/external/wpt/credential-management/support/token.py
index 1ce4348..2f5e807b 100644
--- a/third_party/blink/web_tests/external/wpt/credential-management/support/token.py
+++ b/third_party/blink/web_tests/external/wpt/credential-management/support/token.py
@@ -1,6 +1,22 @@
 def main(request, response):
-  if not b"sec-fetch-dest" in request.headers or request.headers[b"sec-fetch-dest"] != b"webidentity":
-    return (500, [], "Missing Sec-Fetch-Dest header")
-  if not b"cookie" in request.cookies or request.cookies[b"cookie"].value != b"1":
-    return (500, [], "Missing cookie")
+  if request.cookies.get(b"cookie") != b"1":
+    return (530, [], "Missing cookie")
+  if request.method != "POST":
+    return (531, [], "Method is not POST")
+  if request.headers.get(b"Content-Type") != b"application/x-www-form-urlencoded":
+    return (532, [], "Wrong Content-Type")
+  if request.headers.get(b"Accept") != b"application/json":
+    return (533, [], "Wrong Accept")
+  if request.headers.get(b"Sec-Fetch-Dest") != b"webidentity":
+    return (500, [], "Wrong Sec-Fetch-Dest header")
+  if not request.headers.get(b"Referer"):
+    return (534, [], "Missing Referer")
+
+  if not request.POST.get(b"client_id"):
+    return (535, [], "Missing 'client_id' POST parameter")
+  if not request.POST.get(b"account_id"):
+    return (536, [], "Missing 'account_id' POST parameter")
+  if not request.POST.get(b"disclosure_text_shown"):
+    return (537, [], "Missing 'disclosure_text_shown' POST parameter")
+
   return "{\"token\": \"token\"}"
diff --git a/third_party/blink/web_tests/external/wpt/css/compositing/opacity-and-transform-animation-crash.html b/third_party/blink/web_tests/external/wpt/css/compositing/opacity-and-transform-animation-crash.html
new file mode 100644
index 0000000..294a823
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/compositing/opacity-and-transform-animation-crash.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html class=test-wait>
+<style type="text/css">
+  @keyframes anim {
+    from {
+      opacity: 0.99;
+    }
+    to {
+      opacity: 0.9;
+      transform: scale(1, 1);
+    }
+  }
+</style>
+<div id="div" style="transform: scale(1, 10); animation: 50ms linear 0s anim;"></div>
+<script>
+  function boom() {
+    div.style.transform = '';
+    document.execCommand("SelectAll", false, "");
+    requestAnimationFrame(function() {
+      requestAnimationFrame(function() {
+        document.documentElement.classList.remove('test-wait');
+      });
+    });
+  }
+  setTimeout(boom, 50);
+</script>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-position/crashtests/scroll-tree-parent-construction.html b/third_party/blink/web_tests/external/wpt/css/css-position/crashtests/scroll-tree-parent-construction.html
new file mode 100644
index 0000000..8a5bf542
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-position/crashtests/scroll-tree-parent-construction.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html class="test-wait">
+<head>
+<style>
+  #ifr { border: 5px solid #ddd; width: 200px; height: 150px; }
+</style>
+</head>
+<body>
+<iframe id=ifr srcdoc="
+  <style>
+  body { margin: 0 }
+  * { box-sizing: border-box }
+  .c1 { width: 90px; height: 300px; }
+  .f1 {
+    position: fixed;
+    background: #ddf;
+    left: 30px;
+    top: 10px;
+    width: 120px;
+    height: 120px;
+  }
+  .s1 {
+    overflow: scroll;
+    margin: 10px;
+    height: 100px;
+    width: 100px;
+    border: 5px solid gray;
+  }
+  </style>
+  <div class=c1>AAA</div>
+  <div class=f1>
+    <div class=s1>
+      <div class=c1>AAA</div>
+    </div>
+  </div>"></iframe>
+<script>
+  raf = async () => {
+    return new Promise(resolve => {
+      requestAnimationFrame(resolve);
+    });
+  }
+  onload = async () => {
+    await raf();
+    await raf();
+    ifr.contentWindow.location.reload();
+    for (let i = 0; i < 10; i++)
+      await raf();
+    document.documentElement.className = "";
+  };
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference-expected.txt
deleted file mode 100644
index 8f80ec1d..0000000
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference-expected.txt
+++ /dev/null
@@ -1,174 +0,0 @@
-This is a testharness.js-based test.
-Found 168 tests; 143 PASS, 25 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS HTML (standards) IMG usemap="no-hash-name"
-PASS HTML (standards) OBJECT usemap="no-hash-name"
-PASS HTML (standards) IMG usemap="no-hash-id"
-PASS HTML (standards) OBJECT usemap="no-hash-id"
-PASS HTML (standards) IMG usemap="#hash-name"
-PASS HTML (standards) OBJECT usemap="#hash-name"
-FAIL HTML (standards) IMG usemap="#hash-id" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-hash-id... but got Element node <img src="/images/threecolors.png" usemap="#hash-id"></img>
-PASS HTML (standards) OBJECT usemap="#hash-id"
-PASS HTML (standards) IMG usemap="#non-map-with-this-name"
-PASS HTML (standards) OBJECT usemap="#non-map-with-this-name"
-FAIL HTML (standards) IMG usemap="#non-map-with-this-id" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-non-map... but got Element node <img src="/images/threecolors.png" usemap="#non-map-with-...
-PASS HTML (standards) OBJECT usemap="#non-map-with-this-id"
-PASS HTML (standards) IMG usemap="#two-maps-with-this-name"
-PASS HTML (standards) OBJECT usemap="#two-maps-with-this-name"
-FAIL HTML (standards) IMG usemap="#two-maps-with-this-id" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-two-map... but got Element node <img src="/images/threecolors.png" usemap="#two-maps-with...
-PASS HTML (standards) OBJECT usemap="#two-maps-with-this-id"
-PASS HTML (standards) IMG usemap="#two-maps-with-this-name-or-id"
-PASS HTML (standards) OBJECT usemap="#two-maps-with-this-name-or-id"
-FAIL HTML (standards) IMG usemap="#two-maps-with-this-id-or-name" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-two-map... but got Element node <area shape="rect" coords="0,0,99,50" href="#area-two-map...
-PASS HTML (standards) OBJECT usemap="#two-maps-with-this-id-or-name"
-FAIL HTML (standards) IMG usemap="hash-last#" assert_equals: expected Element node <img src="/images/threecolors.png" usemap="hash-last#"></... but got Element node <area shape="rect" coords="0,0,99,50" href="#area-empty-u...
-PASS HTML (standards) OBJECT usemap="hash-last#"
-PASS HTML (standards) IMG usemap=""
-PASS HTML (standards) OBJECT usemap=""
-FAIL HTML (standards) IMG usemap="#" assert_equals: expected Element node <img src="/images/threecolors.png" usemap="#"></img> but got Element node <area shape="rect" coords="0,0,99,50" href="#area-empty-u...
-PASS HTML (standards) OBJECT usemap="#"
-PASS HTML (standards) IMG usemap="# "
-PASS HTML (standards) OBJECT usemap="# "
-FAIL HTML (standards) IMG usemap="#\n" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-hash-LF... but got Element node <img src="/images/threecolors.png" usemap="#
-"></img>
-PASS HTML (standards) OBJECT usemap="#\n"
-PASS HTML (standards) IMG usemap="#percent-escape-name-%41"
-PASS HTML (standards) OBJECT usemap="#percent-escape-name-%41"
-PASS HTML (standards) IMG usemap="#percent-escape-id-%41"
-PASS HTML (standards) OBJECT usemap="#percent-escape-id-%41"
-PASS HTML (standards) IMG usemap="#percent-escape-name-%42"
-PASS HTML (standards) OBJECT usemap="#percent-escape-name-%42"
-FAIL HTML (standards) IMG usemap="#percent-escape-id-%42" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-percent... but got Element node <img src="/images/threecolors.png" usemap="#percent-escap...
-PASS HTML (standards) OBJECT usemap="#percent-escape-id-%42"
-PASS HTML (standards) IMG usemap="# hash-space-name"
-PASS HTML (standards) OBJECT usemap="# hash-space-name"
-FAIL HTML (standards) IMG usemap="# hash-space-id" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-hash-sp... but got Element node <img src="/images/threecolors.png" usemap="# hash-space-i...
-PASS HTML (standards) OBJECT usemap="# hash-space-id"
-PASS HTML (standards) IMG usemap=" #space-before-hash-name"
-PASS HTML (standards) OBJECT usemap=" #space-before-hash-name"
-FAIL HTML (standards) IMG usemap=" #space-before-hash-id" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-space-b... but got Element node <img src="/images/threecolors.png" usemap=" #space-before...
-PASS HTML (standards) OBJECT usemap=" #space-before-hash-id"
-PASS HTML (standards) IMG usemap="http://example.org/#garbage-before-hash-name"
-PASS HTML (standards) OBJECT usemap="http://example.org/#garbage-before-hash-name"
-FAIL HTML (standards) IMG usemap="http://example.org/#garbage-before-hash-id" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-garbage... but got Element node <img src="/images/threecolors.png" usemap="http://example...
-PASS HTML (standards) OBJECT usemap="http://example.org/#garbage-before-hash-id"
-PASS HTML (standards) IMG usemap="#no-such-map"
-PASS HTML (standards) OBJECT usemap="#no-such-map"
-PASS HTML (standards) IMG usemap="#different-CASE-name"
-PASS HTML (standards) OBJECT usemap="#different-CASE-name"
-PASS HTML (standards) IMG usemap="#different-CASE-id"
-PASS HTML (standards) OBJECT usemap="#different-CASE-id"
-PASS HTML (quirks) IMG usemap="no-hash-name"
-PASS HTML (quirks) OBJECT usemap="no-hash-name"
-PASS HTML (quirks) IMG usemap="no-hash-id"
-PASS HTML (quirks) OBJECT usemap="no-hash-id"
-PASS HTML (quirks) IMG usemap="#hash-name"
-PASS HTML (quirks) OBJECT usemap="#hash-name"
-FAIL HTML (quirks) IMG usemap="#hash-id" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-hash-id... but got Element node <img src="/images/threecolors.png" usemap="#hash-id"></img>
-PASS HTML (quirks) OBJECT usemap="#hash-id"
-PASS HTML (quirks) IMG usemap="#non-map-with-this-name"
-PASS HTML (quirks) OBJECT usemap="#non-map-with-this-name"
-FAIL HTML (quirks) IMG usemap="#non-map-with-this-id" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-non-map... but got Element node <img src="/images/threecolors.png" usemap="#non-map-with-...
-PASS HTML (quirks) OBJECT usemap="#non-map-with-this-id"
-PASS HTML (quirks) IMG usemap="#two-maps-with-this-name"
-PASS HTML (quirks) OBJECT usemap="#two-maps-with-this-name"
-FAIL HTML (quirks) IMG usemap="#two-maps-with-this-id" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-two-map... but got Element node <img src="/images/threecolors.png" usemap="#two-maps-with...
-PASS HTML (quirks) OBJECT usemap="#two-maps-with-this-id"
-PASS HTML (quirks) IMG usemap="#two-maps-with-this-name-or-id"
-PASS HTML (quirks) OBJECT usemap="#two-maps-with-this-name-or-id"
-FAIL HTML (quirks) IMG usemap="#two-maps-with-this-id-or-name" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-two-map... but got Element node <area shape="rect" coords="0,0,99,50" href="#area-two-map...
-PASS HTML (quirks) OBJECT usemap="#two-maps-with-this-id-or-name"
-FAIL HTML (quirks) IMG usemap="hash-last#" assert_equals: expected Element node <img src="/images/threecolors.png" usemap="hash-last#"></... but got Element node <area shape="rect" coords="0,0,99,50" href="#area-empty-u...
-PASS HTML (quirks) OBJECT usemap="hash-last#"
-PASS HTML (quirks) IMG usemap=""
-PASS HTML (quirks) OBJECT usemap=""
-FAIL HTML (quirks) IMG usemap="#" assert_equals: expected Element node <img src="/images/threecolors.png" usemap="#"></img> but got Element node <area shape="rect" coords="0,0,99,50" href="#area-empty-u...
-PASS HTML (quirks) OBJECT usemap="#"
-PASS HTML (quirks) IMG usemap="# "
-PASS HTML (quirks) OBJECT usemap="# "
-FAIL HTML (quirks) IMG usemap="#\n" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-hash-LF... but got Element node <img src="/images/threecolors.png" usemap="#
-"></img>
-PASS HTML (quirks) OBJECT usemap="#\n"
-PASS HTML (quirks) IMG usemap="#percent-escape-name-%41"
-PASS HTML (quirks) OBJECT usemap="#percent-escape-name-%41"
-PASS HTML (quirks) IMG usemap="#percent-escape-id-%41"
-PASS HTML (quirks) OBJECT usemap="#percent-escape-id-%41"
-PASS HTML (quirks) IMG usemap="#percent-escape-name-%42"
-PASS HTML (quirks) OBJECT usemap="#percent-escape-name-%42"
-FAIL HTML (quirks) IMG usemap="#percent-escape-id-%42" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-percent... but got Element node <img src="/images/threecolors.png" usemap="#percent-escap...
-PASS HTML (quirks) OBJECT usemap="#percent-escape-id-%42"
-PASS HTML (quirks) IMG usemap="# hash-space-name"
-PASS HTML (quirks) OBJECT usemap="# hash-space-name"
-FAIL HTML (quirks) IMG usemap="# hash-space-id" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-hash-sp... but got Element node <img src="/images/threecolors.png" usemap="# hash-space-i...
-PASS HTML (quirks) OBJECT usemap="# hash-space-id"
-PASS HTML (quirks) IMG usemap=" #space-before-hash-name"
-PASS HTML (quirks) OBJECT usemap=" #space-before-hash-name"
-FAIL HTML (quirks) IMG usemap=" #space-before-hash-id" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-space-b... but got Element node <img src="/images/threecolors.png" usemap=" #space-before...
-PASS HTML (quirks) OBJECT usemap=" #space-before-hash-id"
-PASS HTML (quirks) IMG usemap="http://example.org/#garbage-before-hash-name"
-PASS HTML (quirks) OBJECT usemap="http://example.org/#garbage-before-hash-name"
-FAIL HTML (quirks) IMG usemap="http://example.org/#garbage-before-hash-id" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-garbage... but got Element node <img src="/images/threecolors.png" usemap="http://example...
-PASS HTML (quirks) OBJECT usemap="http://example.org/#garbage-before-hash-id"
-PASS HTML (quirks) IMG usemap="#no-such-map"
-PASS HTML (quirks) OBJECT usemap="#no-such-map"
-PASS HTML (quirks) IMG usemap="#different-CASE-name"
-PASS HTML (quirks) OBJECT usemap="#different-CASE-name"
-PASS HTML (quirks) IMG usemap="#different-CASE-id"
-PASS HTML (quirks) OBJECT usemap="#different-CASE-id"
-PASS XHTML img usemap="no-hash-name"
-PASS XHTML object usemap="no-hash-name"
-PASS XHTML img usemap="no-hash-id"
-PASS XHTML object usemap="no-hash-id"
-PASS XHTML img usemap="#hash-name"
-PASS XHTML object usemap="#hash-name"
-PASS XHTML img usemap="#hash-id"
-PASS XHTML object usemap="#hash-id"
-PASS XHTML img usemap="#non-map-with-this-name"
-PASS XHTML object usemap="#non-map-with-this-name"
-FAIL XHTML img usemap="#non-map-with-this-id" assert_equals: expected Element node <area shape="rect" coords="0,0,99,50" href="#area-non-map... but got Element node <img src="/images/threecolors.png" usemap="#non-map-with-...
-PASS XHTML object usemap="#non-map-with-this-id"
-PASS XHTML img usemap="#two-maps-with-this-name"
-PASS XHTML object usemap="#two-maps-with-this-name"
-PASS XHTML img usemap="#two-maps-with-this-id"
-PASS XHTML object usemap="#two-maps-with-this-id"
-PASS XHTML img usemap="#two-maps-with-this-name-or-id"
-PASS XHTML object usemap="#two-maps-with-this-name-or-id"
-PASS XHTML img usemap="#two-maps-with-this-id-or-name"
-PASS XHTML object usemap="#two-maps-with-this-id-or-name"
-FAIL XHTML img usemap="hash-last#" assert_equals: expected Element node <img src="/images/threecolors.png" usemap="hash-last#"></... but got Element node <area shape="rect" coords="0,0,99,50" href="#area-empty-u...
-PASS XHTML object usemap="hash-last#"
-PASS XHTML img usemap=""
-PASS XHTML object usemap=""
-FAIL XHTML img usemap="#" assert_equals: expected Element node <img src="/images/threecolors.png" usemap="#"></img> but got Element node <area shape="rect" coords="0,0,99,50" href="#area-empty-u...
-PASS XHTML object usemap="#"
-PASS XHTML img usemap="# "
-PASS XHTML object usemap="# "
-PASS XHTML img usemap="#\n"
-PASS XHTML object usemap="#\n"
-PASS XHTML img usemap="#percent-escape-name-%41"
-PASS XHTML object usemap="#percent-escape-name-%41"
-PASS XHTML img usemap="#percent-escape-id-%41"
-PASS XHTML object usemap="#percent-escape-id-%41"
-PASS XHTML img usemap="#percent-escape-name-%42"
-PASS XHTML object usemap="#percent-escape-name-%42"
-PASS XHTML img usemap="#percent-escape-id-%42"
-PASS XHTML object usemap="#percent-escape-id-%42"
-PASS XHTML img usemap="# hash-space-name"
-PASS XHTML object usemap="# hash-space-name"
-PASS XHTML img usemap="# hash-space-id"
-PASS XHTML object usemap="# hash-space-id"
-PASS XHTML img usemap=" #space-before-hash-name"
-PASS XHTML object usemap=" #space-before-hash-name"
-PASS XHTML img usemap=" #space-before-hash-id"
-PASS XHTML object usemap=" #space-before-hash-id"
-PASS XHTML img usemap="http://example.org/#garbage-before-hash-name"
-PASS XHTML object usemap="http://example.org/#garbage-before-hash-name"
-PASS XHTML img usemap="http://example.org/#garbage-before-hash-id"
-PASS XHTML object usemap="http://example.org/#garbage-before-hash-id"
-PASS XHTML img usemap="#no-such-map"
-PASS XHTML object usemap="#no-such-map"
-PASS XHTML img usemap="#different-CASE-name"
-PASS XHTML object usemap="#different-CASE-name"
-PASS XHTML img usemap="#different-CASE-id"
-PASS XHTML object usemap="#different-CASE-id"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference-test-data.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference-test-data.html
index 735ba8c..1fb71431 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference-test-data.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/image-maps/image-map-processing-model/hash-name-reference-test-data.html
@@ -54,7 +54,7 @@
 <div data-expect="area-non-map-with-this-id">
  <img src="/images/threecolors.png" usemap="#non-map-with-this-id" id="non-map-with-this-id"/>
  <object data="/images/threecolors.png" usemap="#non-map-with-this-id"></object>
- <map id="non-map-with-this-name">
+ <map id="non-map-with-this-id">
   <area shape="rect" coords="0,0,99,50" href="#area-non-map-with-this-id"/>
  </map>
 </div>
diff --git a/third_party/blink/web_tests/external/wpt/lint.ignore b/third_party/blink/web_tests/external/wpt/lint.ignore
index dd3f7b6..42cee94 100644
--- a/third_party/blink/web_tests/external/wpt/lint.ignore
+++ b/third_party/blink/web_tests/external/wpt/lint.ignore
@@ -145,6 +145,7 @@
 SET TIMEOUT: common/reftest-wait.js
 SET TIMEOUT: conformance-checkers/*
 SET TIMEOUT: content-security-policy/*
+SET TIMEOUT: css/compositing/opacity-and-transform-animation-crash.html
 SET TIMEOUT: css/css-display/display-contents-shadow-dom-1.html
 SET TIMEOUT: css/CSS2/normal-flow/crashtests/block-in-inline-ax-crash.html
 SET TIMEOUT: css/selectors/selector-placeholder-shown-type-change-001.html
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-timeline-named-scroll-progress-timeline.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-timeline-named-scroll-progress-timeline.tentative.html
index 408f62f8..b31e402 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-timeline-named-scroll-progress-timeline.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-timeline-named-scroll-progress-timeline.tentative.html
@@ -257,7 +257,6 @@
   // Ensure that #main (an ancestor of the scroller) needs style recalc.
   main.style.background = 'lightgray';
   sibling.style.scrollTimelineName = 'timeline';
-  await waitForNextFrame();
   assert_equals(getComputedStyle(target).translate, '100px');
 
   main.remove();
@@ -302,7 +301,6 @@
 
   scroller.style.scrollTimelineName = 'timeline';
   target.style.animation = 'anim 10s linear timeline';
-  await waitForNextFrame();
 
   assert_equals(getComputedStyle(target).translate, '100px');
 
@@ -353,7 +351,6 @@
 
   scroller.style.scrollTimelineName = 'timeline';
   target.style.animation = 'anim 10s linear timeline';
-  await waitForNextFrame();
 
   assert_equals(getComputedStyle(target).translate, '100px');
 
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/progress-based-animation-animation-longhand-properties.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/progress-based-animation-animation-longhand-properties.tentative.html
index 17604e5..43401aca 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/progress-based-animation-animation-longhand-properties.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/progress-based-animation-animation-longhand-properties.tentative.html
@@ -104,12 +104,10 @@
 
   // Let animation become 50% in the 1st iteration.
   target.style.animationIterationCount = '2';
-  await waitForNextFrame();
   assert_equals(getComputedStyle(target).translate, '50px');
 
   // Let animation become 0% in the 2nd iteration.
   target.style.animationIterationCount = '4';
-  await waitForNextFrame();
   assert_equals(getComputedStyle(target).translate, '0px');
 }, 'animation-iteration-count');
 
@@ -219,7 +217,6 @@
 
   await scrollTop(scroller, 20); // [0, 100].
   target.style.animationDelay = '-5s';
-  await waitForNextFrame();
   assert_equals(getComputedStyle(target).translate, '60px');
 }, 'animation-delay with a negative value');
 
@@ -237,7 +234,6 @@
   assert_equals(getComputedStyle(target).translate, 'none');
 
   target.style.animationFillMode = 'backwards';
-  await waitForNextFrame();
   assert_equals(getComputedStyle(target).translate, '0px');
 }, 'animation-fill-mode');
 
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-default-iframe-print.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-default-iframe-print.html
index 6349622..fc8e4f1 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-default-iframe-print.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-default-iframe-print.html
@@ -5,7 +5,6 @@
 <link rel="help" href="https://drafts.csswg.org/css-animations-2/#animation-timeline">
 <meta name="assert" content="CSS animation correctly updates values when using the default scroll() timeline">
 <link rel="match" href="scroll-timeline-default-iframe-ref.html">
-<meta name="fuzzy" content="25;100">
 
 <iframe id="target" width="400" height="400" srcdoc='
   <html>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-document-scroller-quirks.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-document-scroller-quirks.html
deleted file mode 100644
index ca40161..0000000
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-document-scroller-quirks.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!-- Quirks mode -->
-<title>Tests the document scroller in quirks mode</title>
-<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#scroll-notation">
-<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1180575">
-<link rel="author" href="mailto:andruud@chromium.org">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/scroll-animations/scroll-timelines/testcommon.js"></script>
-<script src="/css/css-animations/support/testcommon.js"></script>
-<style>
-  @keyframes anim {
-    from { z-index: 100; }
-    to { z-index: 100; }
-  }
-  #element {
-    animation: anim forwards scroll(root);
-  }
-</style>
-<div id=element></div>
-
-<script>
-promise_test(async () => {
-  await waitForNextFrame();
-  assert_equals(getComputedStyle(element).zIndex, "100");
-});
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-dynamic.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-dynamic.tentative.html
index c827d10..7bf35cd 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-dynamic.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-dynamic.tentative.html
@@ -71,7 +71,6 @@
     // Verify that the computed style is as expected immediately after the
     // rule change took place.
     instantiate(async (element, expected) => {
-      await waitForNextFrame();
       assert_equals(getComputedStyle(element).width, expected);
     }, description + ' [immediate]');
 
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-in-container-query.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-in-container-query.html
index 0c216c7..f74fa59 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-in-container-query.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-in-container-query.html
@@ -60,9 +60,8 @@
     assert_equals(getComputedStyle(element).backgroundColor, 'rgb(100, 100, 100)');
     // This causes the timeline to be created.
     outer.style.width = '250px';
-    // Check value with getComputedStyle immediately, which is the unanimated
-    // value since the scroll timeline is inactive before the next frame.
-    assert_equals(getComputedStyle(element).backgroundColor, 'rgb(0, 0, 0)');
+    // Check value with getComputedStyle immediately.
+    assert_equals(getComputedStyle(element).backgroundColor, 'rgb(150, 150, 150)');
     // Also check value after one frame.
     await waitForNextFrame();
     assert_equals(getComputedStyle(element).backgroundColor, 'rgb(150, 150, 150)');
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-paused-animations.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-paused-animations.html
index b7611fb..a8117fa 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-paused-animations.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-paused-animations.html
@@ -36,8 +36,6 @@
   t.add_cleanup(resetScrollPosition);
 
   div.style.animation = 'anim 100s linear paused scroll(root)';
-  await waitForNextFrame();
-
   const anim = div.getAnimations()[0];
   await anim.ready;
   assert_percents_equal(anim.currentTime, 0, 'timeline time reset');
@@ -57,8 +55,6 @@
   await waitForNextFrame();
 
   div.style.animation = 'anim 100s linear forwards scroll(root)';
-  await waitForNextFrame();
-
   const anim = div.getAnimations()[0];
   await anim.ready;
   assert_percents_equal(anim.currentTime, 0, 'timeline time reset');
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-responsiveness-from-endpoint.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-responsiveness-from-endpoint.html
index 180704ee..5555fd0 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-responsiveness-from-endpoint.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-responsiveness-from-endpoint.html
@@ -32,8 +32,6 @@
   await waitForNextFrame();
 
   div.style.animation = 'anim 100s linear scroll(root)';
-  await waitForNextFrame();
-
   const anim = div.getAnimations()[0];
   await anim.ready;
   assert_percents_equal(anim.timeline.currentTime, 0,
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-sibling-gcs.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-sibling-gcs.html
index 8500b39..21d5b8c 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-sibling-gcs.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-sibling-gcs.html
@@ -42,7 +42,6 @@
     // Unknown timeline, time held at zero.
     assert_equals(getComputedStyle(element).backgroundColor, 'rgb(100, 100, 100)');
     scroller.style.scrollTimeline = 'timeline';
-    await waitForNextFrame();
     assert_equals(getComputedStyle(element).backgroundColor, 'rgb(150, 150, 150)');
   }, 'Timelines appearing on preceding siblings are visible to getComputedStyle');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/view-timeline-dynamic.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/view-timeline-dynamic.html
index 545e735..483fa36c 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/view-timeline-dynamic.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/view-timeline-dynamic.html
@@ -63,17 +63,14 @@
 
     // scrollTop=50 is 75% for div75.
     div75.classList.add('timeline');
-    await waitForNextFrame();
     assert_equals(getComputedStyle(target).zIndex, '75');
 
     // scrollTop=50 is 25% for div25.
     div25.classList.add('timeline');
-    await waitForNextFrame();
     assert_equals(getComputedStyle(target).zIndex, '25');
 
     // scrollTop=50 is before the timeline start for div_before.
     div_before.classList.add('timeline');
-    await waitForNextFrame();
     assert_equals(getComputedStyle(target).zIndex, '-1');
     // Scroll to 25% (for div_before) to verify that we're linked to that
     // timeline.
@@ -83,7 +80,6 @@
     // Now we should be back to div25's timeline, although with the new
     // scrollTop=150, it's actually at 75%.
     div_before.classList.remove('timeline');
-    await waitForNextFrame();
     assert_equals(getComputedStyle(target).zIndex, '75');
   }, 'Dynamically changing view-timeline-name');
 </script>
@@ -114,7 +110,6 @@
 
     assert_equals(getComputedStyle(target).zIndex, '25');
     timeline.style.viewTimelineAxis = 'horizontal';
-    await waitForNextFrame();
     assert_equals(getComputedStyle(target).zIndex, '10');
   }, 'Dynamically changing view-timeline-axis');
 </script>
@@ -144,7 +139,6 @@
 
     assert_equals(getComputedStyle(target).zIndex, '25');
     timeline.style.viewTimelineInset = '0px 50px';
-    await waitForNextFrame();
     assert_equals(getComputedStyle(target).zIndex, '0');
   }, 'Dynamically changing view-timeline-inset');
 </script>
@@ -173,4 +167,4 @@
     timeline.style.display = 'none';
     assert_equals(getComputedStyle(target).zIndex, '-1');
   }, 'Element with view-timeline becoming display:none');
-</script>
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/media-foundation-for-clear-dcomp/README.md b/third_party/blink/web_tests/virtual/media-foundation-for-clear-dcomp/README.md
new file mode 100644
index 0000000..3aa5407
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/media-foundation-for-clear-dcomp/README.md
@@ -0,0 +1,5 @@
+This suite runs the tests with
+--use-gpu-in-tests
+--enable-features=MediaFoundationClearRendering:strategy/direct-composition
+
+Media Foundation for Clear Direct Composition (DComp) video rendering will be used for the test suite.
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/media-foundation-for-clear-dcomp/media/README.txt b/third_party/blink/web_tests/virtual/media-foundation-for-clear-dcomp/media/README.txt
new file mode 100644
index 0000000..3f995a81
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/media-foundation-for-clear-dcomp/media/README.txt
@@ -0,0 +1 @@
+This directory is dedicated for testing the "Media Foundation for Clear" feature.
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/media-foundation-for-clear-frameserver/README.md b/third_party/blink/web_tests/virtual/media-foundation-for-clear-frameserver/README.md
new file mode 100644
index 0000000..65965e4
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/media-foundation-for-clear-frameserver/README.md
@@ -0,0 +1,6 @@
+This suite runs the tests with
+--use-gpu-in-tests
+--force-mfmediaengine-renderer
+--enable-features=MediaFoundationClearRendering:strategy/frame-server
+
+Media Foundation for Clear Frame Server mode video rendering will be used for the test suite.
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/media-foundation-for-clear-frameserver/media/README.txt b/third_party/blink/web_tests/virtual/media-foundation-for-clear-frameserver/media/README.txt
new file mode 100644
index 0000000..3f995a81
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/media-foundation-for-clear-frameserver/media/README.txt
@@ -0,0 +1 @@
+This directory is dedicated for testing the "Media Foundation for Clear" feature.
\ No newline at end of file
diff --git a/third_party/instrumented_libraries/focal/BUILD.gn b/third_party/instrumented_libraries/focal/BUILD.gn
index 6e0e36b..946b817 100644
--- a/third_party/instrumented_libraries/focal/BUILD.gn
+++ b/third_party/instrumented_libraries/focal/BUILD.gn
@@ -239,6 +239,10 @@
 instrumented_library("libatspi2.0-0") {
   build_method = "meson"
   extra_configure_flags = [ "-Dintrospection=no" ]
+  package_cflags = [
+    "-Wno-incompatible-function-pointer-types",
+    "-Wno-implicit-function-declaration",
+  ]
 }
 
 instrumented_library("libavahi-client3") {
@@ -440,6 +444,7 @@
   ]
   msan_ignorelist = "ignorelists/msan/libglib2.0-0.txt"
   pre_build = "scripts/pre-build/libglib2.0-0.sh"
+  package_cflags = [ "-Wno-int-conversion" ]
 }
 
 instrumented_library("libgnutls30") {
@@ -474,7 +479,11 @@
 }
 
 instrumented_library("libgtk-3-0") {
-  package_cflags = [ "-Wno-return-type" ]
+  package_cflags = [
+    "-Wno-implicit-function-declaration",
+    "-Wno-int-conversion",
+    "-Wno-return-type",
+  ]
   extra_configure_flags = [
     "--disable-static",
     "--disable-introspection",
@@ -536,7 +545,7 @@
     "--enable-local",
     "--with-subdir=ldap",
     "--with-cyrus-sasl",
-    "--with-threads",
+    "--without-threads",
     "--with-gssapi",
     "--with-tls=gnutls",
     "--with-odbc=unixodbc",
@@ -545,6 +554,11 @@
   # Debian adds a custom patch that adds @VERSION_OPTION@, which must
   # be substituted before building.
   pre_build = "scripts/pre-build/dh_autoreconf.sh"
+
+  package_cflags = [
+    "-Wno-implicit-function-declaration",
+    "-Wno-implicit-int",
+  ]
 }
 
 instrumented_library("libnspr4") {
@@ -610,7 +624,10 @@
 instrumented_library("libsasl2-2") {
   build_method = "debian"
   pre_build = "scripts/pre-build/libsasl2-2.sh"
-  package_cflags = [ "-Wno-return-type" ]
+  package_cflags = [
+    "-Wno-implicit-function-declaration",
+    "-Wno-return-type",
+  ]
 }
 
 instrumented_library("libsecret") {
@@ -644,6 +661,7 @@
 instrumented_library("libunity9") {
   extra_configure_flags = [ "--disable-static" ]
   pre_build = "scripts/pre-build/autogen.sh"
+  package_cflags = [ "-Wno-incompatible-function-pointer-types" ]
 }
 
 instrumented_library("libva2") {
diff --git a/third_party/instrumented_libraries/focal/scripts/install-build-deps.sh b/third_party/instrumented_libraries/focal/scripts/install-build-deps.sh
index 6f93945..7dd684b 100755
--- a/third_party/instrumented_libraries/focal/scripts/install-build-deps.sh
+++ b/third_party/instrumented_libraries/focal/scripts/install-build-deps.sh
@@ -40,6 +40,7 @@
 gtk+3.0 \
 ido \
 jasper-initramfs \
+libappindicator3-1 \
 libcap2 \
 libdbusmenu \
 libdbusmenu-gtk3-dev \
@@ -48,9 +49,10 @@
 libidn \
 libindicator \
 libjpeg-turbo \
+libldap-2.4-2 \
 libmicrohttpd \
 libpng1.6 \
-libsasl2-dev \
+libsasl2-2 \
 libunity \
 libx11 \
 libxau \
diff --git a/third_party/ipcz/src/ipcz/driver_object.cc b/third_party/ipcz/src/ipcz/driver_object.cc
index 967b3b4..8dbec71 100644
--- a/third_party/ipcz/src/ipcz/driver_object.cc
+++ b/third_party/ipcz/src/ipcz/driver_object.cc
@@ -86,7 +86,7 @@
   return dimensions;
 }
 
-void DriverObject::Serialize(const DriverTransport& transport,
+bool DriverObject::Serialize(const DriverTransport& transport,
                              absl::Span<uint8_t> data,
                              absl::Span<IpczDriverHandle> handles) {
   size_t num_bytes = data.size();
@@ -94,8 +94,11 @@
   IpczResult result = driver_->Serialize(
       handle_, transport.driver_object().handle(), IPCZ_NO_FLAGS, nullptr,
       data.data(), &num_bytes, handles.data(), &num_handles);
-  ABSL_ASSERT(result == IPCZ_RESULT_OK);
-  release();
+  if (result == IPCZ_RESULT_OK) {
+    release();
+    return true;
+  }
+  return false;
 }
 
 // static
diff --git a/third_party/ipcz/src/ipcz/driver_object.h b/third_party/ipcz/src/ipcz/driver_object.h
index 44b0c68..eed6a9b 100644
--- a/third_party/ipcz/src/ipcz/driver_object.h
+++ b/third_party/ipcz/src/ipcz/driver_object.h
@@ -58,7 +58,7 @@
   // transmissible by the driver without further serialization. Must only be
   // called on valid objects which are known to be serializable and
   // transmissible over `transport`.
-  void Serialize(const DriverTransport& transport,
+  bool Serialize(const DriverTransport& transport,
                  absl::Span<uint8_t> data,
                  absl::Span<IpczDriverHandle> handles);
 
diff --git a/third_party/ipcz/src/ipcz/driver_transport.cc b/third_party/ipcz/src/ipcz/driver_transport.cc
index 0103284..a8cb7a1 100644
--- a/third_party/ipcz/src/ipcz/driver_transport.cc
+++ b/third_party/ipcz/src/ipcz/driver_transport.cc
@@ -102,7 +102,12 @@
 
 IpczResult DriverTransport::Transmit(Message& message) {
   ABSL_ASSERT(message.CanTransmitOn(*this));
-  message.Serialize(*this);
+  if (!message.Serialize(*this)) {
+    // If serialization fails despite the object appearing to be serializable,
+    // we have to assume the transport is in a dysfunctional state and will be
+    // torn down by the driver soon. Discard the transmission.
+    return IPCZ_RESULT_FAILED_PRECONDITION;
+  }
 
   const absl::Span<const uint8_t> data = message.data_view();
   const absl::Span<const IpczDriverHandle> handles =
diff --git a/third_party/ipcz/src/ipcz/driver_transport_test.cc b/third_party/ipcz/src/ipcz/driver_transport_test.cc
index e64dab37..79bb836 100644
--- a/third_party/ipcz/src/ipcz/driver_transport_test.cc
+++ b/third_party/ipcz/src/ipcz/driver_transport_test.cc
@@ -166,5 +166,41 @@
   EXPECT_CALL(driver(), Close(kTransport0, _, _));
 }
 
+TEST_F(DriverTransportTest, SerializationFailure) {
+  // Verifies that if a serializable object fails to serialize, it's properly
+  // destroyed by the transport and no transmission occurs.
+  constexpr IpczDriverHandle kTransport0 = 5;
+  constexpr IpczDriverHandle kTransport1 = 42;
+  auto [a, b] = CreateTransportPair(kTransport0, kTransport1);
+
+  constexpr IpczDriverHandle kFakeObject = 12345678;
+  test::msg::MessageWithDriverObject message;
+  message.params().object =
+      message.AppendDriverObject(DriverObject(test::kMockDriver, kFakeObject));
+
+  EXPECT_CALL(driver(), Serialize(kFakeObject, kTransport0, _, _, _, _, _, _))
+      .WillRepeatedly([&](IpczDriverHandle, IpczDriverHandle, uint32_t,
+                          const void*, void* data, size_t* num_bytes,
+                          IpczDriverHandle* handles, size_t* num_handles) {
+        if (!data && !handles) {
+          // Return valid outputs when ipcz is sizing the object.
+          *num_bytes = 1;
+          *num_handles = 1;
+          return IPCZ_RESULT_RESOURCE_EXHAUSTED;
+        }
+
+        // Return error when serializing.
+        return IPCZ_RESULT_FAILED_PRECONDITION;
+      });
+
+  // The object handle should be closed upon transmission failure.
+  EXPECT_CALL(driver(), Close(kFakeObject, _, _)).Times(1);
+
+  a->Transmit(message);
+
+  EXPECT_CALL(driver(), Close(kTransport1, _, _));
+  EXPECT_CALL(driver(), Close(kTransport0, _, _));
+}
+
 }  // namespace
 }  // namespace ipcz
diff --git a/third_party/ipcz/src/ipcz/message.cc b/third_party/ipcz/src/ipcz/message.cc
index fea6a45..b736842 100644
--- a/third_party/ipcz/src/ipcz/message.cc
+++ b/third_party/ipcz/src/ipcz/message.cc
@@ -29,7 +29,7 @@
 // transmissible handles emitted by the driver are appended to
 // `transmissible_handles`, with relevant index and count also stashed in the
 // DriverObjectData.
-IpczResult SerializeDriverObject(
+bool SerializeDriverObject(
     DriverObject object,
     const DriverTransport& transport,
     Message& message,
@@ -38,7 +38,7 @@
   if (!object.is_valid()) {
     // This is not a valid driver handle and it cannot be serialized.
     data.num_driver_handles = 0;
-    return IPCZ_RESULT_INVALID_ARGUMENT;
+    return false;
   }
 
   uint32_t driver_data_array = 0;
@@ -60,10 +60,13 @@
                                dimensions.num_driver_handles);
 
   auto handles_view = absl::MakeSpan(transmissible_handles);
-  object.Serialize(
-      transport, driver_data,
-      handles_view.subspan(first_handle, dimensions.num_driver_handles));
-  return IPCZ_RESULT_OK;
+  if (!object.Serialize(
+          transport, driver_data,
+          handles_view.subspan(first_handle, dimensions.num_driver_handles))) {
+    return false;
+  }
+
+  return true;
 }
 
 // Returns `true` if and only if it will be safe to use GetArrayView() to access
@@ -208,10 +211,10 @@
   return true;
 }
 
-void Message::Serialize(const DriverTransport& transport) {
+bool Message::Serialize(const DriverTransport& transport) {
   ABSL_ASSERT(CanTransmitOn(transport));
   if (driver_objects_.empty()) {
-    return;
+    return true;
   }
 
   const uint32_t array_offset =
@@ -222,16 +225,19 @@
   // handles attached. Since these objects are small, we inline some storage on
   // the stack to avoid some heap allocation in the most common cases.
   absl::InlinedVector<IpczDriverHandle, 2> transmissible_handles;
+  bool ok = true;
   for (size_t i = 0; i < driver_objects().size(); ++i) {
     internal::DriverObjectData data = {};
-    const IpczResult result =
-        SerializeDriverObject(std::move(driver_objects()[i]), transport, *this,
-                              data, transmissible_handles);
-    ABSL_ASSERT(result == IPCZ_RESULT_OK);
+    ok &= SerializeDriverObject(std::move(driver_objects()[i]), transport,
+                                *this, data, transmissible_handles);
     GetArrayView<internal::DriverObjectData>(array_offset)[i] = data;
   }
 
-  transmissible_driver_handles_ = std::move(transmissible_handles);
+  if (ok) {
+    transmissible_driver_handles_ = std::move(transmissible_handles);
+    return true;
+  }
+  return false;
 }
 
 bool Message::DeserializeUnknownType(const DriverTransport::RawMessage& message,
diff --git a/third_party/ipcz/src/ipcz/message.h b/third_party/ipcz/src/ipcz/message.h
index 879afcd..7187528e 100644
--- a/third_party/ipcz/src/ipcz/message.h
+++ b/third_party/ipcz/src/ipcz/message.h
@@ -322,7 +322,7 @@
   // NOTE: It is invalid to call this on a message for which
   // `CanTransmitOn(transport)` does not return true, and doing so results in
   // unspecified behavior.
-  void Serialize(const DriverTransport& transport);
+  bool Serialize(const DriverTransport& transport);
 
   // Validates and deserializes a Message of an unrecognized type. DriverObjects
   // are deserialized and much of the message structure is validated, but the
diff --git a/tools/cast3p/cast_core.version b/tools/cast3p/cast_core.version
index 44fb13d9..1c4d2147 100644
--- a/tools/cast3p/cast_core.version
+++ b/tools/cast3p/cast_core.version
@@ -1 +1 @@
-cast_20220930_0600_RC00
+cast_20220930_0600_RC03
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index d093ce1..8cfbb69 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -31229,6 +31229,24 @@
   <description>Please enter the description of this user action.</description>
 </action>
 
+<action name="StatusArea_Audio_AutoInputSelectionOverridden">
+  <owner>aaronyu@google.com</owner>
+  <owner>judyhsiao@google.com</owner>
+  <owner>chromeos-audio-sw@google.com</owner>
+  <description>
+    The user overrode the automatically selected audio input device.
+  </description>
+</action>
+
+<action name="StatusArea_Audio_AutoOutputSelectionOverridden">
+  <owner>aaronyu@google.com</owner>
+  <owner>judyhsiao@google.com</owner>
+  <owner>chromeos-audio-sw@google.com</owner>
+  <description>
+    The user overrode the automatically selected audio output device.
+  </description>
+</action>
+
 <action name="StatusArea_Audio_CurrentInputDevice">
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
   <description>Please enter the description of this user action.</description>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index f8f36a345..2067f4c 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -83139,6 +83139,11 @@
   <int value="9" label="CollapseButton"/>
   <int value="10" label="FeedBackButton"/>
   <int value="11" label="VersionButton"/>
+  <int value="12" label="PowerOffMenuButton"/>
+  <int value="13" label="PowerRestartMenuButton"/>
+  <int value="14" label="PowerSignoutMenuButton"/>
+  <int value="15" label="PowerLockMenuButton"/>
+  <int value="16" label="SupervisedButton"/>
 </enum>
 
 <enum name="QsFeatureCatalogName">
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml
index 248d277a..426b0b42 100644
--- a/tools/metrics/histograms/metadata/chromeos/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -673,7 +673,7 @@
 </histogram>
 
 <histogram name="ChromeOS.CWP.PSIMemPressure.{PType}" units="failures"
-    expires_after="2022-12-01">
+    expires_after="2023-10-13">
   <owner>raging@google.com</owner>
   <owner>chromeos-memory@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/omnibox/histograms.xml b/tools/metrics/histograms/metadata/omnibox/histograms.xml
index fd2e099..b886711f7 100644
--- a/tools/metrics/histograms/metadata/omnibox/histograms.xml
+++ b/tools/metrics/histograms/metadata/omnibox/histograms.xml
@@ -168,7 +168,7 @@
 </histogram>
 
 <histogram
-    name="Omnibox.AsyncAutocompletionTime.Provider.{Provider}{Completed}"
+    name="Omnibox.AsyncAutocompletionTime2.Provider.{Provider}{Completed}"
     units="ms" expires_after="2022-12-01">
   <owner>manukh@chromium.org</owner>
   <owner>jdonnelly@chromium.org</owner>
@@ -231,7 +231,7 @@
   </token>
 </histogram>
 
-<histogram name="Omnibox.AsyncAutocompletionTime.{Change}{Completed}"
+<histogram name="Omnibox.AsyncAutocompletionTime2.{Change}{Completed}"
     units="ms" expires_after="2022-12-01">
   <owner>manukh@chromium.org</owner>
   <owner>jdonnelly@chromium.org</owner>
@@ -996,7 +996,7 @@
 </histogram>
 
 <histogram
-    name="Omnibox.MatchStability.MatchChangeInAnyPosition{OmniboxAutocompleteUpdateSlice}"
+    name="Omnibox.MatchStability2.MatchChangeInAnyPosition{OmniboxAutocompleteUpdateSlice}"
     enum="BooleanChanged" expires_after="2022-12-04">
   <owner>tommycli@chromium.org</owner>
   <owner>manukh@chromium.org</owner>
@@ -1030,11 +1030,12 @@
     Omnibox.Start.WantAsyncMatches, which will yield the number of match changes
     per keystroke or other user gesture.
 
-    There're the related Omnibox.MatchStability.InAnyPosition.* slices for
+    There're the related Omnibox.MatchStability2.InAnyPosition.* slices for
     tracking match instability for all, asynchronous, and synchronous updates.
 
     This histogram can be considered a boolean analogue to the
-    Omnibox.MatchStability.Index.* histogram which tracks which matches changed.
+    Omnibox.MatchStability2.Index.* histogram which tracks which matches
+    changed.
 
     {OmniboxAutocompleteUpdateSlice}
   </summary>
@@ -1043,7 +1044,7 @@
 </histogram>
 
 <histogram
-    name="Omnibox.MatchStability.MatchChangeIndex{OmniboxAutocompleteUpdateSlice}"
+    name="Omnibox.MatchStability2.MatchChangeIndex{OmniboxAutocompleteUpdateSlice}"
     units="position" expires_after="2022-12-04">
   <owner>tommycli@chromium.org</owner>
   <owner>manukh@chromium.org</owner>
@@ -1084,10 +1085,10 @@
     Omnibox.Start.WantAsyncMatches, which will yield the number of match changes
     per keystroke or other user gesture.
 
-    There're the related Omnibox.MatchStability.Index.* slices for tracking
+    There're the related Omnibox.MatchStability2.Index.* slices for tracking
     match instability for all, asynchronous, and synchronous updates.
 
-    There's the related Omnibox.MatchStability.InAnyPosition histogram for
+    There's the related Omnibox.MatchStability2.InAnyPosition histogram for
     tracking whether any match changed per autocomplete update.
 
     {OmniboxAutocompleteUpdateSlice}
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index dc3fa53..aee5edd1 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,8 +5,8 @@
             "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux_arm64/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "win": {
-            "hash": "cd36010f4aaa8261c0ead7927e3b5b0c51b23680",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/7bd83a8f9af8604e932cd717fdd56e586c54d3a4/trace_processor_shell.exe"
+            "hash": "7131cf0f3a0b0858b4396c30e5f0c3327569e690",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/082982f29eab767cb18b2dfa7196b2774b3c65ee/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "58893933be305d3bfe0a72ebebcacde2ac3ca893",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v30.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "9ec936285839034b556c7e91136aeda7c8a59150",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/7bd83a8f9af8604e932cd717fdd56e586c54d3a4/trace_processor_shell"
+            "hash": "75fa34af55fadc9e5df81463cae3e0dcc7637865",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/082982f29eab767cb18b2dfa7196b2774b3c65ee/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/typescript/definitions/system_display.d.ts b/tools/typescript/definitions/system_display.d.ts
new file mode 100644
index 0000000..f836365
--- /dev/null
+++ b/tools/typescript/definitions/system_display.d.ts
@@ -0,0 +1,144 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/** @fileoverview Type definitions for chrome.system.display API. */
+// TODO(crbug.com/1203307): Auto-generate this file.
+
+declare namespace chrome {
+  export namespace system {
+    export namespace display {
+      /**
+       * @see https://developer.chrome.com/extensions/system.display#type-Bounds
+       */
+      export interface Bounds {
+        left: number;
+        top: number;
+        width: number;
+        height: number;
+      }
+
+      /**
+       * @see https://developer.chrome.com/extensions/system.display#type-Insets
+       */
+      export interface Insets {
+        left: number;
+        top: number;
+        right: number;
+        bottom: number;
+      }
+
+      /**
+       * @see https://developer.chrome.com/extensions/system.display#type-DisplayMode
+       */
+      export interface DisplayMode {
+        width: number;
+        height: number;
+        widthInNativePixels: number;
+        heightInNativePixels: number;
+        uiScale?: number;
+        deviceScaleFactor: number;
+        refreshRate: number;
+        isNative: boolean;
+        isSelected: boolean;
+        isInterlaced?: boolean;
+      }
+
+      /**
+       * @see https://developer.chrome.com/extensions/system.display#type-LayoutPosition
+       */
+      export enum LayoutPosition {
+        TOP = 'top',
+        RIGHT = 'right',
+        BOTTOM = 'bottom',
+        LEFT = 'left',
+      }
+
+      /**
+       * @see https://developer.chrome.com/extensions/system.display#type-DisplayLayout
+       */
+      export interface DisplayLayout {
+        id: string;
+        parentId: string;
+        position: LayoutPosition;
+        offset: number;
+      }
+
+      /**
+       * @see https://developer.chrome.com/extensions/system.display#type-Edid
+       */
+      export interface Edid {
+        manufacturerId: string;
+        productId: string;
+        yearOfManufacture: number;
+      }
+
+      /**
+       * @see https://developer.chrome.com/extensions/system.display#type-DisplayUnitInfo
+       */
+      export interface DisplayUnitInfo {
+        id: string;
+        name: string;
+        edid?: Edid;
+        mirroringSourceId: string;
+        mirroringDestinationIds: string[];
+        isPrimary: boolean;
+        isInternal: boolean;
+        isEnabled: boolean;
+        isUnified: boolean;
+        isAutoRotationAllowed?: boolean;
+        dpiX: number;
+        dpiY: number;
+        rotation: number;
+        bounds: Bounds;
+        overscan: Insets;
+        workArea: Bounds;
+        modes: DisplayMode[];
+        hasTouchSupport: boolean;
+        hasAccelerometerSupport: boolean;
+        availableDisplayZoomFactors: number[];
+        displayZoomFactor: number;
+      }
+
+      /**
+       * @see https://developer.chrome.com/extensions/system.display#type-DisplayProperties
+       */
+      export interface DisplayProperties {
+        isUnified?: boolean;
+        mirroringSourceId?: string;
+        isPrimary?: boolean;
+        overscan?: Insets;
+        rotation?: number;
+        boundsOriginX?: number;
+        boundsOriginY?: number;
+        displayMode?: DisplayMode;
+        displayZoomFactor?: number;
+      }
+
+      /**
+       * @see https://developer.chrome.com/extensions/system.display#type-GetInfoFlags
+       */
+      export interface GetInfoFlags {
+        singleUnified?: boolean;
+      }
+
+      /**
+       * @see https://developer.chrome.com/extensions/system.display#type-MirrorMode
+       */
+      export enum MirrorMode {
+        OFF = 'off',
+        NORMAL = 'normal',
+        MIXED = 'mixed',
+      }
+
+      /**
+       * @see https://developer.chrome.com/extensions/system.display#type-MirrorModeInfo
+       */
+      export interface MirrorModeInfo {
+        mode: MirrorMode;
+        mirroringSourceId?: string;
+        mirroringDestinationIds?: string[];
+      }
+    }
+  }
+}
diff --git a/ui/base/resource/resource_bundle_ios.mm b/ui/base/resource/resource_bundle_ios.mm
index e7f29d2..ba3c71ae 100644
--- a/ui/base/resource/resource_bundle_ios.mm
+++ b/ui/base/resource/resource_bundle_ios.mm
@@ -131,13 +131,10 @@
       base::ScopedCFTypeRef<CGColorSpaceRef> color_space(
           CGColorSpaceCreateDeviceRGB());
       base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate(
-          NULL,
-          target_size.width,
-          target_size.height,
-          8,
-          target_size.width * 4,
+          NULL, target_size.width, target_size.height, 8, target_size.width * 4,
           color_space,
-          kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
+          kCGImageAlphaPremultipliedFirst |
+              static_cast<CGImageAlphaInfo>(kCGBitmapByteOrder32Host)));
 
       CGRect target_rect = CGRectMake(0, 0,
                                       target_size.width, target_size.height);
diff --git a/ui/color/color_provider_manager.cc b/ui/color/color_provider_manager.cc
index 97c8467a..2f6bf04 100644
--- a/ui/color/color_provider_manager.cc
+++ b/ui/color/color_provider_manager.cc
@@ -23,13 +23,12 @@
 
 namespace {
 
-// Cache at most 5 ColorProviders to prevent unbounded storage from user_color.
-constexpr size_t kCacheSize = 5;
+// Estimated upper limit of what we should record for cache size.
+constexpr int kCacheHistogramMax = 100;
 
 class GlobalManager : public ColorProviderManager {
  public:
-  explicit GlobalManager(size_t cache_size = kCacheSize)
-      : ColorProviderManager(cache_size) {}
+  GlobalManager() = default;
   GlobalManager(const GlobalManager&) = delete;
   GlobalManager& operator=(const GlobalManager&) = delete;
   ~GlobalManager() override = default;
@@ -84,8 +83,7 @@
 
 ColorProviderManager::Key::~Key() = default;
 
-ColorProviderManager::ColorProviderManager(size_t cache_size)
-    : color_providers_(cache_size) {
+ColorProviderManager::ColorProviderManager() {
   ResetColorProviderInitializerList();
 }
 
@@ -106,10 +104,10 @@
 }
 
 // static
-ColorProviderManager& ColorProviderManager::GetForTesting(size_t cache_size) {
+ColorProviderManager& ColorProviderManager::GetForTesting() {
   absl::optional<GlobalManager>& manager = GetGlobalManager();
   if (!manager.has_value())
-    manager.emplace(cache_size);
+    manager.emplace();
   return manager.value();
 }
 
@@ -126,7 +124,7 @@
 
 void ColorProviderManager::ResetColorProviderCache() {
   if (!color_providers_.empty())
-    color_providers_.Clear();
+    color_providers_.clear();
 }
 
 void ColorProviderManager::AppendColorProviderInitializer(
@@ -139,7 +137,7 @@
 }
 
 ColorProvider* ColorProviderManager::GetColorProviderFor(Key key) {
-  auto iter = color_providers_.Get(key);
+  auto iter = color_providers_.find(key);
   if (iter == color_providers_.end()) {
     auto provider = std::make_unique<ColorProvider>();
     DCHECK(initializer_list_);
@@ -147,9 +145,9 @@
       initializer_list_->Notify(provider.get(), key);
 
     provider->GenerateColorMap();
-    iter = color_providers_.Put(key, std::move(provider));
+    iter = color_providers_.emplace(key, std::move(provider)).first;
     base::UmaHistogramExactLinear("Views.ColorProviderCacheSize",
-                                  color_providers_.size(), kCacheSize);
+                                  color_providers_.size(), kCacheHistogramMax);
   }
   ColorProvider* provider = iter->second.get();
   DCHECK(provider);
diff --git a/ui/color/color_provider_manager.h b/ui/color/color_provider_manager.h
index 3cb6105..b76df0f 100644
--- a/ui/color/color_provider_manager.h
+++ b/ui/color/color_provider_manager.h
@@ -7,12 +7,11 @@
 
 #include <memory>
 #include <tuple>
-#include <vector>
 
 #include "base/callback.h"
 #include "base/callback_list.h"
 #include "base/component_export.h"
-#include "base/containers/lru_cache.h"
+#include "base/containers/flat_map.h"
 #include "base/memory/weak_ptr.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/skia/include/core/SkColor.h"
@@ -138,7 +137,7 @@
   ColorProviderManager& operator=(const ColorProviderManager&) = delete;
 
   static ColorProviderManager& Get();
-  static ColorProviderManager& GetForTesting(size_t cache_size = 1);
+  static ColorProviderManager& GetForTesting();
   static void ResetForTesting();
 
   // Resets the current `initializer_list_`.
@@ -155,9 +154,7 @@
   ColorProvider* GetColorProviderFor(Key key);
 
  protected:
-  // Creates a ColorProviderManager that stores at most |cache_size|
-  // ColorProviders.
-  explicit ColorProviderManager(size_t cache_size);
+  ColorProviderManager();
   virtual ~ColorProviderManager();
 
  private:
@@ -167,7 +164,7 @@
   // Holds the subscriptions for initializers in the `initializer_list_`.
   std::vector<base::CallbackListSubscription> initializer_subscriptions_;
 
-  base::LRUCache<Key, std::unique_ptr<ColorProvider>> color_providers_;
+  base::flat_map<Key, std::unique_ptr<ColorProvider>> color_providers_;
 };
 
 }  // namespace ui
diff --git a/ui/color/color_provider_manager_unittest.cc b/ui/color/color_provider_manager_unittest.cc
index 2711f26..0878ba4 100644
--- a/ui/color/color_provider_manager_unittest.cc
+++ b/ui/color/color_provider_manager_unittest.cc
@@ -37,14 +37,6 @@
        ColorProviderManager::FrameType::kChromium, absl::nullopt, nullptr});
 }
 
-// Returns a Key where |color| is the user_color value.
-ColorProviderManager::Key UserColorKey(SkColor color) {
-  return ColorProviderManager::Key(
-      ColorProviderManager::ColorMode::kLight,
-      ColorProviderManager::ContrastMode::kNormal, ui::SystemTheme::kDefault,
-      ColorProviderManager::FrameType::kChromium, color, nullptr);
-}
-
 class TestInitializerSupplier
     : public ColorProviderManager::InitializerSupplier {
   void AddColorMixers(ColorProvider* provider,
@@ -125,46 +117,4 @@
   EXPECT_LT(keys[0], keys[1]);
 }
 
-TEST_F(ColorProviderManagerTest, CacheLimits) {
-  // Count each time colors are generated.
-  int counter = 0;
-  auto initializer = base::BindRepeating(
-      [](int* inc, ColorProvider* provider, const ColorProviderManager::Key&) {
-        provider->AddMixer()[kColorTest0] = {SK_ColorBLUE};
-        (*inc)++;
-      },
-      &counter);
-
-  // Only keep 4 color providers.
-  ColorProviderManager& manager = ColorProviderManager::GetForTesting(4U);
-  manager.AppendColorProviderInitializer(initializer);
-
-  // We need 5 keys to test this.
-  ColorProviderManager::Key keys[5] = {
-      UserColorKey(SK_ColorGRAY), UserColorKey(SK_ColorWHITE),
-      UserColorKey(SK_ColorRED), UserColorKey(SK_ColorBLUE),
-      UserColorKey(SK_ColorMAGENTA)};
-
-  for (const ColorProviderManager::Key& key : keys) {
-    manager.GetColorProviderFor(key);
-  }
-  // 5 requests for different keys yields 5 runs of the initializer.
-  EXPECT_EQ(5, counter);
-
-  counter = 0;
-  // Magenta is the most recent so it should not result in an evaluation.
-  manager.GetColorProviderFor(keys[4]);
-  EXPECT_EQ(0, counter);
-
-  // Gray should have been evicted so it causes an evaluation.
-  manager.GetColorProviderFor(keys[0]);
-  EXPECT_EQ(1, counter);
-
-  counter = 0;
-  // The most recently used keys are grey, magenta, blue and red. Magenta should
-  // not result in an evaluation.
-  manager.GetColorProviderFor(keys[4]);
-  EXPECT_EQ(0, counter);
-}
-
 }  // namespace ui
diff --git a/ui/gfx/image/image_ios.mm b/ui/gfx/image/image_ios.mm
index 1709f7ae..685bf67 100644
--- a/ui/gfx/image/image_ios.mm
+++ b/ui/gfx/image/image_ios.mm
@@ -35,7 +35,9 @@
       16,       // height
       8,        // bitsPerComponent
       0,        // CG will calculate by default.
-      color_space, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
+      color_space,
+      kCGImageAlphaPremultipliedFirst |
+          static_cast<CGImageAlphaInfo>(kCGBitmapByteOrder32Host)));
   CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0);
   CGContextFillRect(context, CGRectMake(0.0, 0.0, 16, 16));
   base::ScopedCFTypeRef<CGImageRef> cg_image(
diff --git a/ui/gfx/image/image_ios_unittest.mm b/ui/gfx/image/image_ios_unittest.mm
index abc094e..f7d8c49 100644
--- a/ui/gfx/image/image_ios_unittest.mm
+++ b/ui/gfx/image/image_ios_unittest.mm
@@ -24,13 +24,10 @@
   base::ScopedCFTypeRef<CGColorSpaceRef> color_space(
       CGColorSpaceCreateDeviceRGB());
   base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate(
-      NULL,
-      target_size.width,
-      target_size.height,
-      8,
-      target_size.width * 4,
+      NULL, target_size.width, target_size.height, 8, target_size.width * 4,
       color_space,
-      kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
+      kCGImageAlphaPremultipliedFirst |
+          static_cast<CGImageAlphaInfo>(kCGBitmapByteOrder32Host)));
 
   CGRect target_rect = CGRectMake(0, 0,
                                   target_size.width, target_size.height);
diff --git a/ui/gfx/image/image_unittest_util_ios.mm b/ui/gfx/image/image_unittest_util_ios.mm
index 9fd15449..a37b17f0 100644
--- a/ui/gfx/image/image_unittest_util_ios.mm
+++ b/ui/gfx/image/image_unittest_util_ios.mm
@@ -22,13 +22,13 @@
   base::ScopedCFTypeRef<CGColorSpaceRef> color_space(
       CGColorSpaceCreateDeviceRGB());
   base::ScopedCFTypeRef<CGContextRef> bitmap_context(CGBitmapContextCreate(
-      /*data=*/ NULL,
-      /*width=*/ 1,
-      /*height=*/ 1,
-      /*bitsPerComponent=*/ 8,
-      /*bytesPerRow=*/ 4,
-      color_space,
-      kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
+      /*data=*/NULL,
+      /*width=*/1,
+      /*height=*/1,
+      /*bitsPerComponent=*/8,
+      /*bytesPerRow=*/4, color_space,
+      kCGImageAlphaPremultipliedFirst |
+          static_cast<CGImageAlphaInfo>(kCGBitmapByteOrder32Host)));
   CGContextDrawImage(bitmap_context, CGRectMake(0, 0, 1, 1), pixel_image);
 
   // The CGBitmapContext has the same memory layout as SkColor, so we can just
diff --git a/ui/gl/gl_surface.cc b/ui/gl/gl_surface.cc
index 6cae225..b9b136acf 100644
--- a/ui/gl/gl_surface.cc
+++ b/ui/gl/gl_surface.cc
@@ -157,7 +157,7 @@
 void GLSurface::SetVSyncEnabled(bool enabled) {}
 
 bool GLSurface::ScheduleOverlayPlane(
-    GLImage* image,
+    OverlayImage image,
     std::unique_ptr<gfx::GpuFence> gpu_fence,
     const gfx::OverlayPlaneData& overlay_plane_data) {
   NOTIMPLEMENTED();
@@ -479,7 +479,7 @@
 }
 
 bool GLSurfaceAdapter::ScheduleOverlayPlane(
-    GLImage* image,
+    OverlayImage image,
     std::unique_ptr<gfx::GpuFence> gpu_fence,
     const gfx::OverlayPlaneData& overlay_plane_data) {
   return surface_->ScheduleOverlayPlane(image, std::move(gpu_fence),
diff --git a/ui/gl/gl_surface.h b/ui/gl/gl_surface.h
index 226059a..bcdfe21 100644
--- a/ui/gl/gl_surface.h
+++ b/ui/gl/gl_surface.h
@@ -30,6 +30,10 @@
 #include "ui/gl/gl_surface_format.h"
 #include "ui/gl/gpu_preference.h"
 
+#if defined(USE_OZONE)
+#include "ui/gfx/native_pixmap.h"
+#endif
+
 namespace gfx {
 namespace mojom {
 class DelegatedInkPointRenderer;
@@ -51,6 +55,13 @@
 class GLImage;
 class EGLTimestampClient;
 
+// OverlayImage is a platform specific type for overlay plane image data.
+#if defined(USE_OZONE)
+using OverlayImage = scoped_refptr<gfx::NativePixmap>;
+#else
+using OverlayImage = GLImage*;
+#endif
+
 // Contains per frame data, and is passed along with SwapBuffer, PostSubbuffer,
 // CommitOverlayPlanes type methods.
 struct FrameData {
@@ -255,7 +266,7 @@
   // |overlay_plane_data| specifies overlay data such as opacity, z_order, size,
   // etc.
   virtual bool ScheduleOverlayPlane(
-      GLImage* image,
+      OverlayImage image,
       std::unique_ptr<gfx::GpuFence> gpu_fence,
       const gfx::OverlayPlaneData& overlay_plane_data);
 
@@ -424,7 +435,7 @@
   gfx::VSyncProvider* GetVSyncProvider() override;
   void SetVSyncEnabled(bool enabled) override;
   bool ScheduleOverlayPlane(
-      GLImage* image,
+      OverlayImage image,
       std::unique_ptr<gfx::GpuFence> gpu_fence,
       const gfx::OverlayPlaneData& overlay_plane_data) override;
   bool ScheduleDCLayer(
diff --git a/ui/gl/gl_surface_egl.cc b/ui/gl/gl_surface_egl.cc
index cf79f62..6e56cf7b 100644
--- a/ui/gl/gl_surface_egl.cc
+++ b/ui/gl/gl_surface_egl.cc
@@ -1040,7 +1040,7 @@
 }
 
 bool NativeViewGLSurfaceEGL::ScheduleOverlayPlane(
-    GLImage* image,
+    OverlayImage image,
     std::unique_ptr<gfx::GpuFence> gpu_fence,
     const gfx::OverlayPlaneData& overlay_plane_data) {
   NOTIMPLEMENTED();
diff --git a/ui/gl/gl_surface_egl.h b/ui/gl/gl_surface_egl.h
index 2ec34b5..2886986be 100644
--- a/ui/gl/gl_surface_egl.h
+++ b/ui/gl/gl_surface_egl.h
@@ -98,7 +98,7 @@
   gfx::VSyncProvider* GetVSyncProvider() override;
   void SetVSyncEnabled(bool enabled) override;
   bool ScheduleOverlayPlane(
-      GLImage* image,
+      OverlayImage image,
       std::unique_ptr<gfx::GpuFence> gpu_fence,
       const gfx::OverlayPlaneData& overlay_plane_data) override;
   gfx::SurfaceOrigin GetOrigin() const override;
diff --git a/ui/gl/gl_surface_egl_surface_control.cc b/ui/gl/gl_surface_egl_surface_control.cc
index e6bf35d..a491acc 100644
--- a/ui/gl/gl_surface_egl_surface_control.cc
+++ b/ui/gl/gl_surface_egl_surface_control.cc
@@ -21,6 +21,7 @@
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_features.h"
 #include "ui/gl/gl_fence_android_native_fence_sync.h"
+#include "ui/gl/gl_image.h"
 #include "ui/gl/gl_utils.h"
 
 namespace gl {
@@ -318,7 +319,7 @@
 }
 
 bool GLSurfaceEGLSurfaceControl::ScheduleOverlayPlane(
-    GLImage* image,
+    OverlayImage image,
     std::unique_ptr<gfx::GpuFence> gpu_fence,
     const gfx::OverlayPlaneData& overlay_plane_data) {
   if (surface_lost_) {
diff --git a/ui/gl/gl_surface_egl_surface_control.h b/ui/gl/gl_surface_egl_surface_control.h
index a9484d7c..43e20c8e 100644
--- a/ui/gl/gl_surface_egl_surface_control.h
+++ b/ui/gl/gl_surface_egl_surface_control.h
@@ -50,7 +50,7 @@
   gfx::Size GetSize() override;
   bool OnMakeCurrent(GLContext* context) override;
   bool ScheduleOverlayPlane(
-      GLImage* image,
+      OverlayImage image,
       std::unique_ptr<gfx::GpuFence> gpu_fence,
       const gfx::OverlayPlaneData& overlay_plane_data) override;
   bool IsSurfaceless() const override;
diff --git a/ui/gl/gl_surface_overlay.cc b/ui/gl/gl_surface_overlay.cc
index 2917cea..1d1a671 100644
--- a/ui/gl/gl_surface_overlay.cc
+++ b/ui/gl/gl_surface_overlay.cc
@@ -11,10 +11,10 @@
 namespace gl {
 
 GLSurfaceOverlay::GLSurfaceOverlay(
-    GLImage* image,
+    scoped_refptr<gfx::NativePixmap> pixmap,
     std::unique_ptr<gfx::GpuFence> gpu_fence,
     const gfx::OverlayPlaneData& overlay_plane_data)
-    : image_(image),
+    : pixmap_(std::move(pixmap)),
       gpu_fence_(std::move(gpu_fence)),
       overlay_plane_data_(overlay_plane_data) {}
 
@@ -27,10 +27,9 @@
   if (gpu_fence_)
     acquire_fences.push_back(std::move(*gpu_fence_));
 
-  auto pixmap = image_->GetNativePixmap();
-  DCHECK(pixmap);
-  return pixmap->ScheduleOverlayPlane(widget, overlay_plane_data_,
-                                      std::move(acquire_fences), {});
+  DCHECK(pixmap_);
+  return pixmap_->ScheduleOverlayPlane(widget, overlay_plane_data_,
+                                       std::move(acquire_fences), {});
 }
 
 }  // namespace gl
diff --git a/ui/gl/gl_surface_overlay.h b/ui/gl/gl_surface_overlay.h
index 73306908..46032ceb 100644
--- a/ui/gl/gl_surface_overlay.h
+++ b/ui/gl/gl_surface_overlay.h
@@ -7,10 +7,10 @@
 
 #include "base/memory/ref_counted.h"
 #include "ui/gfx/gpu_fence.h"
+#include "ui/gfx/native_pixmap.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/gfx/overlay_plane_data.h"
 #include "ui/gl/gl_export.h"
-#include "ui/gl/gl_image.h"
 
 namespace gfx {
 class GpuFence;
@@ -21,7 +21,7 @@
 // For saving the properties of a GLImage overlay plane and scheduling it later.
 class GL_EXPORT GLSurfaceOverlay {
  public:
-  GLSurfaceOverlay(GLImage* image,
+  GLSurfaceOverlay(scoped_refptr<gfx::NativePixmap> pixmap,
                    std::unique_ptr<gfx::GpuFence> gpu_fence,
                    const gfx::OverlayPlaneData& overlay_plane_data);
   GLSurfaceOverlay(GLSurfaceOverlay&& other);
@@ -37,7 +37,7 @@
   int z_order() const { return overlay_plane_data_.z_order; }
 
  private:
-  scoped_refptr<GLImage> image_;
+  scoped_refptr<gfx::NativePixmap> pixmap_;
   std::unique_ptr<gfx::GpuFence> gpu_fence_;
   gfx::OverlayPlaneData overlay_plane_data_;
 };
diff --git a/ui/gl/swap_chain_presenter.cc b/ui/gl/swap_chain_presenter.cc
index 31deec1..4f4ace5b 100644
--- a/ui/gl/swap_chain_presenter.cc
+++ b/ui/gl/swap_chain_presenter.cc
@@ -8,6 +8,7 @@
 #include <d3d11_4.h>
 
 #include "base/debug/alias.h"
+#include "base/debug/crash_logging.h"
 #include "base/debug/dump_without_crashing.h"
 #include "base/feature_list.h"
 #include "base/memory/raw_ptr.h"
@@ -702,17 +703,40 @@
   visual_transform->set_rc(1, 1, scale_y);
 
 #if DCHECK_IS_ON()
-  // The new transform matrix should transform the swap chain correctly
-  gfx::Rect new_swap_chain_rect(params.quad_rect.origin(), *swap_chain_size);
-  gfx::Rect result_rect = visual_transform->MapRect(new_swap_chain_rect);
-  if (IsWithinMargin(clipped_onscreen_rect.x(), 0)) {
-    DCHECK_EQ(result_rect.x(), 0);
-    DCHECK_EQ(result_rect.width(), monitor_size.width());
-  }
+  {
+    // The new transform matrix should transform the swap chain correctly
+    gfx::Rect new_swap_chain_rect(params.quad_rect.origin(), *swap_chain_size);
+    gfx::Rect result_rect = visual_transform->MapRect(new_swap_chain_rect);
+    gfx::Rect new_clipped_onscreen_rect = clipped_onscreen_rect;
+    gfx::Transform new_visual_transform = *visual_transform;
+    base::debug::Alias(&new_swap_chain_rect);
+    base::debug::Alias(&result_rect);
+    base::debug::Alias(&new_clipped_onscreen_rect);
+    base::debug::Alias(&new_visual_transform);
+    // https://crbug.com/1366493: "DCHECK_EQ(result_rect.x(), 0);" sometimes
+    // failed in the field. But here we collect possible crashes in general.
+    static auto* new_swap_chain_rect_key = base::debug::AllocateCrashKeyString(
+        "new-swap-chain-rect", base::debug::CrashKeySize::Size256);
+    base::debug::ScopedCrashKeyString scoped_crash_key_1(
+        new_swap_chain_rect_key, new_swap_chain_rect.ToString());
+    static auto* visual_transform_key = base::debug::AllocateCrashKeyString(
+        "visual-transform", base::debug::CrashKeySize::Size256);
+    base::debug::ScopedCrashKeyString scoped_crash_key_2(
+        visual_transform_key, visual_transform->ToString());
+    static auto* result_rect_key = base::debug::AllocateCrashKeyString(
+        "result-rect", base::debug::CrashKeySize::Size256);
+    base::debug::ScopedCrashKeyString scoped_crash_key_3(
+        result_rect_key, result_rect.ToString());
 
-  if (IsWithinMargin(clipped_onscreen_rect.y(), 0)) {
-    DCHECK_EQ(result_rect.y(), 0);
-    DCHECK_EQ(result_rect.height(), monitor_size.height());
+    if (IsWithinMargin(clipped_onscreen_rect.x(), 0)) {
+      DCHECK_EQ(result_rect.x(), 0);
+      DCHECK_EQ(result_rect.width(), monitor_size.width());
+    }
+
+    if (IsWithinMargin(clipped_onscreen_rect.y(), 0)) {
+      DCHECK_EQ(result_rect.y(), 0);
+      DCHECK_EQ(result_rect.height(), monitor_size.height());
+    }
   }
 #endif
 }
diff --git a/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc b/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc
index 5e6124fe..8e1b4430b 100644
--- a/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc
+++ b/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc
@@ -24,6 +24,7 @@
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/geometry/rrect_f.h"
 #include "ui/gfx/gpu_fence.h"
+#include "ui/gfx/native_pixmap.h"
 #include "ui/gfx/overlay_plane_data.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_context.h"
@@ -79,7 +80,9 @@
   BufferWrapper();
   ~BufferWrapper();
 
-  gl::GLImage* image() const { return image_.get(); }
+  scoped_refptr<gfx::NativePixmap> image() const {
+    return image_->GetNativePixmap();
+  }
   SkSurface* sk_surface() const { return sk_surface_.get(); }
 
   bool Initialize(GrDirectContext* gr_context,
diff --git a/ui/ozone/demo/surfaceless_gl_renderer.cc b/ui/ozone/demo/surfaceless_gl_renderer.cc
index 6cf6e7e..7e0e3cc 100644
--- a/ui/ozone/demo/surfaceless_gl_renderer.cc
+++ b/ui/ozone/demo/surfaceless_gl_renderer.cc
@@ -76,6 +76,11 @@
   }
 }
 
+scoped_refptr<gfx::NativePixmap> SurfacelessGlRenderer::BufferWrapper::image()
+    const {
+  return image_->GetNativePixmap();
+}
+
 bool SurfacelessGlRenderer::BufferWrapper::Initialize(
     gfx::AcceleratedWidget widget,
     const gfx::Size& size) {
diff --git a/ui/ozone/demo/surfaceless_gl_renderer.h b/ui/ozone/demo/surfaceless_gl_renderer.h
index 590e8e8..624f6d0 100644
--- a/ui/ozone/demo/surfaceless_gl_renderer.h
+++ b/ui/ozone/demo/surfaceless_gl_renderer.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "ui/gfx/geometry/rect_f.h"
+#include "ui/gfx/native_pixmap.h"
 #include "ui/ozone/demo/gl_renderer.h"
 
 namespace gl {
@@ -45,7 +46,7 @@
     BufferWrapper();
     ~BufferWrapper();
 
-    gl::GLImage* image() const { return image_.get(); }
+    scoped_refptr<gfx::NativePixmap> image() const;
 
     bool Initialize(gfx::AcceleratedWidget widget, const gfx::Size& size);
     void BindFramebuffer();
diff --git a/ui/ozone/platform/drm/gpu/gbm_surfaceless.cc b/ui/ozone/platform/drm/gpu/gbm_surfaceless.cc
index 9e37b2d6..1a1efd6 100644
--- a/ui/ozone/platform/drm/gpu/gbm_surfaceless.cc
+++ b/ui/ozone/platform/drm/gpu/gbm_surfaceless.cc
@@ -70,11 +70,11 @@
 }
 
 bool GbmSurfaceless::ScheduleOverlayPlane(
-    gl::GLImage* image,
+    gl::OverlayImage image,
     std::unique_ptr<gfx::GpuFence> gpu_fence,
     const gfx::OverlayPlaneData& overlay_plane_data) {
-  unsubmitted_frames_.back()->overlays.push_back(
-      gl::GLSurfaceOverlay(image, std::move(gpu_fence), overlay_plane_data));
+  unsubmitted_frames_.back()->overlays.emplace_back(
+      std::move(image), std::move(gpu_fence), overlay_plane_data);
   return true;
 }
 
diff --git a/ui/ozone/platform/drm/gpu/gbm_surfaceless.h b/ui/ozone/platform/drm/gpu/gbm_surfaceless.h
index 74102c0..a34cea4 100644
--- a/ui/ozone/platform/drm/gpu/gbm_surfaceless.h
+++ b/ui/ozone/platform/drm/gpu/gbm_surfaceless.h
@@ -47,7 +47,7 @@
   gfx::SwapResult SwapBuffers(PresentationCallback callback,
                               gl::FrameData data) override;
   bool ScheduleOverlayPlane(
-      gl::GLImage* image,
+      gl::OverlayImage image,
       std::unique_ptr<gfx::GpuFence> gpu_fence,
       const gfx::OverlayPlaneData& overlay_plane_data) override;
   bool Resize(const gfx::Size& size,
diff --git a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc
index 00edfa6..c6355fb4 100644
--- a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc
+++ b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc
@@ -125,7 +125,7 @@
 }
 
 bool GbmSurfacelessWayland::ScheduleOverlayPlane(
-    gl::GLImage* image,
+    gl::OverlayImage image,
     std::unique_ptr<gfx::GpuFence> gpu_fence,
     const gfx::OverlayPlaneData& overlay_plane_data) {
   auto* frame = unsubmitted_frames_.back().get();
@@ -162,9 +162,7 @@
     if (gpu_fence)
       acquire_fences.push_back(std::move(*gpu_fence));
 
-    auto pixmap = image->GetNativePixmap();
-    DCHECK(pixmap);
-    frame->schedule_planes_succeeded = pixmap->ScheduleOverlayPlane(
+    frame->schedule_planes_succeeded = image->ScheduleOverlayPlane(
         widget_, overlay_plane_data, std::move(acquire_fences), {});
   }
   return frame->schedule_planes_succeeded;
diff --git a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h
index ef70bc1..638cadfc 100644
--- a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h
+++ b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h
@@ -43,7 +43,7 @@
 
   // gl::GLSurface:
   bool ScheduleOverlayPlane(
-      gl::GLImage* image,
+      gl::OverlayImage image,
       std::unique_ptr<gfx::GpuFence> gpu_fence,
       const gfx::OverlayPlaneData& overlay_plane_data) override;
   bool IsOffscreen() override;
diff --git a/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc b/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc
index fef154d..e90874b 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc
+++ b/ui/ozone/platform/wayland/gpu/wayland_surface_factory_unittest.cc
@@ -22,7 +22,6 @@
 #include "ui/gfx/native_pixmap.h"
 #include "ui/gfx/overlay_plane_data.h"
 #include "ui/gfx/overlay_priority_hint.h"
-#include "ui/gl/gl_image_egl.h"
 #include "ui/gl/gl_utils.h"
 #include "ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h"
 #include "ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h"
@@ -49,14 +48,16 @@
 
 constexpr uint32_t kAugmentedSurfaceNotSupportedVersion = 0;
 
-// Fake GLImage that just schedules overlay plane. It must become busy when
-// scheduled and be associated with the swap id to track correct order of swaps
-// and releases of the image.
-class FakeGLImageNativePixmap : public gl::GLImageEGL {
+// Holds a NativePixmap used for scheduling overlay planes. It must become busy
+// when scheduled and be associated with the swap id to track correct order of
+// swaps and releases of the image.
+// TODO(rjkroege): Consider putting extra state inside a test NativePixmap
+// implementation instead of a wrapper class.
+class OverlayImageHolder : public base::RefCounted<OverlayImageHolder> {
  public:
-  FakeGLImageNativePixmap(scoped_refptr<gfx::NativePixmap> pixmap,
-                          const gfx::Size& size)
-      : gl::GLImageEGL(size), pixmap_(pixmap) {}
+  OverlayImageHolder(scoped_refptr<gfx::NativePixmap> pixmap,
+                     const gfx::Size& size)
+      : pixmap_(pixmap) {}
 
   // Associates swap id with this image.
   void AssociateWithSwapId(uint32_t swap_id) {
@@ -76,18 +77,16 @@
   void SetDisplayed(bool displayed) { displayed_ = displayed; }
   bool displayed() const { return displayed_; }
 
-  // Overridden from GLImage:
-  scoped_refptr<gfx::NativePixmap> GetNativePixmap() override {
-    return pixmap_;
-  }
-
- protected:
-  ~FakeGLImageNativePixmap() override {}
+  scoped_refptr<gfx::NativePixmap> GetNativePixmap() { return pixmap_; }
 
  private:
+  friend class base::RefCounted<OverlayImageHolder>;
+
+  ~OverlayImageHolder() = default;
+
   scoped_refptr<gfx::NativePixmap> pixmap_;
 
-  // Indicated if the gl image is busy. If yes, it was scheduled as overlay
+  // Indicated if the overlay image is busy. If yes, it was scheduled as overlay
   // plane for further submission and can't be reused until it's freed.
   bool busy_ = false;
 
@@ -119,27 +118,27 @@
   }
 
   // Finishes the submission by setting the swap id of completed buffer swap and
-  // sets the associated gl_image as displayed and non-busy, which indicates
-  // that 1) the image has been sent to be shown after being scheduled 2) the
-  // image is displayed. This sort of mimics a buffer queue, but in a simpliear
-  // way.
+  // sets the associated overlay_image as displayed and non-busy, which
+  // indicates that 1) the image has been sent to be shown after being scheduled
+  // 2) the image is displayed. This sort of mimics a buffer queue, but in a
+  // simpler way.
   void FinishSwapBuffersAsync(
       uint32_t local_swap_id,
-      std::vector<scoped_refptr<FakeGLImageNativePixmap>> gl_images,
+      std::vector<scoped_refptr<OverlayImageHolder>> overlay_images,
       gfx::SwapCompletionResult result) {
     last_finish_swap_id_ = pending_local_swap_ids_.front();
     pending_local_swap_ids_.pop();
 
-    for (auto& gl_image : gl_images) {
-      EXPECT_EQ(gl_image->GetAssociateWithSwapId(), last_finish_swap_id_);
-      EXPECT_TRUE(gl_image->busy() && !gl_image->displayed());
-      gl_image->SetBusy(false);
-      gl_image->SetDisplayed(true);
+    for (auto& overlay_image : overlay_images) {
+      EXPECT_EQ(overlay_image->GetAssociateWithSwapId(), last_finish_swap_id_);
+      EXPECT_TRUE(overlay_image->busy() && !overlay_image->displayed());
+      overlay_image->SetBusy(false);
+      overlay_image->SetDisplayed(true);
     }
 
     for (auto& displayed_image : displayed_images_)
       displayed_image->SetDisplayed(false);
-    displayed_images_ = std::move(gl_images);
+    displayed_images_ = std::move(overlay_images);
   }
 
   void BufferPresented(uint64_t local_swap_id,
@@ -158,7 +157,7 @@
   base::queue<uint64_t> pending_local_swap_ids_;
 
   // Keeps track of a displayed image.
-  std::vector<scoped_refptr<FakeGLImageNativePixmap>> displayed_images_;
+  std::vector<scoped_refptr<OverlayImageHolder>> displayed_images_;
 };
 
 }  // namespace
@@ -210,7 +209,7 @@
   }
 
   void ScheduleOverlayPlane(gl::GLSurface* gl_surface,
-                            gl::GLImage* image,
+                            gl::OverlayImage image,
                             int z_order) {
     gl_surface->ScheduleOverlayPlane(
         image, nullptr,
@@ -248,12 +247,12 @@
   EXPECT_CALL(*server_.zwp_linux_dmabuf_v1(), CreateParams(_, _, _)).Times(4);
 
   // Create buffers and FakeGlImageNativePixmap.
-  std::vector<scoped_refptr<FakeGLImageNativePixmap>> fake_gl_image;
+  std::vector<scoped_refptr<OverlayImageHolder>> fake_overlay_image;
   for (int i = 0; i < 4; ++i) {
     auto native_pixmap = surface_factory_->CreateNativePixmap(
         widget_, nullptr, window_->size_px(), gfx::BufferFormat::BGRA_8888,
         gfx::BufferUsage::SCANOUT);
-    fake_gl_image.push_back(base::MakeRefCounted<FakeGLImageNativePixmap>(
+    fake_overlay_image.push_back(base::MakeRefCounted<OverlayImageHolder>(
         native_pixmap, window_->size_px()));
   }
 
@@ -270,32 +269,34 @@
     auto swap_id = cbs_helper.GetNextLocalSwapId();
     // Associate the image with the next swap id so that we can easily track if
     // it became free to reuse.
-    fake_gl_image[0]->AssociateWithSwapId(swap_id);
+    fake_overlay_image[0]->AssociateWithSwapId(swap_id);
     // And set it to be busy...
-    fake_gl_image[0]->SetBusy(true);
+    fake_overlay_image[0]->SetBusy(true);
 
     // Prepare background.
-    ScheduleOverlayPlane(gl_surface.get(), fake_gl_image[0].get(),
+    ScheduleOverlayPlane(gl_surface.get(),
+                         fake_overlay_image[0]->GetNativePixmap(),
                          /*z_order=*/INT32_MIN);
 
     // Associate the image with the next swap id so that we can easily track if
     // it became free to reuse.
-    fake_gl_image[1]->AssociateWithSwapId(swap_id);
+    fake_overlay_image[1]->AssociateWithSwapId(swap_id);
     // And set it to be busy...
-    fake_gl_image[1]->SetBusy(true);
+    fake_overlay_image[1]->SetBusy(true);
 
     // Prepare overlay plane.
-    ScheduleOverlayPlane(gl_surface.get(), fake_gl_image[1].get(),
+    ScheduleOverlayPlane(gl_surface.get(),
+                         fake_overlay_image[1]->GetNativePixmap(),
                          /*z_order=*/1);
 
-    std::vector<scoped_refptr<FakeGLImageNativePixmap>> gl_images;
-    gl_images.push_back(fake_gl_image[0]);
-    gl_images.push_back(fake_gl_image[1]);
+    std::vector<scoped_refptr<OverlayImageHolder>> overlay_images;
+    overlay_images.push_back(fake_overlay_image[0]);
+    overlay_images.push_back(fake_overlay_image[1]);
 
     // And submit each image. They will be executed in FIFO manner.
     gl_surface->SwapBuffersAsync(
         base::BindOnce(&CallbacksHelper::FinishSwapBuffersAsync,
-                       base::Unretained(&cbs_helper), swap_id, gl_images),
+                       base::Unretained(&cbs_helper), swap_id, overlay_images),
         base::BindOnce(&CallbacksHelper::BufferPresented,
                        base::Unretained(&cbs_helper), swap_id),
         gl::FrameData());
@@ -339,14 +340,14 @@
 
   cbs_helper.ResetLastFinishedSwapId();
 
-  for (const auto& gl_image : fake_gl_image) {
+  for (const auto& overlay_image : fake_overlay_image) {
     // All the images except the first one, which was associated with swap
     // id=0u, must be busy and not displayed. The first one must be displayed.
-    if (gl_image->GetAssociateWithSwapId() == 0u) {
-      EXPECT_FALSE(gl_image->busy());
-      EXPECT_TRUE(gl_image->displayed());
+    if (overlay_image->GetAssociateWithSwapId() == 0u) {
+      EXPECT_FALSE(overlay_image->busy());
+      EXPECT_TRUE(overlay_image->displayed());
     } else {
-      EXPECT_FALSE(gl_image->displayed());
+      EXPECT_FALSE(overlay_image->displayed());
     }
   }
 
@@ -362,21 +363,22 @@
     auto swap_id = cbs_helper.GetNextLocalSwapId();
     // Associate the image with the next swap id so that we can easily track if
     // it became free to reuse.
-    fake_gl_image[2]->AssociateWithSwapId(swap_id);
+    fake_overlay_image[2]->AssociateWithSwapId(swap_id);
     // And set it to be busy...
-    fake_gl_image[2]->SetBusy(true);
+    fake_overlay_image[2]->SetBusy(true);
 
     // Prepare overlay plane.
-    ScheduleOverlayPlane(gl_surface.get(), fake_gl_image[2].get(),
+    ScheduleOverlayPlane(gl_surface.get(),
+                         fake_overlay_image[2]->GetNativePixmap(),
                          /*z_order=*/1);
 
-    std::vector<scoped_refptr<FakeGLImageNativePixmap>> gl_images;
-    gl_images.push_back(fake_gl_image[2]);
+    std::vector<scoped_refptr<OverlayImageHolder>> overlay_images;
+    overlay_images.push_back(fake_overlay_image[2]);
 
     // And submit each image. They will be executed in FIFO manner.
     gl_surface->SwapBuffersAsync(
         base::BindOnce(&CallbacksHelper::FinishSwapBuffersAsync,
-                       base::Unretained(&cbs_helper), swap_id, gl_images),
+                       base::Unretained(&cbs_helper), swap_id, overlay_images),
         base::BindOnce(&CallbacksHelper::BufferPresented,
                        base::Unretained(&cbs_helper), swap_id),
         gl::FrameData());
@@ -433,21 +435,22 @@
     auto swap_id = cbs_helper.GetNextLocalSwapId();
     // Associate the image with the next swap id so that we can easily track if
     // it became free to reuse.
-    fake_gl_image[3]->AssociateWithSwapId(swap_id);
+    fake_overlay_image[3]->AssociateWithSwapId(swap_id);
     // And set it to be busy...
-    fake_gl_image[3]->SetBusy(true);
+    fake_overlay_image[3]->SetBusy(true);
 
     // Prepare primary plane.
-    ScheduleOverlayPlane(gl_surface.get(), fake_gl_image[3].get(),
+    ScheduleOverlayPlane(gl_surface.get(),
+                         fake_overlay_image[3]->GetNativePixmap(),
                          /*z_order=*/0);
 
-    std::vector<scoped_refptr<FakeGLImageNativePixmap>> gl_images;
-    gl_images.push_back(fake_gl_image[3]);
+    std::vector<scoped_refptr<OverlayImageHolder>> overlay_images;
+    overlay_images.push_back(fake_overlay_image[3]);
 
     // And submit each image. They will be executed in FIFO manner.
     gl_surface->SwapBuffersAsync(
         base::BindOnce(&CallbacksHelper::FinishSwapBuffersAsync,
-                       base::Unretained(&cbs_helper), swap_id, gl_images),
+                       base::Unretained(&cbs_helper), swap_id, overlay_images),
         base::BindOnce(&CallbacksHelper::BufferPresented,
                        base::Unretained(&cbs_helper), swap_id),
         gl::FrameData());
@@ -511,12 +514,12 @@
 
   cbs_helper.ResetLastFinishedSwapId();
 
-  for (const auto& gl_image : fake_gl_image) {
-    if (gl_image->GetAssociateWithSwapId() == 2u) {
-      EXPECT_TRUE(gl_image->displayed());
-      EXPECT_FALSE(gl_image->busy());
+  for (const auto& overlay_image : fake_overlay_image) {
+    if (overlay_image->GetAssociateWithSwapId() == 2u) {
+      EXPECT_TRUE(overlay_image->displayed());
+      EXPECT_FALSE(overlay_image->busy());
     } else {
-      EXPECT_FALSE(gl_image->displayed());
+      EXPECT_FALSE(overlay_image->displayed());
     }
   }
 }
@@ -541,12 +544,12 @@
       ->SetNoGLFlushForTests();
 
   // Create buffers and FakeGlImageNativePixmap.
-  std::vector<scoped_refptr<FakeGLImageNativePixmap>> fake_gl_image;
+  std::vector<scoped_refptr<OverlayImageHolder>> fake_overlay_image;
   for (int i = 0; i < 5; ++i) {
     auto native_pixmap = surface_factory_->CreateNativePixmap(
         widget_, nullptr, window_->size_px(), gfx::BufferFormat::BGRA_8888,
         gfx::BufferUsage::SCANOUT);
-    fake_gl_image.push_back(base::MakeRefCounted<FakeGLImageNativePixmap>(
+    fake_overlay_image.push_back(base::MakeRefCounted<OverlayImageHolder>(
         native_pixmap, window_->size_px()));
   }
 
@@ -563,43 +566,46 @@
     auto swap_id = cbs_helper.GetNextLocalSwapId();
     // Associate the image with the next swap id so that we can easily track if
     // it became free to reuse.
-    fake_gl_image[0]->AssociateWithSwapId(swap_id);
+    fake_overlay_image[0]->AssociateWithSwapId(swap_id);
     // And set it to be busy...
-    fake_gl_image[0]->SetBusy(true);
+    fake_overlay_image[0]->SetBusy(true);
 
     // Prepare background.
-    ScheduleOverlayPlane(gl_surface.get(), fake_gl_image[0].get(),
+    ScheduleOverlayPlane(gl_surface.get(),
+                         fake_overlay_image[0]->GetNativePixmap(),
                          /*z_order=*/INT32_MIN);
 
     // Associate the image with the next swap id so that we can easily track if
     // it became free to reuse.
-    fake_gl_image[1]->AssociateWithSwapId(swap_id);
+    fake_overlay_image[1]->AssociateWithSwapId(swap_id);
     // And set it to be busy...
-    fake_gl_image[1]->SetBusy(true);
+    fake_overlay_image[1]->SetBusy(true);
 
     // Prepare primary plane.
-    ScheduleOverlayPlane(gl_surface.get(), fake_gl_image[1].get(),
+    ScheduleOverlayPlane(gl_surface.get(),
+                         fake_overlay_image[1]->GetNativePixmap(),
                          /*z_order=*/0);
 
     // Associate the image with the next swap id so that we can easily track if
     // it became free to reuse.
-    fake_gl_image[2]->AssociateWithSwapId(swap_id);
+    fake_overlay_image[2]->AssociateWithSwapId(swap_id);
     // And set it to be busy...
-    fake_gl_image[2]->SetBusy(true);
+    fake_overlay_image[2]->SetBusy(true);
 
     // Prepare underlay plane.
-    ScheduleOverlayPlane(gl_surface.get(), fake_gl_image[2].get(),
+    ScheduleOverlayPlane(gl_surface.get(),
+                         fake_overlay_image[2]->GetNativePixmap(),
                          /*z_order=*/-1);
 
-    std::vector<scoped_refptr<FakeGLImageNativePixmap>> gl_images;
-    gl_images.push_back(fake_gl_image[0]);
-    gl_images.push_back(fake_gl_image[1]);
-    gl_images.push_back(fake_gl_image[2]);
+    std::vector<scoped_refptr<OverlayImageHolder>> overlay_images;
+    overlay_images.push_back(fake_overlay_image[0]);
+    overlay_images.push_back(fake_overlay_image[1]);
+    overlay_images.push_back(fake_overlay_image[2]);
 
     // And submit each image. They will be executed in FIFO manner.
     gl_surface->SwapBuffersAsync(
         base::BindOnce(&CallbacksHelper::FinishSwapBuffersAsync,
-                       base::Unretained(&cbs_helper), swap_id, gl_images),
+                       base::Unretained(&cbs_helper), swap_id, overlay_images),
         base::BindOnce(&CallbacksHelper::BufferPresented,
                        base::Unretained(&cbs_helper), swap_id),
         gl::FrameData());
@@ -647,14 +653,14 @@
 
   cbs_helper.ResetLastFinishedSwapId();
 
-  for (const auto& gl_image : fake_gl_image) {
+  for (const auto& overlay_image : fake_overlay_image) {
     // All the images except the first one, which was associated with swap
     // id=0u, must be busy and not displayed. The first one must be displayed.
-    if (gl_image->GetAssociateWithSwapId() == 0u) {
-      EXPECT_FALSE(gl_image->busy());
-      EXPECT_TRUE(gl_image->displayed());
+    if (overlay_image->GetAssociateWithSwapId() == 0u) {
+      EXPECT_FALSE(overlay_image->busy());
+      EXPECT_TRUE(overlay_image->displayed());
     } else {
-      EXPECT_FALSE(gl_image->displayed());
+      EXPECT_FALSE(overlay_image->displayed());
     }
   }
 
@@ -665,32 +671,34 @@
     auto swap_id = cbs_helper.GetNextLocalSwapId();
     // Associate the image with the next swap id so that we can easily track if
     // it became free to reuse.
-    fake_gl_image[3]->AssociateWithSwapId(swap_id);
+    fake_overlay_image[3]->AssociateWithSwapId(swap_id);
     // And set it to be busy...
-    fake_gl_image[3]->SetBusy(true);
+    fake_overlay_image[3]->SetBusy(true);
 
     // Prepare primary plane.
-    ScheduleOverlayPlane(gl_surface.get(), fake_gl_image[3].get(),
+    ScheduleOverlayPlane(gl_surface.get(),
+                         fake_overlay_image[3]->GetNativePixmap(),
                          /*z_order=*/0);
 
     // Associate the image with the next swap id so that we can easily track if
     // it became free to reuse.
-    fake_gl_image[4]->AssociateWithSwapId(swap_id);
+    fake_overlay_image[4]->AssociateWithSwapId(swap_id);
     // And set it to be busy...
-    fake_gl_image[4]->SetBusy(true);
+    fake_overlay_image[4]->SetBusy(true);
 
     // Prepare overlay plane.
-    ScheduleOverlayPlane(gl_surface.get(), fake_gl_image[4].get(),
+    ScheduleOverlayPlane(gl_surface.get(),
+                         fake_overlay_image[4]->GetNativePixmap(),
                          /*z_order=*/1);
 
-    std::vector<scoped_refptr<FakeGLImageNativePixmap>> gl_images;
-    gl_images.push_back(fake_gl_image[3]);
-    gl_images.push_back(fake_gl_image[4]);
+    std::vector<scoped_refptr<OverlayImageHolder>> overlay_images;
+    overlay_images.push_back(fake_overlay_image[3]);
+    overlay_images.push_back(fake_overlay_image[4]);
 
     // And submit each image. They will be executed in FIFO manner.
     gl_surface->SwapBuffersAsync(
         base::BindOnce(&CallbacksHelper::FinishSwapBuffersAsync,
-                       base::Unretained(&cbs_helper), swap_id, gl_images),
+                       base::Unretained(&cbs_helper), swap_id, overlay_images),
         base::BindOnce(&CallbacksHelper::BufferPresented,
                        base::Unretained(&cbs_helper), swap_id),
         gl::FrameData());
@@ -776,12 +784,12 @@
   // SwapCompletionCallback should run.
   EXPECT_EQ(cbs_helper.GetLastFinishedSwapId(), 1u);
 
-  for (const auto& gl_image : fake_gl_image) {
-    if (gl_image->GetAssociateWithSwapId() == 1u) {
-      EXPECT_TRUE(gl_image->displayed());
-      EXPECT_FALSE(gl_image->busy());
+  for (const auto& overlay_image : fake_overlay_image) {
+    if (overlay_image->GetAssociateWithSwapId() == 1u) {
+      EXPECT_TRUE(overlay_image->displayed());
+      EXPECT_FALSE(overlay_image->busy());
     } else {
-      EXPECT_FALSE(gl_image->displayed());
+      EXPECT_FALSE(overlay_image->displayed());
     }
   }
 }
@@ -935,11 +943,11 @@
                      window_->size_px().height()));
 
   // Create buffer and FakeGlImageNativePixmap.
-  std::vector<scoped_refptr<FakeGLImageNativePixmap>> fake_gl_image;
+  std::vector<scoped_refptr<OverlayImageHolder>> fake_overlay_image;
   auto native_pixmap = surface_factory_->CreateNativePixmap(
       widget_, nullptr, test_buffer_size, gfx::BufferFormat::BGRA_8888,
       gfx::BufferUsage::SCANOUT);
-  fake_gl_image.push_back(base::MakeRefCounted<FakeGLImageNativePixmap>(
+  fake_overlay_image.push_back(base::MakeRefCounted<OverlayImageHolder>(
       native_pixmap, test_buffer_size));
 
   auto* root_surface = server_.GetObject<wl::MockSurface>(
@@ -954,26 +962,26 @@
     auto swap_id = cbs_helper.GetNextLocalSwapId();
     // Associate the image with the next swap id so that we can easily track if
     // it became free to reuse.
-    fake_gl_image[0]->AssociateWithSwapId(swap_id);
+    fake_overlay_image[0]->AssociateWithSwapId(swap_id);
     // And set it to be busy...
-    fake_gl_image[0]->SetBusy(true);
+    fake_overlay_image[0]->SetBusy(true);
 
     // Prepare background.
     gl_surface->ScheduleOverlayPlane(
-        fake_gl_image[0].get(), nullptr,
+        fake_overlay_image[0]->GetNativePixmap(), nullptr,
         gfx::OverlayPlaneData(
             INT32_MIN, gfx::OverlayTransform::OVERLAY_TRANSFORM_ROTATE_270,
             gfx::RectF(window_->GetBoundsInPixels()), crop_uv, false,
             gfx::Rect(test_buffer_dmg), 1.0f, gfx::OverlayPriorityHint::kNone,
             gfx::RRectF(), gfx::ColorSpace::CreateSRGB(), absl::nullopt));
 
-    std::vector<scoped_refptr<FakeGLImageNativePixmap>> gl_images;
-    gl_images.push_back(fake_gl_image[0]);
+    std::vector<scoped_refptr<OverlayImageHolder>> overlay_images;
+    overlay_images.push_back(fake_overlay_image[0]);
 
     // And submit each image. They will be executed in FIFO manner.
     gl_surface->SwapBuffersAsync(
         base::BindOnce(&CallbacksHelper::FinishSwapBuffersAsync,
-                       base::Unretained(&cbs_helper), swap_id, gl_images),
+                       base::Unretained(&cbs_helper), swap_id, overlay_images),
         base::BindOnce(&CallbacksHelper::BufferPresented,
                        base::Unretained(&cbs_helper), swap_id),
         gl::FrameData());
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
index b5b2c94..4937df8 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
@@ -1032,6 +1032,9 @@
     const gfx::Point& location,
     int operation,
     ui::mojom::DragEventSource source) {
+  if (!content_window_)
+    return;
+
   views::RunShellDrag(content_window_, std::move(data), location, operation,
                       source);
 }
diff --git a/ui/views/widget/native_widget_mac.mm b/ui/views/widget/native_widget_mac.mm
index 50c4b63..90abe2e1 100644
--- a/ui/views/widget/native_widget_mac.mm
+++ b/ui/views/widget/native_widget_mac.mm
@@ -738,6 +738,8 @@
                                    const gfx::Point& location,
                                    int operation,
                                    ui::mojom::DragEventSource source) {
+  if (!ns_window_host_)
+    return;
   ns_window_host_->drag_drop_client()->StartDragAndDrop(view, std::move(data),
                                                         operation, source);
 }
diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc
index f2442cb..1209461 100644
--- a/ui/views/widget/widget.cc
+++ b/ui/views/widget/widget.cc
@@ -283,6 +283,7 @@
 // static
 void Widget::ReparentNativeView(gfx::NativeView native_view,
                                 gfx::NativeView new_parent) {
+  DCHECK(native_view);
   internal::NativeWidgetPrivate::ReparentNativeView(native_view, new_parent);
   Widget* child_widget = GetWidgetForNativeView(native_view);
   Widget* parent_widget =
@@ -640,6 +641,9 @@
     const gfx::Vector2d& drag_offset,
     MoveLoopSource source,
     MoveLoopEscapeBehavior escape_behavior) {
+  if (!native_widget_)
+    return MoveLoopResult::kCanceled;
+
   return native_widget_->RunMoveLoop(drag_offset, source, escape_behavior);
 }
 
diff --git a/ui/views/widget/widget_unittest.cc b/ui/views/widget/widget_unittest.cc
index 43d2c75..066e37af 100644
--- a/ui/views/widget/widget_unittest.cc
+++ b/ui/views/widget/widget_unittest.cc
@@ -18,6 +18,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/accessibility/ax_node_data.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
 #include "ui/base/hit_test.h"
 #include "ui/color/color_id.h"
 #include "ui/color/color_provider.h"
@@ -1174,6 +1175,16 @@
   widget()->NotifyWillRemoveView(widget()->non_client_view());
 }
 
+TEST_P(WidgetWithDestroyedNativeViewTest, parent) {
+  widget()->parent();
+}
+
+TEST_P(WidgetWithDestroyedNativeViewTest,
+       RegisterPaintAsActiveChangedCallback) {
+  auto subscription =
+      widget()->RegisterPaintAsActiveChangedCallback(base::DoNothing());
+}
+
 TEST_P(WidgetWithDestroyedNativeViewTest, ReleaseCapture) {
   widget()->ReleaseCapture();
 }
@@ -1182,10 +1193,26 @@
   widget()->ReorderNativeViews();
 }
 
+TEST_P(WidgetWithDestroyedNativeViewTest, ReparentNativeView) {
+  EXPECT_DCHECK_DEATH(
+      Widget::ReparentNativeView(widget()->GetNativeView(), nullptr));
+}
+
 TEST_P(WidgetWithDestroyedNativeViewTest, Restore) {
   widget()->Restore();
 }
 
+TEST_P(WidgetWithDestroyedNativeViewTest, RunMoveLoop) {
+  widget()->RunMoveLoop(gfx::Vector2d(), views::Widget::MoveLoopSource::kMouse,
+                        views::Widget::MoveLoopEscapeBehavior::kHide);
+}
+
+TEST_P(WidgetWithDestroyedNativeViewTest, RunShellDrag) {
+  std::unique_ptr<OSExchangeData> data(std::make_unique<OSExchangeData>());
+  widget()->RunShellDrag(nullptr, std::move(data), gfx::Point(), 0,
+                         ui::mojom::DragEventSource::kMouse);
+}
+
 TEST_P(WidgetWithDestroyedNativeViewTest, SchedulePaintInRect) {
   widget()->SchedulePaintInRect(gfx::Rect(0, 0, 1, 2));
 }