diff --git a/DEPS b/DEPS
index cf8f2cc6..52f271488c 100644
--- a/DEPS
+++ b/DEPS
@@ -306,19 +306,19 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'src_internal_revision': '25029d94751c6df826cee247c0d57890c2cedbaa',
+  'src_internal_revision': '523a084bc51377f455f0e042222acbc522a95165',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'c653705482bc2a85d42e19cb441ce5e250d81e39',
+  'skia_revision': 'd503bc9c6e46b399fe7f89057c21b7c393ccd845',
   # 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': 'fb8803a8ccd3ccbca07e40deb509505b665af175',
+  'v8_revision': 'ee50bc8415cbf6756bfa4cb078155e498c89a4f7',
   # 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': 'e784b1ec822888844ec60abcf50d169f0347f6cf',
+  'angle_revision': 'cb7d3cc206d6a4165c0012d78387b714b55a0556',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -326,7 +326,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'aad37f1a2976c5379b8a2aba60a330d7aba55164',
+  'pdfium_revision': '64571d5d1f1eb7e91b7237b37d7bffecebb77981',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -381,7 +381,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': 'd80f7d1ae4c6c010ecc8e660588f33f5768cb177',
+  'catapult_revision': '99cae5876c51001792d8225f77579eb537188490',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling chromium_variations
   # and whatever else without interference from each other.
@@ -401,7 +401,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'cb3c970179dfea108e67796c202f519068d1d48f',
+  'devtools_frontend_revision': 'f12c052fc715d18e765fe3f78fdb3584a14baf4b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -429,7 +429,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.
-  'quiche_revision': 'bccc7cc648236d93d8cd4f07c82d7a15a6218bf6',
+  'quiche_revision': 'bb4f68479fd8fa4738339cdcc5782b04b985b72b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -825,7 +825,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '2c1a38e990de645f3a7871f5e3f44558d9d0ad17',
+    '2d0a791e87f12aa439b145774e2efe96e6d82225',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1203,7 +1203,7 @@
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '1cdf05204cf5365bcb63fd8b306750b054be5c73',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + 'd5d8b1bb4ddbde9f4752ca8ae28543a31b64e211',
     'condition': 'checkout_src_internal',
   },
 
@@ -1808,7 +1808,7 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@d8fc43000b3e5e924679432724228bf75695ca27',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@52f57acdf69e747b0cea3c458a6ec6467dcb6d8a',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '56300b29fbfcc693ee6609ddad3fdd5b7a449a21',
@@ -1960,7 +1960,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/eche_app/app',
-        'version': 'cj6U89uuybmhL60Yo05WMPs4iRpeWzw1AKnPZYs1jrwC',
+        'version': 'DnPrMxwYi3z-CBmheuwa4UpmeI5g0AUOWlPLCPDLbA4C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3995,7 +3995,7 @@
 
   'src/components/optimization_guide/internal': {
       'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' +
-        'f4aea18550bf707b1d06d65028e7e9edcae7d2c1',
+        '1f827a8c547d3157e40bbed7e375469c1585d5f7',
       'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 503aed5..c08f638c 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -2833,6 +2833,8 @@
     "wm/window_restore/pine_contents_view.h",
     "wm/window_restore/pine_context_menu_model.cc",
     "wm/window_restore/pine_context_menu_model.h",
+    "wm/window_restore/pine_controller.cc",
+    "wm/window_restore/pine_controller.h",
     "wm/window_restore/window_restore_controller.cc",
     "wm/window_restore/window_restore_controller.h",
     "wm/window_restore/window_restore_util.cc",
diff --git a/ash/accelerators/debug_commands.cc b/ash/accelerators/debug_commands.cc
index c5ab008e..7625b5c 100644
--- a/ash/accelerators/debug_commands.cc
+++ b/ash/accelerators/debug_commands.cc
@@ -38,7 +38,7 @@
 #include "ash/wallpaper/wallpaper_controller_impl.h"
 #include "ash/wm/float/float_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
-#include "ash/wm/window_restore/window_restore_controller.h"
+#include "ash/wm/window_restore/pine_controller.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
 #include "base/command_line.h"
@@ -254,7 +254,7 @@
 void HandleShowInformedRestore() {
   if (features::IsForestFeatureEnabled()) {
     Shell::Get()
-        ->window_restore_controller()
+        ->pine_controller()
         ->MaybeStartPineOverviewSessionDevAccelerator();
   }
 }
diff --git a/ash/birch/birch_model_unittest.cc b/ash/birch/birch_model_unittest.cc
index 6a6e854..418687b 100644
--- a/ash/birch/birch_model_unittest.cc
+++ b/ash/birch/birch_model_unittest.cc
@@ -144,9 +144,17 @@
   EXPECT_THAT(consumer.items_ready_responses(), testing::ElementsAre("0", "1"));
 }
 
+// TODO(https://crbug.com/324963992): Fix `BirchModel*Test.DataFetchTimeout`
+// for debug builds.
+#if defined(NDEBUG)
+#define MAYBE_DataFetchTimeout DataFetchTimeout
+#else
+#define MAYBE_DataFetchTimeout DISABLED_DataFetchTimeout
+#endif
+
 // Test that consumer is notified when waiting a set amount of time after
 // requesting birch data.
-TEST_F(BirchModelTest, DataFetchTimeout) {
+TEST_F(BirchModelTest, MAYBE_DataFetchTimeout) {
   BirchModel* model = Shell::Get()->birch_model();
   TestModelConsumer consumer;
   EXPECT_TRUE(model);
@@ -195,7 +203,7 @@
   EXPECT_FALSE(model->IsDataFresh());
 }
 
-TEST_F(BirchModelWithoutWeatherTest, DataFetchTimeout) {
+TEST_F(BirchModelWithoutWeatherTest, MAYBE_DataFetchTimeout) {
   BirchModel* model = Shell::Get()->birch_model();
   TestModelConsumer consumer;
   EXPECT_TRUE(model);
diff --git a/ash/components/arc/arc_features.cc b/ash/components/arc/arc_features.cc
index b437e3e..b0d82d65 100644
--- a/ash/components/arc/arc_features.cc
+++ b/ash/components/arc/arc_features.cc
@@ -224,7 +224,7 @@
 // Settings page.
 BASE_FEATURE(kPerAppLanguage,
              "PerAppLanguage",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Controls ARC picture-in-picture feature. If this is enabled, then Android
 // will control which apps can enter PIP. If this is disabled, then ARC PIP
diff --git a/ash/events/peripheral_customization_event_rewriter.cc b/ash/events/peripheral_customization_event_rewriter.cc
index 3589d69..cdac91a4 100644
--- a/ash/events/peripheral_customization_event_rewriter.cc
+++ b/ash/events/peripheral_customization_event_rewriter.cc
@@ -164,6 +164,7 @@
     case mojom::CustomizationRestriction::kAllowAlphabetKeyEventRewrites:
     case mojom::CustomizationRestriction::
         kAllowAlphabetOrNumberKeyEventRewrites:
+    case mojom::CustomizationRestriction::kAllowTabEventRewrites:
       return false;
     case mojom::CustomizationRestriction::kAllowHorizontalScrollWheelRewrites:
     case mojom::CustomizationRestriction::kAllowCustomizations:
@@ -769,6 +770,8 @@
         kAllowAlphabetOrNumberKeyEventRewrites:
       return IsAlphaKeyboardCode(key_event.key_code()) ||
              IsNumberKeyboardCode(key_event.key_code());
+    case mojom::CustomizationRestriction::kAllowTabEventRewrites:
+      return key_event.key_code() == ui::VKEY_TAB;
     case mojom::CustomizationRestriction::kDisallowCustomizations:
     case mojom::CustomizationRestriction::kDisableKeyEventRewrites:
     case mojom::CustomizationRestriction::kAllowHorizontalScrollWheelRewrites:
diff --git a/ash/game_dashboard/game_dashboard_main_menu_view.cc b/ash/game_dashboard/game_dashboard_main_menu_view.cc
index d8cfe1ad..f2eed00 100644
--- a/ash/game_dashboard/game_dashboard_main_menu_view.cc
+++ b/ash/game_dashboard/game_dashboard_main_menu_view.cc
@@ -51,7 +51,9 @@
 #include "ui/views/background.h"
 #include "ui/views/border.h"
 #include "ui/views/bubble/bubble_border.h"
+#include "ui/views/controls/button/button.h"
 #include "ui/views/controls/highlight_path_generator.h"
+#include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/fill_layout.h"
 #include "ui/views/view.h"
 #include "ui/views/view_class_properties.h"
@@ -77,6 +79,11 @@
                          /*upper_right=*/kDetailRowCornerRadius,
                          /*lower_right=*/2.0f,
                          /*lower_left=*/2.0f);
+constexpr gfx::RoundedCornersF kScreenSizeRowCorners =
+    gfx::RoundedCornersF(/*upper_left=*/2.0f,
+                         /*upper_right=*/2.0f,
+                         /*lower_right=*/kDetailRowCornerRadius,
+                         /*lower_left=*/kDetailRowCornerRadius);
 
 // For setup button pulse animation.
 constexpr int kSetupPulseExtraHalfSize = 32;
@@ -131,56 +138,52 @@
   return game_dashboard_utils::IsFlagSet(flags, ArcGameControlsFlag::kEnabled);
 }
 
-}  // namespace
+// Helper function to configure the feature row button designs and return the
+// layout manager.
+views::BoxLayout* ConfigureFeatureRowLayout(views::Button* button,
+                                            const gfx::RoundedCornersF& corners,
+                                            bool enabled) {
+  auto* layout = button->SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kHorizontal,
+      /*inside_border_insets=*/gfx::Insets::VH(16, 16)));
+  layout->set_cross_axis_alignment(
+      views::BoxLayout::CrossAxisAlignment::kCenter);
+  button->SetNotifyEnterExitOnChild(true);
+  button->SetEnabled(enabled);
+  button->SetBackground(views::CreateThemedRoundedRectBackground(
+      enabled ? cros_tokens::kCrosSysSystemOnBase
+              : cros_tokens::kCrosSysDisabledContainer,
+      corners));
+
+  // Set up highlight ink drop and focus ring.
+  views::HighlightPathGenerator::Install(
+      button, std::make_unique<views::RoundRectHighlightPathGenerator>(
+                  gfx::Insets(), corners));
+  StyleUtil::SetUpInkDropForButton(button, gfx::Insets(),
+                                   /*highlight_on_hover=*/false,
+                                   /*highlight_on_focus=*/true);
+  auto* focus_ring = views::FocusRing::Get(button);
+  focus_ring->SetHaloInset(-4);
+  focus_ring->SetHaloThickness(2);
+
+  return layout;
+}
 
 // -----------------------------------------------------------------------------
-// GameDashboardMainMenuView::GameControlsDetailsRow:
+// FeatureHeader:
 
-// Game Controls details row includes feature icon, title and sub-title, set up
-// button or switch button with drill in arrow icon.
-// If there is no Game Controls set up, it shows as:
-// +------------------------------------------------+
-// | |icon|  |title|                |set_up button|||
-// |         |sub-title|                            |
-// +------------------------------------------------+
-// Otherwise, it shows as:
-// +------------------------------------------------+
-// | |icon|  |title|       |switch| |drill in arrow||
-// |         |sub-title|                            |
-// +------------------------------------------------+
-class GameDashboardMainMenuView::GameControlsDetailsRow : public views::Button {
-  METADATA_HEADER(GameControlsDetailsRow, views::Button)
+// `FeatureHeader` includes icon, title and sub-title.
+// +---------------------+
+// | |icon|  |title|     |
+// |         |sub-title| |
+// +---------------------+
+class FeatureHeader : public views::View {
+  METADATA_HEADER(FeatureHeader, views::View)
 
  public:
-  GameControlsDetailsRow(GameDashboardMainMenuView* main_menu)
-      : Button(base::BindRepeating(&GameControlsDetailsRow::OnButtonPressed,
-                                   base::Unretained(this))),
-        main_menu_(main_menu) {
-    CacheAppName();
-    SetID(VIEW_ID_GD_CONTROLS_DETAILS_ROW);
-
-    const auto flags =
-        game_dashboard_utils::GetGameControlsFlag(GetGameWindow());
-    CHECK(flags);
-
-    const bool is_available = game_dashboard_utils::IsFlagSet(
-        *flags, ArcGameControlsFlag::kAvailable);
-    SetEnabled(is_available);
-
-    const auto title = l10n_util::GetStringUTF16(
-        IDS_ASH_GAME_DASHBOARD_CONTROLS_TILE_BUTTON_TITLE);
-    SetAccessibleName(title);
-    SetTooltipText(title);
-    SetBackground(views::CreateThemedRoundedRectBackground(
-        is_available ? cros_tokens::kCrosSysSystemOnBase
-                     : cros_tokens::kCrosSysDisabledContainer,
-        kGCDetailRowCorners));
-    SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(16, 16)));
-
-    views::HighlightPathGenerator::Install(
-        this, std::make_unique<views::RoundRectHighlightPathGenerator>(
-                  gfx::Insets(), kGCDetailRowCorners));
-
+  FeatureHeader(bool is_enabled,
+                const gfx::VectorIcon& icon,
+                const std::u16string& title) {
     auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>());
     layout->set_cross_axis_alignment(
         views::BoxLayout::CrossAxisAlignment::kCenter);
@@ -189,17 +192,17 @@
     auto* icon_container = AddChildView(std::make_unique<views::View>());
     icon_container->SetLayoutManager(std::make_unique<views::FillLayout>());
     icon_container->SetBackground(views::CreateThemedRoundedRectBackground(
-        is_available ? cros_tokens::kCrosSysSystemOnBase
-                     : cros_tokens::kCrosSysDisabledContainer,
+        is_enabled ? cros_tokens::kCrosSysSystemOnBase
+                   : cros_tokens::kCrosSysDisabledContainer,
         /*radius=*/12.0f));
     icon_container->SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(6, 6)));
     icon_container->SetProperty(views::kMarginsKey,
                                 gfx::Insets::TLBR(0, 0, 0, 16));
     icon_container->AddChildView(
         std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
-            kGdGameControlsIcon,
-            is_available ? cros_tokens::kCrosSysOnSurface
-                         : cros_tokens::kCrosSysDisabled,
+            icon,
+            is_enabled ? cros_tokens::kCrosSysOnSurface
+                       : cros_tokens::kCrosSysDisabled,
             /*icon_size=*/20)));
 
     // Add title and sub-title.
@@ -215,7 +218,7 @@
     auto* feature_title =
         tag_container->AddChildView(std::make_unique<views::Label>(title));
     feature_title->SetAutoColorReadabilityEnabled(false);
-    feature_title->SetEnabledColorId(is_available
+    feature_title->SetEnabledColorId(is_enabled
                                          ? cros_tokens::kCrosSysOnSurface
                                          : cros_tokens::kCrosSysDisabled);
     feature_title->SetFontList(
@@ -226,16 +229,150 @@
     // Add sub-title.
     sub_title_ = tag_container->AddChildView(bubble_utils::CreateLabel(
         TypographyToken::kCrosAnnotation2, u"",
-        is_available ? cros_tokens::kCrosSysOnSurfaceVariant
-                     : cros_tokens::kCrosSysDisabled));
+        is_enabled ? cros_tokens::kCrosSysOnSurfaceVariant
+                   : cros_tokens::kCrosSysDisabled));
     sub_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
     sub_title_->SetMultiLine(true);
+  }
+
+  FeatureHeader(const FeatureHeader&) = delete;
+  FeatureHeader& operator=(const FeatureHeader) = delete;
+  ~FeatureHeader() override = default;
+
+  void UpdateSubtitle(const std::u16string& text) {
+    // For multiline label, if the fixed width is not set, the preferred size is
+    // re-calcuated based on previous label size as available size instead of
+    // its real available size when changing the text. For `sub_title_`, it
+    // takes the whole width of its parent's width as fixed width after layout.
+    if (!sub_title_->GetFixedWidth()) {
+      if (int width = sub_title_->parent()->size().width(); width != 0) {
+        sub_title_->SizeToFit(width);
+      }
+    }
+    sub_title_->SetText(text);
+  }
+
+ private:
+  raw_ptr<views::Label> sub_title_ = nullptr;
+};
+
+BEGIN_METADATA(FeatureHeader, views::View)
+END_METADATA
+
+// -----------------------------------------------------------------------------
+// ScreenSizeRow:
+
+// ScreenSizeRow includes `FeatureHeader` and right arrow icon.
+// +------------------------------------------------+
+// | |feature header|                           |>| |
+// +------------------------------------------------+
+class ScreenSizeRow : public views::Button {
+  METADATA_HEADER(ScreenSizeRow, views::View)
+
+ public:
+  ScreenSizeRow(PressedCallback callback,
+                ResizeCompatMode resize_mode,
+                ArcResizeLockType resize_lock_type)
+      : views::Button(std::move(callback)) {
+    SetID(VIEW_ID_GD_SCREEN_SIZE_TILE);
+
+    bool enabled = false;
+    int tooltip = 0;
+    switch (resize_lock_type) {
+      case ArcResizeLockType::RESIZE_DISABLED_TOGGLABLE:
+      case ArcResizeLockType::RESIZE_ENABLED_TOGGLABLE:
+        enabled = true;
+        break;
+      case ArcResizeLockType::RESIZE_DISABLED_NONTOGGLABLE:
+        enabled = false;
+        tooltip =
+            IDS_ASH_ARC_APP_COMPAT_DISABLED_COMPAT_MODE_BUTTON_TOOLTIP_PHONE;
+        break;
+      case ArcResizeLockType::NONE:
+        enabled = false;
+        break;
+    }
+
+    const std::u16string title = l10n_util::GetStringUTF16(
+        IDS_ASH_GAME_DASHBOARD_SCREEN_SIZE_SETTINGS_TITLE);
+    SetAccessibleName(title);
+    SetTooltipText(tooltip ? l10n_util::GetStringUTF16(tooltip) : title);
+
+    auto* layout =
+        ConfigureFeatureRowLayout(this, kScreenSizeRowCorners, enabled);
+    // Add header.
+    auto* header = AddChildView(std::make_unique<FeatureHeader>(
+        enabled, compat_mode_util::GetIcon(resize_mode), title));
+    layout->SetFlexForView(header, /*flex=*/1);
+    header->UpdateSubtitle(compat_mode_util::GetText(resize_mode));
+    // Add arrow icon.
+    AddChildView(
+        std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
+            kQuickSettingsRightArrowIcon,
+            enabled ? cros_tokens::kCrosSysOnSurface
+                    : cros_tokens::kCrosSysDisabled)));
+  }
+
+  ScreenSizeRow(const ScreenSizeRow&) = delete;
+  ScreenSizeRow& operator=(const ScreenSizeRow) = delete;
+  ~ScreenSizeRow() override = default;
+};
+
+BEGIN_METADATA(ScreenSizeRow, views::Button)
+END_METADATA
+
+}  // namespace
+
+// -----------------------------------------------------------------------------
+// GameDashboardMainMenuView::GameControlsDetailsRow:
+
+// `GameControlsDetailsRow` includes `FeatureHeader`, set up button or switch
+// button with drill in arrow icon. If there is no Game Controls set up, it
+// shows as:
+// +------------------------------------------------+
+// | |feature header|                |set_up button||
+// +------------------------------------------------+
+// Otherwise, it shows as:
+// +------------------------------------------------+
+// | |feature header|      |switch| |drill in arrow||
+// +------------------------------------------------+
+class GameDashboardMainMenuView::GameControlsDetailsRow : public views::Button {
+  METADATA_HEADER(GameControlsDetailsRow, views::Button)
+
+ public:
+  GameControlsDetailsRow(GameDashboardMainMenuView* main_menu)
+      : views::Button(
+            base::BindRepeating(&GameControlsDetailsRow::OnButtonPressed,
+                                base::Unretained(this))),
+        main_menu_(main_menu) {
+    CacheAppName();
+    SetID(VIEW_ID_GD_CONTROLS_DETAILS_ROW);
+
+    const auto flags =
+        game_dashboard_utils::GetGameControlsFlag(GetGameWindow());
+    CHECK(flags);
+
+    const auto title = l10n_util::GetStringUTF16(
+        IDS_ASH_GAME_DASHBOARD_CONTROLS_TILE_BUTTON_TITLE);
+    SetAccessibleName(title);
+    SetTooltipText(title);
+
+    const bool is_available = game_dashboard_utils::IsFlagSet(
+        *flags, ArcGameControlsFlag::kAvailable);
+    auto* layout =
+        ConfigureFeatureRowLayout(this, kGCDetailRowCorners, is_available);
+
+    // Add header.
+    header_ = AddChildView(std::make_unique<FeatureHeader>(
+        /*is_enabled=*/is_available, kGdGameControlsIcon, title));
+    // Flex `header_` to fill the empty space.
+    layout->SetFlexForView(header_, /*flex=*/1);
 
     // Add setup button, or feature switch and drill-in arrow.
     if (!is_available ||
         game_dashboard_utils::IsFlagSet(*flags, ArcGameControlsFlag::kEmpty)) {
       // Add setup button.
-      sub_title_->SetText(l10n_util::GetStringUTF16(
+      header_->UpdateSubtitle(l10n_util::GetStringUTF16(
           IDS_ASH_GAME_DASHBOARD_GC_SET_UP_SUB_TITLE));
       setup_button_ = AddChildView(std::make_unique<PillButton>(
           base::BindRepeating(&GameControlsDetailsRow::OnSetUpButtonPressed,
@@ -319,8 +456,13 @@
         is_feature_enabled
             ? IDS_ASH_GAME_DASHBOARD_GC_DETAILS_SUB_TITLE_ON_TEMPLATE
             : IDS_ASH_GAME_DASHBOARD_GC_DETAILS_SUB_TITLE_OFF_TEMPLATE;
-    sub_title_->SetText(
+    header_->UpdateSubtitle(
         l10n_util::GetStringFUTF16(string_id, base::UTF8ToUTF16(app_name_)));
+
+    // In case the sub-title turns to two lines from one line.
+    if (GetWidget()) {
+      main_menu_->SizeToContents();
+    }
   }
 
   void CacheAppName() {
@@ -342,27 +484,9 @@
 
   aura::Window* GetGameWindow() { return main_menu_->context_->game_window(); }
 
-  // views::View:
-  void OnThemeChanged() override {
-    views::View::OnThemeChanged();
-
-    // Set up highlight and focus ring for the whole row.
-    StyleUtil::SetUpInkDropForButton(
-        /*button=*/this, gfx::Insets(), /*highlight_on_hover=*/true,
-        /*highlight_on_focus=*/true, /*background_color=*/
-        GetColorProvider()->GetColor(cros_tokens::kCrosSysHoverOnSubtle));
-
-    // `StyleUtil::SetUpInkDropForButton()` reinstalls the focus ring, so it
-    // needs to set the focus ring size after calling
-    // `StyleUtil::SetUpInkDropForButton()`.
-    auto* focus_ring = views::FocusRing::Get(this);
-    focus_ring->SetHaloInset(-4);
-    focus_ring->SetHaloThickness(2);
-  }
-
   const raw_ptr<GameDashboardMainMenuView> main_menu_;
 
-  raw_ptr<views::Label> sub_title_ = nullptr;
+  raw_ptr<FeatureHeader> header_ = nullptr;
   raw_ptr<PillButton> setup_button_ = nullptr;
   raw_ptr<Switch> feature_switch_ = nullptr;
 
@@ -401,7 +525,7 @@
       /*between_child_spacing=*/16));
 
   AddShortcutTilesRow();
-  AddFeatureDetailsRows();
+  MaybeAddArcFeatureRows();
   AddUtilityClusterRow();
 
   SizeToPreferredSize();
@@ -544,7 +668,11 @@
       /*sub_label=*/std::nullopt));
 }
 
-void GameDashboardMainMenuView::AddFeatureDetailsRows() {
+void GameDashboardMainMenuView::MaybeAddArcFeatureRows() {
+  if (!IsArcWindow(context_->game_window())) {
+    return;
+  }
+
   auto* feature_details_container =
       AddChildView(std::make_unique<views::View>());
   feature_details_container->SetLayoutManager(
@@ -553,15 +681,8 @@
           /*inside_border_insets=*/gfx::Insets(),
           /*between_child_spacing=*/2));
 
-  // Set the container's corner radius.
-  feature_details_container->SetPaintToLayer();
-  auto* container_layer = feature_details_container->layer();
-  container_layer->SetFillsBoundsOpaquely(false);
-  container_layer->SetRoundedCornerRadius(
-      gfx::RoundedCornersF(kDetailRowCornerRadius));
-
-  MaybeAddGameControlsDetailsRow(feature_details_container);
-  MaybeAddScreenSizeSettingsRow(feature_details_container);
+  AddGameControlsDetailsRow(feature_details_container);
+  AddScreenSizeSettingsRow(feature_details_container);
 }
 
 void GameDashboardMainMenuView::MaybeAddGameControlsTile(
@@ -588,52 +709,23 @@
   game_controls_tile_->SetSubLabelVisibility(true);
 }
 
-void GameDashboardMainMenuView::MaybeAddGameControlsDetailsRow(
+void GameDashboardMainMenuView::AddGameControlsDetailsRow(
     views::View* container) {
-  if (IsArcWindow(context_->game_window())) {
-    game_controls_details_ =
-        container->AddChildView(std::make_unique<GameControlsDetailsRow>(this));
-  }
+  DCHECK(IsArcWindow(context_->game_window()));
+  game_controls_details_ =
+      container->AddChildView(std::make_unique<GameControlsDetailsRow>(this));
 }
 
-void GameDashboardMainMenuView::MaybeAddScreenSizeSettingsRow(
+void GameDashboardMainMenuView::AddScreenSizeSettingsRow(
     views::View* container) {
   aura::Window* game_window = context_->game_window();
-  if (!IsArcWindow(game_window)) {
-    return;
-  }
-
-  const auto resize_mode = compat_mode_util::PredictCurrentMode(game_window);
-  auto* screen_size_row = container->AddChildView(CreateFeatureTile(
+  DCHECK(IsArcWindow(game_window));
+  container->AddChildView(std::make_unique<ScreenSizeRow>(
       base::BindRepeating(
           &GameDashboardMainMenuView::OnScreenSizeSettingsButtonPressed,
           base::Unretained(this)),
-      /*is_togglable=*/false, FeatureTile::TileType::kPrimary,
-      VIEW_ID_GD_SCREEN_SIZE_TILE,
-      /*icon=*/compat_mode_util::GetIcon(resize_mode),
-      l10n_util::GetStringUTF16(
-          IDS_ASH_GAME_DASHBOARD_SCREEN_SIZE_SETTINGS_TITLE),
-      /*sub_label=*/compat_mode_util::GetText(resize_mode)));
-
-  const ArcResizeLockType resize_lock_type =
-      game_window->GetProperty(kArcResizeLockTypeKey);
-  switch (resize_lock_type) {
-    case ArcResizeLockType::RESIZE_DISABLED_TOGGLABLE:
-    case ArcResizeLockType::RESIZE_ENABLED_TOGGLABLE:
-      screen_size_row->SetEnabled(true);
-      // TODO(b/303351905): Investigate why drill in arrow isn't placed in
-      // correct location.
-      screen_size_row->CreateDecorativeDrillInArrow();
-      break;
-    case ArcResizeLockType::RESIZE_DISABLED_NONTOGGLABLE:
-      screen_size_row->SetEnabled(false);
-      screen_size_row->SetTooltipText(l10n_util::GetStringUTF16(
-          IDS_ASH_ARC_APP_COMPAT_DISABLED_COMPAT_MODE_BUTTON_TOOLTIP_PHONE));
-      break;
-    case ArcResizeLockType::NONE:
-      screen_size_row->SetEnabled(false);
-      break;
-  }
+      /*resize_mode=*/compat_mode_util::PredictCurrentMode(game_window),
+      /*resize_lock_type=*/game_window->GetProperty(kArcResizeLockTypeKey)));
 }
 
 void GameDashboardMainMenuView::AddUtilityClusterRow() {
diff --git a/ash/game_dashboard/game_dashboard_main_menu_view.h b/ash/game_dashboard/game_dashboard_main_menu_view.h
index 0e5fed6..fe5e0ccd 100644
--- a/ash/game_dashboard/game_dashboard_main_menu_view.h
+++ b/ash/game_dashboard/game_dashboard_main_menu_view.h
@@ -82,18 +82,18 @@
 
   // Adds feature details rows, for example, including Game Controls or window
   // size.
-  void AddFeatureDetailsRows();
+  void MaybeAddArcFeatureRows();
 
   // Adds Game Controls feature tile in `container` if it is the ARC game window
   // and Game Controls is available.
   void MaybeAddGameControlsTile(views::View* container);
 
   // Adds menu controls row for Game Controls.
-  void MaybeAddGameControlsDetailsRow(views::View* container);
+  void AddGameControlsDetailsRow(views::View* container);
 
   // Adds a row to access a settings page controlling the screen size if the
   // given game window is an ARC app.
-  void MaybeAddScreenSizeSettingsRow(views::View* container);
+  void AddScreenSizeSettingsRow(views::View* container);
 
   // Adds the dashboard cluster (containing feedback, settings, and help
   // buttons) to the Game Controls tile view.
diff --git a/ash/glanceables/glanceables_metrics.cc b/ash/glanceables/glanceables_metrics.cc
index d6d8887de..f814fd6 100644
--- a/ash/glanceables/glanceables_metrics.cc
+++ b/ash/glanceables/glanceables_metrics.cc
@@ -24,11 +24,40 @@
 constexpr char kTimeManagementClassroomPrefix[] =
     "Ash.Glanceables.TimeManagement.Classroom";
 
-void RecordTasksUserAction() {
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class TasksUserAction {
+  kActiveTaskListChanged = 0,
+  kTaskMarkedComplete = 1,
+  kTaskMarkedIncomplete = 2,
+  kAddTaskStarted = 3,
+  kModifyTaskStarted = 4,
+  kHeaderButtonClicked = 5,
+  kAddNewTaskButtonClicked = 6,
+  kFooterButtonClicked = 7,
+  kEditInGoogleTasksButtonClicked = 8,
+  kMaxValue = kEditInGoogleTasksButtonClicked
+};
+
+void RecordTasksUserAction(TasksUserAction action) {
+  base::UmaHistogramEnumeration(
+      base::JoinString({kTimeManagementTaskPrefix, "UserAction"}, "."), action);
   base::RecordAction(base::UserMetricsAction("Glanceables_Tasks_UserAction"));
 }
 
-void RecordClassroomUserAction() {
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class ClassroomUserAction {
+  kAssignmentListSelected = 0,
+  kHeaderIconPressed = 1,
+  kStudentAssignmentPressed = 2,
+  kMaxValue = kStudentAssignmentPressed,
+};
+
+void RecordClassroomUserAction(ClassroomUserAction action) {
+  base::UmaHistogramEnumeration(
+      base::JoinString({kTimeManagementClassroomPrefix, "UserAction"}, "."),
+      action);
   base::RecordAction(
       base::UserMetricsAction("Glanceables_Classroom_UserAction"));
 }
@@ -38,13 +67,14 @@
 namespace ash {
 
 void RecordActiveTaskListChanged() {
-  RecordTasksUserAction();
+  RecordTasksUserAction(TasksUserAction::kActiveTaskListChanged);
   base::RecordAction(
       base::UserMetricsAction("Glanceables_Tasks_ActiveTaskListChanged"));
 }
 
 void RecordTaskMarkedAsCompleted(bool complete) {
-  RecordTasksUserAction();
+  RecordTasksUserAction(complete ? TasksUserAction::kTaskMarkedComplete
+                                 : TasksUserAction::kTaskMarkedIncomplete);
 
   if (complete) {
     base::RecordAction(
@@ -56,7 +86,7 @@
 }
 
 void RecordUserStartedAddingTask() {
-  RecordTasksUserAction();
+  RecordTasksUserAction(TasksUserAction::kAddTaskStarted);
 
   base::RecordAction(
       base::UserMetricsAction("Glanceables_Tasks_AddTaskStarted"));
@@ -83,7 +113,7 @@
 }
 
 void RecordUserModifyingTask() {
-  RecordTasksUserAction();
+  RecordTasksUserAction(TasksUserAction::kModifyTaskStarted);
 
   base::RecordAction(
       base::UserMetricsAction("Glanceables_Tasks_ModifyTaskStarted"));
@@ -95,22 +125,25 @@
 }
 
 void RecordTasksLaunchSource(TasksLaunchSource source) {
-  RecordTasksUserAction();
 
   switch (source) {
     case TasksLaunchSource::kHeaderButton:
+      RecordTasksUserAction(TasksUserAction::kHeaderButtonClicked);
       base::RecordAction(base::UserMetricsAction(
           "Glanceables_Tasks_LaunchTasksApp_HeaderButton"));
       break;
     case TasksLaunchSource::kAddNewTaskButton:
+      RecordTasksUserAction(TasksUserAction::kAddNewTaskButtonClicked);
       base::RecordAction(base::UserMetricsAction(
           "Glanceables_Tasks_LaunchTasksApp_AddNewTaskButton"));
       break;
     case TasksLaunchSource::kFooterButton:
+      RecordTasksUserAction(TasksUserAction::kFooterButtonClicked);
       base::RecordAction(base::UserMetricsAction(
           "Glanceables_Tasks_LaunchTasksApp_FooterButton"));
       break;
     case TasksLaunchSource::kEditInGoogleTasksButton:
+      RecordTasksUserAction(TasksUserAction::kEditInGoogleTasksButtonClicked);
       base::RecordAction(base::UserMetricsAction(
           "Glanceables_Tasks_LaunchTasksApp_EditInGoogleTasksButton"));
       break;
@@ -261,7 +294,7 @@
 }
 
 void RecordStudentAssignmentPressed(bool default_list) {
-  RecordClassroomUserAction();
+  RecordClassroomUserAction(ClassroomUserAction::kStudentAssignmentPressed);
 
   base::RecordAction(
       base::UserMetricsAction("Glanceables_Classroom_AssignmentPressed"));
@@ -273,14 +306,14 @@
 }
 
 void RecordClassroomHeaderIconPressed() {
-  RecordClassroomUserAction();
+  RecordClassroomUserAction(ClassroomUserAction::kHeaderIconPressed);
 
   base::RecordAction(
       base::UserMetricsAction("Glanceables_Classroom_HeaderIconPressed"));
 }
 
 void RecordStudentAssignmentListSelected(StudentAssignmentsListType list_type) {
-  RecordClassroomUserAction();
+  RecordClassroomUserAction(ClassroomUserAction::kAssignmentListSelected);
 
   base::UmaHistogramEnumeration(
       "Ash.Glanceables.Classroom.Student.ListSelected", list_type);
diff --git a/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc b/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc
index 8244460..30cb13b 100644
--- a/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc
+++ b/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc
@@ -18,6 +18,7 @@
 #include "ash/test/ash_test_base.h"
 #include "base/memory/raw_ptr.h"
 #include "base/strings/strcat.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/metrics/user_action_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/types/cxx23_to_underlying.h"
@@ -167,6 +168,7 @@
 }
 
 TEST_F(GlanceablesTasksViewTest, ShowsProgressBarWhileAddingTask) {
+  base::HistogramTester histogram_tester;
   tasks_client()->set_paused(true);
 
   // Initially progress bar is hidden.
@@ -184,9 +186,13 @@
   // After replying to pending callbacks, the progress bar should become hidden.
   EXPECT_EQ(tasks_client()->RunPendingAddTaskCallbacks(), 1u);
   EXPECT_FALSE(GetProgressBar()->GetVisible());
+
+  histogram_tester.ExpectUniqueSample(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 3, 1);
 }
 
 TEST_F(GlanceablesTasksViewTest, ShowsProgressBarWhileEditingTask) {
+  base::HistogramTester histogram_tester;
   tasks_client()->set_paused(true);
 
   // Initially progress bar is hidden.
@@ -211,6 +217,9 @@
   // After replying to pending callbacks, the progress bar should become hidden.
   EXPECT_EQ(tasks_client()->RunPendingUpdateTaskCallbacks(), 1u);
   EXPECT_FALSE(GetProgressBar()->GetVisible());
+
+  histogram_tester.ExpectUniqueSample(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 4, 1);
 }
 
 TEST_F(GlanceablesTasksViewTest, OnlyShowsFooterIfAtLeast100Tasks) {
@@ -235,6 +244,7 @@
 }
 
 TEST_F(GlanceablesTasksViewTest, SupportsEditingRightAfterAdding) {
+  base::HistogramTester histogram_tester;
   tasks_client()->set_paused(true);
 
   // Add a task.
@@ -263,6 +273,13 @@
   // Verify executed callbacks number.
   EXPECT_EQ(tasks_client()->RunPendingAddTaskCallbacks(), 0u);
   EXPECT_EQ(tasks_client()->RunPendingUpdateTaskCallbacks(), 1u);
+
+  histogram_tester.ExpectTotalCount(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 2);
+  histogram_tester.ExpectBucketCount(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 3, 1);
+  histogram_tester.ExpectBucketCount(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 4, 1);
 }
 
 TEST_F(GlanceablesTasksViewTest, AllowsPressingAddNewTaskButtonWhileAdding) {
@@ -453,6 +470,7 @@
 }
 
 TEST_F(GlanceablesTasksViewTest, ShowTasksWebUIFromEditInBrowserView) {
+  base::HistogramTester histogram_tester;
   base::UserActionTester user_actions;
   const auto* const title_label = views::AsViewClass<views::Label>(
       GetTaskItemsContainerView()->children()[0]->GetViewByID(
@@ -470,6 +488,12 @@
   GestureTapOn(edit_in_browser_button);
   EXPECT_EQ(1, user_actions.GetActionCount(
                    "Glanceables_Tasks_LaunchTasksApp_EditInGoogleTasksButton"));
+  histogram_tester.ExpectTotalCount(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 2);
+  histogram_tester.ExpectBucketCount(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 4, 1);
+  histogram_tester.ExpectBucketCount(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 8, 1);
 
   // Simulate that the widget is hidden safely after opening a browser window.
   view()->GetWidget()->Hide();
diff --git a/ash/picker/picker_controller.cc b/ash/picker/picker_controller.cc
index 46123204..abc872b 100644
--- a/ash/picker/picker_controller.cc
+++ b/ash/picker/picker_controller.cc
@@ -146,10 +146,10 @@
       u"Matching links",
       {{
           PickerSearchResult::BrowsingHistory(
-              GURL("http://www.foo.com"),
+              GURL("http://www.foo.com"), u"Foo",
               ui::ImageModel::FromVectorIcon(kPlaceholderIcon)),
           PickerSearchResult::BrowsingHistory(
-              GURL("http://crbug.com"),
+              GURL("http://crbug.com"), u"Crbug",
               ui::ImageModel::FromVectorIcon(kPlaceholderIcon)),
       }});
 }
@@ -172,8 +172,7 @@
 
 }  // namespace
 
-PickerController::PickerController()
-    : should_paint_(MatchPickerFeatureKeyHash() == PickerFeatureKeyType::kDev) {
+PickerController::PickerController() {
   asset_fetcher_ = std::make_unique<PickerAssetFetcherImpl>(base::BindRepeating(
       &PickerController::DownloadGifToString, weak_ptr_factory_.GetWeakPtr()));
   if (auto* manager = ash::input_method::InputMethodManager::Get()) {
@@ -263,10 +262,6 @@
       input_method, ResultToInsertMediaData(result), kInsertMediaTimeout);
 }
 
-bool PickerController::ShouldPaint() {
-  return should_paint_;
-}
-
 PickerAssetFetcher* PickerController::GetAssetFetcher() {
   return asset_fetcher_.get();
 }
diff --git a/ash/picker/picker_controller.h b/ash/picker/picker_controller.h
index e2743260..13ffb99 100644
--- a/ash/picker/picker_controller.h
+++ b/ash/picker/picker_controller.h
@@ -68,7 +68,6 @@
                    std::optional<PickerCategory> category,
                    SearchResultsCallback callback) override;
   void InsertResultOnNextFocus(const PickerSearchResult& result) override;
-  bool ShouldPaint() override;
   PickerAssetFetcher* GetAssetFetcher() override;
 
   // ash::input_method::ImeKeyboard::Observer:
@@ -87,7 +86,6 @@
 
   raw_ptr<PickerClient> client_ = nullptr;
   views::UniqueWidgetPtr widget_;
-  bool should_paint_ = false;
   std::unique_ptr<PickerAssetFetcher> asset_fetcher_;
   std::unique_ptr<PickerInsertMediaRequest> insert_media_request_;
 
diff --git a/ash/picker/picker_controller_unittest.cc b/ash/picker/picker_controller_unittest.cc
index 2fc98ce..8899fc2 100644
--- a/ash/picker/picker_controller_unittest.cc
+++ b/ash/picker/picker_controller_unittest.cc
@@ -175,7 +175,7 @@
       Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
 
   controller.InsertResultOnNextFocus(PickerSearchResult::BrowsingHistory(
-      GURL("http://foo.com"), ui::ImageModel{}));
+      GURL("http://foo.com"), u"Foo", ui::ImageModel{}));
   controller.widget_for_testing()->CloseNow();
   ui::FakeTextInputClient input_field(input_method,
                                       {.type = ui::TEXT_INPUT_TYPE_TEXT});
diff --git a/ash/picker/views/picker_contents_view.cc b/ash/picker/views/picker_contents_view.cc
index a66297c..ee3df259 100644
--- a/ash/picker/views/picker_contents_view.cc
+++ b/ash/picker/views/picker_contents_view.cc
@@ -16,6 +16,7 @@
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/compositor/layer.h"
 #include "ui/gfx/geometry/insets.h"
+#include "ui/views/border.h"
 #include "ui/views/controls/scroll_view.h"
 #include "ui/views/controls/scrollbar/scroll_bar.h"
 #include "ui/views/layout/fill_layout.h"
@@ -28,6 +29,8 @@
 
 constexpr int kScrollViewGradientHeight = 16;
 
+constexpr auto kScrollViewContentsBorderInsets = gfx::Insets::TLBR(0, 0, 8, 0);
+
 gfx::Insets GetPickerScrollBarInsets(PickerView::PickerLayoutType layout_type) {
   switch (layout_type) {
     case PickerView::PickerLayoutType::kResultsBelowSearchField:
@@ -88,6 +91,8 @@
   auto page_container = std::make_unique<views::FlexLayoutView>();
   page_container->SetOrientation(views::LayoutOrientation::kVertical);
   page_container->SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
+  page_container->SetBorder(
+      views::CreateEmptyBorder(kScrollViewContentsBorderInsets));
   page_container_ = scroll_view->SetContents(std::move(page_container));
 }
 
diff --git a/ash/picker/views/picker_search_results_view.cc b/ash/picker/views/picker_search_results_view.cc
index 77cf1e88..d9f8ef0 100644
--- a/ash/picker/views/picker_search_results_view.cc
+++ b/ash/picker/views/picker_search_results_view.cc
@@ -113,7 +113,8 @@
           [&](const PickerSearchResult::BrowsingHistoryData& data) {
             auto item_view = std::make_unique<PickerItemView>(
                 std::move(select_result_callback));
-            item_view->SetPrimaryText(base::UTF8ToUTF16(data.url.spec()));
+            item_view->SetPrimaryText(data.title);
+            item_view->SetSecondaryText(base::UTF8ToUTF16(data.url.spec()));
             item_view->SetLeadingIcon(data.icon);
             section_view->AddListItem(std::move(item_view));
           },
diff --git a/ash/picker/views/picker_view.cc b/ash/picker/views/picker_view.cc
index 28d52f04..b5bd0ea8 100644
--- a/ash/picker/views/picker_view.cc
+++ b/ash/picker/views/picker_view.cc
@@ -53,6 +53,11 @@
 constexpr ui::ColorId kBackgroundColor =
     cros_tokens::kCrosSysSystemBaseElevated;
 
+// Padding to separate the Picker window from the caret.
+constexpr gfx::Outsets kPaddingAroundCaret(4);
+// Padding to separate the Picker window from the screen edge.
+constexpr gfx::Insets kPaddingFromScreenEdge(16);
+
 std::unique_ptr<views::BubbleBorder> CreateBorder() {
   auto border = std::make_unique<views::BubbleBorder>(
       views::BubbleBorder::NONE, views::BubbleBorder::NO_SHADOW);
@@ -74,10 +79,14 @@
 gfx::Rect GetPickerAnchorBounds(const gfx::Rect& caret_bounds,
                                 const gfx::Point& cursor_point,
                                 const gfx::Rect& focused_window_bounds) {
-  return caret_bounds != gfx::Rect() &&
-                 focused_window_bounds.Contains(caret_bounds)
-             ? caret_bounds
-             : gfx::Rect(cursor_point, gfx::Size());
+  if (caret_bounds != gfx::Rect() &&
+      focused_window_bounds.Contains(caret_bounds)) {
+    gfx::Rect anchor_rect = caret_bounds;
+    anchor_rect.Outset(kPaddingAroundCaret);
+    return anchor_rect;
+  } else {
+    return gfx::Rect(cursor_point, gfx::Size());
+  }
 }
 
 // Gets the preferred layout to use given `anchor_bounds` in screen coordinates.
@@ -102,9 +111,10 @@
                               PickerView::PickerLayoutType layout_type,
                               const gfx::Size& picker_view_size,
                               int picker_view_search_field_vertical_offset) {
-  const gfx::Rect screen_work_area = display::Screen::GetScreen()
-                                         ->GetDisplayMatching(anchor_bounds)
-                                         .work_area();
+  gfx::Rect screen_work_area = display::Screen::GetScreen()
+                                   ->GetDisplayMatching(anchor_bounds)
+                                   .work_area();
+  screen_work_area.Inset(kPaddingFromScreenEdge);
   gfx::Rect picker_view_bounds(picker_view_size);
   if (anchor_bounds.right() + picker_view_size.width() <=
       screen_work_area.right()) {
@@ -220,12 +230,6 @@
   return true;
 }
 
-void PickerView::PaintChildren(const views::PaintInfo& paint_info) {
-  if (delegate_->ShouldPaint()) {
-    views::View::PaintChildren(paint_info);
-  }
-}
-
 std::unique_ptr<views::NonClientFrameView> PickerView::CreateNonClientFrameView(
     views::Widget* widget) {
   auto frame =
diff --git a/ash/picker/views/picker_view.h b/ash/picker/views/picker_view.h
index 3e9538a..97a2462 100644
--- a/ash/picker/views/picker_view.h
+++ b/ash/picker/views/picker_view.h
@@ -69,7 +69,6 @@
 
   // views::WidgetDelegateView:
   bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
-  void PaintChildren(const views::PaintInfo& paint_info) override;
   std::unique_ptr<views::NonClientFrameView> CreateNonClientFrameView(
       views::Widget* widget) override;
   void AddedToWidget() override;
diff --git a/ash/picker/views/picker_view_delegate.h b/ash/picker/views/picker_view_delegate.h
index a13c0e20..a63408a 100644
--- a/ash/picker/views/picker_view_delegate.h
+++ b/ash/picker/views/picker_view_delegate.h
@@ -45,10 +45,6 @@
   // the result is dropped silently.
   virtual void InsertResultOnNextFocus(const PickerSearchResult& result) = 0;
 
-  // Whether the view should paint. Certain test scenarios do not need
-  // painting, so it is better to skip painting.
-  virtual bool ShouldPaint() = 0;
-
   virtual PickerAssetFetcher* GetAssetFetcher() = 0;
 };
 
diff --git a/ash/picker/views/picker_view_unittest.cc b/ash/picker/views/picker_view_unittest.cc
index c9bf7f1..b35eb88 100644
--- a/ash/picker/views/picker_view_unittest.cc
+++ b/ash/picker/views/picker_view_unittest.cc
@@ -96,8 +96,6 @@
     last_inserted_result_ = result;
   }
 
-  bool ShouldPaint() override { return true; }
-
   PickerAssetFetcher* GetAssetFetcher() override { return &asset_fetcher_; }
 
   std::optional<PickerSearchResult> last_inserted_result() const {
@@ -401,7 +399,7 @@
                   .work_area()
                   .Contains(view->GetBoundsInScreen()));
   // Should be to the right of the caret.
-  EXPECT_GE(view->GetBoundsInScreen().x(), kDefaultCaretBounds.right());
+  EXPECT_GT(view->GetBoundsInScreen().x(), kDefaultCaretBounds.right());
   // Center of the search field should be vertically aligned with the caret.
   EXPECT_EQ(view->search_field_view_for_testing()
                 .GetBoundsInScreen()
@@ -425,7 +423,7 @@
   // Should be entirely on screen.
   EXPECT_TRUE(screen_work_area.Contains(view->GetBoundsInScreen()));
   // Should be to the right of the caret.
-  EXPECT_GE(view->GetBoundsInScreen().x(), caret_bounds.right());
+  EXPECT_GT(view->GetBoundsInScreen().x(), caret_bounds.right());
   // Center of the search field should be vertically aligned with the caret.
   EXPECT_EQ(view->search_field_view_for_testing()
                 .GetBoundsInScreen()
@@ -449,7 +447,7 @@
   // Should be entirely on screen.
   EXPECT_TRUE(screen_work_area.Contains(view->GetBoundsInScreen()));
   // Should be to the right of the caret.
-  EXPECT_GE(view->GetBoundsInScreen().x(), caret_bounds.right());
+  EXPECT_GT(view->GetBoundsInScreen().x(), caret_bounds.right());
   // Center of the search field should be vertically aligned with the caret.
   EXPECT_EQ(view->search_field_view_for_testing()
                 .GetBoundsInScreen()
@@ -473,7 +471,7 @@
   // Should be entirely on screen.
   EXPECT_TRUE(screen_work_area.Contains(view->GetBoundsInScreen()));
   // Should be below the caret.
-  EXPECT_GE(view->GetBoundsInScreen().y(), caret_bounds.bottom());
+  EXPECT_GT(view->GetBoundsInScreen().y(), caret_bounds.bottom());
 }
 
 TEST_F(PickerViewTest, BoundsAboveCaretForCaretNearBottomRightOfScreen) {
@@ -491,7 +489,7 @@
   // Should be entirely on screen.
   EXPECT_TRUE(screen_work_area.Contains(view->GetBoundsInScreen()));
   // Should be above the caret.
-  EXPECT_LE(view->GetBoundsInScreen().bottom(), caret_bounds.y());
+  EXPECT_LT(view->GetBoundsInScreen().bottom(), caret_bounds.y());
 }
 
 TEST_F(PickerViewTest, BoundsAlignedWithCursorForEmptyCaretBounds) {
diff --git a/ash/public/cpp/external_arc/BUILD.gn b/ash/public/cpp/external_arc/BUILD.gn
index 34c6933f..6f6845101 100644
--- a/ash/public/cpp/external_arc/BUILD.gn
+++ b/ash/public/cpp/external_arc/BUILD.gn
@@ -93,6 +93,7 @@
     "//testing/gmock",
     "//ui/aura:test_support",
     "//ui/base:test_support",
+    "//ui/compositor:test_support",
     "//ui/events:test_support",
     "//ui/message_center:test_support",
     "//ui/views:test_support",
diff --git a/ash/public/cpp/external_arc/message_center/DEPS b/ash/public/cpp/external_arc/message_center/DEPS
index 2afa256..eac8a73 100644
--- a/ash/public/cpp/external_arc/message_center/DEPS
+++ b/ash/public/cpp/external_arc/message_center/DEPS
@@ -5,6 +5,7 @@
   "+ash/components/arc/session/mojo_channel.h",
   "+ash/system/message_center/message_center_constants.h"
   "+ash/system/message_center/message_view_factory.h",
+  "+ash/system/notification_center/message_center_utils.h",
   "+components/arc/metrics/arc_metrics_constants.h",
   "+components/arc/test/connection_holder_util.h",
   "+components/arc/test/fake_notifications_instance.h",
diff --git a/ash/public/cpp/external_arc/message_center/arc_notification_content_view.cc b/ash/public/cpp/external_arc/message_center/arc_notification_content_view.cc
index ea88cfc..6589d642 100644
--- a/ash/public/cpp/external_arc/message_center/arc_notification_content_view.cc
+++ b/ash/public/cpp/external_arc/message_center/arc_notification_content_view.cc
@@ -425,10 +425,10 @@
   // of the hosting widget's focus chain. It could only be created when both
   // are present. Further, if we are being destroyed (|item_| is null), don't
   // create the control buttons.
-  if (!surface_ || !GetWidget() || !item_)
+  if (!surface_ || !GetWidget() || !item_ || control_buttons_view_.parent()) {
     return;
+  }
 
-  DCHECK(!control_buttons_view_.parent());
   DCHECK(!floating_control_buttons_widget_);
 
   views::Widget::InitParams params(views::Widget::InitParams::TYPE_CONTROL);
@@ -554,6 +554,23 @@
   UpdateMask(false /* force_update */);
 }
 
+void ArcNotificationContentView::EnsureSurfaceAttached() {
+  if (!surface_ || surface_->IsAttached()) {
+    return;
+  }
+  AttachSurface();
+}
+
+void ArcNotificationContentView::EnsureSurfaceDetached() {
+  if (!GetWidget()) {
+    return;
+  }
+
+  if (surface_ && surface_->IsAttached()) {
+    surface_->Detach();
+  }
+}
+
 void ArcNotificationContentView::ShowCopiedSurface() {
   if (!surface_)
     return;
diff --git a/ash/public/cpp/external_arc/message_center/arc_notification_content_view.h b/ash/public/cpp/external_arc/message_center/arc_notification_content_view.h
index 6650712f..16e93bf3 100644
--- a/ash/public/cpp/external_arc/message_center/arc_notification_content_view.h
+++ b/ash/public/cpp/external_arc/message_center/arc_notification_content_view.h
@@ -65,6 +65,8 @@
   void OnContainerAnimationStarted();
   void OnContainerAnimationEnded();
   void ActivateWidget(bool activate);
+  void EnsureSurfaceAttached();
+  void EnsureSurfaceDetached();
 
   bool slide_in_progress() const { return slide_in_progress_; }
 
diff --git a/ash/public/cpp/external_arc/message_center/arc_notification_view.cc b/ash/public/cpp/external_arc/message_center/arc_notification_view.cc
index 21796f61..b96ca3b2 100644
--- a/ash/public/cpp/external_arc/message_center/arc_notification_view.cc
+++ b/ash/public/cpp/external_arc/message_center/arc_notification_view.cc
@@ -6,11 +6,15 @@
 
 #include <algorithm>
 
+#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/external_arc/message_center/arc_notification_content_view.h"
 #include "ash/public/cpp/external_arc/message_center/arc_notification_item.h"
 #include "ash/public/cpp/message_center/arc_notification_constants.h"
 #include "ash/public/cpp/style/color_provider.h"
+#include "ash/style/typography.h"
 #include "ash/system/notification_center/message_center_constants.h"
+#include "ash/system/notification_center/message_center_utils.h"
+#include "ash/system/notification_center/notification_grouping_controller.h"
 #include "base/time/time.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "third_party/skia/include/core/SkColor.h"
@@ -26,11 +30,12 @@
 #include "ui/compositor/layer.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/message_center/public/cpp/message_center_constants.h"
-#include "ui/message_center/views/notification_background_painter.h"
-#include "ui/message_center/views/notification_control_buttons_view.h"
+#include "ui/message_center/views/notification_view_base.h"
+#include "ui/views/animation/animation_builder.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
 #include "ui/views/view_utils.h"
 
 DEFINE_UI_CLASS_PROPERTY_TYPE(ash::ArcNotificationView*)
@@ -40,6 +45,32 @@
 // Android side.
 constexpr base::TimeDelta kArcNotificationAnimationDuration =
     base::Milliseconds(360);
+
+// Create a view containing the title and message for the notification in a
+// single line. This is used when a grouped child notification is in a
+// collapsed parent notification.
+views::Builder<views::BoxLayoutView> CreateCollapsedSummaryBuilder(
+    const message_center::Notification& notification) {
+  return views::Builder<views::BoxLayoutView>()
+      .SetID(
+          message_center::NotificationViewBase::ViewId::kCollapsedSummaryView)
+      .SetInsideBorderInsets(ash::kGroupedCollapsedSummaryInsets)
+      .SetBetweenChildSpacing(ash::kGroupedCollapsedSummaryLabelSpacing)
+      .SetOrientation(views::BoxLayout::Orientation::kHorizontal)
+      .SetVisible(false)
+      .AddChild(views::Builder<views::Label>()
+                    .SetText(notification.title())
+                    .SetFontList(
+                        ash::TypographyProvider::Get()->ResolveTypographyToken(
+                            ash::TypographyToken::kCrosButton2)))
+      .AddChild(views::Builder<views::Label>()
+                    .SetText(notification.message())
+                    .SetTextContext(views::style::CONTEXT_DIALOG_BODY_TEXT)
+                    .SetTextStyle(views::style::STYLE_SECONDARY)
+                    .SetFontList(
+                        ash::TypographyProvider::Get()->ResolveTypographyToken(
+                            ash::TypographyToken::kCrosBody2)));
+}
 }  // namespace
 
 namespace ash {
@@ -71,6 +102,13 @@
 
   AddChildView(content_view_.get());
 
+  AddChildView(CreateCollapsedSummaryBuilder(notification)
+                   .CopyAddressTo(&collapsed_summary_view_)
+                   .Build());
+
+  SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kVertical, gfx::Insets()));
+
   if (shown_in_popup) {
     layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
     layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
@@ -116,6 +154,7 @@
 void ArcNotificationView::UpdateWithNotification(
     const message_center::Notification& notification) {
   message_center::MessageView::UpdateWithNotification(notification);
+  is_group_child_ = notification.group_child();
   content_view_->Update(notification);
 }
 
@@ -148,8 +187,13 @@
 }
 
 void ArcNotificationView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
-  // This data is never used since this view is never focused when the content
-  // view is focusable.
+  if (content_view_->IsFocusable()) {
+    // This data is never used since this view is never focused when the content
+    // view is focusable.
+    return;
+  }
+  CHECK(features::IsRenderArcNotificationsByChromeEnabled());
+  message_center::MessageView::GetAccessibleNodeData(node_data);
 }
 
 message_center::NotificationControlButtonsView*
@@ -221,19 +265,129 @@
   return kArcNotificationAnimationDuration;
 }
 
+void ArcNotificationView::SetGroupedChildExpanded(bool expanded) {
+  if (!collapsed_summary_view_) {
+    return;
+  }
+  CHECK(features::IsRenderArcNotificationsByChromeEnabled());
+
+  collapsed_summary_view_->SetVisible(!expanded);
+  content_view_->SetVisible(expanded);
+  if (expanded) {
+    content_view_->EnsureSurfaceAttached();
+  } else {
+    content_view_->EnsureSurfaceDetached();
+  }
+}
+
+void ArcNotificationView::AnimateGroupedChildExpandedCollapse(bool expanded) {
+  if (!collapsed_summary_view_) {
+    return;
+  }
+  CHECK(features::IsRenderArcNotificationsByChromeEnabled());
+
+  message_center_utils::InitLayerForAnimations(collapsed_summary_view_);
+  message_center_utils::InitLayerForAnimations(content_view_);
+  // Fade out `collapsed_summary_view_`, then fade in `content_view_` in
+  // expanded state and vice versa in collapsed state.
+  if (expanded) {
+    message_center_utils::FadeOutView(
+        collapsed_summary_view_,
+        base::BindRepeating(
+            [](base::WeakPtr<ash::ArcNotificationView> parent,
+               views::View* collapsed_summary_view) {
+              if (parent) {
+                collapsed_summary_view->layer()->SetOpacity(1.0f);
+                collapsed_summary_view->SetVisible(false);
+              }
+            },
+            weak_factory_.GetWeakPtr(), collapsed_summary_view_),
+        0, kCollapsedSummaryViewAnimationDurationMs, gfx::Tween::LINEAR,
+        "Arc.NotificationView.CollapsedSummaryView.FadeOut."
+        "AnimationSmoothness");
+    message_center_utils::FadeInView(
+        content_view_, kCollapsedSummaryViewAnimationDurationMs,
+        kChildMainViewFadeInAnimationDurationMs, gfx::Tween::LINEAR,
+        "Arc.NotificationView.ContentView.FadeIn.AnimationSmoothness");
+    return;
+  }
+
+  message_center_utils::FadeOutView(
+      content_view_,
+      base::BindRepeating(
+          [](base::WeakPtr<ash::ArcNotificationView> parent,
+             views::View* content_view) {
+            if (parent) {
+              content_view->layer()->SetOpacity(1.0f);
+              content_view->SetVisible(false);
+            }
+          },
+          weak_factory_.GetWeakPtr(), content_view_),
+      0, kChildMainViewFadeOutAnimationDurationMs, gfx::Tween::LINEAR,
+      "Arc.NotificationView.ContentView.FadeOut.AnimationSmoothness");
+  message_center_utils::FadeInView(
+      collapsed_summary_view_, kChildMainViewFadeOutAnimationDurationMs,
+      kCollapsedSummaryViewAnimationDurationMs, gfx::Tween::LINEAR,
+      "Arc.NotificationView.CollapsedSummaryView.FadeIn.AnimationSmoothness");
+}
+
+void ArcNotificationView::AnimateSingleToGroup(
+    const std::string& notification_id,
+    std::string parent_id) {
+  if (!collapsed_summary_view_) {
+    return;
+  }
+  CHECK(features::IsRenderArcNotificationsByChromeEnabled());
+
+  auto on_animation_ended = base::BindOnce(
+      [](base::WeakPtr<ash::ArcNotificationView> parent,
+         const std::string& notification_id, std::string parent_id) {
+        if (!parent) {
+          return;
+        }
+
+        auto* parent_notification =
+            message_center::MessageCenter::Get()->FindNotificationById(
+                parent_id);
+        auto* child_notification =
+            message_center::MessageCenter::Get()->FindNotificationById(
+                notification_id);
+        // The child and parent notifications are not guaranteed to exist.
+        //  If they were deleted avoid the animation cleanup.
+        if (!parent_notification || !child_notification) {
+          return;
+        }
+
+        auto* grouping_controller =
+            message_center_utils::GetGroupingControllerForNotificationView(
+                parent.get());
+        if (grouping_controller) {
+          grouping_controller
+              ->ConvertFromSingleToGroupNotificationAfterAnimation(
+                  notification_id, parent_id, parent_notification);
+        }
+        parent->DeprecatedLayoutImmediately();
+      },
+      weak_factory_.GetWeakPtr(), notification_id, parent_id);
+
+  std::pair<base::OnceClosure, base::OnceClosure> split =
+      base::SplitOnceCallback(std::move(on_animation_ended));
+
+  views::AnimationBuilder()
+      .SetPreemptionStrategy(
+          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
+      .OnEnded(std::move(split.first))
+      .OnAborted(std::move(split.second))
+      .Once()
+      .SetDuration(
+          base::Milliseconds(kConvertFromSingleToGroupFadeOutDurationMs));
+}
+
 void ArcNotificationView::OnSlideChanged(bool in_progress) {
   MessageView::OnSlideChanged(in_progress);
   content_view_->OnSlideChanged(in_progress);
 }
 
-gfx::Size ArcNotificationView::CalculatePreferredSize() const {
-  const gfx::Insets insets = GetInsets();
-  const int contents_width = kNotificationInMessageCenterWidth;
-  const int contents_height = content_view_->GetHeightForWidth(contents_width);
-  return gfx::Size(contents_width + insets.width(),
-                   contents_height + insets.height());
-}
-
 void ArcNotificationView::Layout(PassKey) {
   // Setting the bounds before calling the parent to prevent double Layout.
   content_view_->SetBoundsRect(GetContentsBounds());
diff --git a/ash/public/cpp/external_arc/message_center/arc_notification_view.h b/ash/public/cpp/external_arc/message_center/arc_notification_view.h
index 3437d85f..ac0b807a7 100644
--- a/ash/public/cpp/external_arc/message_center/arc_notification_view.h
+++ b/ash/public/cpp/external_arc/message_center/arc_notification_view.h
@@ -64,12 +64,15 @@
   void UpdateBackgroundPainter() override;
   base::TimeDelta GetBoundsAnimationDuration(
       const message_center::Notification&) const override;
+  void AnimateGroupedChildExpandedCollapse(bool expanded) override;
+  void AnimateSingleToGroup(const std::string& notification_id,
+                            std::string parent_id) override;
+  void SetGroupedChildExpanded(bool expanded) override;
 
   // views::SlideOutControllerDelegate:
   void OnSlideChanged(bool in_progress) override;
 
   // Overridden from views::View:
-  gfx::Size CalculatePreferredSize() const override;
   void Layout(PassKey) override;
   bool HasFocus() const override;
   void RequestFocus() override;
@@ -100,7 +103,11 @@
 
   const bool shown_in_popup_;
 
-  const bool is_group_child_;
+  bool is_group_child_;
+
+  raw_ptr<views::View> collapsed_summary_view_ = nullptr;
+
+  base::WeakPtrFactory<ArcNotificationView> weak_factory_{this};
 };
 
 }  // namespace ash
diff --git a/ash/public/cpp/external_arc/message_center/arc_notification_view_unittest.cc b/ash/public/cpp/external_arc/message_center/arc_notification_view_unittest.cc
index 95d2309..c2070e8f 100644
--- a/ash/public/cpp/external_arc/message_center/arc_notification_view_unittest.cc
+++ b/ash/public/cpp/external_arc/message_center/arc_notification_view_unittest.cc
@@ -19,6 +19,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/ime/dummy_text_input_client.h"
@@ -27,6 +28,8 @@
 #include "ui/base/ui_base_features.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
+#include "ui/compositor/test/layer_animation_stopped_waiter.h"
+#include "ui/compositor/test/test_utils.h"
 #include "ui/events/event.h"
 #include "ui/events/event_utils.h"
 #include "ui/events/test/event_generator.h"
@@ -193,6 +196,13 @@
   ArcNotificationContentView* content_view() {
     return notification_view_->content_view_;
   }
+
+  views::View* collapsed_summary_view() {
+    return notification_view_->collapsed_summary_view_;
+  }
+
+  bool IsGroupChild() { return notification_view_->is_group_child_; }
+
   views::Widget* widget() { return notification_view_->GetWidget(); }
   ArcNotificationView* notification_view() { return notification_view_; }
 
@@ -373,19 +383,19 @@
   // Default size.
   gfx::Size size = notification_view()->GetPreferredSize();
   size.Enlarge(0, -notification_view()->GetInsets().height());
-  EXPECT_EQ("344x100", size.ToString());
+  EXPECT_EQ("100x100", size.ToString());
 
   // Allow small notifications.
   content_view()->SetPreferredSize(gfx::Size(10, 10));
   size = notification_view()->GetPreferredSize();
   size.Enlarge(0, -notification_view()->GetInsets().height());
-  EXPECT_EQ("344x10", size.ToString());
+  EXPECT_EQ("10x10", size.ToString());
 
   // The long notification.
   content_view()->SetPreferredSize(gfx::Size(1000, 1000));
   size = notification_view()->GetPreferredSize();
   size.Enlarge(0, -notification_view()->GetInsets().height());
-  EXPECT_EQ("344x1000", size.ToString());
+  EXPECT_EQ("1000x1000", size.ToString());
 }
 
 class NotificationGestureUpdateTest : public ArcNotificationViewTest {
@@ -418,4 +428,101 @@
   EXPECT_TRUE(IsPopupRemovedAfterIdle(kDefaultNotificationId));
 }
 
+class ArcNotificationViewRenderByChromeEnabledTest
+    : public ArcNotificationViewTest {
+ public:
+  ArcNotificationViewRenderByChromeEnabledTest() = default;
+
+  ArcNotificationViewRenderByChromeEnabledTest(
+      const ArcNotificationViewRenderByChromeEnabledTest&) = delete;
+  ArcNotificationViewRenderByChromeEnabledTest& operator=(
+      const ArcNotificationViewRenderByChromeEnabledTest&) = delete;
+
+  ~ArcNotificationViewRenderByChromeEnabledTest() override = default;
+
+  // Overridden from ViewsTestBase:
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(
+        ash::features::kRenderArcNotificationsByChrome);
+
+    ArcNotificationViewTest::SetUp();
+  }
+
+  // Check that smoothness should be recorded after an animation is performed on
+  // a particular view.
+  // This is copied from
+  // ash/system/notification_center/views/ash_notification_view_unittest.cc.
+  void CheckSmoothnessRecorded(base::HistogramTester& histograms,
+                               views::View* view,
+                               const char* animation_histogram_name,
+                               int data_point_count = 1) {
+    ui::Compositor* compositor = view->layer()->GetCompositor();
+
+    ui::LayerAnimationStoppedWaiter animation_waiter;
+    animation_waiter.Wait(view->layer());
+
+    // Force frames and wait for all throughput trackers to be gone to allow
+    // animation throughput data to be passed from cc to ui.
+    while (compositor->has_throughput_trackers_for_testing()) {
+      compositor->ScheduleFullRedraw();
+      std::ignore = ui::WaitForNextFrameToBePresented(compositor,
+                                                      base::Milliseconds(500));
+    }
+
+    // Smoothness should be recorded.
+    histograms.ExpectTotalCount(animation_histogram_name, data_point_count);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// TODO(b/324991437)): the test is disabled due to recent flaky results.
+TEST_F(ArcNotificationViewRenderByChromeEnabledTest,
+       DISABLED_AnimateGroupedChildExpandedCollapseChanged) {
+  // Enable animations.
+  ui::ScopedAnimationDurationScaleMode duration(
+      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+
+  std::unique_ptr<Notification> notification = CreateSimpleNotification();
+  notification->SetGroupChild();
+  UpdateNotificationViews(*notification);
+  EXPECT_TRUE(IsGroupChild());
+  EXPECT_NE(nullptr, collapsed_summary_view());
+
+  // Expected histogram logged when expanding/collapsing.
+  notification_view()->AnimateGroupedChildExpandedCollapse(true);
+
+  base::HistogramTester tester_;
+  CheckSmoothnessRecorded(
+      tester_, collapsed_summary_view(),
+      "Arc.NotificationView.CollapsedSummaryView.FadeOut.AnimationSmoothness");
+
+  // Expected behavior in collapsed state.
+  notification_view()->AnimateGroupedChildExpandedCollapse(false);
+
+  CheckSmoothnessRecorded(
+      tester_, collapsed_summary_view(),
+      "Arc.NotificationView.CollapsedSummaryView.FadeIn.AnimationSmoothness");
+}
+
+TEST_F(ArcNotificationViewRenderByChromeEnabledTest,
+       GroupedChildExpandStateChanged) {
+  std::unique_ptr<Notification> notification = CreateSimpleNotification();
+  notification->SetGroupChild();
+  UpdateNotificationViews(*notification);
+  EXPECT_TRUE(IsGroupChild());
+  EXPECT_NE(nullptr, collapsed_summary_view());
+
+  // Expected behavior in expanded state.
+  notification_view()->SetGroupedChildExpanded(true);
+  EXPECT_TRUE(content_view()->GetVisible());
+  EXPECT_FALSE(collapsed_summary_view()->GetVisible());
+
+  // Expected behavior in collapsed state.
+  notification_view()->SetGroupedChildExpanded(false);
+  EXPECT_FALSE(content_view()->GetVisible());
+  EXPECT_TRUE(collapsed_summary_view()->GetVisible());
+}
+
 }  // namespace ash
diff --git a/ash/public/cpp/input_device_settings_controller.h b/ash/public/cpp/input_device_settings_controller.h
index 3b92a187..172f183 100644
--- a/ash/public/cpp/input_device_settings_controller.h
+++ b/ash/public/cpp/input_device_settings_controller.h
@@ -65,6 +65,10 @@
     virtual void OnCustomizablePenButtonPressed(
         const mojom::GraphicsTablet& mouse,
         const mojom::Button& button) {}
+
+    virtual void OnCustomizableMouseObservingStarted(
+        const mojom::Mouse& mouse) {}
+    virtual void OnCustomizableMouseObservingStopped() {}
   };
 
   static InputDeviceSettingsController* Get();
diff --git a/ash/public/cpp/picker/picker_search_result.cc b/ash/public/cpp/picker/picker_search_result.cc
index 9c081270..3f7f9d84 100644
--- a/ash/public/cpp/picker/picker_search_result.cc
+++ b/ash/public/cpp/picker/picker_search_result.cc
@@ -65,9 +65,10 @@
 }
 
 PickerSearchResult PickerSearchResult::BrowsingHistory(const GURL& url,
+                                                       std::u16string title,
                                                        ui::ImageModel icon) {
-  return PickerSearchResult(
-      BrowsingHistoryData{.url = url, .icon = std::move(icon)});
+  return PickerSearchResult(BrowsingHistoryData{
+      .url = url, .title = std::move(title), .icon = std::move(icon)});
 }
 
 bool PickerSearchResult::operator==(const PickerSearchResult&) const = default;
diff --git a/ash/public/cpp/picker/picker_search_result.h b/ash/public/cpp/picker/picker_search_result.h
index fbf168f..1a56526a 100644
--- a/ash/public/cpp/picker/picker_search_result.h
+++ b/ash/public/cpp/picker/picker_search_result.h
@@ -58,6 +58,7 @@
 
   struct BrowsingHistoryData {
     GURL url;
+    std::u16string title;
     ui::ImageModel icon;
 
     bool operator==(const BrowsingHistoryData&) const;
@@ -75,6 +76,7 @@
   ~PickerSearchResult();
 
   static PickerSearchResult BrowsingHistory(const GURL& url,
+                                            std::u16string title,
                                             ui::ImageModel icon);
   static PickerSearchResult Text(std::u16string_view text);
   static PickerSearchResult Emoji(std::u16string_view emoji);
diff --git a/ash/public/mojom/input_device_settings.mojom b/ash/public/mojom/input_device_settings.mojom
index 1f948b9c..9fd6213 100644
--- a/ash/public/mojom/input_device_settings.mojom
+++ b/ash/public/mojom/input_device_settings.mojom
@@ -407,4 +407,6 @@
   // Allow horizontal scroll wheel as well as normal mouse buttons. Identical to
   // kDisableKeyEventRewrites but permits horizontal scroll wheel buttons.
   kAllowHorizontalScrollWheelRewrites = 5,
+  // Allow tab events in addition to normal mouse events to be rewritten.
+  kAllowTabEventRewrites = 6,
 };
diff --git a/ash/shell.cc b/ash/shell.cc
index bbc68e8..f846d7b 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -229,6 +229,7 @@
 #include "ash/wm/window_animations.h"
 #include "ash/wm/window_cycle/window_cycle_controller.h"
 #include "ash/wm/window_properties.h"
+#include "ash/wm/window_restore/pine_controller.h"
 #include "ash/wm/window_restore/window_restore_controller.h"
 #include "ash/wm/window_util.h"
 #include "ash/wm/wm_shadow_controller_delegate.h"
@@ -974,6 +975,7 @@
   backlights_forced_off_setter_.reset();
 
   float_controller_.reset();
+  pine_controller_.reset();
   pip_controller_.reset();
   screen_pinning_controller_.reset();
 
@@ -1746,6 +1748,9 @@
   projector_controller_ = std::make_unique<ProjectorControllerImpl>();
 
   float_controller_ = std::make_unique<FloatController>();
+  if (features::IsForestFeatureEnabled()) {
+    pine_controller_ = std::make_unique<PineController>();
+  }
   pip_controller_ = std::make_unique<PipController>();
 
   multitask_menu_nudge_delegate_ =
diff --git a/ash/shell.h b/ash/shell.h
index c4e4bda7..b757654 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -152,7 +152,6 @@
 class EventClientImpl;
 class EventRewriterControllerImpl;
 class EventTransformationHandler;
-class WindowRestoreController;
 class FirmwareUpdateManager;
 class FirmwareUpdateNotificationController;
 class FloatController;
@@ -187,7 +186,6 @@
 class LoginUnlockThroughputRecorder;
 class MediaNotificationProvider;
 class TabClusterUIController;
-class TabletModeController;
 class MediaControllerImpl;
 class MessageCenterAshImpl;
 class MessageCenterController;
@@ -209,6 +207,7 @@
 class PeripheralBatteryNotifier;
 class PersistentWindowController;
 class PickerController;
+class PineController;
 class PipController;
 class PolicyRecommendationRestorer;
 class PostLoginGlanceablesMetricsRecorder;
@@ -252,6 +251,7 @@
 class SystemSoundsDelegate;
 class SystemTrayModel;
 class SystemTrayNotifier;
+class TabletModeController;
 class ToastManagerImpl;
 class ToplevelWindowEventHandler;
 class ClipboardHistoryControllerImpl;
@@ -264,6 +264,7 @@
 class WallpaperControllerImpl;
 class WindowBoundsTracker;
 class WindowCycleController;
+class WindowRestoreController;
 class WindowTilingController;
 class WindowTreeHostManager;
 class WmModeController;
@@ -667,6 +668,7 @@
     return peripheral_battery_listener_.get();
   }
   PickerController* picker_controller() { return picker_controller_.get(); }
+  PineController* pine_controller() { return pine_controller_.get(); }
   PipController* pip_controller() { return pip_controller_.get(); }
   PolicyRecommendationRestorer* policy_recommendation_restorer() {
     return policy_recommendation_restorer_.get();
@@ -1078,6 +1080,7 @@
       feature_discover_reporter_;
   std::unique_ptr<AshColorProvider> ash_color_provider_;
   std::unique_ptr<NightLightControllerImpl> night_light_controller_;
+  std::unique_ptr<PineController> pine_controller_;
   std::unique_ptr<PipController> pip_controller_;
   std::unique_ptr<PrivacyScreenController> privacy_screen_controller_;
   std::unique_ptr<PolicyRecommendationRestorer> policy_recommendation_restorer_;
diff --git a/ash/system/input_device_settings/input_device_duplicate_id_finder.cc b/ash/system/input_device_settings/input_device_duplicate_id_finder.cc
index df7311c3..ff37600 100644
--- a/ash/system/input_device_settings/input_device_duplicate_id_finder.cc
+++ b/ash/system/input_device_settings/input_device_duplicate_id_finder.cc
@@ -13,10 +13,8 @@
 template <typename T>
 void UpdateList(
     const std::vector<T>& devices,
-    base::flat_map<InputDeviceDuplicateIdFinder::VidPidIdentifier,
-                   base::flat_set<int>>& duplicate_ids_map,
-    base::flat_map<int, InputDeviceDuplicateIdFinder::VidPidIdentifier>&
-        vid_pid_map) {
+    base::flat_map<VendorProductId, base::flat_set<int>>& duplicate_ids_map,
+    base::flat_map<int, VendorProductId>& vid_pid_map) {
   for (const auto& device : devices) {
     duplicate_ids_map[{device.vendor_id, device.product_id}].insert(device.id);
     vid_pid_map[device.id] = {device.vendor_id, device.product_id};
@@ -25,14 +23,6 @@
 
 }  // namespace
 
-bool operator<(const InputDeviceDuplicateIdFinder::VidPidIdentifier& lhs,
-               const InputDeviceDuplicateIdFinder::VidPidIdentifier& rhs) {
-  if (lhs.vendor_id != rhs.vendor_id) {
-    return lhs.vendor_id < rhs.vendor_id;
-  }
-  return lhs.product_id < rhs.product_id;
-}
-
 InputDeviceDuplicateIdFinder::InputDeviceDuplicateIdFinder() {
   ui::DeviceDataManager::GetInstance()->AddObserver(this);
 }
@@ -55,6 +45,26 @@
   return &dedupes_iter->second;
 }
 
+const base::flat_set<int>* InputDeviceDuplicateIdFinder::GetDuplicateDeviceIds(
+    const VendorProductId& vid_pid) const {
+  auto dedupes_iter = duplicate_ids_map_.find(vid_pid);
+  if (dedupes_iter == duplicate_ids_map_.end()) {
+    return nullptr;
+  }
+
+  return &dedupes_iter->second;
+}
+
+std::optional<VendorProductId>
+InputDeviceDuplicateIdFinder::GetVendorProductIdForDevice(int id) const {
+  auto vid_pid_iter = vid_pid_map_.find(id);
+  if (vid_pid_iter == vid_pid_map_.end()) {
+    return std::nullopt;
+  }
+
+  return vid_pid_iter->second;
+}
+
 void InputDeviceDuplicateIdFinder::OnInputDeviceConfigurationChanged(
     uint8_t input_device_type) {
   duplicate_ids_map_.clear();
@@ -67,6 +77,8 @@
   RefreshTouchscreens();
   RefreshGraphicsTablets();
   RefreshUncategorized();
+
+  NotifyObservers();
 }
 
 void InputDeviceDuplicateIdFinder::OnDeviceListsComplete() {
@@ -80,6 +92,8 @@
   RefreshTouchscreens();
   RefreshGraphicsTablets();
   RefreshUncategorized();
+
+  NotifyObservers();
 }
 
 void InputDeviceDuplicateIdFinder::RefreshKeyboards() {
@@ -117,4 +131,18 @@
              duplicate_ids_map_, vid_pid_map_);
 }
 
+void InputDeviceDuplicateIdFinder::NotifyObservers() {
+  for (auto& observer : observers_) {
+    observer.OnDuplicateDevicesUpdated();
+  }
+}
+
+void InputDeviceDuplicateIdFinder::AddObserver(Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void InputDeviceDuplicateIdFinder::RemoveObserver(Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
 }  // namespace ash
diff --git a/ash/system/input_device_settings/input_device_duplicate_id_finder.h b/ash/system/input_device_settings/input_device_duplicate_id_finder.h
index 0bdad28..b0f40e3 100644
--- a/ash/system/input_device_settings/input_device_duplicate_id_finder.h
+++ b/ash/system/input_device_settings/input_device_duplicate_id_finder.h
@@ -6,8 +6,11 @@
 #define ASH_SYSTEM_INPUT_DEVICE_SETTINGS_INPUT_DEVICE_DUPLICATE_ID_FINDER_H_
 
 #include "ash/ash_export.h"
+#include "ash/system/input_device_settings/input_device_settings_utils.h"
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
+#include "base/observer_list.h"
+#include "base/observer_list_types.h"
 #include "ui/events/devices/input_device_event_observer.h"
 
 namespace ash {
@@ -17,11 +20,9 @@
 class ASH_EXPORT InputDeviceDuplicateIdFinder
     : public ui::InputDeviceEventObserver {
  public:
-  struct VidPidIdentifier {
-    uint16_t vendor_id, product_id;
-
-    friend bool operator<(const VidPidIdentifier& lhs,
-                          const VidPidIdentifier& rhs);
+  class Observer : public base::CheckedObserver {
+   public:
+    virtual void OnDuplicateDevicesUpdated() {}
   };
 
   InputDeviceDuplicateIdFinder();
@@ -36,6 +37,12 @@
 
   // Gets the set of duplicate device ids for the given `device_id`.
   const base::flat_set<int>* GetDuplicateDeviceIds(int id) const;
+  const base::flat_set<int>* GetDuplicateDeviceIds(
+      const VendorProductId& vid_pid) const;
+  std::optional<VendorProductId> GetVendorProductIdForDevice(int id) const;
+
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
 
  private:
   void RefreshKeyboards();
@@ -46,11 +53,15 @@
   void RefreshGraphicsTablets();
   void RefreshUncategorized();
 
+  void NotifyObservers();
+
   // Contains the list of device ids that map back to the given VID/PID
   // Identifier.
-  base::flat_map<VidPidIdentifier, base::flat_set<int>> duplicate_ids_map_;
+  base::flat_map<VendorProductId, base::flat_set<int>> duplicate_ids_map_;
   // Maps from id -> VID/PID identifier to make searching quicker.
-  base::flat_map<int, VidPidIdentifier> vid_pid_map_;
+  base::flat_map<int, VendorProductId> vid_pid_map_;
+
+  base::ObserverList<Observer> observers_;
 };
 
 }  // namespace ash
diff --git a/ash/system/input_device_settings/input_device_duplicate_id_finder_unittest.cc b/ash/system/input_device_settings/input_device_duplicate_id_finder_unittest.cc
index 5c8dc1c5..b2562b2 100644
--- a/ash/system/input_device_settings/input_device_duplicate_id_finder_unittest.cc
+++ b/ash/system/input_device_settings/input_device_duplicate_id_finder_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "ash/system/input_device_settings/input_device_duplicate_id_finder.h"
 
+#include <memory>
+
 #include "ash/test/ash_test_base.h"
 #include "ui/events/devices/device_data_manager_test_api.h"
 #include "ui/events/devices/input_device.h"
@@ -21,6 +23,17 @@
                          base::FilePath(), vendor, product, 0);
 }
 
+class TestObserver : public InputDeviceDuplicateIdFinder::Observer {
+ public:
+  // InputDeviceDuplicateIdFinder::Observer:
+  void OnDuplicateDevicesUpdated() override { num_times_updated_++; }
+
+  int num_times_updated() { return num_times_updated_; }
+
+ private:
+  int num_times_updated_ = 0;
+};
+
 }  // namespace
 
 class InputDeviceDuplicateIdFinderTest : public AshTestBase {
@@ -37,15 +50,20 @@
     AshTestBase::SetUp();
     // Duplicate ID finder should be created after the test base is setup.
     duplicate_id_finder_ = std::make_unique<InputDeviceDuplicateIdFinder>();
+    observer_ = std::make_unique<TestObserver>();
+    duplicate_id_finder_->AddObserver(observer_.get());
   }
 
   void TearDown() override {
+    duplicate_id_finder_->RemoveObserver(observer_.get());
+    observer_.reset();
     duplicate_id_finder_.reset();
     AshTestBase::TearDown();
   }
 
  protected:
   std::unique_ptr<InputDeviceDuplicateIdFinder> duplicate_id_finder_;
+  std::unique_ptr<TestObserver> observer_;
 };
 
 TEST_F(InputDeviceDuplicateIdFinderTest, DuplicateIdFinding) {
@@ -63,13 +81,18 @@
 
   ui::DeviceDataManagerTestApi().SetMouseDevices(
       {duplicate_1_1, duplicate_2_1, duplicate_3_1});
+  EXPECT_EQ(1, observer_->num_times_updated());
   ui::DeviceDataManagerTestApi().SetGraphicsTabletDevices(
       {duplicate_1_2, duplicate_2_2, duplicate_3_2});
+  EXPECT_EQ(2, observer_->num_times_updated());
   ui::DeviceDataManagerTestApi().SetUncategorizedDevices({duplicate_1_3});
+  EXPECT_EQ(3, observer_->num_times_updated());
   ui::DeviceDataManagerTestApi().SetKeyboardDevices(
       {ui::KeyboardDevice(duplicate_2_3)});
+  EXPECT_EQ(4, observer_->num_times_updated());
   ui::DeviceDataManagerTestApi().SetTouchpadDevices(
       {ui::TouchpadDevice(duplicate_3_3)});
+  EXPECT_EQ(5, observer_->num_times_updated());
 
   {
     auto* duplicate_group =
diff --git a/ash/system/input_device_settings/input_device_settings_controller_impl.cc b/ash/system/input_device_settings/input_device_settings_controller_impl.cc
index 8ce3767..023d6ac 100644
--- a/ash/system/input_device_settings/input_device_settings_controller_impl.cc
+++ b/ash/system/input_device_settings/input_device_settings_controller_impl.cc
@@ -1804,6 +1804,9 @@
       rewriter->StartObservingMouse(duplicate_id,
                                     mouse->customization_restriction);
     }
+    for (auto& observer : observers_) {
+      observer.OnCustomizableMouseObservingStarted(*mouse);
+    }
     return;
   }
 
@@ -1830,6 +1833,10 @@
           ->peripheral_customization_event_rewriter();
   CHECK(rewriter);
   rewriter->StopObserving();
+
+  for (auto& observer : observers_) {
+    observer.OnCustomizableMouseObservingStopped();
+  }
 }
 
 void InputDeviceSettingsControllerImpl::OnMouseButtonPressed(
diff --git a/ash/system/input_device_settings/input_device_settings_controller_unittest.cc b/ash/system/input_device_settings/input_device_settings_controller_unittest.cc
index b745f9a8..0e95cf82 100644
--- a/ash/system/input_device_settings/input_device_settings_controller_unittest.cc
+++ b/ash/system/input_device_settings/input_device_settings_controller_unittest.cc
@@ -387,6 +387,14 @@
     num_pen_buttons_pressed_++;
   }
 
+  void OnCustomizableMouseObservingStarted(const mojom::Mouse& mouse) override {
+    num_mouse_observing_started_++;
+  }
+
+  void OnCustomizableMouseObservingStopped() override {
+    num_mouse_observing_stopped_++;
+  }
+
   uint32_t num_keyboards_connected() { return num_keyboards_connected_; }
   uint32_t num_graphics_tablets_connected() {
     return num_graphics_tablets_connected_;
@@ -407,6 +415,12 @@
   uint32_t num_mouse_buttons_pressed() { return num_mouse_buttons_pressed_; }
   uint32_t num_pen_buttons_pressed() { return num_pen_buttons_pressed_; }
   uint32_t num_tablet_buttons_pressed() { return num_tablet_buttons_pressed_; }
+  uint32_t num_mouse_observing_started() {
+    return num_mouse_observing_started_;
+  }
+  uint32_t num_mouse_observing_stopped() {
+    return num_mouse_observing_stopped_;
+  }
 
  private:
   uint32_t num_keyboards_connected_ = 0;
@@ -419,6 +433,8 @@
   uint32_t num_mouse_buttons_pressed_ = 0;
   uint32_t num_tablet_buttons_pressed_ = 0;
   uint32_t num_pen_buttons_pressed_ = 0;
+  uint32_t num_mouse_observing_started_ = 0;
+  uint32_t num_mouse_observing_stopped_ = 0;
 };
 
 class InputDeviceSettingsControllerTest : public NoSessionAshTestBase {
@@ -1314,10 +1330,12 @@
   controller_->StartObservingButtons(kSampleMouseUsb.id);
   ASSERT_EQ(1u, rewriter->mice_to_observe().size());
   EXPECT_TRUE(rewriter->mice_to_observe().contains(kSampleMouseUsb.id));
+  EXPECT_EQ(1u, observer_->num_mouse_observing_started());
 
   controller_->StopObservingButtons();
   ASSERT_EQ(0u, rewriter->mice_to_observe().size());
   ASSERT_EQ(0u, rewriter->graphics_tablets_to_observe().size());
+  EXPECT_EQ(1u, observer_->num_mouse_observing_stopped());
 
   controller_->StartObservingButtons(kSampleGraphicsTablet.id);
   ASSERT_EQ(1u, rewriter->graphics_tablets_to_observe().size());
diff --git a/ash/system/input_device_settings/input_device_settings_metadata.cc b/ash/system/input_device_settings/input_device_settings_metadata.cc
index 8c81a33..d3ba1749 100644
--- a/ash/system/input_device_settings/input_device_settings_metadata.cc
+++ b/ash/system/input_device_settings/input_device_settings_metadata.cc
@@ -54,6 +54,10 @@
           {{0x046d, 0xb037},
            {mojom::CustomizationRestriction::kDisableKeyEventRewrites,
             mojom::MouseButtonConfig::kFiveKey}},
+          // Logitech MX Master 2S (Bluetooth)
+          {{0x046d, 0xb019},
+           {mojom::CustomizationRestriction::kAllowTabEventRewrites,
+            mojom::MouseButtonConfig::kNoConfig}},
       });
   return *mouse_metadata_list;
 }
diff --git a/ash/system/input_device_settings/input_device_settings_utils.cc b/ash/system/input_device_settings/input_device_settings_utils.cc
index 2e93e78..ab5177e 100644
--- a/ash/system/input_device_settings/input_device_settings_utils.cc
+++ b/ash/system/input_device_settings/input_device_settings_utils.cc
@@ -106,6 +106,11 @@
       return false;
     case mojom::CustomizationRestriction::kAllowHorizontalScrollWheelRewrites:
       return remapping.button->is_vkey();
+    case mojom::CustomizationRestriction::kAllowTabEventRewrites:
+      if (remapping.button->is_customizable_button()) {
+        return false;
+      }
+      return remapping.button->get_vkey() != ui::VKEY_TAB;
   }
 }
 
diff --git a/ash/system/notification_center/message_center_utils.h b/ash/system/notification_center/message_center_utils.h
index a6b3573d..e2a609f 100644
--- a/ash/system/notification_center/message_center_utils.h
+++ b/ash/system/notification_center/message_center_utils.h
@@ -70,28 +70,30 @@
 // Gets the grouping controller for the provided notification view. Each display
 // has it's own `NotificationGroupingController` so we need to look up the
 // display for the provide view first.
-NotificationGroupingController* GetGroupingControllerForNotificationView(
-    views::View* notification_view);
+NotificationGroupingController* ASH_EXPORT
+GetGroupingControllerForNotificationView(views::View* notification_view);
 
 // Utils for animation within a notification view.
 
 // Initializes the layer for the specified `view` for animations.
-void InitLayerForAnimations(views::View* view);
+void ASH_EXPORT InitLayerForAnimations(views::View* view);
 
 // Fade in animation using AnimationBuilder.
-void FadeInView(views::View* view,
-                int delay_in_ms,
-                int duration_in_ms,
-                gfx::Tween::Type tween_type = gfx::Tween::LINEAR,
-                const std::string& animation_histogram_name = std::string());
+void ASH_EXPORT
+FadeInView(views::View* view,
+           int delay_in_ms,
+           int duration_in_ms,
+           gfx::Tween::Type tween_type = gfx::Tween::LINEAR,
+           const std::string& animation_histogram_name = std::string());
 
 // Fade out animation using AnimationBuilder.
-void FadeOutView(views::View* view,
-                 base::OnceClosure on_animation_ended,
-                 int delay_in_ms,
-                 int duration_in_ms,
-                 gfx::Tween::Type tween_type = gfx::Tween::LINEAR,
-                 const std::string& animation_histogram_name = std::string());
+void ASH_EXPORT
+FadeOutView(views::View* view,
+            base::OnceClosure on_animation_ended,
+            int delay_in_ms,
+            int duration_in_ms,
+            gfx::Tween::Type tween_type = gfx::Tween::LINEAR,
+            const std::string& animation_histogram_name = std::string());
 
 // Slide out animation using AnimationBuilder.
 void SlideOutView(views::View* view,
diff --git a/ash/system/notification_center/views/ash_notification_view.cc b/ash/system/notification_center/views/ash_notification_view.cc
index 986d5ee5..12edbe2 100644
--- a/ash/system/notification_center/views/ash_notification_view.cc
+++ b/ash/system/notification_center/views/ash_notification_view.cc
@@ -1858,7 +1858,17 @@
     if (needs_layout()) {
       DeprecatedLayoutImmediately();
     }
-    DCHECK(!needs_layout());
+    auto* notification =
+        message_center::MessageCenter::Get()->FindNotificationById(
+            notification_id());
+
+    // When the notification is ArcNotification and not group parent, the view
+    // is rendered in Android then attached to message center. Ash does not
+    // directly control the layout so we should not check `needs_layout()`.
+    if (message_center_utils::IsAshNotification(notification) &&
+        !notification->group_parent()) {
+      DCHECK(!needs_layout());
+    }
 
     expand_button_->AnimateExpandCollapse();
   }
diff --git a/ash/system/unified/classroom_bubble_view_unittest.cc b/ash/system/unified/classroom_bubble_view_unittest.cc
index fd88371..2ab6e13f 100644
--- a/ash/system/unified/classroom_bubble_view_unittest.cc
+++ b/ash/system/unified/classroom_bubble_view_unittest.cc
@@ -405,6 +405,7 @@
 
 TEST_F(ClassroomBubbleStudentViewTest, OpensClassroomUrlForListItem) {
   base::UserActionTester user_actions;
+  base::HistogramTester histogram_tester;
   EXPECT_CALL(classroom_client_, GetCompletedStudentAssignments(_))
       .WillOnce([](GlanceablesClassroomClient::GetAssignmentsCallback cb) {
         std::move(cb).Run(/*success=*/true, CreateAssignments(1));
@@ -424,6 +425,14 @@
 
   EXPECT_EQ(1, user_actions.GetActionCount(
                    "Glanceables_Classroom_AssignmentPressed"));
+  histogram_tester.ExpectTotalCount(
+      "Ash.Glanceables.TimeManagement.Classroom.UserAction", 2);
+  histogram_tester.ExpectBucketCount(
+      "Ash.Glanceables.TimeManagement.Classroom.UserAction", 0,
+      /*expected_bucket_count=*/1);
+  histogram_tester.ExpectBucketCount(
+      "Ash.Glanceables.TimeManagement.Classroom.UserAction", 2,
+      /*expected_bucket_count=*/1);
 }
 
 TEST_F(ClassroomBubbleStudentViewTest, ShowsProgressBar) {
@@ -445,6 +454,7 @@
 
 TEST_F(ClassroomBubbleStudentViewTest, ClickHeaderIconButton) {
   base::UserActionTester user_actions;
+  base::HistogramTester histogram_tester;
 
   LeftClickOn(GetHeaderIcon());
   EXPECT_EQ(new_window_delegate_.GetLastOpenedUrl(),
@@ -452,6 +462,9 @@
 
   EXPECT_EQ(1, user_actions.GetActionCount(
                    "Glanceables_Classroom_HeaderIconPressed"));
+  histogram_tester.ExpectUniqueSample(
+      "Ash.Glanceables.TimeManagement.Classroom.UserAction", 1,
+      /*expected_bucket_count=*/1);
 }
 
 TEST_F(ClassroomBubbleStudentViewTest, ClickItemViewUserAction) {
@@ -481,6 +494,14 @@
   histogram_tester.ExpectUniqueSample(
       "Ash.Glanceables.Classroom.Student.ListSelected", 3,
       /*expected_bucket_count=*/1);
+  histogram_tester.ExpectTotalCount(
+      "Ash.Glanceables.TimeManagement.Classroom.UserAction", 3);
+  histogram_tester.ExpectBucketCount(
+      "Ash.Glanceables.TimeManagement.Classroom.UserAction", 0,
+      /*expected_bucket_count=*/1);
+  histogram_tester.ExpectBucketCount(
+      "Ash.Glanceables.TimeManagement.Classroom.UserAction", 2,
+      /*expected_bucket_count=*/2);
 }
 
 TEST_F(ClassroomBubbleStudentViewTest, ShowErrorMessageBubble) {
diff --git a/ash/system/unified/tasks_bubble_view_unittest.cc b/ash/system/unified/tasks_bubble_view_unittest.cc
index dc94500..9f4d0ed 100644
--- a/ash/system/unified/tasks_bubble_view_unittest.cc
+++ b/ash/system/unified/tasks_bubble_view_unittest.cc
@@ -18,6 +18,7 @@
 #include "ash/test/ash_test_base.h"
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/metrics/user_action_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/types/cxx23_to_underlying.h"
@@ -131,6 +132,7 @@
 };
 
 TEST_F(TasksBubbleViewTest, ShowTasksComboModel) {
+  base::HistogramTester histogram_tester;
   EXPECT_FALSE(IsMenuRunning());
   EXPECT_TRUE(GetComboBoxView()->GetVisible());
 
@@ -194,10 +196,13 @@
       GetComboBoxView()->MenuItemAtIndex(0)->GetBoundsInScreen()));
   EXPECT_TRUE(GetComboBoxView()->MenuView()->GetBoundsInScreen().Intersects(
       GetComboBoxView()->MenuItemAtIndex(5)->GetBoundsInScreen()));
+  histogram_tester.ExpectUniqueSample(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 0, 3);
 }
 
 TEST_F(TasksBubbleViewTest, MarkTaskAsComplete) {
   base::UserActionTester user_actions;
+  base::HistogramTester histogram_tester;
   EXPECT_EQ(GetTaskItemsContainerView()->children().size(), 2u);
 
   auto* const task_view = views::AsViewClass<GlanceablesTaskView>(
@@ -231,10 +236,18 @@
   EXPECT_EQ(0, tasks_client()->completed_task_count());
   tasks_client()->OnGlanceablesBubbleClosed();
   EXPECT_EQ(1, tasks_client()->completed_task_count());
+
+  histogram_tester.ExpectTotalCount(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 3);
+  histogram_tester.ExpectBucketCount(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 1, 2);
+  histogram_tester.ExpectBucketCount(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 2, 1);
 }
 
 TEST_F(TasksBubbleViewTest, ShowTasksWebUIFromFooterView) {
   base::UserActionTester user_actions;
+  base::HistogramTester histogram_tester;
   const auto* const see_all_button =
       views::AsViewClass<views::LabelButton>(GetListFooterView()->GetViewByID(
           base::to_underlying(GlanceablesViewId::kListFooterSeeAllButton)));
@@ -245,10 +258,13 @@
                    "Glanceables_Tasks_LaunchTasksApp_FooterButton"));
   EXPECT_EQ(0, user_actions.GetActionCount(
                    "Glanceables_Tasks_ActiveTaskListChanged"));
+  histogram_tester.ExpectUniqueSample(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 7, 1);
 }
 
 TEST_F(TasksBubbleViewTest, ShowTasksWebUIFromAddNewButton) {
   base::UserActionTester user_actions;
+  base::HistogramTester histogram_tester;
 
   ASSERT_EQ(GetComboBoxView()->GetTextForRow(2), u"Task List 3 Title (empty)");
   MenuSelectionAt(2);
@@ -265,9 +281,16 @@
       1, user_actions.GetActionCount("Glanceables_Tasks_AddTaskButtonShown"));
   EXPECT_EQ(1, user_actions.GetActionCount(
                    "Glanceables_Tasks_ActiveTaskListChanged"));
+  histogram_tester.ExpectTotalCount(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 2);
+  histogram_tester.ExpectBucketCount(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 0, 1);
+  histogram_tester.ExpectBucketCount(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 6, 1);
 }
 
 TEST_F(TasksBubbleViewTest, ShowTasksWebUIFromHeaderView) {
+  base::HistogramTester histogram_tester;
   base::UserActionTester user_actions;
   const auto* const header_icon_button = GetHeaderIconView();
   GestureTapOn(header_icon_button);
@@ -277,10 +300,13 @@
                    "Glanceables_Tasks_LaunchTasksApp_HeaderButton"));
   EXPECT_EQ(0, user_actions.GetActionCount(
                    "Glanceables_Tasks_ActiveTaskListChanged"));
+  histogram_tester.ExpectUniqueSample(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 5, 1);
 }
 
 TEST_F(TasksBubbleViewTest, ShowsAndHidesAddNewButton) {
   base::UserActionTester user_actions;
+  base::HistogramTester histogram_tester;
 
   // Shows items from the first / default task list.
   EXPECT_TRUE(GetTaskItemsContainerView()->GetVisible());
@@ -297,9 +323,13 @@
   EXPECT_FALSE(GetListFooterView()->GetVisible());
   EXPECT_EQ(
       1, user_actions.GetActionCount("Glanceables_Tasks_AddTaskButtonShown"));
+
+  histogram_tester.ExpectUniqueSample(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 0, 1);
 }
 
 TEST_F(TasksBubbleViewTest, ShowsProgressBarWhileLoadingTasks) {
+  base::HistogramTester histogram_tester;
   ASSERT_TRUE(GetProgressBar());
   ASSERT_TRUE(GetComboBoxView());
 
@@ -315,6 +345,8 @@
   // After replying to pending callbacks, the progress bar should become hidden.
   EXPECT_EQ(tasks_client()->RunPendingGetTasksCallbacks(), 1u);
   EXPECT_FALSE(GetProgressBar()->GetVisible());
+  histogram_tester.ExpectUniqueSample(
+      "Ash.Glanceables.TimeManagement.Tasks.UserAction", 0, 1);
 }
 
 }  // namespace ash
diff --git a/ash/webui/common/resources/sea_pen/sea_pen_actions.ts b/ash/webui/common/resources/sea_pen/sea_pen_actions.ts
index 41b5707..f811677 100644
--- a/ash/webui/common/resources/sea_pen/sea_pen_actions.ts
+++ b/ash/webui/common/resources/sea_pen/sea_pen_actions.ts
@@ -29,7 +29,6 @@
   SET_RECENT_SEA_PEN_IMAGES = 'set_recent_sea_pen_images',
   SET_RECENT_SEA_PEN_IMAGE_DATA = 'set_recent_sea_pen_image_data',
   SET_SELECTED_RECENT_SEA_PEN_IMAGE = 'set_selected_recent_sea_pen_image',
-  SET_SEA_PEN_ATTRIBUTION = 'set_sea_pen_attribution',
   SET_SHOULD_SHOW_SEA_PEN_TERMS_OF_SERVICE_DIALOG =
       'set_should_show_sea_pen_terms_of_service_dialog',
 }
@@ -40,8 +39,8 @@
     ClearSeaPenThumbnailsAction|EndSelectRecentSeaPenImageAction|
     SetThumbnailResponseStatusCodeAction|SetSeaPenThumbnailsAction|
     SetRecentSeaPenImagesAction|SetRecentSeaPenImageDataAction|
-    SetSelectedRecentSeaPenImageAction|SetSeaPenAttributionAction|
-    BeginSelectSeaPenThumbnailAction|EndSelectSeaPenThumbnailAction|
+    SetSelectedRecentSeaPenImageAction|BeginSelectSeaPenThumbnailAction|
+    EndSelectSeaPenThumbnailAction|
     SetShouldShowSeaPenTermsOfServiceDialogAction;
 
 export interface BeginSearchSeaPenThumbnailsAction extends Action {
@@ -199,17 +198,6 @@
   };
 }
 
-/** Sets the attribution for recent Sea Pen image. */
-export interface SetSeaPenAttributionAction extends Action {
-  name: SeaPenActionName.SET_SEA_PEN_ATTRIBUTION;
-}
-
-export function setSeaPenAttributionAction(): SetSeaPenAttributionAction {
-  return {
-    name: SeaPenActionName.SET_SEA_PEN_ATTRIBUTION,
-  };
-}
-
 /** Sets the Sea Pen thumbnail response status code. */
 export interface SetThumbnailResponseStatusCodeAction extends Action {
   name: SeaPenActionName.SET_THUMBNAIL_RESPONSE_STATUS_CODE;
diff --git a/ash/webui/common/resources/sea_pen/sea_pen_reducer.ts b/ash/webui/common/resources/sea_pen/sea_pen_reducer.ts
index 96dea1e..103eda5 100644
--- a/ash/webui/common/resources/sea_pen/sea_pen_reducer.ts
+++ b/ash/webui/common/resources/sea_pen/sea_pen_reducer.ts
@@ -84,35 +84,18 @@
     case SeaPenActionName.BEGIN_LOAD_SELECTED_RECENT_SEA_PEN_IMAGE:
       return {
         ...state,
-        selected: {
-          attribution: true,
-          image: true,
-        },
+        currentSelected: true,
       };
     case SeaPenActionName.BEGIN_SELECT_SEA_PEN_THUMBNAIL:
       return {
         ...state,
+        currentSelected: true,
         setImage: state.setImage + 1,
-        selected: {
-          attribution: true,
-          image: true,
-        },
       };
     case SeaPenActionName.SET_SELECTED_RECENT_SEA_PEN_IMAGE:
       return {
         ...state,
-        selected: {
-          ...state.selected,
-          image: false,
-        },
-      };
-    case SeaPenActionName.SET_SEA_PEN_ATTRIBUTION:
-      return {
-        ...state,
-        selected: {
-          ...state.selected,
-          attribution: false,
-        },
+        currentSelected: false,
       };
     default:
       return state;
diff --git a/ash/webui/common/resources/sea_pen/sea_pen_state.ts b/ash/webui/common/resources/sea_pen/sea_pen_state.ts
index a55ca32..d103b44 100644
--- a/ash/webui/common/resources/sea_pen/sea_pen_state.ts
+++ b/ash/webui/common/resources/sea_pen/sea_pen_state.ts
@@ -11,10 +11,7 @@
   recentImageData: Record<FilePath['path'], boolean>;
   recentImages: boolean;
   thumbnails: boolean;
-  selected: {
-    attribution: boolean,
-    image: boolean,
-  };
+  currentSelected: boolean;
   setImage: number;
 }
 
@@ -35,10 +32,7 @@
       recentImages: false,
       recentImageData: {},
       thumbnails: false,
-      selected: {
-        attribution: false,
-        image: false,
-      },
+      currentSelected: false,
       setImage: 0,
     },
     recentImageData: {},
diff --git a/ash/webui/personalization_app/resources/js/sea_pen_store_adapter.ts b/ash/webui/personalization_app/resources/js/sea_pen_store_adapter.ts
index 845b5f67..4e8f4cb6 100644
--- a/ash/webui/personalization_app/resources/js/sea_pen_store_adapter.ts
+++ b/ash/webui/personalization_app/resources/js/sea_pen_store_adapter.ts
@@ -2,12 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {SeaPenActions} from 'chrome://resources/ash/common/sea_pen/sea_pen_actions.js';
+import {SeaPenActionName, SeaPenActions} from 'chrome://resources/ash/common/sea_pen/sea_pen_actions.js';
 import {SeaPenState} from 'chrome://resources/ash/common/sea_pen/sea_pen_state.js';
 import {SeaPenStoreInterface, setSeaPenStore} from 'chrome://resources/ash/common/sea_pen/sea_pen_store.js';
 import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
 import {DeferredAction, StoreObserver} from 'chrome://resources/js/store.js';
 
+import {beginLoadSelectedImageAction} from './personalization_app.js';
 import {PersonalizationState} from './personalization_state.js';
 import {PersonalizationStore} from './personalization_store.js';
 
@@ -71,7 +72,19 @@
   }
 
   dispatch(action: SeaPenActions|null) {
-    return PersonalizationStore.getInstance().dispatch(action);
+    const store = PersonalizationStore.getInstance();
+    this.beginBatchUpdate();
+    // Dispatch additional actions to set proper wallpaper state when necessary.
+    switch (action?.name) {
+      // Dispatch action to set wallpaper loading state to true when Sea Pen
+      // image is selected.
+      case SeaPenActionName.BEGIN_SELECT_SEA_PEN_THUMBNAIL:
+      case SeaPenActionName.BEGIN_LOAD_SELECTED_RECENT_SEA_PEN_IMAGE:
+        store.dispatch(beginLoadSelectedImageAction());
+        break;
+    }
+    store.dispatch(action);
+    store.endBatchUpdate();
   }
 
   onStateChanged({wallpaper: {seaPen}}: PersonalizationState) {
diff --git a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_observer.ts b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_observer.ts
index 7d58ad0a..ae96171a 100644
--- a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_observer.ts
+++ b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_observer.ts
@@ -2,8 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {setSeaPenAttributionAction, setSelectedRecentSeaPenImageAction} from 'chrome://resources/ash/common/sea_pen/sea_pen_actions.js';
-import {assert} from 'chrome://resources/js/assert.js';
+import {setSelectedRecentSeaPenImageAction} from 'chrome://resources/ash/common/sea_pen/sea_pen_actions.js';
 
 import {CurrentAttribution, CurrentWallpaper, WallpaperObserverInterface, WallpaperObserverReceiver, WallpaperProviderInterface, WallpaperType} from '../../personalization_app.mojom-webui.js';
 import {PersonalizationStore} from '../personalization_store.js';
@@ -60,17 +59,6 @@
   onAttributionChanged(attribution: CurrentAttribution|null) {
     const store = PersonalizationStore.getInstance();
     store.dispatch(setAttributionAction(attribution));
-    // Set the Sea Pen wallpaper attribution loading state completed if it is
-    // loading.
-    if (store.data.wallpaper.seaPen.loading.selected.attribution) {
-      // Sea Pen currentSelected state should have been set before the
-      // attribution is notified, and match with the attribution key.
-      assert(attribution, 'attribution should be available');
-      assert(
-          store.data.wallpaper.seaPen.currentSelected === attribution!.key,
-          'attribution key should match currentSelected');
-      store.dispatch(setSeaPenAttributionAction());
-    }
   }
 
   onWallpaperChanged(currentWallpaper: CurrentWallpaper|null) {
diff --git a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_selected_element.ts b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_selected_element.ts
index 2217b2aa..54e6e99 100644
--- a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_selected_element.ts
+++ b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_selected_element.ts
@@ -204,16 +204,14 @@
     this.watch('error_', state => state.error);
     this.watch('attribution_', state => state.wallpaper.attribution);
     this.watch('image_', state => state.wallpaper.currentSelected);
-    this.watch('isLoading_', state => {
-      const isWallpaperLoading = state.wallpaper.loading.setImage > 0 ||
-          state.wallpaper.loading.selected.image ||
-          state.wallpaper.loading.selected.attribution ||
-          state.wallpaper.loading.refreshWallpaper;
-      const isSeaPenLoading = state.wallpaper.seaPen.loading.setImage > 0 ||
-          state.wallpaper.seaPen.loading.selected.image ||
-          state.wallpaper.seaPen.loading.selected.attribution;
-      return isWallpaperLoading || isSeaPenLoading;
-    });
+    this.watch(
+        'isLoading_',
+        state => state.wallpaper.loading.setImage > 0 ||
+            state.wallpaper.loading.selected.image ||
+            state.wallpaper.loading.selected.attribution ||
+            state.wallpaper.loading.refreshWallpaper ||
+            state.wallpaper.seaPen.loading.currentSelected ||
+            state.wallpaper.seaPen.loading.setImage > 0);
     this.watch('dailyRefreshState_', state => state.wallpaper.dailyRefresh);
     this.watch(
         'imagesByCollectionId_', state => state.wallpaper.backdrop.images);
diff --git a/ash/webui/print_preview_cros/BUILD.gn b/ash/webui/print_preview_cros/BUILD.gn
new file mode 100644
index 0000000..8fd6ae4
--- /dev/null
+++ b/ash/webui/print_preview_cros/BUILD.gn
@@ -0,0 +1,37 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/chromeos/ui_mode.gni")
+
+assert(is_chromeos_ash, "Print Preview CrOS is ash-chrome only")
+
+static_library("print_preview_cros") {
+  sources = [
+    "print_preview_cros_ui.cc",
+    "print_preview_cros_ui.h",
+  ]
+
+  deps = [
+    ":url_constants",
+    "//ash/constants:constants",
+    "//ash/webui/common:chrome_os_webui_config",
+    "//ash/webui/common:trusted_types_util",
+    "//ash/webui/print_preview_cros/resources:resources",
+    "//ash/webui/web_applications",
+    "//chromeos/strings/",
+    "//content/public/browser",
+    "//ui/base",
+    "//ui/resources",
+    "//ui/web_dialogs:web_dialogs",
+    "//ui/webui",
+  ]
+}
+
+# Separating out constants to be used test support.
+source_set("url_constants") {
+  sources = [
+    "url_constants.cc",
+    "url_constants.h",
+  ]
+}
diff --git a/ash/webui/print_preview_cros/DIR_METADATA b/ash/webui/print_preview_cros/DIR_METADATA
new file mode 100644
index 0000000..ebfefd1
--- /dev/null
+++ b/ash/webui/print_preview_cros/DIR_METADATA
@@ -0,0 +1,4 @@
+# ChromeOS > Software > System Services > Peripherals > Printing
+buganizer {
+  component_id: 1131981
+}
\ No newline at end of file
diff --git a/ash/webui/print_preview_cros/OWNERS b/ash/webui/print_preview_cros/OWNERS
new file mode 100644
index 0000000..149a448
--- /dev/null
+++ b/ash/webui/print_preview_cros/OWNERS
@@ -0,0 +1,7 @@
+# Primary OWNERS
+gavinwill@chromium.org
+jimmyxgong@chromium.org
+ashleydp@google.com
+
+# Backup OWNERS
+zentaro@chromium.org
diff --git a/ash/webui/print_preview_cros/README.md b/ash/webui/print_preview_cros/README.md
new file mode 100644
index 0000000..f2d7162
--- /dev/null
+++ b/ash/webui/print_preview_cros/README.md
@@ -0,0 +1,6 @@
+# CrOS Print Preview
+
+//ash/webui/print_preview_cros contains code that is Chrome OS-specific print
+preview experience.
+
+See b/323421684 for more information on the project.
\ No newline at end of file
diff --git a/ash/webui/print_preview_cros/print_preview_cros_ui.cc b/ash/webui/print_preview_cros/print_preview_cros_ui.cc
new file mode 100644
index 0000000..0d7e98f
--- /dev/null
+++ b/ash/webui/print_preview_cros/print_preview_cros_ui.cc
@@ -0,0 +1,70 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/webui/print_preview_cros/print_preview_cros_ui.h"
+
+#include "ash/constants/ash_features.h"
+#include "ash/webui/common/trusted_types_util.h"
+#include "ash/webui/grit/ash_print_preview_cros_app_resources.h"
+#include "ash/webui/grit/ash_print_preview_cros_app_resources_map.h"
+#include "ash/webui/print_preview_cros/url_constants.h"
+#include "base/containers/span.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "ui/resources/grit/webui_resources.h"
+
+namespace ash::printing::print_preview {
+
+namespace {
+
+void ConfigurePolicies(content::WebUIDataSource* source) {
+  // Enable common and test resources.
+  source->OverrideContentSecurityPolicy(
+      network::mojom::CSPDirectiveName::ScriptSrc,
+      "script-src chrome://resources chrome://webui-test 'self';");
+  // Configure ash specific trusted type polices.
+  ash::EnableTrustedTypesCSP(source);
+}
+
+// Setup app resources and ensure default resource is the app index page.
+void ConfigureResources(content::WebUIDataSource* source,
+                        int default_resource) {
+  const auto resources = base::make_span(kAshPrintPreviewCrosAppResources,
+                                         kAshPrintPreviewCrosAppResourcesSize);
+  source->AddResourcePaths(resources);
+  source->SetDefaultResource(default_resource);
+  source->AddResourcePath("", default_resource);
+}
+
+// Setup common test resources used in browser tests.
+void ConfigureTestResources(content::WebUIDataSource* source) {
+  source->AddResourcePath("test_loader.html", IDR_WEBUI_TEST_LOADER_HTML);
+  source->AddResourcePath("test_loader.js", IDR_WEBUI_JS_TEST_LOADER_JS);
+  source->AddResourcePath("test_loader_util.js",
+                          IDR_WEBUI_JS_TEST_LOADER_UTIL_JS);
+}
+
+}  // namespace
+
+bool PrintPreviewCrosUIConfig::IsWebUIEnabled(
+    content::BrowserContext* browser_context) {
+  return ChromeOSWebUIConfig::IsWebUIEnabled(browser_context) &&
+         ash::features::IsPrinterPreviewCrosAppEnabled();
+}
+
+PrintPreviewCrosUI::PrintPreviewCrosUI(content::WebUI* web_ui)
+    : MojoWebDialogUI(web_ui) {
+  // Configure resources.
+  auto* source = content::WebUIDataSource::CreateAndAdd(
+      web_ui->GetWebContents()->GetBrowserContext(),
+      ash::kChromeUIPrintPreviewCrosHost);
+  ConfigurePolicies(source);
+  ConfigureResources(source, IDR_ASH_PRINT_PREVIEW_CROS_APP_INDEX_HTML);
+  ConfigureTestResources(source);
+}
+
+PrintPreviewCrosUI::~PrintPreviewCrosUI() = default;
+
+}  // namespace ash::printing::print_preview
diff --git a/ash/webui/print_preview_cros/print_preview_cros_ui.h b/ash/webui/print_preview_cros/print_preview_cros_ui.h
new file mode 100644
index 0000000..6265655
--- /dev/null
+++ b/ash/webui/print_preview_cros/print_preview_cros_ui.h
@@ -0,0 +1,45 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_WEBUI_PRINT_PREVIEW_CROS_PRINT_PREVIEW_CROS_UI_H_
+#define ASH_WEBUI_PRINT_PREVIEW_CROS_PRINT_PREVIEW_CROS_UI_H_
+
+#include "ash/webui/common/chrome_os_webui_config.h"
+#include "ash/webui/print_preview_cros/url_constants.h"
+#include "content/public/common/url_constants.h"
+#include "ui/web_dialogs/web_dialog_ui.h"
+
+namespace content {
+class BrowserContext;
+class WebUI;
+}  // namespace content
+
+namespace ash::printing::print_preview {
+
+class PrintPreviewCrosUI;
+
+// The WebUI configuration for chrome://os-print.
+class PrintPreviewCrosUIConfig
+    : public ash::ChromeOSWebUIConfig<PrintPreviewCrosUI> {
+ public:
+  PrintPreviewCrosUIConfig()
+      : ChromeOSWebUIConfig(content::kChromeUIScheme,
+                            ash::kChromeUIPrintPreviewCrosHost) {}
+
+  // ash::ChromeOSWebUIConfig:
+  bool IsWebUIEnabled(content::BrowserContext* browser_context) override;
+};
+
+// The WebUI controller for chrome://os-print.
+class PrintPreviewCrosUI : public ui::MojoWebDialogUI {
+ public:
+  explicit PrintPreviewCrosUI(content::WebUI* web_ui);
+  PrintPreviewCrosUI(const PrintPreviewCrosUI&) = delete;
+  PrintPreviewCrosUI& operator=(const PrintPreviewCrosUI&) = delete;
+  ~PrintPreviewCrosUI() override;
+};
+
+}  // namespace ash::printing::print_preview
+
+#endif  // ASH_WEBUI_PRINT_PREVIEW_CROS_PRINT_PREVIEW_CROS_UI_H_
diff --git a/ash/webui/print_preview_cros/resources/BUILD.gn b/ash/webui/print_preview_cros/resources/BUILD.gn
new file mode 100644
index 0000000..5d13767
--- /dev/null
+++ b/ash/webui/print_preview_cros/resources/BUILD.gn
@@ -0,0 +1,29 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/chromeos/ui_mode.gni")
+import("//ui/webui/resources/tools/build_webui.gni")
+
+assert(is_chromeos_ash, "Print Preview CrOS is ChromeOS only")
+
+build_webui("build") {
+  static_files = [
+    "images/app_icon_192.png",
+    "index.html",
+  ]
+
+  web_component_files = [ "js/print_preview_cros_app.ts" ]
+
+  ts_composite = true
+
+  ts_deps = [
+    "//ash/webui/common/resources:build_ts",
+    "//third_party/polymer/v3_0:library",
+    "//ui/webui/resources/js:build_ts",
+    "//ui/webui/resources/mojo:build_ts",
+  ]
+
+  grd_prefix = "ash_print_preview_cros_app"
+  grit_output_dir = "$root_gen_dir/ash/webui"
+}
diff --git a/ash/webui/print_preview_cros/resources/images/app_icon_192.png b/ash/webui/print_preview_cros/resources/images/app_icon_192.png
new file mode 100644
index 0000000..1419460f
--- /dev/null
+++ b/ash/webui/print_preview_cros/resources/images/app_icon_192.png
Binary files differ
diff --git a/ash/webui/print_preview_cros/resources/index.html b/ash/webui/print_preview_cros/resources/index.html
new file mode 100644
index 0000000..661aefb4
--- /dev/null
+++ b/ash/webui/print_preview_cros/resources/index.html
@@ -0,0 +1,15 @@
+<!-- Copyright 2024 The Chromium Authors
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<!DOCTYPE html>
+<html dir="$i18n{textdirection}" lang="$i18n{language}">
+  <head>
+    <meta charset="utf-8">
+    <title>Print</title>
+  </head>
+  <body>
+    <print-preview-cros-app></print-preview-cros-app>
+    <script type="module" src="/js/print_preview_cros_app.js"></script>
+    </script>
+  </body>
+</html>
diff --git a/ash/webui/print_preview_cros/resources/js/print_preview_cros_app.html b/ash/webui/print_preview_cros/resources/js/print_preview_cros_app.html
new file mode 100644
index 0000000..71e6316
--- /dev/null
+++ b/ash/webui/print_preview_cros/resources/js/print_preview_cros_app.html
@@ -0,0 +1 @@
+<span>Print</span>
\ No newline at end of file
diff --git a/ash/webui/print_preview_cros/resources/js/print_preview_cros_app.ts b/ash/webui/print_preview_cros/resources/js/print_preview_cros_app.ts
new file mode 100644
index 0000000..c0ecce7e
--- /dev/null
+++ b/ash/webui/print_preview_cros/resources/js/print_preview_cros_app.ts
@@ -0,0 +1,32 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {getTemplate} from './print_preview_cros_app.html.js';
+
+/**
+ * @fileoverview
+ * 'print-preview-cros-app' is the main landing page for the print preview
+ * for ChromeOS app.
+ */
+
+export class PrintPreviewCrosAppElement extends PolymerElement {
+  static get is() {
+    return 'print-preview-cros-app' as const;
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    [PrintPreviewCrosAppElement.is]: PrintPreviewCrosAppElement;
+  }
+}
+
+customElements.define(
+    PrintPreviewCrosAppElement.is, PrintPreviewCrosAppElement);
diff --git a/ash/webui/print_preview_cros/url_constants.cc b/ash/webui/print_preview_cros/url_constants.cc
new file mode 100644
index 0000000..c2722b9
--- /dev/null
+++ b/ash/webui/print_preview_cros/url_constants.cc
@@ -0,0 +1,12 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/webui/print_preview_cros/url_constants.h"
+
+namespace ash {
+
+const char kChromeUIPrintPreviewCrosHost[] = "os-print";
+const char kChromeUIPrintPreviewCrosURL[] = "chrome://os-print";
+
+}  // namespace ash
diff --git a/ash/webui/print_preview_cros/url_constants.h b/ash/webui/print_preview_cros/url_constants.h
new file mode 100644
index 0000000..592209c
--- /dev/null
+++ b/ash/webui/print_preview_cros/url_constants.h
@@ -0,0 +1,15 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_WEBUI_PRINT_PREVIEW_CROS_URL_CONSTANTS_H_
+#define ASH_WEBUI_PRINT_PREVIEW_CROS_URL_CONSTANTS_H_
+
+namespace ash {
+
+extern const char kChromeUIPrintPreviewCrosHost[];
+extern const char kChromeUIPrintPreviewCrosURL[];
+
+}  // namespace ash
+
+#endif  // ASH_WEBUI_PRINT_PREVIEW_CROS_URL_CONSTANTS_H_
diff --git a/ash/webui/system_apps/public/system_web_app_type.h b/ash/webui/system_apps/public/system_web_app_type.h
index d1cf32e3..93b3c97 100644
--- a/ash/webui/system_apps/public/system_web_app_type.h
+++ b/ash/webui/system_apps/public/system_web_app_type.h
@@ -116,6 +116,11 @@
   // Contact: assistive-eng@google.com
   VC_BACKGROUND = 25,
 
+  // CrOS implementation of the print preview surface.
+  // Source: //ash/webui/print_preview_cros/
+  // Contact: cros-peripherals@google.com
+  PRINT_PREVIEW_CROS = 26,
+
   // When adding a new System App, remember to:
   //
   // 1. Add a corresponding histogram suffix in WebAppSystemAppInternalName
@@ -155,7 +160,7 @@
   //
   // 8. Have one of System Web App Platform owners review the CL.
   //    See: //ash/webui/PLATFORM_OWNERS
-  kMaxValue = VC_BACKGROUND,
+  kMaxValue = PRINT_PREVIEW_CROS,
 };
 
 }  // namespace ash
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index 4eed6c34..7188038 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -2249,6 +2249,13 @@
          container->save_desk_for_later_button()->GetVisible();
 }
 
+void OverviewGrid::OnTabletModeChanged() {
+  // We may not show virtual desk bar in clamshell mode such as in faster split
+  // screen setup session, and the desk bar will be created in tablet mode
+  // either. In this case, we may need to init the virtual desk bar.
+  MaybeInitDesksWidget();
+}
+
 size_t OverviewGrid::GetNumWindows() const {
   size_t size = 0u;
   for (const std::unique_ptr<OverviewItemBase>& item : item_list_) {
@@ -2311,8 +2318,7 @@
   if (state == SplitViewController::State::kBothSnapped ||
       unsnappable_window_activated ||
       (split_view_controller->InClamshellSplitViewMode() &&
-       overview_session_->IsEmpty() &&
-       !window_util::IsInFasterSplitScreenSetupSession(root_window_))) {
+       overview_session_->IsEmpty())) {
     overview_session_->RestoreWindowActivation(false);
     overview_controller->EndOverview(
         state == SplitViewController::State::kBothSnapped
diff --git a/ash/wm/overview/overview_grid.h b/ash/wm/overview/overview_grid.h
index b8d630f..b0094e37e 100644
--- a/ash/wm/overview/overview_grid.h
+++ b/ash/wm/overview/overview_grid.h
@@ -386,6 +386,10 @@
   bool IsSaveDeskAsTemplateButtonVisible() const;
   bool IsSaveDeskForLaterButtonVisible() const;
 
+  // Called by `OverviewSession` when tablet mode changes to update necessary UI
+  // if needed.
+  void OnTabletModeChanged();
+
   // This is different from `item_list_.size()` which contains the drop target
   // if it exists, and if two windows are in a snap group, they are a single
   // item.
diff --git a/ash/wm/overview/overview_session.cc b/ash/wm/overview/overview_session.cc
index 693222c..efa51e42 100644
--- a/ash/wm/overview/overview_session.cc
+++ b/ash/wm/overview/overview_session.cc
@@ -1592,6 +1592,10 @@
 }
 
 void OverviewSession::OnTabletModeChanged() {
+  for (auto& overview_grid : grid_list_) {
+    overview_grid->OnTabletModeChanged();
+  }
+
   DCHECK(saved_desk_util::ShouldShowSavedDesksButtons());
   DCHECK(saved_desk_presenter_);
   saved_desk_presenter_->UpdateUIForSavedDeskLibrary();
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index 6da0c73c..c6140e9c 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -9329,6 +9329,53 @@
   EXPECT_FALSE(split_view_controller()->InSplitViewMode());
 }
 
+// Tests that there will be no crash when dragging a snapped window in overview
+// toward the edge. In this case, the overview components will become too small
+// to meet the minimum requirement of the fundamental UI layers such as virtual
+// desk bar, shadow. See the regression behavior in http://b/324478757.
+TEST_F(SplitViewOverviewSessionInClamshellTest,
+       NoCrashWhenDraggingSnappedWindowToEdge) {
+  ui::ScopedAnimationDurationScaleMode animation_scale(
+      ui::ScopedAnimationDurationScaleMode::SLOW_DURATION);
+
+  // Create another desk to ensure the desk bar shows in overview.
+  auto* desks_controller = DesksController::Get();
+  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
+  ASSERT_EQ(2u, desks_controller->desks().size());
+
+  ToggleOverview();
+  WaitForOverviewEnterAnimation();
+  EXPECT_TRUE(IsInOverviewSession());
+  std::unique_ptr<aura::Window> window1(
+      CreateAppWindow(gfx::Rect(0, 0, 200, 100)));
+  std::unique_ptr<aura::Window> window2(
+      CreateAppWindow(gfx::Rect(100, 100, 200, 100)));
+  const WindowSnapWMEvent event(
+      WM_EVENT_SNAP_PRIMARY, chromeos::kDefaultSnapRatio,
+      WindowSnapActionSource::kSnapByWindowLayoutMenu);
+  auto* window1_state = WindowState::Get(window1.get());
+  window1_state->OnWMEvent(&event);
+  WaitForOverviewEntered();
+  EXPECT_TRUE(window1_state->IsSnapped());
+  EXPECT_TRUE(IsInOverviewSession());
+
+  auto* event_generator = GetEventGenerator();
+  event_generator->set_current_screen_location(
+      window1.get()->GetBoundsInScreen().right_center());
+  gfx::Point drag_end_point = GetWorkAreaInScreen(window1.get()).right_center();
+  drag_end_point.Offset(/*delta_x=*/-10, 0);
+  event_generator->PressLeftButton();
+  event_generator->MoveMouseTo(drag_end_point);
+  EXPECT_TRUE(IsInOverviewSession());
+  EXPECT_TRUE(WindowState::Get(window1.get())->is_dragged());
+
+  // Verify that shadow is applied on the overview item.
+  auto* overview_item2 = GetOverviewItemForWindow(window2.get());
+  const auto shadow_content_bounds =
+      overview_item2->get_shadow_content_bounds_for_testing();
+  EXPECT_FALSE(shadow_content_bounds.IsEmpty());
+}
+
 // Tests that when a split view window carries over to clamshell split view
 // while the divider is being dragged, the window resize is properly completed.
 TEST_F(SplitViewOverviewSessionInClamshellTest,
diff --git a/ash/wm/overview/overview_utils.cc b/ash/wm/overview/overview_utils.cc
index be5dfa31..1dc3cb36 100644
--- a/ash/wm/overview/overview_utils.cc
+++ b/ash/wm/overview/overview_utils.cc
@@ -201,8 +201,14 @@
   if (auto* split_view_overview_session =
           RootWindowController::ForWindow(target_root)
               ->split_view_overview_session()) {
-    gfx::Rect target_bounds_in_screen(
-        split_view_overview_session->window()->GetTargetBounds());
+    aura::Window* snapped_window = split_view_overview_session->window();
+    gfx::Rect target_bounds_in_screen(snapped_window->GetTargetBounds());
+    WindowState* window_state = WindowState::Get(snapped_window);
+    CHECK(window_state->IsSnapped());
+    opposite_position = window_state->GetStateType() ==
+                                chromeos::WindowStateType::kPrimarySnapped
+                            ? SnapPosition::kSecondary
+                            : SnapPosition::kPrimary;
     wm::ConvertRectToScreen(target_root, &target_bounds_in_screen);
     bounds.Subtract(target_bounds_in_screen);
   } else {
@@ -256,12 +262,8 @@
     }
   }
 
-  if (!opposite_position) {
-    // `opposite_position` is only non-empty if we are in split view state not
-    // `kNoSnap`.
-    return bounds;
-  }
-
+  // Clamp the bounds of the overview grid such that it doesn't go below 1/3 of
+  // the work area length
   const bool horizontal = IsLayoutHorizontal(target_root);
   const int min_length =
       (horizontal ? work_area.width() : work_area.height()) / 3;
@@ -270,13 +272,21 @@
   if (current_length > min_length)
     return bounds;
 
-  // Clamp bounds' length to the minimum length.
+  // Clamp bounds' corresponding length to the minimum length.
   if (horizontal)
     bounds.set_width(min_length);
   else
     bounds.set_height(min_length);
 
-  if (IsPhysicalLeftOrTop(*opposite_position, target_root)) {
+  // These changes below are crucial for better visualization and help
+  // preventing crashes when dragging the snapped window towards the edge. In
+  // this case, the overview components will become too small to allow any
+  // gradient painting on desk bar or applying shadows. Please ensure to go
+  // through the bounds update below when one window is snapped in overview both
+  // in clamshell and tablet mode. See the regression behavior in
+  // http://b/324478757.
+  if (opposite_position &&
+      IsPhysicalLeftOrTop(*opposite_position, target_root)) {
     // If we are shifting to the left or top we need to update the origin as
     // well.
     const int offset = min_length - current_length;
diff --git a/ash/wm/snap_group/snap_group_unittest.cc b/ash/wm/snap_group/snap_group_unittest.cc
index a9bcfabf..1244f1a 100644
--- a/ash/wm/snap_group/snap_group_unittest.cc
+++ b/ash/wm/snap_group/snap_group_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "ash/wm/snap_group/snap_group.h"
 
+#include <algorithm>
 #include <memory>
 #include <vector>
 
@@ -23,6 +24,7 @@
 #include "ash/system/toast/toast_manager_impl.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/test/ash_test_util.h"
+#include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/desks/desks_test_util.h"
 #include "ash/wm/desks/desks_util.h"
 #include "ash/wm/desks/legacy_desk_bar_view.h"
@@ -155,7 +157,7 @@
 SplitViewOverviewSession* VerifySplitViewOverviewSession(
     aura::Window* window,
     bool faster_split_screen_setup = true) {
-  auto* overview_controller = Shell::Get()->overview_controller();
+  auto* overview_controller = OverviewController::Get();
   EXPECT_TRUE(overview_controller->InOverviewSession());
   EXPECT_FALSE(
       overview_controller->overview_session()->IsWindowInOverview(window));
@@ -170,13 +172,25 @@
     expected_grid_bounds.Subtract(split_view_divider_bounds_in_screen());
   }
 
+  // Clamp the length on the side that can be shrunk by resizing to avoid going
+  // below the threshold i.e. 1/3 of the corresponding work area length.
+  const bool is_horizontal = IsLayoutHorizontal(Shell::GetPrimaryRootWindow());
+  const int min_length = (is_horizontal ? work_area_bounds().width()
+                                        : work_area_bounds().height()) /
+                         3;
+  if (is_horizontal) {
+    expected_grid_bounds.set_width(
+        std::max(expected_grid_bounds.width(), min_length));
+  } else {
+    expected_grid_bounds.set_height(
+        std::max(expected_grid_bounds.height(), min_length));
+  }
+
   if (!Shell::Get()->IsInTabletMode()) {
     EXPECT_EQ(expected_grid_bounds, GetOverviewGridBounds());
   }
 
   EXPECT_TRUE(expected_grid_bounds.Contains(GetOverviewGridBounds()));
-  // Hotseat may be on the bottom of the work area.
-  EXPECT_TRUE(work_area_bounds().Contains(expected_grid_bounds));
 
   if (!Shell::Get()->IsInTabletMode() && faster_split_screen_setup) {
     auto* overview_grid = GetOverviewGridForRoot(window->GetRootWindow());
@@ -339,13 +353,31 @@
   EXPECT_EQ(chromeos::WindowStateType::kPrimarySnapped,
             WindowState::Get(w3.get())->GetStateType());
 
-  // Enter overview normally. Test no widget.
+  // Enter overview normally. Test that no windows widget will not show.
   ToggleOverview();
   auto* overview_grid = GetOverviewGridForRoot(w1->GetRootWindow());
   EXPECT_FALSE(overview_grid->no_windows_widget());
   EXPECT_FALSE(overview_grid->faster_splitview_widget());
 }
 
+// Tests that on one window snapped, `SnapGroupController` starts
+// `SplitViewOverviewSession` (snap group creation session).
+TEST_F(FasterSplitScreenTest, CloseSnappedWindowEndsSplitViewOverviewSession) {
+  std::unique_ptr<aura::Window> w1(CreateAppWindow());
+  std::unique_ptr<aura::Window> w2(CreateAppWindow());
+
+  // Snap `w1` to the left. Test that we are in split view overview, excluding
+  // `w1` and taking half the screen.
+  SnapOneTestWindow(w1.get(),
+                    /*state_type=*/chromeos::WindowStateType::kPrimarySnapped,
+                    chromeos::kDefaultSnapRatio);
+  VerifySplitViewOverviewSession(w1.get());
+
+  // Close `w1`. Test that we end overview.
+  w1.reset();
+  EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
+}
+
 // Tests that faster split screen can only start with certain snap action
 // sources.
 TEST_F(FasterSplitScreenTest, SnapActionSourceLimitations) {
@@ -399,6 +431,7 @@
 
 TEST_F(FasterSplitScreenTest, EndSplitViewOverviewSession) {
   std::unique_ptr<aura::Window> w1(CreateAppWindow());
+  std::unique_ptr<aura::Window> w2(CreateAppWindow());
   SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kSecondarySnapped,
                     chromeos::kDefaultSnapRatio);
   VerifySplitViewOverviewSession(w1.get());
@@ -429,7 +462,9 @@
 }
 
 TEST_F(FasterSplitScreenTest, ResizeSplitViewOverviewAndWindow) {
+  UpdateDisplay("900x600");
   std::unique_ptr<aura::Window> w1(CreateAppWindow());
+  std::unique_ptr<aura::Window> w2(CreateAppWindow());
   SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped,
                     chromeos::kDefaultSnapRatio);
   VerifySplitViewOverviewSession(w1.get());
@@ -442,25 +477,28 @@
   generator->set_current_screen_location(start_point);
 
   // Resize to less than 1/3. Test we don't end overview.
-  int x = 200;
-  ASSERT_LT(x, work_area_bounds().width() * chromeos::kOneThirdSnapRatio);
-  generator->DragMouseTo(gfx::Point(x, start_point.y()));
+  const auto drag_point1 =
+      gfx::Point(work_area_bounds().width() * chromeos::kOneThirdSnapRatio - 10,
+                 start_point.y());
+  generator->DragMouseTo(drag_point1);
   gfx::Rect expected_window_bounds(initial_bounds);
-  expected_window_bounds.set_width(x);
+  expected_window_bounds.set_width(drag_point1.x());
   EXPECT_EQ(expected_window_bounds, w1->GetBoundsInScreen());
   VerifySplitViewOverviewSession(w1.get());
 
   // Resize to greater than 2/3. Test we don't end overview.
-  x = 600;
-  ASSERT_GT(x, work_area_bounds().width() * chromeos::kTwoThirdSnapRatio);
-  generator->DragMouseTo(gfx::Point(x, start_point.y()));
-  expected_window_bounds.set_width(x);
+  const auto drag_point2 =
+      gfx::Point(work_area_bounds().width() * chromeos::kTwoThirdSnapRatio + 10,
+                 start_point.y());
+  generator->DragMouseTo(drag_point2);
+  expected_window_bounds.set_width(drag_point2.x());
   EXPECT_EQ(expected_window_bounds, w1->GetBoundsInScreen());
   VerifySplitViewOverviewSession(w1.get());
 }
 
 TEST_F(FasterSplitScreenTest, ResizeAndAutoSnap) {
-  std::unique_ptr<aura::Window> w1(CreateTestWindow());
+  std::unique_ptr<aura::Window> w1(CreateAppWindow());
+  std::unique_ptr<aura::Window> w2(CreateAppWindow());
   SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped,
                     chromeos::kDefaultSnapRatio);
   const gfx::Rect initial_bounds(w1->GetBoundsInScreen());
@@ -481,11 +519,11 @@
   expected_grid_bounds.Subtract(w1->GetBoundsInScreen());
   EXPECT_EQ(expected_grid_bounds, GetOverviewGridBounds());
 
-  // Select a window to auto snap. Test it snaps to the correct ratio.
-  std::unique_ptr<aura::Window> w2(CreateTestWindow());
+  // Create a window and test that it auto snaps.
+  std::unique_ptr<aura::Window> w3(CreateAppWindow());
   EXPECT_EQ(chromeos::WindowStateType::kSecondarySnapped,
-            WindowState::Get(w2.get())->GetStateType());
-  EXPECT_EQ(expected_grid_bounds, w2->GetBoundsInScreen());
+            WindowState::Get(w3.get())->GetStateType());
+  EXPECT_EQ(expected_grid_bounds, w3->GetBoundsInScreen());
 }
 
 TEST_F(FasterSplitScreenTest, DragToPartialOverview) {
@@ -759,6 +797,41 @@
   EXPECT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
 }
 
+// Tests that partial overview will not be triggered if the window to be snapped
+// is only one window for the active desk and on the current display.
+TEST_F(FasterSplitScreenTest, DontStartPartiOverviewIfThereIsOnlyOneWindow) {
+  UpdateDisplay("900x600, 901+0-900x600");
+  ASSERT_EQ(Shell::GetAllRootWindows().size(), 2u);
+
+  DesksController* desks_controller = DesksController::Get();
+  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
+  ASSERT_EQ(2u, desks_controller->desks().size());
+  Desk* desk0 = desks_controller->GetDeskAtIndex(0);
+  Desk* desk1 = desks_controller->GetDeskAtIndex(1);
+
+  std::unique_ptr<aura::Window> w1(
+      CreateAppWindow(gfx::Rect(10, 20, 200, 100)));
+
+  // Create the 2nd window and move it to another desk.
+  std::unique_ptr<aura::Window> w2(
+      CreateAppWindow(gfx::Rect(100, 20, 200, 100)));
+  ASSERT_EQ(desks_util::GetDeskForContext(w1.get()), desk0);
+  ASSERT_EQ(desks_util::GetDeskForContext(w2.get()), desk0);
+  desks_controller->MoveWindowFromActiveDeskTo(
+      w2.get(), desk1, w2->GetRootWindow(),
+      DesksMoveWindowFromActiveDeskSource::kShortcut);
+  ASSERT_EQ(desks_util::GetDeskForContext(w2.get()), desk1);
+
+  // Create the 3rd window on the 2nd display.
+  std::unique_ptr<aura::Window> w3(
+      CreateAppWindow(gfx::Rect(1000, 20, 200, 100)));
+
+  // Verify that snapping `w1` won't trigger partial overview.
+  SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped,
+                    chromeos::kDefaultSnapRatio);
+  EXPECT_FALSE(IsInOverviewSession());
+}
+
 // Tests that only when there is a non-occluded window snapped on the opposite
 // side should we skip showing partial overview on window snapped. This test
 // focuses on the window layout setup **with** intersections.
@@ -888,18 +961,21 @@
   display::test::DisplayManagerTestApi display_manager_test(display_manager());
 
   // Snap `window` on the second display. Test its bounds are updated.
-  std::unique_ptr<aura::Window> window(
+  std::unique_ptr<aura::Window> window1(
       CreateTestWindowInShellWithBounds(gfx::Rect(900, 0, 100, 100)));
-  SnapOneTestWindow(window.get(), chromeos::WindowStateType::kPrimarySnapped,
+  std::unique_ptr<aura::Window> window2(
+      CreateTestWindowInShellWithBounds(gfx::Rect(1000, 0, 100, 100)));
+  SnapOneTestWindow(window1.get(), chromeos::WindowStateType::kPrimarySnapped,
                     chromeos::kDefaultSnapRatio);
-  ASSERT_EQ(
-      display_manager_test.GetSecondaryDisplay().id(),
-      display::Screen::GetScreen()->GetDisplayNearestWindow(window.get()).id());
+  ASSERT_EQ(display_manager_test.GetSecondaryDisplay().id(),
+            display::Screen::GetScreen()
+                ->GetDisplayNearestWindow(window1.get())
+                .id());
   const gfx::Rect work_area(
       display_manager_test.GetSecondaryDisplay().work_area());
   EXPECT_EQ(gfx::Rect(800, 0, work_area.width() / 2, work_area.height()),
-            window->GetBoundsInScreen());
-  VerifySplitViewOverviewSession(window.get());
+            window1->GetBoundsInScreen());
+  VerifySplitViewOverviewSession(window1.get());
 
   // Disconnect the second display. Test no crash.
   UpdateDisplay("800x600");
@@ -967,13 +1043,15 @@
 // Tests we start partial overview if there's an opposite snapped window on
 // another display.
 TEST_F(FasterSplitScreenTest, OppositeSnappedWindowOnOtherDisplay) {
-  UpdateDisplay("800x600,1000x600");
+  UpdateDisplay("800x600,801+0-800x600");
 
   // Create 3 test windows, with `w3` on display 2.
   std::unique_ptr<aura::Window> w1(CreateAppWindow());
   std::unique_ptr<aura::Window> w2(CreateAppWindow());
   std::unique_ptr<aura::Window> w3(
       CreateAppWindow(gfx::Rect(900, 0, 100, 100)));
+  std::unique_ptr<aura::Window> w4(
+      CreateAppWindow(gfx::Rect(1000, 0, 100, 100)));
 
   // Snap `w1` to primary on display 1.
   SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped,
@@ -1008,26 +1086,27 @@
 // preserve the snap ratio.
 TEST_F(FasterSplitScreenTest, WindowBoundsRefreshedOnDisplayChanges) {
   UpdateDisplay("900x600");
-  std::unique_ptr<aura::Window> window(CreateAppWindow());
-  SnapOneTestWindow(window.get(), chromeos::WindowStateType::kPrimarySnapped,
+  std::unique_ptr<aura::Window> window1(CreateAppWindow());
+  std::unique_ptr<aura::Window> window2(CreateAppWindow());
+  SnapOneTestWindow(window1.get(), chromeos::WindowStateType::kPrimarySnapped,
                     chromeos::kTwoThirdSnapRatio,
                     WindowSnapActionSource::kSnapByWindowLayoutMenu);
-  VerifySplitViewOverviewSession(window.get());
-  ASSERT_EQ(WindowState::Get(window.get())->snap_ratio(),
+  VerifySplitViewOverviewSession(window1.get());
+  ASSERT_EQ(WindowState::Get(window1.get())->snap_ratio(),
             chromeos::kTwoThirdSnapRatio);
   const auto work_area_bounds_1 = work_area_bounds();
   ASSERT_EQ(
-      window->GetBoundsInScreen(),
+      window1->GetBoundsInScreen(),
       gfx::Rect(0, 0, work_area_bounds_1.width() * chromeos::kTwoThirdSnapRatio,
                 work_area_bounds_1.height()));
 
   UpdateDisplay("1200x600");
-  VerifySplitViewOverviewSession(window.get());
-  EXPECT_EQ(WindowState::Get(window.get())->snap_ratio(),
+  VerifySplitViewOverviewSession(window1.get());
+  EXPECT_EQ(WindowState::Get(window1.get())->snap_ratio(),
             chromeos::kTwoThirdSnapRatio);
   const auto work_area_bounds_2 = work_area_bounds();
   EXPECT_EQ(
-      window->GetBoundsInScreen(),
+      window1->GetBoundsInScreen(),
       gfx::Rect(0, 0, work_area_bounds_2.width() * chromeos::kTwoThirdSnapRatio,
                 work_area_bounds_2.height()));
 }
@@ -1035,16 +1114,17 @@
 // Test to verify that there will be no crash when dragging the snapped window
 // out without resizing the window see crash in b/321111182.
 TEST_F(FasterSplitScreenTest, NoCrashWhenDraggingTheSnappedWindow) {
-  std::unique_ptr<aura::Window> window(CreateAppWindow());
-  SnapOneTestWindow(window.get(), chromeos::WindowStateType::kPrimarySnapped,
+  std::unique_ptr<aura::Window> window1(CreateAppWindow());
+  std::unique_ptr<aura::Window> window2(CreateAppWindow());
+  SnapOneTestWindow(window1.get(), chromeos::WindowStateType::kPrimarySnapped,
                     chromeos::kTwoThirdSnapRatio,
                     WindowSnapActionSource::kSnapByWindowLayoutMenu);
-  VerifySplitViewOverviewSession(window.get());
+  VerifySplitViewOverviewSession(window1.get());
 
   std::unique_ptr<WindowResizer> resizer(CreateWindowResizer(
-      window.get(), gfx::PointF(), HTCAPTION, wm::WINDOW_MOVE_SOURCE_MOUSE));
+      window1.get(), gfx::PointF(), HTCAPTION, wm::WINDOW_MOVE_SOURCE_MOUSE));
   resizer->Drag(gfx::PointF(500, 100), /*event_flags=*/0);
-  WindowState* window_state = WindowState::Get(window.get());
+  WindowState* window_state = WindowState::Get(window1.get());
   EXPECT_TRUE(window_state->is_dragged());
   resizer->CompleteDrag();
   EXPECT_FALSE(window_state->IsSnapped());
@@ -1106,7 +1186,8 @@
 // Verifies that there will be no crash when transitioning the
 // `SplitViewOverviewSession` between clamshell and tablet mode.
 TEST_F(FasterSplitScreenTest, ClamshellTabletTransitionOneSnappedWindow) {
-  std::unique_ptr<aura::Window> w1(CreateTestWindow());
+  std::unique_ptr<aura::Window> w1(CreateAppWindow());
+  std::unique_ptr<aura::Window> w2(CreateAppWindow());
   SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped,
                     chromeos::kDefaultSnapRatio);
   VerifySplitViewOverviewSession(w1.get());
@@ -1482,10 +1563,11 @@
       kOverviewStartActionHistogram,
       OverviewStartAction::kFasterSplitScreenSetup,
       /*expected_count=*/0);
-  std::unique_ptr<aura::Window> window(CreateAppWindow());
-  SnapOneTestWindow(window.get(), chromeos::WindowStateType::kPrimarySnapped,
+  std::unique_ptr<aura::Window> window1(CreateAppWindow());
+  std::unique_ptr<aura::Window> window2(CreateAppWindow());
+  SnapOneTestWindow(window1.get(), chromeos::WindowStateType::kPrimarySnapped,
                     chromeos::kDefaultSnapRatio);
-  VerifySplitViewOverviewSession(window.get());
+  VerifySplitViewOverviewSession(window1.get());
   histogram_tester_.ExpectBucketCount(
       kOverviewStartActionHistogram,
       OverviewStartAction::kFasterSplitScreenSetup,
@@ -1496,15 +1578,51 @@
 // setup session.
 TEST_F(FasterSplitScreenTest, A11yAlertOnEnteringFaterSplitScreenSetup) {
   TestAccessibilityControllerClient client;
-  std::unique_ptr<aura::Window> window(CreateAppWindow());
+  std::unique_ptr<aura::Window> window1(CreateAppWindow());
+  std::unique_ptr<aura::Window> window2(CreateAppWindow());
   EXPECT_NE(AccessibilityAlert::FASTER_SPLIT_SCREEN_SETUP,
             client.last_a11y_alert());
-  SnapOneTestWindow(window.get(), chromeos::WindowStateType::kPrimarySnapped,
+  SnapOneTestWindow(window1.get(), chromeos::WindowStateType::kPrimarySnapped,
                     chromeos::kDefaultSnapRatio);
   EXPECT_EQ(AccessibilityAlert::FASTER_SPLIT_SCREEN_SETUP,
             client.last_a11y_alert());
 }
 
+// Tests that there will be no crash when dragging a snapped window in overview
+// toward the edge. In this case, the overview components will become too small
+// to meet the minimum requirement of the fundamental UI layer such as shadow.
+// See the regression behavior in http://b/324478757.
+TEST_F(FasterSplitScreenTest, NoCrashWhenDraggingSnappedWindowToEdge) {
+  std::unique_ptr<aura::Window> window1(
+      CreateAppWindow(gfx::Rect(0, 0, 200, 100)));
+  std::unique_ptr<aura::Window> window2(
+      CreateAppWindow(gfx::Rect(100, 100, 200, 100)));
+  SnapOneTestWindow(window1.get(), chromeos::WindowStateType::kPrimarySnapped,
+                    chromeos::kDefaultSnapRatio,
+                    WindowSnapActionSource::kSnapByWindowLayoutMenu);
+  WaitForOverviewEntered();
+  VerifySplitViewOverviewSession(window1.get());
+
+  // Drag the snapped window towards the edge of the work area and verify that
+  // there is no crash.
+  auto* event_generator = GetEventGenerator();
+  event_generator->set_current_screen_location(
+      window1.get()->GetBoundsInScreen().right_center());
+  gfx::Point drag_end_point = work_area_bounds().right_center();
+  drag_end_point.Offset(/*delta_x=*/-10, 0);
+  event_generator->PressLeftButton();
+  event_generator->MoveMouseTo(drag_end_point);
+
+  // Verify that shadow exists for overview item.
+  auto* overview_item2 = GetOverviewItemForWindow(window2.get());
+  const auto shadow_content_bounds =
+      overview_item2->get_shadow_content_bounds_for_testing();
+  EXPECT_FALSE(shadow_content_bounds.IsEmpty());
+
+  VerifySplitViewOverviewSession(window1.get());
+  EXPECT_TRUE(WindowState::Get(window1.get())->is_dragged());
+}
+
 // -----------------------------------------------------------------------------
 // SnapGroupTest:
 
@@ -1729,32 +1847,6 @@
   EXPECT_FALSE(split_view_controller()->InSplitViewMode());
 }
 
-// Tests that on one window snapped, `SnapGroupController` starts
-// `SplitViewOverviewSession` (snap group creation session).
-TEST_F(SnapGroupTest, SnapOneTestWindowStartsOverview) {
-  std::unique_ptr<aura::Window> w(CreateAppWindow());
-  // Snap `w` to the left. Test that we are in split view overview, excluding
-  // `w` and taking half the screen.
-  SnapOneTestWindow(w.get(),
-                    /*state_type=*/chromeos::WindowStateType::kPrimarySnapped);
-  VerifySplitViewOverviewSession(w.get());
-
-  // Snap `w` to the left again. Test we are still in split view overview.
-  SnapOneTestWindow(w.get(),
-                    /*state_type=*/chromeos::WindowStateType::kPrimarySnapped);
-  VerifySplitViewOverviewSession(w.get());
-
-  // Snap `w` to the right. Test we are still in split view overview.
-  SnapOneTestWindow(
-      w.get(),
-      /*state_type=*/chromeos::WindowStateType::kSecondarySnapped);
-  VerifySplitViewOverviewSession(w.get());
-
-  // Close `w`. Test that we end overview.
-  w.reset();
-  EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
-}
-
 // Tests that when there is one snapped window and overview open, creating a new
 // window, i.e. by clicking the shelf icon, will auto-snap it.
 // TODO(michelefan): Re-enable this test after the divider refactor work is
@@ -1845,24 +1937,27 @@
 // Tests that removing a display during split view overview session doesn't
 // crash.
 TEST_F(SnapGroupTest, RemoveDisplay) {
-  UpdateDisplay("800x600,800x600");
+  UpdateDisplay("800x600,801+0-800x600");
   display::test::DisplayManagerTestApi display_manager_test(display_manager());
 
   // Snap `window` on the second display to start split view overview session.
-  std::unique_ptr<aura::Window> window(
+  std::unique_ptr<aura::Window> window1(
       CreateTestWindowInShellWithBounds(gfx::Rect(900, 0, 100, 100)));
-  WindowState* window_state = WindowState::Get(window.get());
+  std::unique_ptr<aura::Window> window2(
+      CreateTestWindowInShellWithBounds(gfx::Rect(1000, 0, 100, 100)));
+  WindowState* window_state = WindowState::Get(window1.get());
   const WindowSnapWMEvent snap_type(
       WM_EVENT_SNAP_PRIMARY,
       /*snap_action_source=*/WindowSnapActionSource::kTest);
   window_state->OnWMEvent(&snap_type);
-  ASSERT_EQ(
-      display_manager_test.GetSecondaryDisplay().id(),
-      display::Screen::GetScreen()->GetDisplayNearestWindow(window.get()).id());
+  ASSERT_EQ(display_manager_test.GetSecondaryDisplay().id(),
+            display::Screen::GetScreen()
+                ->GetDisplayNearestWindow(window1.get())
+                .id());
   EXPECT_EQ(chromeos::WindowStateType::kPrimarySnapped,
             window_state->GetStateType());
   EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
-  EXPECT_TRUE(RootWindowController::ForWindow(window.get())
+  EXPECT_TRUE(RootWindowController::ForWindow(window1.get())
                   ->split_view_overview_session());
 
   // Disconnect the second display. Test no crash.
@@ -1995,44 +2090,6 @@
   EXPECT_TRUE(window_to_snap_group_map.empty());
 }
 
-// Tests that, after a window is snapped with overview on the other side,
-// resizing overview works as expected.
-// TODO(b/308170967): Combine this with FasterSplitScreenTest.
-TEST_F(SnapGroupTest, ResizeSplitViewOverviewAndWindow) {
-  auto* snap_group_controller = SnapGroupController::Get();
-  // TODO(sophiewen): Make this the default for SnapGroupTest.
-  snap_group_controller->set_can_enter_overview_for_testing(
-      /*can_enter_overview=*/true);
-
-  std::unique_ptr<aura::Window> w1(CreateAppWindow());
-  SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
-  VerifySplitViewOverviewSession(w1.get());
-  const gfx::Rect initial_bounds(w1->GetBoundsInScreen());
-
-  // Drag the right edge of the window to resize the window and overview at the
-  // same time. Test that the bounds are updated.
-  const gfx::Point start_point(w1->GetBoundsInScreen().right_center());
-  auto* generator = GetEventGenerator();
-  generator->set_current_screen_location(start_point);
-
-  // Resize to less than 1/3. Test we don't end overview.
-  int x = 200;
-  ASSERT_LT(x, work_area_bounds().width() * chromeos::kOneThirdSnapRatio);
-  generator->DragMouseTo(gfx::Point(x, start_point.y()));
-  gfx::Rect expected_window_bounds(initial_bounds);
-  expected_window_bounds.set_width(x);
-  EXPECT_EQ(expected_window_bounds, w1->GetBoundsInScreen());
-  VerifySplitViewOverviewSession(w1.get());
-
-  // Resize to greater than 2/3. Test we don't end overview.
-  x = 600;
-  ASSERT_GT(x, work_area_bounds().width() * chromeos::kTwoThirdSnapRatio);
-  generator->DragMouseTo(gfx::Point(x, start_point.y()));
-  expected_window_bounds.set_width(x);
-  EXPECT_EQ(expected_window_bounds, w1->GetBoundsInScreen());
-  VerifySplitViewOverviewSession(w1.get());
-}
-
 // Tests that the split view divider will be stacked on top of both windows in
 // the snap group and that on a third window activated the split view divider
 // will be stacked below the newly activated window.
@@ -2596,7 +2653,7 @@
   for (const auto& overview_item : window_list) {
     const auto shadow_content_bounds =
         overview_item->get_shadow_content_bounds_for_testing();
-    ASSERT_TRUE(!shadow_content_bounds.IsEmpty());
+    ASSERT_FALSE(shadow_content_bounds.IsEmpty());
     EXPECT_EQ(shadow_content_bounds.size(),
               gfx::ToRoundedSize(overview_item->target_bounds().size()));
   }
@@ -3640,41 +3697,42 @@
 // snap action source with top-usage in clamshell.
 TEST_F(SnapGroupHistogramTest, SnapActionSourcePipeline) {
   UpdateDisplay("800x600");
-  std::unique_ptr<aura::Window> window(CreateAppWindow(gfx::Rect(100, 100)));
+  std::unique_ptr<aura::Window> window1(CreateAppWindow(gfx::Rect(100, 100)));
+  std::unique_ptr<aura::Window> window2(CreateAppWindow(gfx::Rect(200, 100)));
 
   // Drag a window to snap and verify the snap action source info.
   std::unique_ptr<WindowResizer> resizer(CreateWindowResizer(
-      window.get(), gfx::PointF(), HTCAPTION, wm::WINDOW_MOVE_SOURCE_MOUSE));
+      window1.get(), gfx::PointF(), HTCAPTION, wm::WINDOW_MOVE_SOURCE_MOUSE));
   resizer->Drag(gfx::PointF(0, 400), /*event_flags=*/0);
   resizer->CompleteDrag();
   resizer.reset();
   SplitViewOverviewSession* split_view_overview_session =
-      VerifySplitViewOverviewSession(window.get());
+      VerifySplitViewOverviewSession(window1.get());
   EXPECT_EQ(split_view_overview_session->snap_action_source_for_testing(),
             WindowSnapActionSource::kDragWindowToEdgeToSnap);
-  MaximizeToClearTheSession(window.get());
+  MaximizeToClearTheSession(window1.get());
 
   // Mock snap from window layout menu and verify the snap action source info.
   chromeos::SnapController::Get()->CommitSnap(
-      window.get(), chromeos::SnapDirection::kSecondary,
+      window1.get(), chromeos::SnapDirection::kSecondary,
       chromeos::kDefaultSnapRatio,
       chromeos::SnapController::SnapRequestSource::kWindowLayoutMenu);
-  split_view_overview_session = VerifySplitViewOverviewSession(window.get());
+  split_view_overview_session = VerifySplitViewOverviewSession(window1.get());
   EXPECT_TRUE(split_view_overview_session);
   EXPECT_EQ(split_view_overview_session->snap_action_source_for_testing(),
             WindowSnapActionSource::kSnapByWindowLayoutMenu);
-  MaximizeToClearTheSession(window.get());
+  MaximizeToClearTheSession(window1.get());
 
   // Mock snap from window snap button and verify the snap action source info.
   chromeos::SnapController::Get()->CommitSnap(
-      window.get(), chromeos::SnapDirection::kPrimary,
+      window1.get(), chromeos::SnapDirection::kPrimary,
       chromeos::kDefaultSnapRatio,
       chromeos::SnapController::SnapRequestSource::kSnapButton);
-  split_view_overview_session = VerifySplitViewOverviewSession(window.get());
+  split_view_overview_session = VerifySplitViewOverviewSession(window1.get());
   EXPECT_TRUE(split_view_overview_session);
   EXPECT_EQ(split_view_overview_session->snap_action_source_for_testing(),
             WindowSnapActionSource::kLongPressCaptionButtonToSnap);
-  MaximizeToClearTheSession(window.get());
+  MaximizeToClearTheSession(window1.get());
 }
 
 }  // namespace ash
diff --git a/ash/wm/splitview/split_view_controller.cc b/ash/wm/splitview/split_view_controller.cc
index c689314..a6371bd 100644
--- a/ash/wm/splitview/split_view_controller.cc
+++ b/ash/wm/splitview/split_view_controller.cc
@@ -32,6 +32,7 @@
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_grid.h"
 #include "ash/wm/overview/overview_item.h"
+#include "ash/wm/overview/overview_metrics.h"
 #include "ash/wm/overview/overview_types.h"
 #include "ash/wm/overview/overview_utils.h"
 #include "ash/wm/snap_group/snap_group.h"
@@ -2571,11 +2572,21 @@
     const int divider_position =
         GetClosestFixedDividerPosition(ash::GetEquivalentDividerPosition(
             primary_window_ ? primary_window_ : secondary_window_,
-            /*should_consider_divider=*/false));
+            /*account_for_divider_width=*/false));
     split_view_divider_.ShowFor(divider_position);
 
     UpdateSnappedWindowsAndDividerBounds();
     NotifyDividerPositionChanged();
+
+    // Ends `SplitViewOverviewSession` if it is currently alive, as
+    // `SplitViewOverviewSession` is for clamshell only.
+    RootWindowController* root_window_controller =
+        RootWindowController::ForWindow(root_window_);
+    if (SplitViewOverviewSession* split_view_overview_session =
+            root_window_controller->split_view_overview_session()) {
+      root_window_controller->EndSplitViewOverviewSession(
+          SplitViewOverviewSessionExitPoint::kTabletConversion);
+    }
   }
 }
 
diff --git a/ash/wm/splitview/split_view_overview_session.h b/ash/wm/splitview/split_view_overview_session.h
index cebe8d0..5635971 100644
--- a/ash/wm/splitview/split_view_overview_session.h
+++ b/ash/wm/splitview/split_view_overview_session.h
@@ -34,14 +34,15 @@
 
 // Enumeration of the exit point of the `SplitViewOverviewSession`.
 // Please keep in sync with "OverviewEndAction" in
-// tools/metrics/histograms/enums.xml.
+// tools/metrics/histograms/metadata/ash/enums.xml.
 enum class SplitViewOverviewSessionExitPoint {
   kCompleteByActivating,
   kSkip,
   kWindowDestroy,
   kShutdown,
   kUnspecified,
-  kMaxValue = kUnspecified,
+  kTabletConversion,
+  kMaxValue = kTabletConversion,
 };
 
 // Encapsulates the split view state with a single snapped window and
diff --git a/ash/wm/splitview/split_view_utils.cc b/ash/wm/splitview/split_view_utils.cc
index d3541180..561d528 100644
--- a/ash/wm/splitview/split_view_utils.cc
+++ b/ash/wm/splitview/split_view_utils.cc
@@ -231,21 +231,20 @@
   // state of the opposite snapped window.
   gfx::Rect union_bounds;
   for (aura::Window* top_window : windows) {
-    if (top_window->GetRootWindow() != window->GetRootWindow()) {
-      // Skip any windows that aren't on the same root as `window`.
-      continue;
-    }
-
     const auto* top_window_state = WindowState::Get(top_window);
     // The `top_window` should be excluded for occlusion check under the
     // following conditions:
     // 1. When it is the `window` itself;
-    // 2. When it is not visible or minimized;
+    // 2. When `top_window` is not on the same root window of the given
+    // `window`;
     // 3. When it is the transient child of the `window`, for example the window
     // layout menu or other bubble widget;
-    // 4. When it is a float or pip window.
+    // 4. When it is not visible or minimized;
+    // 5. When it is a float or pip window.
     const bool should_be_excluded_for_occlusion_check =
-        top_window == window || wm::GetTransientRoot(top_window) == window ||
+        top_window == window ||
+        top_window->GetRootWindow() != window->GetRootWindow() ||
+        wm::GetTransientRoot(top_window) == window ||
         !top_window->IsVisible() || top_window_state->IsMinimized() ||
         top_window_state->IsFloated() || top_window_state->IsPip();
 
@@ -274,6 +273,20 @@
   return false;
 }
 
+// Returns true if there is no window in partial overview (excluding the given
+// `window`).
+bool IsPartialOverviewEmptyForActiveDesk(aura::Window* window) {
+  for (auto win :
+       Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk)) {
+    if (win != window && wm::GetTransientRoot(win) != window &&
+        win->GetRootWindow() == window->GetRootWindow()) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
 }  // namespace
 
 WindowTransformAnimationObserver::WindowTransformAnimationObserver(
@@ -888,7 +901,8 @@
     return false;
   }
 
-  if (!OverviewController::Get()->CanEnterOverview()) {
+  if (!OverviewController::Get()->CanEnterOverview() ||
+      IsPartialOverviewEmptyForActiveDesk(window)) {
     return false;
   }
 
diff --git a/ash/wm/window_restore/pine_contents_data.cc b/ash/wm/window_restore/pine_contents_data.cc
index d267a06..ff25566 100644
--- a/ash/wm/window_restore/pine_contents_data.cc
+++ b/ash/wm/window_restore/pine_contents_data.cc
@@ -4,12 +4,22 @@
 
 #include "ash/wm/window_restore/pine_contents_data.h"
 
-#include "components/app_restore/restore_data.h"
-
 namespace ash {
 
 PineContentsData::PineContentsData() = default;
 
 PineContentsData::~PineContentsData() = default;
 
+PineContentsData::AppInfo::AppInfo(const std::string& app_id)
+    : app_id(app_id) {}
+
+PineContentsData::AppInfo::AppInfo(const std::string& app_id,
+                                   const std::string& tab_title,
+                                   const std::vector<std::string>& tab_urls)
+    : app_id(app_id), tab_title(tab_title), tab_urls(tab_urls) {}
+
+PineContentsData::AppInfo::AppInfo(const AppInfo&) = default;
+
+PineContentsData::AppInfo::~AppInfo() = default;
+
 }  // namespace ash
diff --git a/ash/wm/window_restore/pine_contents_data.h b/ash/wm/window_restore/pine_contents_data.h
index 0b1d73d5..2ec99ca 100644
--- a/ash/wm/window_restore/pine_contents_data.h
+++ b/ash/wm/window_restore/pine_contents_data.h
@@ -5,15 +5,12 @@
 #ifndef ASH_WM_WINDOW_RESTORE_PINE_CONTENTS_DATA_H_
 #define ASH_WM_WINDOW_RESTORE_PINE_CONTENTS_DATA_H_
 
-#include <memory>
+#include <string>
+#include <vector>
 
 #include "ash/ash_export.h"
 #include "ui/gfx/image/image_skia.h"
 
-namespace app_restore {
-class RestoreData;
-}
-
 namespace ash {
 
 // Various data needed to populate the pine dialog.
@@ -24,18 +21,38 @@
   PineContentsData& operator=(const PineContentsData&) = delete;
   ~PineContentsData();
 
+  struct AppInfo {
+    explicit AppInfo(const std::string& id);
+    AppInfo(const std::string& app_id,
+            const std::string& tab_title,
+            const std::vector<std::string>& tab_urls);
+    AppInfo(const AppInfo&);
+    ~AppInfo();
+    // App id. Used to retrieve the app name and app icon from the app registry
+    // cache.
+    std::string app_id;
+    // Used for browser and PWAs. Shows a more descriptive title than "Chrome".
+    std::string tab_title;
+    // Used by browser only. Urls of up to 5 tabs including the active tab. Used
+    // to retrieve favicons.
+    std::vector<std::string> tab_urls;
+  };
+
+  using AppsInfos = std::vector<AppInfo>;
+
   // Image read from the pine image file. Will be null if pine image file was
   // missing or decoding failed.
   gfx::ImageSkia image;
 
-  // Contains the app data needed to show app titles, app icons, favicons, etc.
-  // Read from the full restore file.
-  // TODO(sammiequon): Use a subset of `app_restore::RestoreData` here instead
-  // as it contains a lot of unnecessary information.
-  std::unique_ptr<app_restore::RestoreData> restore_data;
+  // List of `AppInfo`'s. Each one representing an app window. There may be
+  // multiple entries with the same app id.
+  AppsInfos apps_infos;
+
+  // True if the previous session crashed. The pine dialog will have slightly
+  // different strings in this case.
+  bool last_session_crashed = false;
 
   // TODO(sammiequon): Add ok/cancel callbacks.
-  // TODO(sammiequon): Add dialog type (crash, update, normal).
 };
 
 }  // namespace ash
diff --git a/ash/wm/window_restore/pine_contents_view.cc b/ash/wm/window_restore/pine_contents_view.cc
index b933a81b..2e66e8e3 100644
--- a/ash/wm/window_restore/pine_contents_view.cc
+++ b/ash/wm/window_restore/pine_contents_view.cc
@@ -15,7 +15,7 @@
 #include "ash/wm/window_properties.h"
 #include "ash/wm/window_restore/pine_contents_data.h"
 #include "ash/wm/window_restore/pine_context_menu_model.h"
-#include "ash/wm/window_restore/window_restore_controller.h"
+#include "ash/wm/window_restore/pine_controller.h"
 #include "base/barrier_callback.h"
 #include "base/i18n/number_formatting.h"
 #include "base/strings/strcat.h"
@@ -214,8 +214,9 @@
   METADATA_HEADER(PineItemsOverflowView, views::BoxLayoutView)
 
  public:
-  explicit PineItemsOverflowView(const PineContentsView::AppsData& apps) {
-    const int elements = static_cast<int>(apps.size());
+  explicit PineItemsOverflowView(
+      const PineContentsData::AppsInfos& apps_infos) {
+    const int elements = static_cast<int>(apps_infos.size());
     CHECK_GE(elements, kOverflowMinElements);
 
     // TODO(hewer): Fix margins so the icons and text are aligned with
@@ -257,9 +258,9 @@
         image_view_map_[i] = image_view;
 
         // The callback may be called synchronously.
-        const auto& [app_id, favicons] = apps[i];
+        const PineContentsData::AppInfo& app_info = apps_infos[i];
         delegate->GetIconForAppId(
-            app_id, kAppIdImageSize,
+            app_info.app_id, kAppIdImageSize,
             base::BindOnce(&PineItemsOverflowView::SetIconForIndex,
                            weak_ptr_factory_.GetWeakPtr(), i));
       }
@@ -301,9 +302,9 @@
         image_view_map_[i] = image_view;
 
         // The callback may be called synchronously.
-        const auto& [app_id, favicons] = apps[i];
+        const PineContentsData::AppInfo& app_info = apps_infos[i];
         delegate->GetIconForAppId(
-            app_id, kAppIdImageSize,
+            app_info.app_id, kAppIdImageSize,
             base::BindOnce(&PineItemsOverflowView::SetIconForIndex,
                            weak_ptr_factory_.GetWeakPtr(), i));
       }
@@ -420,8 +421,9 @@
   METADATA_HEADER(PineItemsContainerView, views::BoxLayoutView)
 
  public:
-  explicit PineItemsContainerView(const PineContentsView::AppsData& apps) {
-    const int elements = static_cast<int>(apps.size());
+  explicit PineItemsContainerView(
+      const PineContentsData::AppsInfos& apps_infos) {
+    const int elements = static_cast<int>(apps_infos.size());
     CHECK_GT(elements, 0);
 
     SetBackground(views::CreateRoundedRectBackground(SK_ColorWHITE,
@@ -438,11 +440,11 @@
     auto* delegate = Shell::Get()->saved_desk_delegate();
 
     for (int i = 0; i < elements; ++i) {
-      const auto& [app_id, favicons] = apps[i];
+      const PineContentsData::AppInfo& app_info = apps_infos[i];
       // If there are more than four elements, we will need to save the last
       // space for the overflow view to condense the remaining info.
       if (elements >= kOverflowMinElements && i >= kOverflowMinThreshold) {
-        AddChildView(std::make_unique<PineItemsOverflowView>(apps));
+        AddChildView(std::make_unique<PineItemsOverflowView>(apps_infos));
         break;
       }
 
@@ -450,17 +452,19 @@
       // `cache` might be null in a test environment. In that case, we will
       // use an empty title.
       if (cache) {
-        cache->ForOneApp(app_id, [&title](const apps::AppUpdate& update) {
-          title = update.Name();
-        });
+        cache->ForOneApp(
+            app_info.app_id,
+            [&title](const apps::AppUpdate& update) { title = update.Name(); });
       }
 
-      PineItemView* item_view =
-          AddChildView(std::make_unique<PineItemView>(title, favicons));
+      // TODO(hewer|sammiequon): `PineItemView` should just take `app_info` and
+      // `cache` as a constructor argument.
+      PineItemView* item_view = AddChildView(
+          std::make_unique<PineItemView>(title, app_info.tab_urls));
 
       // The callback may be called synchronously.
       delegate->GetIconForAppId(
-          app_id, kAppIdImageSize,
+          app_info.app_id, kAppIdImageSize,
           base::BindOnce(
               [](base::WeakPtr<PineItemView> item_view_ptr,
                  const gfx::ImageSkia& icon) {
@@ -548,24 +552,12 @@
       ->SetFlexForView(spacer, 1);
 
   const PineContentsData* pine_contents_data =
-      WindowRestoreController::Get()->pine_contents_data();
+      Shell::Get()->pine_controller()->pine_contents_data();
   CHECK(pine_contents_data);
   if (pine_contents_data->image.isNull()) {
-    // TODO(hewer|sammiequon): Move this developer testing data to
-    // `WindowRestoreController::MaybeStartPineOverviewSessionDevAccelerator()`.
-    AppsData kTestingAppsData = {
-        {"mgndgikekgjfcpckkfioiadnlibdjbkf",  // Chrome
-         {"https://www.cnn.com/", "https://www.youtube.com/",
-          "https://www.google.com/"}},
-        {"njfbnohfdkmbmnjapinfcopialeghnmh", {}},  // Camera
-        {"odknhmnlageboeamepcngndbggdpaobj", {}},  // Settings
-        {"fkiggjmkendpmbegkagpmagjepfkpmeb", {}},  // Files
-        {"oabkinaljpjeilageghcdlnekhphhphl", {}},  // Calculator
-        {"mgndgikekgjfcpckkfioiadnlibdjbkf",       // Chrome
-         {"https://www.google.com/maps/"}},
-    };
-    PineItemsContainerView* container_view = AddChildView(
-        std::make_unique<PineItemsContainerView>(kTestingAppsData));
+    PineItemsContainerView* container_view =
+        AddChildView(std::make_unique<PineItemsContainerView>(
+            pine_contents_data->apps_infos));
     container_view->SetPreferredSize(kItemsContainerPreferredSize);
   } else {
     views::ImageView* preview =
diff --git a/ash/wm/window_restore/pine_contents_view.h b/ash/wm/window_restore/pine_contents_view.h
index 8cc391ec..ded89b4 100644
--- a/ash/wm/window_restore/pine_contents_view.h
+++ b/ash/wm/window_restore/pine_contents_view.h
@@ -28,11 +28,6 @@
   METADATA_HEADER(PineContentsView, views::BoxLayoutView)
 
  public:
-  // Temporary typedefs to describe a bunch of apps. An app is described by an
-  // app id and a vector of urls, which can be empty if the app is not Chrome.
-  using AppData = std::pair<std::string, std::vector<std::string>>;
-  using AppsData = std::vector<AppData>;
-
   PineContentsView();
   PineContentsView(const PineContentsView&) = delete;
   PineContentsView& operator=(const PineContentsView&) = delete;
diff --git a/ash/wm/window_restore/pine_context_menu_model_unittest.cc b/ash/wm/window_restore/pine_context_menu_model_unittest.cc
index 4142462c..82eb06fc 100644
--- a/ash/wm/window_restore/pine_context_menu_model_unittest.cc
+++ b/ash/wm/window_restore/pine_context_menu_model_unittest.cc
@@ -16,7 +16,7 @@
 #include "ash/wm/window_restore/pine_contents_data.h"
 #include "ash/wm/window_restore/pine_contents_view.h"
 #include "ash/wm/window_restore/pine_context_menu_model.h"
-#include "ash/wm/window_restore/window_restore_controller.h"
+#include "ash/wm/window_restore/pine_controller.h"
 #include "ash/wm/window_restore/window_restore_util.h"
 #include "base/test/scoped_feature_list.h"
 #include "components/prefs/pref_registry_simple.h"
@@ -102,7 +102,7 @@
 // context menu.
 TEST_F(PineContextMenuModelTest, ShowContextMenuOnSettingsButtonClicked) {
   Shell::Get()
-      ->window_restore_controller()
+      ->pine_controller()
       ->MaybeStartPineOverviewSessionDevAccelerator();
   WaitForOverviewEntered();
 
diff --git a/ash/wm/window_restore/pine_controller.cc b/ash/wm/window_restore/pine_controller.cc
new file mode 100644
index 0000000..8b208fc22
--- /dev/null
+++ b/ash/wm/window_restore/pine_controller.cc
@@ -0,0 +1,114 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/wm/window_restore/pine_controller.h"
+
+#include "ash/constants/ash_pref_names.h"
+#include "ash/public/cpp/image_util.h"
+#include "ash/shell.h"
+#include "ash/wm/overview/overview_controller.h"
+#include "ash/wm/window_restore/pine_contents_data.h"
+#include "ash/wm/window_restore/window_restore_util.h"
+#include "base/functional/bind.h"
+#include "base/metrics/histogram_functions.h"
+#include "components/prefs/pref_service.h"
+
+namespace ash {
+
+namespace {
+
+// Records the UMA metrics for the pine screenshot taken on the last shutdown.
+// Resets the prefs used to store the metrics across shutdowns.
+void RecordPineScreenshotMetrics(PrefService* local_state) {
+  auto record_uma = [](PrefService* local_state, const std::string& name,
+                       const std::string& pref_name) -> void {
+    const base::TimeDelta duration = local_state->GetTimeDelta(pref_name);
+    // Don't record the metric if we don't have a value.
+    if (!duration.is_zero()) {
+      base::UmaHistogramTimes(name, duration);
+      // Reset the pref in case the next shutdown doesn't take the screenshot.
+      local_state->SetTimeDelta(pref_name, base::TimeDelta());
+    }
+  };
+
+  record_uma(local_state, "Ash.Pine.ScreenshotTakenDuration",
+             prefs::kPineScreenshotTakenDuration);
+  record_uma(local_state, "Ash.Pine.ScreenshotEncodeAndSaveDuration",
+             prefs::kPineScreenshotEncodeAndSaveDuration);
+}
+
+}  // namespace
+
+PineController::PineController() = default;
+
+PineController::~PineController() = default;
+
+void PineController::MaybeStartPineOverviewSessionDevAccelerator() {
+  auto data = std::make_unique<PineContentsData>();
+  data->last_session_crashed = false;
+
+  // Chrome.
+  data->apps_infos.emplace_back(
+      "mgndgikekgjfcpckkfioiadnlibdjbkf", /*tab_title=*/"Cnn",
+      std::vector<std::string>{"https://www.cnn.com/",
+                               "https://www.youtube.com/",
+                               "https://www.google.com/"});
+  // Camera.
+  data->apps_infos.emplace_back("njfbnohfdkmbmnjapinfcopialeghnmh");
+  // Settings.
+  data->apps_infos.emplace_back("odknhmnlageboeamepcngndbggdpaobj");
+  // Files.
+  data->apps_infos.emplace_back("fkiggjmkendpmbegkagpmagjepfkpmeb");
+  // Calculator.
+  data->apps_infos.emplace_back("oabkinaljpjeilageghcdlnekhphhphl");
+  // Chrome.
+  data->apps_infos.emplace_back(
+      "mgndgikekgjfcpckkfioiadnlibdjbkf", /*tab_title=*/"Maps",
+      std::vector<std::string>{"https://www.google.com/maps/"});
+
+  MaybeStartPineOverviewSession(std::move(data));
+}
+
+void PineController::MaybeStartPineOverviewSession(
+    std::unique_ptr<PineContentsData> pine_contents_data) {
+  CHECK(features::IsForestFeatureEnabled());
+
+  if (OverviewController::Get()->InOverviewSession()) {
+    return;
+  }
+
+  // TODO(hewer|sammiequon): This function should only be called once in
+  // production code when `pine_contents_data_` is null. It can be called
+  // multiple times currently via dev accelerator. Remove this block when
+  // `MaybeStartPineOverviewSessionDevAccelerator()` is removed.
+  if (pine_contents_data_) {
+    StartPineOverviewSession();
+    return;
+  }
+
+  pine_contents_data_ = std::move(pine_contents_data);
+
+  // TODO(minch|sammiequon): Record the metrics on start up when determining
+  // whether to show the pine dialog.
+  RecordPineScreenshotMetrics(Shell::Get()->local_state());
+  image_util::DecodeImageFile(
+      base::BindOnce(&PineController::OnPineImageDecoded,
+                     weak_ptr_factory_.GetWeakPtr()),
+      GetShutdownPineImagePath(), data_decoder::mojom::ImageCodec::kPng);
+}
+
+void PineController::OnPineImageDecoded(const gfx::ImageSkia& pine_image) {
+  CHECK(pine_contents_data_);
+  pine_contents_data_->image = pine_image;
+
+  StartPineOverviewSession();
+}
+
+void PineController::StartPineOverviewSession() {
+  // TODO(sammiequon): Add a new start action for this type of overview session.
+  OverviewController::Get()->StartOverview(OverviewStartAction::kAccelerator,
+                                           OverviewEnterExitType::kPine);
+}
+
+}  // namespace ash
diff --git a/ash/wm/window_restore/pine_controller.h b/ash/wm/window_restore/pine_controller.h
new file mode 100644
index 0000000..f677fd1b
--- /dev/null
+++ b/ash/wm/window_restore/pine_controller.h
@@ -0,0 +1,68 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_WM_WINDOW_RESTORE_PINE_CONTROLLER_H_
+#define ASH_WM_WINDOW_RESTORE_PINE_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "base/memory/weak_ptr.h"
+#include "ui/gfx/image/image_skia.h"
+
+namespace ash {
+
+struct PineContentsData;
+
+// Controls showing the pine dialog. Receives data from the full restore
+// service.
+class ASH_EXPORT PineController {
+ public:
+  PineController();
+  PineController(const PineController&) = delete;
+  PineController& operator=(const PineController&) = delete;
+  ~PineController();
+
+  const PineContentsData* pine_contents_data() const {
+    return pine_contents_data_.get();
+  }
+
+  // Starts an overview session with the pine contents view if certain
+  // conditions are met. Uses fake for testing only data.
+  // TODO(hewer): Remove this temporary function.
+  void MaybeStartPineOverviewSessionDevAccelerator();
+
+  // Starts an overview session with the pine contents view if certain
+  // conditions are met. Triggered by developer accelerator or on login.
+  // `pine_contents_data` is stored in `pine_contents_data_` as we will support
+  // re-entering the pine session if no windows have opened for example. It will
+  // be populated with a screenshot if possible and then referenced when an
+  // overview pine session is entered.
+  void MaybeStartPineOverviewSession(
+      std::unique_ptr<PineContentsData> pine_contents_data);
+
+  // TODO(sammiequon): Create a separate controller for pine related things as
+  // we need to support first-time experience and other things.
+  // TODO(sammiequon): Add a `MaybeEndPineOverviewSession()` function which is
+  // controlled by the full restore service. This will clear
+  // `pine_contents_data_`.
+  // TODO(sammiequon): Entering overview normally should show the pine dialog if
+  // `pine_contents_data_` is not null.
+
+ private:
+  // Callback function for when the pine image is finished decoding.
+  void OnPineImageDecoded(const gfx::ImageSkia& pine_image);
+
+  void StartPineOverviewSession();
+
+  // Stores the data needed to display the pine dialog. Created on login, and
+  // deleted after the user interacts with the dialog. If the user exits
+  // overview, this will persist until a window is opened.
+  // TODO(sammiequon): Delete this object when an app window is created.
+  std::unique_ptr<PineContentsData> pine_contents_data_;
+
+  base::WeakPtrFactory<PineController> weak_ptr_factory_{this};
+};
+
+}  // namespace ash
+
+#endif  // ASH_WM_WINDOW_RESTORE_PINE_CONTROLLER_H_
diff --git a/ash/wm/window_restore/pine_unittest.cc b/ash/wm/window_restore/pine_unittest.cc
index d3305643..421ad92 100644
--- a/ash/wm/window_restore/pine_unittest.cc
+++ b/ash/wm/window_restore/pine_unittest.cc
@@ -16,7 +16,7 @@
 #include "ash/wm/window_restore/pine_contents_data.h"
 #include "ash/wm/window_restore/pine_contents_view.h"
 #include "ash/wm/window_restore/pine_context_menu_model.h"
-#include "ash/wm/window_restore/window_restore_controller.h"
+#include "ash/wm/window_restore/pine_controller.h"
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
 #include "ui/views/controls/button/image_button.h"
@@ -39,7 +39,7 @@
 
 TEST_F(PineTest, Show) {
   Shell::Get()
-      ->window_restore_controller()
+      ->pine_controller()
       ->MaybeStartPineOverviewSessionDevAccelerator();
   WaitForOverviewEntered();
 
diff --git a/ash/wm/window_restore/window_restore_controller.cc b/ash/wm/window_restore/window_restore_controller.cc
index 9fe460bcb..263e2b71 100644
--- a/ash/wm/window_restore/window_restore_controller.cc
+++ b/ash/wm/window_restore/window_restore_controller.cc
@@ -8,9 +8,7 @@
 
 #include "ash/app_list/app_list_controller_impl.h"
 #include "ash/constants/app_types.h"
-#include "ash/constants/ash_pref_names.h"
 #include "ash/public/cpp/app_types_util.h"
-#include "ash/public/cpp/image_util.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/session/session_controller_impl.h"
@@ -19,11 +17,9 @@
 #include "ash/wm/desks/desk.h"
 #include "ash/wm/desks/desks_util.h"
 #include "ash/wm/desks/templates/saved_desk_util.h"
-#include "ash/wm/float/float_controller.h"
 #include "ash/wm/mru_window_tracker.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/window_positioning_utils.h"
-#include "ash/wm/window_restore/pine_contents_data.h"
 #include "ash/wm/window_restore/window_restore_util.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/wm_event.h"
@@ -32,7 +28,6 @@
 #include "base/containers/adapters.h"
 #include "base/containers/contains.h"
 #include "base/functional/bind.h"
-#include "base/memory/raw_ptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/task/single_thread_task_runner.h"
 #include "components/app_restore/full_restore_utils.h"
@@ -128,26 +123,6 @@
   }
 }
 
-// Records the UMA metrics for the pine screenshot taken on the last shutdown.
-// Resets the prefs used to store the metrics across shutdowns.
-void RecordPineScreenshotMetrics(PrefService* local_state) {
-  auto record_uma = [](PrefService* local_state, const std::string& name,
-                       const std::string& pref_name) -> void {
-    const base::TimeDelta duration = local_state->GetTimeDelta(pref_name);
-    // Don't record the metric if we don't have a value.
-    if (!duration.is_zero()) {
-      base::UmaHistogramTimes(name, duration);
-      // Reset the pref in case the next shutdown doesn't take the screenshot.
-      local_state->SetTimeDelta(pref_name, base::TimeDelta());
-    }
-  };
-
-  record_uma(local_state, "Ash.Pine.ScreenshotTakenDuration",
-             prefs::kPineScreenshotTakenDuration);
-  record_uma(local_state, "Ash.Pine.ScreenshotEncodeAndSaveDuration",
-             prefs::kPineScreenshotEncodeAndSaveDuration);
-}
-
 // Self deleting class which watches a unparented window and deletes itself once
 // the window has a parent.
 class ParentChangeObserver : public aura::WindowObserver {
@@ -526,38 +501,6 @@
   return windows_observation_.IsObservingSource(window);
 }
 
-void WindowRestoreController::MaybeStartPineOverviewSessionDevAccelerator() {
-  MaybeStartPineOverviewSession(std::make_unique<PineContentsData>());
-}
-
-void WindowRestoreController::MaybeStartPineOverviewSession(
-    std::unique_ptr<PineContentsData> pine_contents_data) {
-  CHECK(features::IsForestFeatureEnabled());
-
-  if (OverviewController::Get()->InOverviewSession()) {
-    return;
-  }
-
-  // TODO(hewer|sammiequon): This function should only be called once in
-  // production code when `pine_contents_data_` is null. It can be called
-  // multiple times currently via dev accelerator. Remove this block when
-  // `MaybeStartPineOverviewSessionDevAccelerator()` is removed.
-  if (pine_contents_data_) {
-    StartPineOverviewSession();
-    return;
-  }
-
-  pine_contents_data_ = std::move(pine_contents_data);
-
-  // TODO(minch|sammiequon): Record the metrics on start up when determining
-  // whether to show the pine dialog.
-  RecordPineScreenshotMetrics(Shell::Get()->local_state());
-  image_util::DecodeImageFile(
-      base::BindOnce(&WindowRestoreController::OnPineImageDecoded,
-                     weak_ptr_factory_.GetWeakPtr()),
-      GetShutdownPineImagePath(), data_decoder::mojom::ImageCodec::kPng);
-}
-
 void WindowRestoreController::SaveWindowImpl(
     WindowState* window_state,
     std::optional<int> activation_index) {
@@ -688,20 +631,6 @@
   restore_property_clear_callbacks_.erase(window);
 }
 
-void WindowRestoreController::OnPineImageDecoded(
-    const gfx::ImageSkia& pine_image) {
-  CHECK(pine_contents_data_);
-  pine_contents_data_->image = pine_image;
-
-  StartPineOverviewSession();
-}
-
-void WindowRestoreController::StartPineOverviewSession() {
-  // TODO(sammiequon): Add a new start action for this type of overview session.
-  OverviewController::Get()->StartOverview(OverviewStartAction::kAccelerator,
-                                           OverviewEnterExitType::kPine);
-}
-
 void WindowRestoreController::SetSaveWindowCallbackForTesting(
     SaveWindowCallback callback) {
   g_save_window_callback_for_testing = std::move(callback);
diff --git a/ash/wm/window_restore/window_restore_controller.h b/ash/wm/window_restore/window_restore_controller.h
index f962a041..409cb153 100644
--- a/ash/wm/window_restore/window_restore_controller.h
+++ b/ash/wm/window_restore/window_restore_controller.h
@@ -18,7 +18,6 @@
 #include "components/app_restore/window_info.h"
 #include "ui/aura/window_observer.h"
 #include "ui/display/display_observer.h"
-#include "ui/gfx/image/image_skia.h"
 
 namespace aura {
 class Window;
@@ -34,7 +33,6 @@
 
 namespace ash {
 
-struct PineContentsData;
 class WindowState;
 
 class ASH_EXPORT WindowRestoreController
@@ -81,10 +79,6 @@
     return to_be_snapped_window_;
   }
 
-  const PineContentsData* pine_contents_data() const {
-    return pine_contents_data_.get();
-  }
-
   // Calls SaveWindowImpl for |window_state|. The activation index will be
   // calculated in SaveWindowImpl.
   void SaveWindow(WindowState* window_state);
@@ -105,28 +99,6 @@
   // `this`.
   bool IsRestoringWindow(aura::Window* window) const;
 
-  // Starts an overview session with the pine contents view if certain
-  // conditions are met. Uses fake for testing only data.
-  // TODO(hewer): Remove this temporary function.
-  void MaybeStartPineOverviewSessionDevAccelerator();
-
-  // Starts an overview session with the pine contents view if certain
-  // conditions are met. Triggered by developer accelerator or on login.
-  // `pine_contents_data` is stored in `pine_contents_data_` as we will support
-  // re-entering the pine session if no windows have opened for example. It will
-  // be populated with a screenshot if possible and then referenced when an
-  // overview pine session is entered.
-  void MaybeStartPineOverviewSession(
-      std::unique_ptr<PineContentsData> pine_contents_data);
-
-  // TODO(sammiequon): Create a separate controller for pine related things as
-  // we need to support first-time experience and other things.
-  // TODO(sammiequon): Add a `MaybeEndPineOverviewSession()` function which is
-  // controlled by the full restore service. This will clear
-  // `pine_contents_data_`.
-  // TODO(sammiequon): Entering overview normally should show the pine dialog if
-  // `pine_contents_data_` is not null.
-
   // display::DisplayObserver:
   void OnDisplayTabletStateChanged(display::TabletState state) override;
 
@@ -175,11 +147,6 @@
   // from `restore_property_clear_callbacks_`.
   void CancelAndRemoveRestorePropertyClearCallback(aura::Window* window);
 
-  // Callback function for when the pine image is finished decoding.
-  void OnPineImageDecoded(const gfx::ImageSkia& pine_image);
-
-  void StartPineOverviewSession();
-
   // Sets a callback for testing that will be fired immediately when
   // `SaveWindowImpl()` is about to notify the window restore component we want
   // to write to file.
@@ -203,12 +170,6 @@
   std::map<aura::Window*, base::CancelableOnceClosure>
       restore_property_clear_callbacks_;
 
-  // Stores the data needed to display the pine dialog. Created on login, and
-  // deleted after the user interacts with the dialog. If the user exits
-  // overview, this will persist until a window is opened.
-  // TODO(sammiequon): Delete this object when an app window is created.
-  std::unique_ptr<PineContentsData> pine_contents_data_;
-
   display::ScopedDisplayObserver display_observer_{this};
 
   base::ScopedObservation<app_restore::AppRestoreInfo,
diff --git a/base/compiler_specific.h b/base/compiler_specific.h
index fbe0bb2..17ba104 100644
--- a/base/compiler_specific.h
+++ b/base/compiler_specific.h
@@ -443,11 +443,12 @@
 // Additionally, the initial implementation in clang <= 16 overwrote the return
 // register(s) in the epilogue of a preserve_most function, so we only use
 // preserve_most in clang >= 17 (see https://reviews.llvm.org/D143425).
+// Clang only supports preserve_most on X86-64 and AArch64 for now.
 // See https://clang.llvm.org/docs/AttributeReference.html#preserve-most for
 // more details.
-#if defined(ARCH_CPU_64_BITS) &&                       \
-    !(BUILDFLAG(IS_WIN) && defined(ARCH_CPU_ARM64)) && \
-    !defined(COMPONENT_BUILD) && defined(__clang__) && \
+#if (defined(ARCH_CPU_ARM64) || defined(ARCH_CPU_X86_64)) && \
+    !(BUILDFLAG(IS_WIN) && defined(ARCH_CPU_ARM64)) &&       \
+    !defined(COMPONENT_BUILD) && defined(__clang__) &&       \
     __clang_major__ >= 17 && HAS_ATTRIBUTE(preserve_most)
 #define PRESERVE_MOST __attribute__((preserve_most))
 #else
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index a109df4..aa6b5be1 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -1850,6 +1850,9 @@
         cflags_cc += [
           # TODO(https://crbug.com/1474434): fix and reenable
           "-Wno-missing-field-initializers",
+
+          # TODO(https://crbug.com/324953188): fix and reenable
+          "-Wno-extra-qualification",
         ]
       }
 
diff --git a/cc/input/input_handler.cc b/cc/input/input_handler.cc
index 5fad139..adfbaf1 100644
--- a/cc/input/input_handler.cc
+++ b/cc/input/input_handler.cc
@@ -547,7 +547,9 @@
 }
 
 PointerResultType InputHandler::HitTest(const gfx::PointF& viewport_point) {
-  return scrollbar_controller_->HitTest(viewport_point).type;
+  return scrollbar_controller_->HitTest(viewport_point)
+             ? PointerResultType::kScrollbarScroll
+             : PointerResultType::kUnhandled;
 }
 
 InputHandlerPointerResult InputHandler::MouseDown(
diff --git a/cc/input/input_handler.h b/cc/input/input_handler.h
index 0b8dba5..1bd128c 100644
--- a/cc/input/input_handler.h
+++ b/cc/input/input_handler.h
@@ -58,13 +58,6 @@
   kMaxValue = kScrollingOnMain,
 };
 
-struct CC_EXPORT PointerHitTestResult {
-  PointerHitTestResult() = default;
-
-  PointerResultType type = PointerResultType::kUnhandled;
-  raw_ptr<const LayerImpl> layer_impl = nullptr;
-};
-
 struct CC_EXPORT InputHandlerPointerResult {
   InputHandlerPointerResult() = default;
   // Tells what type of processing occurred in the input handler as a result of
diff --git a/cc/input/scrollbar_controller.cc b/cc/input/scrollbar_controller.cc
index 64d7762..eee49d58 100644
--- a/cc/input/scrollbar_controller.cc
+++ b/cc/input/scrollbar_controller.cc
@@ -51,7 +51,7 @@
   return nullptr;
 }
 
-PointerHitTestResult ScrollbarController::HitTest(
+const ScrollbarLayerImplBase* ScrollbarController::HitTest(
     const gfx::PointF position_in_widget) const {
   // If a non-custom scrollbar layer was not found, we return early as there is
   // no point in setting additional state in the ScrollbarController. Return an
@@ -59,20 +59,13 @@
   // to InputHandlerProxy::RouteToTypeSpecificHandler, the pointer event gets
   // passed on to the main thread.
   const LayerImpl* layer_impl = GetLayerHitByPoint(position_in_widget);
-  PointerHitTestResult result;
-
   if (!(layer_impl && layer_impl->IsScrollbarLayer()))
-    return result;
+    return nullptr;
 
   // If the scrollbar layer has faded out (eg: Overlay scrollbars), don't
   // initiate a scroll.
   const ScrollbarLayerImplBase* scrollbar = ToScrollbarLayer(layer_impl);
-  if (scrollbar->OverlayScrollbarOpacity() == 0.f)
-    return result;
-
-  result.type = PointerResultType::kScrollbarScroll;
-  result.layer_impl = layer_impl;
-  return result;
+  return scrollbar->OverlayScrollbarOpacity() == 0.f ? nullptr : scrollbar;
 }
 
 // Performs hit test and prepares scroll deltas that will be used by GSB and
@@ -80,15 +73,11 @@
 InputHandlerPointerResult ScrollbarController::HandlePointerDown(
     const gfx::PointF position_in_widget,
     bool jump_key_modifier) {
-  PointerHitTestResult hit_test_result = HitTest(position_in_widget);
-  if (hit_test_result.type != PointerResultType::kScrollbarScroll) {
+  const ScrollbarLayerImplBase* scrollbar = HitTest(position_in_widget);
+  if (!scrollbar) {
     return InputHandlerPointerResult();
   }
 
-  // TODO(arakeri): GetLayerHitByPoint should ideally be called only once per
-  // pointerdown. This needs to be optimized. See crbug.com/1156922.
-  const ScrollbarLayerImplBase* scrollbar =
-      ToScrollbarLayer(hit_test_result.layer_impl);
   captured_scrollbar_metadata_ = CapturedScrollbarMetadata();
   captured_scrollbar_metadata_->scroll_element_id =
       scrollbar->scroll_element_id();
diff --git a/cc/input/scrollbar_controller.h b/cc/input/scrollbar_controller.h
index 692117f..d236416 100644
--- a/cc/input/scrollbar_controller.h
+++ b/cc/input/scrollbar_controller.h
@@ -157,7 +157,8 @@
   ScrollbarLayerImplBase* ScrollbarLayer() const;
   void WillBeginImplFrame();
   void ResetState();
-  PointerHitTestResult HitTest(const gfx::PointF position_in_widget) const;
+  const ScrollbarLayerImplBase* HitTest(
+      const gfx::PointF position_in_widget) const;
 
  private:
   FRIEND_TEST_ALL_PREFIXES(LayerTreeHostImplTest, ThumbDragAfterJumpClick);
diff --git a/cc/trees/layer_tree_host_unittest_animation.cc b/cc/trees/layer_tree_host_unittest_animation.cc
index 4e97945d..f67501c 100644
--- a/cc/trees/layer_tree_host_unittest_animation.cc
+++ b/cc/trees/layer_tree_host_unittest_animation.cc
@@ -1730,9 +1730,8 @@
   FakeContentLayerClient client_;
 };
 
-// Disabled on ChromeOS ASAN due to test flakiness. See
-// https://crbug.com/1517464
-#if BUILDFLAG(IS_CHROMEOS) && defined(ADDRESS_SANITIZER)
+// Disabled on ASAN/debug due to test flakiness. See https://crbug.com/1517464
+#if defined(ADDRESS_SANITIZER) || !defined(NDEBUG)
 SINGLE_THREAD_TEST_F(LayerTreeHostAnimationTestIsAnimating);
 #else
 SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostAnimationTestIsAnimating);
diff --git a/chrome/VERSION b/chrome/VERSION
index 531cb78..8634f4c92 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=123
 MINOR=0
-BUILD=6298
+BUILD=6299
 PATCH=0
diff --git a/chrome/android/features/tab_ui/BUILD.gn b/chrome/android/features/tab_ui/BUILD.gn
index 53d1c46..c6186af 100644
--- a/chrome/android/features/tab_ui/BUILD.gn
+++ b/chrome/android/features/tab_ui/BUILD.gn
@@ -68,7 +68,6 @@
     "java/res/layout/closable_tab_list_card_item.xml",
     "java/res/layout/color_picker_item.xml",
     "java/res/layout/custom_message_card_item.xml",
-    "java/res/layout/data_sharing_group_bar.xml",
     "java/res/layout/declutter_message_card_layout.xml",
     "java/res/layout/incognito_description_container_layout.xml",
     "java/res/layout/iph_drag_and_drop_dialog_layout.xml",
diff --git a/chrome/android/features/tab_ui/java/res/layout/data_sharing_group_bar.xml b/chrome/android/features/tab_ui/java/res/layout/data_sharing_group_bar.xml
deleted file mode 100644
index 76df847c..0000000
--- a/chrome/android/features/tab_ui/java/res/layout/data_sharing_group_bar.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2024 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:id="@+id/dialog_data_sharing_group_bar"
-    android:orientation="vertical"
-    android:layout_alignParentBottom="true"
-    tools:ignore="UseCompoundDrawables">
-    <ImageView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:src="@drawable/toolbar_hairline"
-        tools:ignore="ContentDescription" />
-    <org.chromium.ui.widget.ButtonCompat
-        android:drawableStart="@drawable/ic_person_add_40dp"
-        android:drawableTint="@color/default_icon_color_accent1_tint_list"
-        android:id="@+id/dialog_share_invite_button"
-        android:layout_gravity="end"
-        android:layout_height="@dimen/bottom_sheet_peek_height"
-        android:layout_marginEnd="8dp"
-        android:layout_width="wrap_content"
-        android:paddingEnd="16dp"
-        android:text="@string/tab_grid_invite_button_text"
-        style="@style/TextButton" />
-</LinearLayout>
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java
index d37a60ba..7178b68 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java
@@ -14,7 +14,6 @@
 import android.graphics.RectF;
 import android.graphics.drawable.GradientDrawable;
 import android.util.AttributeSet;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
@@ -35,7 +34,6 @@
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Callback;
 import org.chromium.base.ResettersForTesting;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
 import org.chromium.components.browser_ui.widget.scrim.ScrimProperties;
@@ -815,13 +813,7 @@
         mDialogContainerView.addView(toolbarView);
         mDialogContainerView.addView(recyclerView);
         mDialogContainerView.addView(mUngroupBar);
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.DATA_SHARING_ANDROID)) {
-            // Add the data sharing bottom toolbar view.
-            LayoutInflater inflater = LayoutInflater.from(mDialogContainerView.getContext());
-            inflater.inflate(R.layout.data_sharing_group_bar, mDialogContainerView, true);
-        }
         mDialogContainerView.addView(mSnackBarContainer);
-
         RelativeLayout.LayoutParams params =
                 (RelativeLayout.LayoutParams) recyclerView.getLayoutParams();
         params.setMargins(0, mToolbarHeight, 0, 0);
diff --git a/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings.grd b/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings.grd
index 1b07376..4d1ec48 100644
--- a/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings.grd
+++ b/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings.grd
@@ -205,9 +205,6 @@
       <message name="IDS_ACCESSIBILITY_BOTTOM_TAB_GRID_CLOSE_TAB_SHEET" desc="Accessibility string for BottomTabGridToolbar button indicated visually by the 'v' sign.">
         Hide fullscreen grid
       </message>
-      <message name="IDS_TAB_GRID_INVITE_BUTTON_TEXT" desc="Text for sharing the current tab group from the tab group dialog bottom toolbar.">
-        Invite people
-      </message>
 
       <!-- Bottom Tab Strip strings -->
       <message name="IDS_ACCESSIBILITY_BOTTOM_TAB_STRIP_EXPAND_TAB_SHEET" desc="Accessibility string for BottomTabStripToolbar button indicated visually by the '^' sign.">
diff --git a/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings_grd/IDS_TAB_GRID_INVITE_BUTTON_TEXT.png.sha1 b/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings_grd/IDS_TAB_GRID_INVITE_BUTTON_TEXT.png.sha1
deleted file mode 100644
index 2ab5fe1..0000000
--- a/chrome/android/features/tab_ui/java/strings/android_chrome_tab_ui_strings_grd/IDS_TAB_GRID_INVITE_BUTTON_TEXT.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-6c332f87ae2fd945fa234c9e8d6189b9de0b85a7
\ No newline at end of file
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogViewTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogViewTest.java
index 89ff505..9b324db 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogViewTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogViewTest.java
@@ -21,9 +21,7 @@
 
 import org.hamcrest.Matchers;
 import org.junit.Assert;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.UiThreadTest;
@@ -31,10 +29,6 @@
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.Features;
-import org.chromium.base.test.util.Features.DisableFeatures;
-import org.chromium.base.test.util.Features.EnableFeatures;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.tasks.tab_management.TabGridDialogView.VisibilityListener;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -55,7 +49,6 @@
     private FrameLayout mTestParent;
     private View mSourceView;
     private View mUngroupBar;
-    private View mDataSharingBar;
     private View mAnimationCardView;
     private View mBackgroundFrameView;
     private TextView mUngroupBarTextView;
@@ -63,8 +56,6 @@
     private FrameLayout.LayoutParams mContainerParams;
     private TabGridDialogView mTabGridDialogView;
 
-    @Rule public TestRule mProcessor = new Features.JUnitProcessor();
-
     @Override
     public void setUpTest() throws Exception {
         super.setUpTest();
@@ -80,9 +71,6 @@
                             mTabGridDialogView.findViewById(R.id.dialog_container_view);
                     mUngroupBar = mTabGridDialogContainer.findViewById(R.id.dialog_ungroup_bar);
                     mUngroupBarTextView = mUngroupBar.findViewById(R.id.dialog_ungroup_bar_text);
-                    mDataSharingBar =
-                            mTabGridDialogContainer.findViewById(
-                                    R.id.dialog_data_sharing_group_bar);
                     mContainerParams =
                             (FrameLayout.LayoutParams) mTabGridDialogContainer.getLayoutParams();
                     mAnimationCardView =
@@ -151,7 +139,6 @@
     @Test
     @SmallTest
     @UiThreadTest
-    @DisableFeatures({ChromeFeatureList.DATA_SHARING_ANDROID})
     public void testResetDialog() {
         mTabGridDialogContainer.removeAllViews();
         View toolbarView = new View(getActivity());
@@ -173,25 +160,7 @@
     }
 
     @Test
-    @SmallTest
-    @UiThreadTest
-    @EnableFeatures({ChromeFeatureList.DATA_SHARING_ANDROID})
-    public void testResetDialogWithDataSharing() {
-        mTabGridDialogContainer.removeAllViews();
-        View toolbarView = new View(getActivity());
-        View recyclerView = new View(getActivity());
-        recyclerView.setVisibility(View.GONE);
-
-        mTabGridDialogView.resetDialog(toolbarView, recyclerView);
-
-        // It should contain five child views: top tool bar, recyclerview, ungroup bar, data sharing
-        // bar and undo bar container.
-        Assert.assertEquals(5, mTabGridDialogContainer.getChildCount());
-    }
-
-    @Test
     @MediumTest
-    @DisableFeatures({ChromeFeatureList.DATA_SHARING_ANDROID})
     public void testUpdateUngroupBar() {
         AtomicReference<ColorStateList> showTextColorReference = new AtomicReference<>();
         AtomicReference<ColorStateList> hoverTextColorReference = new AtomicReference<>();
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinderTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinderTest.java
index 87160fb..fec7f67 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinderTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinderTest.java
@@ -48,9 +48,7 @@
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.Features;
-import org.chromium.base.test.util.Features.DisableFeatures;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.tasks.tab_management.TabGridDialogView.VisibilityListener;
 import org.chromium.chrome.browser.theme.ThemeUtils;
 import org.chromium.chrome.tab_ui.R;
@@ -266,7 +264,6 @@
     @Test
     @SmallTest
     @UiThreadTest
-    @DisableFeatures({ChromeFeatureList.DATA_SHARING_ANDROID})
     public void testSetScrimViewObserver() {
         AtomicBoolean scrimViewClicked = new AtomicBoolean();
         scrimViewClicked.set(false);
@@ -282,7 +279,6 @@
 
     @Test
     @SmallTest
-    @DisableFeatures({ChromeFeatureList.DATA_SHARING_ANDROID})
     public void testSetDialogVisibility() {
         Assert.assertNull(mTabGridDialogView.getCurrentDialogAnimatorForTesting());
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSource.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSource.java
index 7ffdf92..ed73c91 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSource.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSource.java
@@ -382,19 +382,15 @@
                     DragDropTabResult.IGNORED_DIFF_MODEL_NOT_SUPPORTED);
             return false;
         }
-        int sourceInstanceId =
-                DragDropGlobalState.getState(sDragTrackerToken).getDragSourceInstance();
         if (!tabDraggedBelongToCurrentModel) {
             mMultiInstanceManager.moveTabToWindow(
                     getActivity(),
                     tabBeingDragged,
-                    mTabModelSelector.getModel(tabBeingDragged.isIncognito()).getCount(),
-                    sourceInstanceId);
+                    mTabModelSelector.getModel(tabBeingDragged.isIncognito()).getCount());
             showDroppedDifferentModelToast(mWindowAndroid.getContext().get());
         } else {
             int tabIndex = helper.getTabIndexForTabDrop(dropEvent.getX() * mPxToDp);
-            mMultiInstanceManager.moveTabToWindow(
-                    getActivity(), tabBeingDragged, tabIndex, sourceInstanceId);
+            mMultiInstanceManager.moveTabToWindow(getActivity(), tabBeingDragged, tabIndex);
             helper.mergeToGroupForTabDropIfNeeded(groupRootId, tabBeingDragged.getId(), tabIndex);
         }
         DragDropMetricUtils.recordTabDragDropType(DragDropType.TAB_STRIP_TO_TAB_STRIP);
@@ -436,6 +432,10 @@
             mMultiInstanceManager.moveTabToNewWindow(tabBeingDragged);
         }
 
+        // Get the drag source Chrome instance id before it is cleared as it may be closed.
+        int sourceInstanceId =
+                DragDropGlobalState.getState(sDragTrackerToken).getDragSourceInstance();
+
         // TODO (crbug.com/1497784): Remove this method.
         mStripLayoutHelperSupplier.get().clearTabDragState();
         if (mShadowView != null) {
@@ -449,6 +449,9 @@
         if (dropHandled) {
             DragDropMetricUtils.recordTabDragDropResult(DragDropTabResult.SUCCESS);
         }
+
+        // Close the source instance window if it has no tabs.
+        mMultiInstanceManager.closeChromeWindowIfEmpty(sourceInstanceId);
         return true;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/dragdrop/ChromeTabbedOnDragListener.java b/chrome/android/java/src/org/chromium/chrome/browser/dragdrop/ChromeTabbedOnDragListener.java
index 96b3524..030eeb5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/dragdrop/ChromeTabbedOnDragListener.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/dragdrop/ChromeTabbedOnDragListener.java
@@ -103,8 +103,7 @@
                         TabModelUtils.getTabIndexById(
                                         mTabModelSelector.getModel(currentTab.isIncognito()),
                                         currentTab.getId())
-                                + 1,
-                        globalState.getDragSourceInstance());
+                                + 1);
                 DragDropMetricUtils.recordTabDragDropType(DragDropType.TAB_STRIP_TO_CONTENT);
                 return true;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManager.java b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManager.java
index 55e4954..b073bd22 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManager.java
@@ -497,7 +497,7 @@
         // Not implemented
     }
 
-    public void moveTabToWindow(Activity activity, Tab tab, int atIndex, int fromWindowInstanceId) {
+    public void moveTabToWindow(Activity activity, Tab tab, int atIndex) {
         // Not implemented
     }
 
@@ -597,4 +597,13 @@
     public int getCurrentInstanceId() {
         return MultiWindowUtils.INVALID_INSTANCE_ID;
     }
+
+    /**
+     * Close a Chrome window instance only if it contains no open tabs including incognito ones.
+     *
+     * @param instanceId Instance id of the Chrome window that needs to be closed.
+     */
+    public void closeChromeWindowIfEmpty(int instanceId) {
+        // Not implemented.
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java
index 369c6da5..4a60015 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java
@@ -12,8 +12,6 @@
 import android.os.Build.VERSION;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Pair;
@@ -76,7 +74,6 @@
     public static final int INVALID_TASK_ID = MultiWindowUtils.INVALID_TASK_ID;
 
     private static final String EMPTY_DATA = "";
-    private static final long DEFAULT_WINDOW_CLOSING_DELAY_FOR_DRAG_DROP_IN_MILLIS = 50;
 
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     protected final int mMaxInstances;
@@ -146,30 +143,19 @@
                 mModalDialogManagerSupplier.get(),
                 new LargeIconBridge(getProfile()),
                 (instanceInfo) -> {
-                    moveTabAction(instanceInfo, tab, TabList.INVALID_TAB_INDEX, mInstanceId, false);
+                    moveTabAction(instanceInfo, tab, TabList.INVALID_TAB_INDEX);
+
+                    // Close the source instance window, if needed.
+                    closeChromeWindowIfEmpty(mInstanceId);
                 },
                 getInstanceInfo());
     }
 
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    void moveTabAction(
-            InstanceInfo info,
-            Tab tab,
-            int tabAtIndex,
-            int fromWindowInstanceId,
-            boolean delayInWindowClosing) {
+    void moveTabAction(InstanceInfo info, Tab tab, int tabAtIndex) {
         Activity targetActivity = getActivityById(info.instanceId);
         if (targetActivity != null) {
             reparentTabToRunningActivity((ChromeTabbedActivity) targetActivity, tab, tabAtIndex);
-
-            // Close the source instance window, if needed.
-            if (canCloseEmptyChromeWindow(fromWindowInstanceId)) {
-                closeEmptyChromeWindowAsynchronously(
-                        fromWindowInstanceId,
-                        delayInWindowClosing
-                                ? DEFAULT_WINDOW_CLOSING_DELAY_FOR_DRAG_DROP_IN_MILLIS
-                                : 0);
-            }
         } else {
             moveAndReparentTabToNewWindow(
                     tab,
@@ -953,16 +939,15 @@
      * @param activity Activity of the Chrome Window in which the tab is to be moved.
      * @param tab Tab that is to be moved to the current instance.
      * @param atIndex Tab position index in the destination window instance.
-     * @param fromWindowInstanceId InNstance Id of the Chrome window it is being moved from.
      */
     @Override
-    public void moveTabToWindow(Activity activity, Tab tab, int atIndex, int fromWindowInstanceId) {
+    public void moveTabToWindow(Activity activity, Tab tab, int atIndex) {
         if (!TabUiFeatureUtilities.isTabDragEnabled()) return;
 
         // Get the current instance and move tab there.
         InstanceInfo info = getInstanceInfoFor(activity);
         if (info != null) {
-            moveTabAction(info, tab, atIndex, fromWindowInstanceId, true);
+            moveTabAction(info, tab, atIndex);
         } else {
             Log.w(TAG, "DnD: InstanceInfo of Chrome Window not found.");
         }
@@ -995,32 +980,35 @@
         return null;
     }
 
-    private boolean canCloseEmptyChromeWindow(int windowInstanceId) {
+    /**
+     * Determine if a Chrome instance can be closed based on the environment.
+     *
+     * @param instanceId Instance Id of the Chrome window that needs to be closed.
+     */
+    private boolean canCloseChromeWindow(int instanceId) {
         // Close the source instance window after reparenting if permitted by the feature flag and
         // the source instance is known.
-        if (TabUiFeatureUtilities.isTabDragAsWindowEnabled()
-                && windowInstanceId != INVALID_INSTANCE_ID) {
-            TabModelSelector selector =
-                    TabWindowManagerSingleton.getInstance()
-                            .getTabModelSelectorById(windowInstanceId);
-            // Lastly determine if the drag source Chrome instance window has any tabs including
-            // incognito ones left so as to close if it is empty.
-            return selector.getTotalTabCount() == 0;
-        }
-        return false;
+        return TabUiFeatureUtilities.isTabDragAsWindowEnabled()
+                && instanceId != INVALID_INSTANCE_ID;
     }
 
-    private void closeEmptyChromeWindowAsynchronously(
-            final int instanceId, long delayInMilliseconds) {
-        new Handler(Looper.getMainLooper())
-                // Posting the message to process at the end of the current event queue.
-                // Delay is needed for closing the instance during drag and drop as the
-                // DragEvent.ACTION_DRAG_ENDED requires processing in the source instance.
-                .postDelayed(
-                        () -> {
-                            Log.i(TAG, "Closing empty Chrome instance as no tabs exist.");
-                            closeInstance(instanceId, INVALID_TASK_ID);
-                        },
-                        delayInMilliseconds);
+    /**
+     * Close a Chrome window instance only if it contains no open tabs including incognito ones.
+     *
+     * @param instanceId Instance id of the Chrome window that needs to be closed.
+     */
+    @Override
+    public void closeChromeWindowIfEmpty(int instanceId) {
+        if (canCloseChromeWindow(instanceId)) {
+            TabModelSelector selector =
+                    TabWindowManagerSingleton.getInstance().getTabModelSelectorById(instanceId);
+            // Determine if the drag source Chrome instance window has any tabs including incognito
+            // ones
+            // left so as to close if it is empty.
+            if (selector.getTotalTabCount() == 0) {
+                Log.i(TAG, "Closing empty Chrome instance as no tabs exist.");
+                closeInstance(instanceId, INVALID_TASK_ID);
+            }
+        }
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
index 3be78a8..7b8be25 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
@@ -42,7 +42,6 @@
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.layouts.content.InvalidationAwareThumbnailProvider;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
-import org.chromium.chrome.browser.download.DownloadManagerService;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
 import org.chromium.chrome.browser.feature_guide.notifications.FeatureNotificationUtils;
 import org.chromium.chrome.browser.feature_guide.notifications.FeatureType;
@@ -71,7 +70,6 @@
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
 import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.profiles.ProfileKey;
 import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
 import org.chromium.chrome.browser.search_resumption.SearchResumptionModuleCoordinator;
 import org.chromium.chrome.browser.search_resumption.SearchResumptionModuleUtils;
@@ -534,9 +532,6 @@
                         });
         mBrowserControlsStateProvider.addObserver(this);
 
-        DownloadManagerService.getDownloadManagerService()
-                .checkForExternallyRemovedDownloads(ProfileKey.getLastUsedRegularProfileKey());
-
         mToolbarHeight =
                 activity.getResources().getDimensionPixelSize(R.dimen.toolbar_height_no_shadow);
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSourceTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSourceTest.java
index be9ffc3..b82f32d 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSourceTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSourceTest.java
@@ -478,8 +478,7 @@
         verify(mSourceStripLayoutHelper, times(1)).onUpOrCancel(anyLong());
         // Verify tab is not moved.
         verify(mSourceMultiInstanceManager, times(0)).moveTabToNewWindow(mTabBeingDragged);
-        verify(mSourceMultiInstanceManager, times(0))
-                .moveTabToWindow(any(), any(), anyInt(), anyInt());
+        verify(mSourceMultiInstanceManager, times(0)).moveTabToWindow(any(), any(), anyInt());
         // Verify clear.
         verify(mSourceStripLayoutHelper, times(1)).clearTabDragState();
         // Verify destination strip not invoked.
@@ -516,8 +515,7 @@
                 .clearForTabDrop(anyLong(), anyBoolean(), anyBoolean());
         // Verify tab is not moved since drop is on source toolbar.
         verify(mSourceMultiInstanceManager, times(0)).moveTabToNewWindow(mTabBeingDragged);
-        verify(mSourceMultiInstanceManager, times(0))
-                .moveTabToWindow(any(), any(), anyInt(), anyInt());
+        verify(mSourceMultiInstanceManager, times(0)).moveTabToWindow(any(), any(), anyInt());
         // Verify tab cleared.
         verify(mSourceStripLayoutHelper, times(1)).clearTabDragState();
         // Verify destination strip not invoked.
@@ -546,8 +544,7 @@
                 .clearForTabDrop(anyLong(), anyBoolean(), anyBoolean());
         // Verify tab is not moved since drop is outside strip.
         verify(mSourceMultiInstanceManager, times(0)).moveTabToNewWindow(mTabBeingDragged);
-        verify(mSourceMultiInstanceManager, times(0))
-                .moveTabToWindow(any(), any(), anyInt(), anyInt());
+        verify(mSourceMultiInstanceManager, times(0)).moveTabToWindow(any(), any(), anyInt());
         // Verify tab cleared.
         verify(mSourceStripLayoutHelper, times(1)).clearTabDragState();
         // Verify destination strip not invoked.
@@ -596,7 +593,7 @@
 
         // Verify - Tab moved to destination window at TAB_INDEX.
         verify(mDestMultiInstanceManager, times(1))
-                .moveTabToWindow(any(), eq(mTabBeingDragged), eq(TAB_INDEX), eq(CURR_INSTANCE_ID));
+                .moveTabToWindow(any(), eq(mTabBeingDragged), eq(TAB_INDEX));
         // Verify tab cleared.
         verify(mSourceStripLayoutHelper, times(1)).clearTabDragState();
         // Verify destination strip calls.
@@ -630,7 +627,7 @@
 
         // Verify - Tab moved to destination window at end.
         verify(mDestMultiInstanceManager, times(1))
-                .moveTabToWindow(any(), eq(mTabBeingDragged), eq(5), eq(CURR_INSTANCE_ID));
+                .moveTabToWindow(any(), eq(mTabBeingDragged), eq(5));
 
         assertNotNull(ShadowToast.getLatestToast());
         TextView textView = (TextView) ShadowToast.getLatestToast().getView();
@@ -696,8 +693,7 @@
                 .clearForTabDrop(anyLong(), anyBoolean(), anyBoolean());
         // Verify tab is not moved since drop is on source toolbar.
         verify(mSourceMultiInstanceManager, times(0)).moveTabToNewWindow(mTabBeingDragged);
-        verify(mSourceMultiInstanceManager, times(0))
-                .moveTabToWindow(any(), any(), anyInt(), anyInt());
+        verify(mSourceMultiInstanceManager, times(0)).moveTabToWindow(any(), any(), anyInt());
         // Verify tab cleared.
         verify(mSourceStripLayoutHelper, times(1)).clearTabDragState();
         histogramExpectation.assertExpected();
@@ -730,8 +726,7 @@
         verify(mSourceStripLayoutHelper, times(1)).onUpOrCancel(anyLong());
         // Verify tab is not moved.
         verify(mSourceMultiInstanceManager, times(0)).moveTabToNewWindow(mTabBeingDragged);
-        verify(mSourceMultiInstanceManager, times(0))
-                .moveTabToWindow(any(), any(), anyInt(), anyInt());
+        verify(mSourceMultiInstanceManager, times(0)).moveTabToWindow(any(), any(), anyInt());
         // Verify clear.
         verify(mSourceStripLayoutHelper, times(1)).clearTabDragState();
         // Verify destination strip not invoked.
@@ -778,7 +773,7 @@
 
         // Verify - Move to new window not invoked.
         verify(mDestMultiInstanceManager, times(0))
-                .moveTabToWindow(any(), eq(mTabBeingDragged), anyInt(), anyInt());
+                .moveTabToWindow(any(), eq(mTabBeingDragged), anyInt());
         histogramExpectation.assertExpected();
     }
 
@@ -805,7 +800,7 @@
 
         // Verify - Tab is not moved to destination window.
         verify(mDestMultiInstanceManager, times(0))
-                .moveTabToWindow(any(), eq(mTabBeingDragged), anyInt(), anyInt());
+                .moveTabToWindow(any(), eq(mTabBeingDragged), anyInt());
 
         assertNull(ShadowToast.getLatestToast());
         histogramExpectation.assertExpected();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java
index 6ec0f46..8496a3b1d 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java
@@ -1131,15 +1131,13 @@
 
         Mockito.doNothing()
                 .when(mMultiInstanceManager)
-                .moveTabAction(any(), eq(mTab1), eq(tabAtIndex), anyInt(), eq(true));
+                .moveTabAction(any(), eq(mTab1), eq(tabAtIndex));
 
         // Action
-        mMultiInstanceManager.moveTabToWindow(
-                mTabbedActivityTask63, mTab1, tabAtIndex, INSTANCE_ID_1);
+        mMultiInstanceManager.moveTabToWindow(mTabbedActivityTask63, mTab1, tabAtIndex);
 
         // Verify moveTabAction and getCurrentInstanceInfo are each called once.
-        verify(mMultiInstanceManager, times(1))
-                .moveTabAction(any(), eq(mTab1), eq(tabAtIndex), anyInt(), eq(true));
+        verify(mMultiInstanceManager, times(1)).moveTabAction(any(), eq(mTab1), eq(tabAtIndex));
         verify(mMultiInstanceManager, times(1)).getInstanceInfoFor(any());
     }
 
@@ -1154,15 +1152,13 @@
                                 INSTANCE_ID_1, TASK_ID_62, List.of(mTab1, mTab2, mTab3)));
         Mockito.doNothing()
                 .when(multiInstanceManager)
-                .moveTabAction(any(), eq(mTab2), eq(tabAtIndex), anyInt(), eq(false));
+                .moveTabAction(any(), eq(mTab2), eq(tabAtIndex));
 
         // Action
-        multiInstanceManager.moveTabToWindow(
-                mTabbedActivityTask62, mTab2, tabAtIndex, INSTANCE_ID_1);
+        multiInstanceManager.moveTabToWindow(mTabbedActivityTask62, mTab2, tabAtIndex);
 
         // Verify moveTabAction is not called.
-        verify(multiInstanceManager, times(0))
-                .moveTabAction(any(), eq(mTab2), eq(tabAtIndex), anyInt(), eq(false));
+        verify(multiInstanceManager, times(0)).moveTabAction(any(), eq(mTab2), eq(tabAtIndex));
     }
 
     @Test
@@ -1177,7 +1173,7 @@
 
         // Action
         InstanceInfo info = mMultiInstanceManager.getInstanceInfoFor(mTabbedActivityTask63);
-        mMultiInstanceManager.moveTabAction(info, mTab1, /* atIndex= */ 0, INSTANCE_ID_1, true);
+        mMultiInstanceManager.moveTabAction(info, mTab1, /* atIndex= */ 0);
 
         // Verify reparentTabToRunningActivity is called once.
         verify(mMultiInstanceManager, times(1))
@@ -1223,7 +1219,7 @@
                         0,
                         false);
 
-        mMultiInstanceManager.moveTabAction(info, mTab1, /* atIndex= */ 0, INSTANCE_ID_1, true);
+        mMultiInstanceManager.moveTabAction(info, mTab1, /* atIndex= */ 0);
 
         // Verify moveAndReparentTabToNewWindow is called made with desired parameters once. The
         // method is validated in integration test here
@@ -1235,4 +1231,43 @@
         verify(mMultiInstanceManager, times(0))
                 .reparentTabToRunningActivity(any(), eq(mTab1), eq(0));
     }
+
+    @Test
+    @UiThreadTest
+    @EnableFeatures(ChromeFeatureList.TAB_DRAG_DROP_ANDROID)
+    @Config(sdk = 31)
+    public void testTabMove_CloseChromeWindowIfEmpty_closed() {
+        mMultiInstanceManager.mTestBuildInstancesList = true;
+        MultiWindowTestUtils.enableMultiInstance();
+        // Create an empty instance before asking it to close. The flag that provides permission to
+        // close is enabled.
+        assertEquals(INSTANCE_ID_1, allocInstanceIndex(INSTANCE_ID_1, mTabbedActivityTask62, true));
+        assertEquals(1, mMultiInstanceManager.getInstanceInfo().size());
+
+        // Action
+        mMultiInstanceManager.closeChromeWindowIfEmpty(INSTANCE_ID_1);
+
+        // Verify moveTabAction and getCurrentInstanceInfo are each called once.
+        verify(mMultiInstanceManager, times(1))
+                .closeInstance(anyInt(), eq(MultiWindowUtils.INVALID_TASK_ID));
+    }
+
+    @Test
+    @UiThreadTest
+    @DisableFeatures(ChromeFeatureList.TAB_DRAG_DROP_ANDROID)
+    @Config(sdk = 31)
+    public void testTabMove_CloseChromeWindowIfEmpty_notClosed() {
+        mMultiInstanceManager.mTestBuildInstancesList = true;
+        MultiWindowTestUtils.enableMultiInstance();
+        // Create an empty instance before asking it to close. The flag that provides permission to
+        // close is disabled.
+        assertEquals(INSTANCE_ID_1, allocInstanceIndex(INSTANCE_ID_1, mTabbedActivityTask62, true));
+        assertEquals(1, mMultiInstanceManager.getInstanceInfo().size());
+
+        // Action
+        mMultiInstanceManager.closeChromeWindowIfEmpty(INSTANCE_ID_1);
+
+        // Verify moveTabAction and getCurrentInstanceInfo are each called once.
+        verify(mMultiInstanceManager, times(0)).closeInstance(anyInt(), anyInt());
+    }
 }
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 162bc89..a1ab750 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -6404,6 +6404,9 @@
       <message name="IDS_EXTENSIONS_MENU_MESSAGE_SECTION_RESTRICTED_ACCESS_TEXT" desc="Text of the message section when this site is restricted to all extensions.">
         No extensions need access to this site
       </message>
+      <message name="IDS_EXTENSIONS_MENU_MESSAGE_SECTION_POLICY_BLOCKED_ACCESS_TEXT" desc="Text of the message section when the site is restricted to all non-enterprise extensions.">
+        Extensions are not allowed on this site
+      </message>
       <message name="IDS_EXTENSIONS_MENU_MESSAGE_SECTION_USER_BLOCKED_ACCESS_TEXT" desc="Text of the message section when the user blocked access to this site to all extensions.">
         Extensions are not allowed on this site
       </message>
@@ -10804,45 +10807,24 @@
         Learn more about tab group suggestions
       </message>
       <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_SIGNED_OUT" desc="The body text for the not started state in the tab organization UI, when not signed in">
-        Sign in and turn on sync to let Chrome suggest tab groups and keep your tabs organized
-      </message>
-      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_UNSYNCED" desc="The body text for the not started state in the tab organization UI, when signed in but unsynced">
-        Turn on sync to let Chrome suggest tab groups and keep your tabs organized
-      </message>
-      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_UNSYNCED_HISTORY" desc="The body text for the not started state in the tab organization UI, when history sync is disabled">
-        Turn on History sync in Settings to let Chrome suggest tab groups and keep your tabs organized
-      </message>
-      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_SYNC_PAUSED" desc="The body text for the not started state in the tab organization UI, sync is paused">
         Sign in to let Chrome suggest tab groups and keep your tabs organized
       </message>
-      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON" desc="The label for the button in the not started state of the tab organization UI, when synced">
+      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON" desc="The label for the button in the not started state of the tab organization UI, when signed in">
         Check now
       </message>
-      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_FRE" desc="The first run experience label for the button in the not started state of the tab organization UI, when synced">
+      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_FRE" desc="The first run experience label for the button in the not started state of the tab organization UI, when signed in">
         Let's go
       </message>
-      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_UNSYNCED" desc="The label for the button in the not started state of the tab organization UI, when not signed in or signed in but unsynced">
-        Turn on sync
-      </message>
-      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_UNSYNCED_HISTORY" desc="The label for the button in the not started state of the tab organization UI, when history sync is disabled">
-        Settings
-      </message>
-      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_SYNC_PAUSED" desc="The label for the button in the not started state of the tab organization UI, when sync is paused">
+      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_SIGNED_OUT" desc="The label for the button in the not started state of the tab organization UI, when not signed in">
         Sign in
       </message>
-      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_ARIA_LABEL" desc="The a11y label for the button in the not started state of the tab organization UI, when synced" is_accessibility_with_no_ui="true">
+      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_ARIA_LABEL" desc="The a11y label for the button in the not started state of the tab organization UI, when signed in" is_accessibility_with_no_ui="true">
         Check now if tabs can be organized
       </message>
-      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_FRE_ARIA_LABEL" desc="The first run experience a11y label for the button in the not started state of the tab organization UI, when synced" is_accessibility_with_no_ui="true">
+      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_FRE_ARIA_LABEL" desc="The first run experience a11y label for the button in the not started state of the tab organization UI, when signed in" is_accessibility_with_no_ui="true">
         Let's go and organize your tabs
       </message>
-      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_UNSYNCED_ARIA_LABEL" desc="The a11y label for the button in the not started state of the tab organization UI, when not signed in or signed in but unsynced" is_accessibility_with_no_ui="true">
-        Turn on sync to let Chrome suggest tab groups
-      </message>
-      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_UNSYNCED_HISTORY_ARIA_LABEL" desc="The a11y label for the button in the not started state of the tab organization UI, when history sync is disabled" is_accessibility_with_no_ui="true">
-        Open settings to let Chrome suggest tab groups
-      </message>
-      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_SYNC_PAUSED_ARIA_LABEL" desc="The a11y label for the button in the not started state of the tab organization UI, when sync is paused" is_accessibility_with_no_ui="true">
+      <message name="IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_SIGNED_OUT_ARIA_LABEL" desc="The a11y label for the button in the not started state of the tab organization UI, when not signed in" is_accessibility_with_no_ui="true">
         Sign in to let Chrome suggest tab groups
       </message>
       <message name="IDS_TAB_ORGANIZATION_IN_PROGRESS_TITLE" desc="The header text for the in progress state in the tab organization UI">
diff --git a/chrome/app/generated_resources_grd/IDS_EXTENSIONS_MENU_MESSAGE_SECTION_POLICY_BLOCKED_ACCESS_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_EXTENSIONS_MENU_MESSAGE_SECTION_POLICY_BLOCKED_ACCESS_TEXT.png.sha1
new file mode 100644
index 0000000..3722c20
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_EXTENSIONS_MENU_MESSAGE_SECTION_POLICY_BLOCKED_ACCESS_TEXT.png.sha1
@@ -0,0 +1 @@
+9781c8bf768b7ae3a705476eee9afadbebdf9434
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_SIGNED_OUT.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_SIGNED_OUT.png.sha1
index 3cce170..bfb6f7e 100644
--- a/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_SIGNED_OUT.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_SIGNED_OUT.png.sha1
@@ -1 +1 @@
-229d5d94f765211db84552b420734b2b414350c6
\ No newline at end of file
+dc79c4fc4f970e8152b9ea60ee0e1cb4aa6984fe
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_SYNC_PAUSED.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_SYNC_PAUSED.png.sha1
deleted file mode 100644
index acecd83..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_SYNC_PAUSED.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-aefddc2ca85392563aac0b8b121109b4bb8983b4
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_UNSYNCED.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_UNSYNCED.png.sha1
deleted file mode 100644
index 738a73b9..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_UNSYNCED.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-9db679e9293cdc7b8abe42195e49f6ef183348cf
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_UNSYNCED_HISTORY.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_UNSYNCED_HISTORY.png.sha1
deleted file mode 100644
index 4107492..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_UNSYNCED_HISTORY.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-2242a9208af02e6f0ca4e650c967b96df8e08a54
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_SIGNED_OUT.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_SIGNED_OUT.png.sha1
new file mode 100644
index 0000000..bfb6f7e
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_SIGNED_OUT.png.sha1
@@ -0,0 +1 @@
+dc79c4fc4f970e8152b9ea60ee0e1cb4aa6984fe
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_SYNC_PAUSED.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_SYNC_PAUSED.png.sha1
deleted file mode 100644
index acecd83..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_SYNC_PAUSED.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-aefddc2ca85392563aac0b8b121109b4bb8983b4
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_UNSYNCED.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_UNSYNCED.png.sha1
deleted file mode 100644
index 738a73b9..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_UNSYNCED.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-9db679e9293cdc7b8abe42195e49f6ef183348cf
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_UNSYNCED_HISTORY.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_UNSYNCED_HISTORY.png.sha1
deleted file mode 100644
index 4107492..0000000
--- a/chrome/app/generated_resources_grd/IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_UNSYNCED_HISTORY.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-2242a9208af02e6f0ca4e650c967b96df8e08a54
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 6eefb9c2..180a1b3 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4819,6 +4819,9 @@
      flag_descriptions::kInstallIsolatedWebAppFromUrlDescription, kOsAll,
      ORIGIN_LIST_VALUE_TYPE(switches::kInstallIsolatedWebAppFromUrl, "")},
 #endif
+    {"enable-controlled-frame", flag_descriptions::kEnableControlledFrameName,
+     flag_descriptions::kEnableControlledFrameDescription, kOsAll,
+     FEATURE_VALUE_TYPE(features::kControlledFrame)},
     {"isolate-origins", flag_descriptions::kIsolateOriginsName,
      flag_descriptions::kIsolateOriginsDescription, kOsAll,
      ORIGIN_LIST_VALUE_TYPE(switches::kIsolateOrigins, "")},
diff --git a/chrome/browser/android/persisted_tab_data/persisted_tab_data_android_browsertest.cc b/chrome/browser/android/persisted_tab_data/persisted_tab_data_android_browsertest.cc
index 538c0cca..247b1db 100644
--- a/chrome/browser/android/persisted_tab_data/persisted_tab_data_android_browsertest.cc
+++ b/chrome/browser/android/persisted_tab_data/persisted_tab_data_android_browsertest.cc
@@ -177,6 +177,12 @@
   OnDeferredStartup();
   content::RunAllTasksUntilIdle();
 
+  if (!tab_android()->GetUserData(FooPersistedTabDataAndroid::UserDataKey())) {
+    tab_android()->SetUserData(
+        FooPersistedTabDataAndroid::UserDataKey(),
+        std::make_unique<FooPersistedTabDataAndroid>(tab_android()));
+  }
+
   FooPersistedTabDataAndroid* foo_persisted_tab_data_android =
       static_cast<FooPersistedTabDataAndroid*>(tab_android()->GetUserData(
           FooPersistedTabDataAndroid::UserDataKey()));
diff --git a/chrome/browser/apps/app_service/policy_util.cc b/chrome/browser/apps/app_service/policy_util.cc
index 2a72e3a..71b30ae8 100644
--- a/chrome/browser/apps/app_service/policy_util.cc
+++ b/chrome/browser/apps/app_service/policy_util.cc
@@ -63,7 +63,8 @@
          {"os_url_handler", ash::SystemWebAppType::OS_URL_HANDLER},
          {"firmware_update", ash::SystemWebAppType::FIRMWARE_UPDATE},
          {"os_flags", ash::SystemWebAppType::OS_FLAGS},
-         {"vc_background", ash::SystemWebAppType::VC_BACKGROUND}});
+         {"vc_background", ash::SystemWebAppType::VC_BACKGROUND},
+         {"print_preview_cros", ash::SystemWebAppType::PRINT_PREVIEW_CROS}});
 
 constexpr ash::SystemWebAppType GetMaxSystemWebAppType() {
   return base::ranges::max(kSystemWebAppsMapping, base::ranges::less{},
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index 2f0043f..7062c0da 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -3407,6 +3407,8 @@
     "system_web_apps/apps/personalization_app/wallpaper_metrics_provider.h",
     "system_web_apps/apps/print_management_web_app_info.cc",
     "system_web_apps/apps/print_management_web_app_info.h",
+    "system_web_apps/apps/print_preview_cros_system_web_app_info.cc",
+    "system_web_apps/apps/print_preview_cros_system_web_app_info.h",
     "system_web_apps/apps/projector_app/untrusted_projector_annotator_ui_config.cc",
     "system_web_apps/apps/projector_app/untrusted_projector_annotator_ui_config.h",
     "system_web_apps/apps/projector_app/untrusted_projector_ui_config.cc",
@@ -3618,6 +3620,8 @@
     "//ash/webui/os_feedback_ui/backend",
     "//ash/webui/personalization_app",
     "//ash/webui/personalization_app/mojom",
+    "//ash/webui/print_preview_cros",
+    "//ash/webui/print_preview_cros:url_constants",
     "//ash/webui/projector_app",
     "//ash/webui/scanning",
     "//ash/webui/scanning/mojom",
@@ -4014,6 +4018,8 @@
     "//ash/webui/print_management",
     "//ash/webui/print_management/backend:backend",
     "//ash/webui/print_management/resources",
+    "//ash/webui/print_preview_cros",
+    "//ash/webui/print_preview_cros/resources:resources",
     "//ash/webui/projector_app/public/cpp",
     "//ash/webui/resources:camera_app_resources",
     "//ash/webui/resources:demo_mode_app_resources",
diff --git a/chrome/browser/ash/app_list/arc/arc_app_unittest.cc b/chrome/browser/ash/app_list/arc/arc_app_unittest.cc
index d64996ca..8bf404ef 100644
--- a/chrome/browser/ash/app_list/arc/arc_app_unittest.cc
+++ b/chrome/browser/ash/app_list/arc/arc_app_unittest.cc
@@ -512,8 +512,6 @@
 
     // Validating decoded content does not fit well for unit tests.
     ArcAppIcon::DisableSafeDecodingForTesting();
-
-    scoped_feature_list_.InitAndEnableFeature(arc::kPerAppLanguage);
   }
 
   void TearDown() override {
@@ -876,8 +874,6 @@
     prefs->SimulateDefaultAppAvailabilityTimeoutForTesting();
   }
 
-  void ResetFeatureFlag() { scoped_feature_list_.Reset(); }
-
   AppListControllerDelegate* controller() { return controller_.get(); }
 
   TestingProfile* profile() { return profile_.get(); }
@@ -920,7 +916,6 @@
       scoped_callback_;
   std::unique_ptr<ChromeShelfController> shelf_controller_;
   std::unique_ptr<ash::ShelfModel> model_;
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 class ArcAppModelBuilderRecreate : public ArcAppModelBuilderTest {
@@ -1455,7 +1450,8 @@
 
 TEST_P(ArcAppModelBuilderTest, ArcPackagePref_PerAppLanguageFlagDisabled) {
   ValidateHavePackages({});
-  ResetFeatureFlag();
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(arc::kPerAppLanguage);
 
   app_instance()->SendRefreshPackageList(
       ArcAppTest::ClonePackages(fake_packages()));
diff --git a/chrome/browser/ash/app_list/search/essential_search/essential_search_manager.cc b/chrome/browser/ash/app_list/search/essential_search/essential_search_manager.cc
index e2bb49f..6a7a2ed 100644
--- a/chrome/browser/ash/app_list/search/essential_search/essential_search_manager.cc
+++ b/chrome/browser/ash/app_list/search/essential_search/essential_search_manager.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ash/app_list/search/essential_search/essential_search_manager.h"
 
+#include "base/check_is_test.h"
 #include "base/time/time.h"
 #include "chrome/browser/ash/app_list/search/essential_search/socs_cookie_fetcher.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
@@ -47,8 +48,12 @@
       retry_backoff_(&kFetchSocsCookieRetryBackoffPolicy) {
   DCHECK(primary_profile_);
   auto* session_controller = ash::SessionController::Get();
-  CHECK(session_controller);
-  scoped_observation_.Observe(session_controller);
+  if (!session_controller) {
+    CHECK_IS_TEST();
+  } else {
+    CHECK(session_controller);
+    session_controller->AddObserver(this);
+  }
 
   // Listen to pref changes.
   pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
@@ -60,12 +65,19 @@
 
   // Handle the case where EssentialSearchManager is initialized after the
   // session was started.
-  if (session_manager::SessionManager::Get()->IsSessionStarted()) {
+  if (!session_manager::SessionManager::Get()) {
+    CHECK_IS_TEST();
+  } else if (session_manager::SessionManager::Get()->IsSessionStarted()) {
     MaybeFetchSocsCookie();
   }
 }
 
-EssentialSearchManager::~EssentialSearchManager() = default;
+EssentialSearchManager::~EssentialSearchManager() {
+  auto* session_controller = ash::SessionController::Get();
+  if (session_controller) {
+    session_controller->RemoveObserver(this);
+  }
+}
 
 // static
 std::unique_ptr<EssentialSearchManager> EssentialSearchManager::Create(
@@ -73,10 +85,6 @@
   return std::make_unique<EssentialSearchManager>(primary_profile);
 }
 
-void EssentialSearchManager::OnChromeTerminating() {
-  scoped_observation_.Reset();
-}
-
 void EssentialSearchManager::OnSessionStateChanged(
     session_manager::SessionState state) {
   // EssentialSearchManager only update SOCS cookie when user sign in/unlock the
diff --git a/chrome/browser/ash/app_list/search/essential_search/essential_search_manager.h b/chrome/browser/ash/app_list/search/essential_search/essential_search_manager.h
index 5255c55..a853f646 100644
--- a/chrome/browser/ash/app_list/search/essential_search/essential_search_manager.h
+++ b/chrome/browser/ash/app_list/search/essential_search/essential_search_manager.h
@@ -46,7 +46,6 @@
 
   // SessionObserver:
   void OnSessionStateChanged(session_manager::SessionState state) override;
-  void OnChromeTerminating() override;
 
   // SocsCookieFetcher::Consumer
   void OnCookieFetched(const std::string& socs_cookie) override;
@@ -69,10 +68,6 @@
   // Cancel all active requests
   void CancelPendingRequests();
 
-  // Used to observe the change in session state.
-  base::ScopedObservation<ash::SessionController, ash::SessionObserver>
-      scoped_observation_{this};
-
   // Observer for EssentialSearch-related prefs.
   std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_;
 
diff --git a/chrome/browser/ash/app_restore/full_restore_service.cc b/chrome/browser/ash/app_restore/full_restore_service.cc
index 8311048b7..208ab14 100644
--- a/chrome/browser/ash/app_restore/full_restore_service.cc
+++ b/chrome/browser/ash/app_restore/full_restore_service.cc
@@ -15,7 +15,7 @@
 #include "ash/webui/settings/public/constants/setting.mojom-shared.h"
 #include "ash/wm/desks/templates/saved_desk_controller.h"
 #include "ash/wm/window_restore/pine_contents_data.h"
-#include "ash/wm/window_restore/window_restore_controller.h"
+#include "ash/wm/window_restore/pine_controller.h"
 #include "ash/wm/window_restore/window_restore_util.h"
 #include "base/command_line.h"
 #include "base/metrics/histogram_functions.h"
@@ -88,6 +88,30 @@
   }
 }
 
+std::unique_ptr<PineContentsData> CreatePineContentsData(
+    ::app_restore::RestoreData* restore_data,
+    bool last_session_crashed) {
+  auto pine_contents_data = std::make_unique<PineContentsData>();
+  pine_contents_data->last_session_crashed = last_session_crashed;
+
+  // Retrieve app id's from `restore_data`. There can be multiple entries with
+  // the same app id, these denote different windows.
+  // TODO(sammiequon): App id's for PWAs are stored in the full restore file
+  // with the chrome browser app id. We need to get the app name from the
+  // session restore.
+  // TODO(sammiequon): Retrieve the browser tab info from session restore.
+  // TODO(sammiequon): Order these by activation index.
+  for (const auto& [app_id, launch_list] :
+       restore_data->app_id_to_launch_list()) {
+    for (size_t i = 0; i < launch_list.size(); ++i) {
+      pine_contents_data->apps_infos.emplace_back(
+          app_id, /*tab_title=*/"",
+          /*tab_urls=*/std::vector<std::string>());
+    }
+  }
+  return pine_contents_data;
+}
+
 }  // namespace
 
 bool g_restore_for_testing = true;
@@ -131,15 +155,11 @@
   ~DelegateImpl() override = default;
 
   void MaybeStartPineOverviewSession(
-      std::unique_ptr<::app_restore::RestoreData> restore_data) override {
+      std::unique_ptr<PineContentsData> pine_contents_data) override {
     // A unit test that does not override this default delegate may not have ash
     // shell.
     if (Shell::HasInstance()) {
-      // TODO(sammiequon): Instead of passing the whole restore data, we should
-      // pull out the data we need.
-      auto pine_contents_data = std::make_unique<PineContentsData>();
-      pine_contents_data->restore_data = std::move(restore_data);
-      Shell::Get()->window_restore_controller()->MaybeStartPineOverviewSession(
+      Shell::Get()->pine_controller()->MaybeStartPineOverviewSession(
           std::move(pine_contents_data));
     }
   }
@@ -480,8 +500,10 @@
   // session restore to help set the browser saving flag.
   ExitTypeService* exit_type_service =
       ExitTypeService::GetInstanceForProfile(profile_);
-  if (id == kRestoreForCrashNotificationId && exit_type_service)
+  const bool last_session_crashed = id == kRestoreForCrashNotificationId;
+  if (last_session_crashed && exit_type_service) {
     crashed_lock_ = exit_type_service->CreateCrashedLock();
+  }
 
   if (Shell::HasInstance()) {
     Shell::Get()
@@ -491,8 +513,8 @@
 
   if (features::IsForestFeatureEnabled()) {
     CHECK(delegate_);
-    delegate_->MaybeStartPineOverviewSession(
-        app_launch_handler_->restore_data()->Clone());
+    delegate_->MaybeStartPineOverviewSession(CreatePineContentsData(
+        app_launch_handler_->restore_data(), last_session_crashed));
     // Set to true as we might want to show the post reboot notification.
     show_notification = true;
     return;
diff --git a/chrome/browser/ash/app_restore/full_restore_service.h b/chrome/browser/ash/app_restore/full_restore_service.h
index 60871e2..3c984956 100644
--- a/chrome/browser/ash/app_restore/full_restore_service.h
+++ b/chrome/browser/ash/app_restore/full_restore_service.h
@@ -21,15 +21,15 @@
 
 class Profile;
 
-namespace app_restore {
-class RestoreData;
-}  // namespace app_restore
-
 namespace message_center {
 class Notification;
 }  // namespace message_center
 
-namespace ash::full_restore {
+namespace ash {
+
+struct PineContentsData;
+
+namespace full_restore {
 
 class FullRestoreAppLaunchHandler;
 class FullRestoreDataHandler;
@@ -78,7 +78,7 @@
     virtual ~Delegate() = default;
     // Starts overview with the pine dialog unless overview is already active.
     virtual void MaybeStartPineOverviewSession(
-        std::unique_ptr<::app_restore::RestoreData> restore_data) = 0;
+        std::unique_ptr<PineContentsData> pine_contents_data) = 0;
   };
 
   static FullRestoreService* GetForProfile(Profile* profile);
@@ -210,6 +210,8 @@
   ~ScopedRestoreForTesting();
 };
 
-}  // namespace ash::full_restore
+}  // namespace full_restore
+
+}  // namespace ash
 
 #endif  // CHROME_BROWSER_ASH_APP_RESTORE_FULL_RESTORE_SERVICE_H_
diff --git a/chrome/browser/ash/app_restore/full_restore_service_unittest.cc b/chrome/browser/ash/app_restore/full_restore_service_unittest.cc
index a0a472d..8d71592c 100644
--- a/chrome/browser/ash/app_restore/full_restore_service_unittest.cc
+++ b/chrome/browser/ash/app_restore/full_restore_service_unittest.cc
@@ -10,6 +10,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
 #include "ash/constants/ash_switches.h"
+#include "ash/wm/window_restore/pine_contents_data.h"
 #include "ash/wm/window_restore/window_restore_util.h"
 #include "base/command_line.h"
 #include "base/files/scoped_temp_dir.h"
@@ -107,7 +108,7 @@
 
   MOCK_METHOD(void,
               MaybeStartPineOverviewSession,
-              (std::unique_ptr<::app_restore::RestoreData> restore_data),
+              (std::unique_ptr<ash::PineContentsData> restore_data),
               (override));
 };
 
@@ -618,33 +619,6 @@
   FullRestoreService::MaybeCloseNotification(profile());
 }
 
-// TODO(sammiequon): These forest tests should be either parameterized or use
-// their own suite, once the direction is more clear.
-TEST_F(FullRestoreServiceTestHavingFullRestoreFile,
-       AskEveryTimeAndRestoreForest) {
-  base::test::ScopedFeatureList scoped_feature_list_{features::kForestFeature};
-  switches::SetIgnoreForestSecretKeyForTest(true);
-
-  profile()->GetPrefs()->SetInteger(
-      prefs::kRestoreAppsAndPagesPrefName,
-      static_cast<int>(RestoreOption::kAskEveryTime));
-  auto mock_delegate = std::make_unique<MockFullRestoreServiceDelegate>();
-  EXPECT_CALL(*mock_delegate, MaybeStartPineOverviewSession(testing::_))
-      .Times(1);
-  CreateFullRestoreServiceForTesting(std::move(mock_delegate));
-
-  // The notification should not show up anymore with forest enabled.
-  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 1);
-  VerifyNotification(/*has_crash_notification=*/false,
-                     /*has_restore_notification=*/false);
-
-  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
-  EXPECT_TRUE(CanPerformRestore(account_id()));
-  EXPECT_TRUE(allow_save());
-
-  switches::SetIgnoreForestSecretKeyForTest(false);
-}
-
 // If the OS restore setting is 'Ask every time', after reboot, show the restore
 // notification, and verify the restore flag when click the Settings button.
 TEST_F(FullRestoreServiceTestHavingFullRestoreFile, AskEveryTimeAndSettings) {
@@ -782,6 +756,71 @@
   EXPECT_FALSE(CanPerformRestore(account_id()));
 }
 
+class ForestFullRestoreServiceTestHavingFullRestoreFile
+    : public FullRestoreServiceTestHavingFullRestoreFile {
+ protected:
+  ForestFullRestoreServiceTestHavingFullRestoreFile() = default;
+  ForestFullRestoreServiceTestHavingFullRestoreFile(
+      const ForestFullRestoreServiceTestHavingFullRestoreFile&) = delete;
+  ForestFullRestoreServiceTestHavingFullRestoreFile& operator=(
+      const ForestFullRestoreServiceTestHavingFullRestoreFile&) = delete;
+  ~ForestFullRestoreServiceTestHavingFullRestoreFile() override = default;
+
+  void SetUp() override {
+    switches::SetIgnoreForestSecretKeyForTest(true);
+    FullRestoreServiceTestHavingFullRestoreFile::SetUp();
+  }
+
+  void TearDown() override {
+    switches::SetIgnoreForestSecretKeyForTest(false);
+    FullRestoreServiceTestHavingFullRestoreFile::TearDown();
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_{features::kForestFeature};
+};
+
+// If the system is crash, the delegate is notified.
+TEST_F(ForestFullRestoreServiceTestHavingFullRestoreFile, CrashAndRestore) {
+  ExitTypeService::GetInstanceForProfile(profile())
+      ->SetLastSessionExitTypeForTest(ExitType::kCrashed);
+
+  auto mock_delegate = std::make_unique<MockFullRestoreServiceDelegate>();
+  EXPECT_CALL(*mock_delegate, MaybeStartPineOverviewSession(testing::_))
+      .WillOnce([](std::unique_ptr<PineContentsData> data) {
+        ASSERT_TRUE(data);
+        EXPECT_TRUE(data->last_session_crashed);
+      });
+  CreateFullRestoreServiceForTesting(std::move(mock_delegate));
+
+  // The notification should not show up anymore with forest enabled.
+  VerifyNotification(/*has_crash_notification=*/false,
+                     /*has_restore_notification=*/false);
+
+  EXPECT_TRUE(CanPerformRestore(account_id()));
+  EXPECT_TRUE(allow_save());
+}
+
+TEST_F(ForestFullRestoreServiceTestHavingFullRestoreFile,
+       AskEveryTimeAndRestore) {
+  profile()->GetPrefs()->SetInteger(
+      prefs::kRestoreAppsAndPagesPrefName,
+      static_cast<int>(RestoreOption::kAskEveryTime));
+  auto mock_delegate = std::make_unique<MockFullRestoreServiceDelegate>();
+  EXPECT_CALL(*mock_delegate, MaybeStartPineOverviewSession(testing::_))
+      .Times(1);
+  CreateFullRestoreServiceForTesting(std::move(mock_delegate));
+
+  // The notification should not show up anymore with forest enabled.
+  VerifyRestoreInitSettingHistogram(RestoreOption::kAskEveryTime, 1);
+  VerifyNotification(/*has_crash_notification=*/false,
+                     /*has_restore_notification=*/false);
+
+  EXPECT_EQ(RestoreOption::kAskEveryTime, GetRestoreOption());
+  EXPECT_TRUE(CanPerformRestore(account_id()));
+  EXPECT_TRUE(allow_save());
+}
+
 class FullRestoreServiceMultipleUsersTest
     : public FullRestoreServiceTestHavingFullRestoreFile {
  protected:
diff --git a/chrome/browser/ash/arc/input_overlay/ui/action_edit_view.cc b/chrome/browser/ash/arc/input_overlay/ui/action_edit_view.cc
index dae1498..d4d67ff 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/action_edit_view.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/action_edit_view.cc
@@ -66,7 +66,9 @@
                                 : kNameTagAndLabelsPaddingForButtonOptionsMenu;
   container
       ->AddColumn(/*h_align=*/views::LayoutAlignment::kStart,
-                  /*v_align=*/views::LayoutAlignment::kStart,
+                  /*v_align=*/
+                  for_editing_list ? views::LayoutAlignment::kCenter
+                                   : views::LayoutAlignment::kStart,
                   /*horizontal_resize=*/1.0f,
                   /*size_type=*/views::TableLayout::ColumnSize::kUsePreferred,
                   /*fixed_width=*/0, /*min_width=*/0)
diff --git a/chrome/browser/ash/arc/input_overlay/ui/edit_label.cc b/chrome/browser/ash/arc/input_overlay/ui/edit_label.cc
index 7af5d2ab..b23023dc 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/edit_label.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/edit_label.cc
@@ -38,6 +38,8 @@
 constexpr float kCornerRadius = 8.0f;
 constexpr int kLabelSize = 32;
 
+constexpr ui::ColorId kPenIconColor = cros_tokens::kCrosSysOnPrimaryContainer;
+
 // Pulse animation specs.
 constexpr int kPulseTimes = 3;
 constexpr int kPulseExtraHalfSize = 32;
@@ -146,10 +148,10 @@
 
   // Clear icon if it is a valid key for new action.
   SetImageModel(views::Button::STATE_NORMAL,
-                output_string.empty() ? ui::ImageModel::FromVectorIcon(
-                                            kGameControlsEditPenIcon,
-                                            cros_tokens::kCrosSysHighlightShape)
-                                      : ui::ImageModel());
+                output_string.empty()
+                    ? ui::ImageModel::FromVectorIcon(kGameControlsEditPenIcon,
+                                                     kPenIconColor)
+                    : ui::ImageModel());
   // Set text label by `output_string` even it is empty to clear the text label.
   SetTextLabel(output_string);
 }
@@ -229,10 +231,9 @@
   }
 
   if (action_->is_new() && GetText().empty()) {
-    SetImageModel(
-        views::Button::STATE_NORMAL,
-        ui::ImageModel::FromVectorIcon(kGameControlsEditPenIcon,
-                                       cros_tokens::kCrosSysHighlightShape));
+    SetImageModel(views::Button::STATE_NORMAL,
+                  ui::ImageModel::FromVectorIcon(kGameControlsEditPenIcon,
+                                                 kPenIconColor));
   }
   SetToDefault();
   // Reset the error state if an reserved key was pressed.
diff --git a/chrome/browser/ash/file_system_provider/fake_provided_file_system.cc b/chrome/browser/ash/file_system_provider/fake_provided_file_system.cc
index 2e78c90..d976ccd2 100644
--- a/chrome/browser/ash/file_system_provider/fake_provided_file_system.cc
+++ b/chrome/browser/ash/file_system_provider/fake_provided_file_system.cc
@@ -504,7 +504,25 @@
     bool persistent,
     storage::AsyncFileUtil::StatusCallback callback,
     storage::WatcherManager::NotificationCallback notification_callback) {
-  // TODO(b/251698485): Implement it once needed.
+  // Check if watcher already exists.
+  const WatcherKey key(entry_watcher, recursive);
+  const Watchers::iterator it = watchers_.find(key);
+  if (it != watchers_.end()) {
+    std::move(callback).Run(base::File::FILE_OK);
+    return PostAbortableTask(
+        base::BindOnce(std::move(callback), base::File::FILE_OK));
+  }
+
+  // Add watcher.
+  Watcher* const watcher = &watchers_[key];
+  watcher->entry_path = entry_watcher;
+  watcher->recursive = recursive;
+
+  // Notify observers.
+  for (auto& observer : observers_) {
+    observer.OnWatcherListChanged(file_system_info_, watchers_);
+  }
+
   return PostAbortableTask(
       base::BindOnce(std::move(callback), base::File::FILE_OK));
 }
@@ -514,7 +532,18 @@
     const base::FilePath& entry_path,
     bool recursive,
     storage::AsyncFileUtil::StatusCallback callback) {
-  // TODO(b/251698485): Implement it once needed.
+  // Remove watcher.
+  const WatcherKey key(entry_path, recursive);
+  const auto it = watchers_.find(key);
+  if (it != watchers_.end()) {
+    watchers_.erase(it);
+  }
+
+  // Notify observers.
+  for (auto& observer : observers_) {
+    observer.OnWatcherListChanged(file_system_info_, watchers_);
+  }
+
   std::move(callback).Run(base::File::FILE_OK);
 }
 
diff --git a/chrome/browser/ash/fileapi/external_file_url_loader_factory_unittest.cc b/chrome/browser/ash/fileapi/external_file_url_loader_factory_unittest.cc
index 314d78f..beae13b 100644
--- a/chrome/browser/ash/fileapi/external_file_url_loader_factory_unittest.cc
+++ b/chrome/browser/ash/fileapi/external_file_url_loader_factory_unittest.cc
@@ -7,6 +7,7 @@
 #include <stddef.h>
 
 #include <memory>
+#include <string_view>
 
 #include "base/functional/bind.h"
 #include "chrome/browser/ash/file_system_provider/fake_extension_provider.h"
@@ -190,7 +191,7 @@
   std::string response_body;
   ASSERT_TRUE(mojo::BlockingCopyToString(client.response_body_release(),
                                          &response_body));
-  EXPECT_EQ(base::StringPiece(kExpectedFileContents).substr(3, 3),
+  EXPECT_EQ(std::string_view(kExpectedFileContents).substr(3, 3),
             response_body);
 }
 
diff --git a/chrome/browser/ash/login/demo_mode/demo_setup_browsertest.cc b/chrome/browser/ash/login/demo_mode/demo_setup_browsertest.cc
index 4c49a850..4c768b0 100644
--- a/chrome/browser/ash/login/demo_mode/demo_setup_browsertest.cc
+++ b/chrome/browser/ash/login/demo_mode/demo_setup_browsertest.cc
@@ -718,6 +718,20 @@
   SetAndVerifyInvalidRetailerNameAndStoreNumber("ValidRetailer", "");
   SetAndVerifyInvalidRetailerNameAndStoreNumber("ValidRetailer", "1234a");
   SetAndVerifyInvalidRetailerNameAndStoreNumber("ValidRetailer", "12-34");
+  // Have the store number numerical but have 257 characters
+  SetAndVerifyInvalidRetailerNameAndStoreNumber(
+      "ValidRetailer",
+      "257257257257257257257257257257257257257257257257257257257257257257257257"
+      "257257257257257257257257257257257257257257257257257257257257257257257257"
+      "257257257257257257257257257257257257257257257257257257257257257257257257"
+      "25725725725725725725725725725725725725725");
+  // Have the retailer Name have 257 characters
+  SetAndVerifyInvalidRetailerNameAndStoreNumber(
+      "257characters257characters257characters257characters257characters257char"
+      "acters257characters257characters257characters257characters257characters2"
+      "57characters257characters257characters257characters257characters257chara"
+      "cters257characters257characters257charact",
+      "1234");
 
   // Verify that continue button goes back to being disabled after enabled
   // for correct input
diff --git a/chrome/browser/ash/system_web_apps/apps/BUILD.gn b/chrome/browser/ash/system_web_apps/apps/BUILD.gn
index 1860613c..f45613e 100644
--- a/chrome/browser/ash/system_web_apps/apps/BUILD.gn
+++ b/chrome/browser/ash/system_web_apps/apps/BUILD.gn
@@ -25,6 +25,7 @@
     "personalization_app/personalization_app_time_of_day_browsertest.cc",
     "personalization_app/personalization_app_wallpaper_daily_refresh_browsertest.cc",
     "print_management_app_integration_browsertest.cc",
+    "print_preview_cros_app_integration_browsertest.cc",
     "projector_app/projector_app_integration_browsertest.cc",
     "scanning_app_integration_browsertest.cc",
     "settings_app_integration_browsertest.cc",
@@ -46,6 +47,7 @@
     "//ash/webui/media_app_ui:buildflags",
     "//ash/webui/os_feedback_ui",
     "//ash/webui/print_management",
+    "//ash/webui/print_preview_cros",
     "//ash/webui/projector_app:buildflags",
     "//ash/webui/web_applications/test:test_support",
     "//chrome/browser/accessibility/media_app/test:test_support",
diff --git a/chrome/browser/ash/system_web_apps/apps/OWNERS b/chrome/browser/ash/system_web_apps/apps/OWNERS
index 9f0105d..1c49fecb 100644
--- a/chrome/browser/ash/system_web_apps/apps/OWNERS
+++ b/chrome/browser/ash/system_web_apps/apps/OWNERS
@@ -10,5 +10,6 @@
 per-file diagnostics*=file://ash/webui/diagnostics_ui/OWNERS
 per-file file_manager*=file://ash/webui/file_manager/OWNERS
 per-file files_internals*=file://ash/webui/file_manager/OWNERS
+per-file print_preview_cros*=file://ash/webui/print_preview_cros/OWNERS
 per-file terminal*=file://chromeos/TERMINAL_OWNERS
 per-file eche*=file://ash/webui/eche_app_ui/OWNERS
diff --git a/chrome/browser/ash/system_web_apps/apps/print_preview_cros_app_integration_browsertest.cc b/chrome/browser/ash/system_web_apps/apps/print_preview_cros_app_integration_browsertest.cc
new file mode 100644
index 0000000..cbe4ad75
--- /dev/null
+++ b/chrome/browser/ash/system_web_apps/apps/print_preview_cros_app_integration_browsertest.cc
@@ -0,0 +1,40 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/constants/ash_features.h"
+#include "ash/webui/print_preview_cros/url_constants.h"
+#include "ash/webui/system_apps/public/system_web_app_type.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ash/system_web_apps/test_support/system_web_app_integration_test.h"
+#include "content/public/test/browser_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+
+class PrintPreviewCrosAppIntegrationTest
+    : public ash::SystemWebAppIntegrationTest {
+ public:
+  PrintPreviewCrosAppIntegrationTest() {
+    features_.InitAndEnableFeature(ash::features::kPrintPreviewCrosApp);
+  }
+
+ private:
+  base::test::ScopedFeatureList features_;
+};
+
+}  // namespace
+
+// Test that the Print Preview CrOS App installs and launches correctly. Runs
+// some spot checks on the manifest.
+IN_PROC_BROWSER_TEST_P(PrintPreviewCrosAppIntegrationTest,
+                       PrintPreviewCrosApp) {
+  const GURL url{ash::kChromeUIPrintPreviewCrosURL};
+  EXPECT_NO_FATAL_FAILURE(
+      ExpectSystemWebAppValid(ash::SystemWebAppType::PRINT_PREVIEW_CROS, url,
+                              /*title=*/"Print"));
+}
+
+INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
+    PrintPreviewCrosAppIntegrationTest);
diff --git a/chrome/browser/ash/system_web_apps/apps/print_preview_cros_system_web_app_info.cc b/chrome/browser/ash/system_web_apps/apps/print_preview_cros_system_web_app_info.cc
new file mode 100644
index 0000000..e2e9189
--- /dev/null
+++ b/chrome/browser/ash/system_web_apps/apps/print_preview_cros_system_web_app_info.cc
@@ -0,0 +1,74 @@
+// Copyright 2024 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/ash/system_web_apps/apps/print_preview_cros_system_web_app_info.h"
+
+#include <memory>
+
+#include "ash/constants/ash_features.h"
+#include "ash/webui/grit/ash_print_preview_cros_app_resources.h"
+#include "ash/webui/print_preview_cros/url_constants.h"
+#include "chrome/browser/ash/system_web_apps/apps/system_web_app_install_utils.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
+#include "chrome/browser/web_applications/web_app_install_info.h"
+#include "url/gurl.h"
+
+namespace {
+
+constexpr char kPrintPreviewCrosInternalName[] = "PrintPreviewCros";
+constexpr char16_t kPrintPreviewCrosTitle[] = u"Print";
+
+}  // namespace
+
+PrintPreviewCrosDelegate::PrintPreviewCrosDelegate(Profile* profile)
+    : ash::SystemWebAppDelegate(
+          ash::SystemWebAppType::PRINT_PREVIEW_CROS,
+          /*internal_name=*/kPrintPreviewCrosInternalName,
+          /*install_url=*/GURL(ash::kChromeUIPrintPreviewCrosURL),
+          profile) {}
+
+std::unique_ptr<web_app::WebAppInstallInfo>
+PrintPreviewCrosDelegate::GetWebAppInfo() const {
+  return CreateWebAppInfoForPrintPreviewCrosSystemWebApp();
+}
+
+bool PrintPreviewCrosDelegate::IsAppEnabled() const {
+  return ash::features::IsPrinterPreviewCrosAppEnabled();
+}
+
+bool PrintPreviewCrosDelegate::ShouldShowInLauncher() const {
+  return false;
+}
+
+bool PrintPreviewCrosDelegate::ShouldShowInSearchAndShelf() const {
+  return false;
+}
+
+bool PrintPreviewCrosDelegate::ShouldCaptureNavigations() const {
+  return true;
+}
+
+std::unique_ptr<web_app::WebAppInstallInfo>
+CreateWebAppInfoForPrintPreviewCrosSystemWebApp() {
+  std::unique_ptr<web_app::WebAppInstallInfo> info =
+      std::make_unique<web_app::WebAppInstallInfo>();
+  const GURL url = GURL(ash::kChromeUIPrintPreviewCrosURL);
+  info->start_url = url;
+  info->scope = url;
+  info->display_mode = blink::mojom::DisplayMode::kStandalone;
+  info->user_display_mode = web_app::mojom::UserDisplayMode::kStandalone;
+
+  // TODO(b/323585997): Localize title.
+  info->title = kPrintPreviewCrosTitle;
+
+  // TODO(b/323421684): Replace with actual app icons when available.
+  web_app::CreateIconInfoForSystemWebApp(
+      info->start_url,
+      {{"app_icon_192.png", 192,
+        IDR_ASH_PRINT_PREVIEW_CROS_APP_IMAGES_APP_ICON_192_PNG}},
+      *info);
+
+  return info;
+}
diff --git a/chrome/browser/ash/system_web_apps/apps/print_preview_cros_system_web_app_info.h b/chrome/browser/ash/system_web_apps/apps/print_preview_cros_system_web_app_info.h
new file mode 100644
index 0000000..57e4396
--- /dev/null
+++ b/chrome/browser/ash/system_web_apps/apps/print_preview_cros_system_web_app_info.h
@@ -0,0 +1,32 @@
+// Copyright 2024 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_ASH_SYSTEM_WEB_APPS_APPS_PRINT_PREVIEW_CROS_SYSTEM_WEB_APP_INFO_H_
+#define CHROME_BROWSER_ASH_SYSTEM_WEB_APPS_APPS_PRINT_PREVIEW_CROS_SYSTEM_WEB_APP_INFO_H_
+
+#include "chrome/browser/ash/system_web_apps/types/system_web_app_delegate.h"
+
+class Profile;
+
+namespace web_app {
+struct WebAppInstallInfo;
+}  // namespace web_app
+
+class PrintPreviewCrosDelegate : public ash::SystemWebAppDelegate {
+ public:
+  explicit PrintPreviewCrosDelegate(Profile* profile);
+
+  // ash::SystemWebAppDelegate:
+  std::unique_ptr<web_app::WebAppInstallInfo> GetWebAppInfo() const override;
+  bool IsAppEnabled() const override;
+  bool ShouldShowInLauncher() const override;
+  bool ShouldShowInSearchAndShelf() const override;
+  bool ShouldCaptureNavigations() const override;
+};
+
+// Returns a WebAppInstallInfo used to install the app.
+std::unique_ptr<web_app::WebAppInstallInfo>
+CreateWebAppInfoForPrintPreviewCrosSystemWebApp();
+
+#endif  // CHROME_BROWSER_ASH_SYSTEM_WEB_APPS_APPS_PRINT_PREVIEW_CROS_SYSTEM_WEB_APP_INFO_H_
diff --git a/chrome/browser/ash/system_web_apps/system_web_app_manager.cc b/chrome/browser/ash/system_web_apps/system_web_app_manager.cc
index 34d2b33..5090e49 100644
--- a/chrome/browser/ash/system_web_apps/system_web_app_manager.cc
+++ b/chrome/browser/ash/system_web_apps/system_web_app_manager.cc
@@ -55,6 +55,7 @@
 #include "chrome/browser/ash/system_web_apps/apps/os_url_handler_system_web_app_info.h"
 #include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_system_app_delegate.h"
 #include "chrome/browser/ash/system_web_apps/apps/print_management_web_app_info.h"
+#include "chrome/browser/ash/system_web_apps/apps/print_preview_cros_system_web_app_info.h"
 #include "chrome/browser/ash/system_web_apps/apps/projector_system_web_app_info.h"
 #include "chrome/browser/ash/system_web_apps/apps/scanning_system_web_app_info.h"
 #include "chrome/browser/ash/system_web_apps/apps/shimless_rma_system_web_app_info.h"
@@ -143,6 +144,7 @@
   info_vec.push_back(
       std::make_unique<vc_background_ui::VcBackgroundUISystemAppDelegate>(
           profile));
+  info_vec.push_back(std::make_unique<PrintPreviewCrosDelegate>(profile));
 
 #if !defined(OFFICIAL_BUILD)
   info_vec.push_back(std::make_unique<SampleSystemAppDelegate>(profile));
diff --git a/chrome/browser/ash/system_web_apps/types/proto/system_web_app_data.proto b/chrome/browser/ash/system_web_apps/types/proto/system_web_app_data.proto
index a993f63..e233d6f 100644
--- a/chrome/browser/ash/system_web_apps/types/proto/system_web_app_data.proto
+++ b/chrome/browser/ash/system_web_apps/types/proto/system_web_app_data.proto
@@ -36,6 +36,7 @@
     OS_FLAGS = 23;
     FACE_ML = 24;
     VC_BACKGROUND = 25;
+    PRINT_PREVIEW_CROS = 26;
   };
 
   optional SystemWebAppType system_app_type = 1;
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index 85b768c..b63a796f4 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -447,10 +447,8 @@
 #endif
 
 #if BUILDFLAG(ENABLE_COMPOSE)
-#include "chrome/browser/compose/compose_enabling.h"
-#include "chrome/browser/ui/webui/compose/compose_ui.h"
+#include "chrome/browser/ui/webui/compose/compose_untrusted_ui.h"
 #include "chrome/common/compose/compose.mojom.h"
-#include "components/compose/core/browser/compose_features.h"  // nogncheck crbug.com/1125897
 #endif
 
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
@@ -1172,9 +1170,6 @@
       ash::multidevice_setup::MultiDeviceSetupDialogUI, ash::ParentAccessUI,
       ash::EmojiUI, ash::RemoteMaintenanceCurtainUI,
 #endif
-#if BUILDFLAG(ENABLE_COMPOSE)
-      ComposeUI,
-#endif
       NewTabPageUI, OmniboxPopupUI, BookmarksSidePanelUI, CustomizeChromeUI,
       InternalsUI, ReadingListUI, TabSearchUI, WebuiGalleryUI,
       HistoryClustersSidePanelUI, PerformanceSidePanelUI,
@@ -1752,14 +1747,6 @@
   }
 #endif
 
-#if BUILDFLAG(ENABLE_COMPOSE)
-  if (ComposeEnabling::IsEnabledForProfile(Profile::FromBrowserContext(
-          render_frame_host->GetBrowserContext()))) {
-    RegisterWebUIControllerInterfaceBinder<
-        compose::mojom::ComposeSessionPageHandlerFactory, ComposeUI>(map);
-  }
-#endif  // BUILDFLAG(ENABLE_COMPOSE)
-
   if (base::FeatureList::IsEnabled(
           privacy_sandbox::kPrivacySandboxInternalsDevUI)) {
     RegisterWebUIControllerInterfaceBinder<
@@ -1840,6 +1827,11 @@
   registry.ForWebUI<feed::FeedUI>()
       .Add<feed::mojom::FeedSidePanelHandlerFactory>();
 #endif  // !BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(ENABLE_COMPOSE)
+  registry.ForWebUI<ComposeUntrustedUI>()
+      .Add<color_change_listener::mojom::PageHandler>()
+      .Add<compose::mojom::ComposeSessionPageHandlerFactory>();
+#endif  // BUILDFLAG(ENABLE_COMPOSE)
 #if !BUILDFLAG(IS_ANDROID)
   if (companion::IsCompanionFeatureEnabled()) {
     registry.ForWebUI<CompanionSidePanelUntrustedUI>()
diff --git a/chrome/browser/compose/chrome_compose_client.cc b/chrome/browser/compose/chrome_compose_client.cc
index c3f530b..3748422 100644
--- a/chrome/browser/compose/chrome_compose_client.cc
+++ b/chrome/browser/compose/chrome_compose_client.cc
@@ -57,8 +57,6 @@
 
 namespace {
 
-const char kComposeURL[] = "chrome://compose/";
-
 bool ShouldResumeSessionFromEntryPoint(
     ChromeComposeClient::EntryPoint entry_point) {
   switch (entry_point) {
@@ -122,7 +120,8 @@
 
   url::Origin origin =
       GetWebContents().GetPrimaryMainFrame()->GetLastCommittedOrigin();
-  if (origin == url::Origin::Create(GURL(kComposeURL))) {
+  if (origin ==
+      url::Origin::Create(GURL(chrome::kChromeUIUntrustedComposeUrl))) {
     debug_session_ = std::make_unique<ComposeSession>(
         &GetWebContents(), GetModelExecutor(), GetModelQualityLogsUploader(),
         GetSessionId(), GetInnerTextProvider(), autofill::FieldRendererId(-1));
diff --git a/chrome/browser/compose/chrome_compose_client.h b/chrome/browser/compose/chrome_compose_client.h
index a1d1b54..8cebe54 100644
--- a/chrome/browser/compose/chrome_compose_client.h
+++ b/chrome/browser/compose/chrome_compose_client.h
@@ -227,7 +227,7 @@
   // Time that the last call to show the dialog was started.
   base::TimeTicks show_dialog_start_;
 
-  // Used to test Compose in a tab at |chrome://compose|.
+  // Used to test Compose in a tab at |chrome-untrusted://compose|.
   std::unique_ptr<ComposeSession> debug_session_;
 
   // Collects per-pageload UKM metrics and reports them on destruction (if any
diff --git a/chrome/browser/compose/chrome_compose_client_unittest.cc b/chrome/browser/compose/chrome_compose_client_unittest.cc
index bfa030c..8b11fca 100644
--- a/chrome/browser/compose/chrome_compose_client_unittest.cc
+++ b/chrome/browser/compose/chrome_compose_client_unittest.cc
@@ -1121,7 +1121,7 @@
 }
 
 TEST_F(ChromeComposeClientTest, NoStateWorksAtChromeCompose) {
-  NavigateAndCommitActiveTab(GURL("chrome://compose"));
+  NavigateAndCommitActiveTab(GURL(chrome::kChromeUIUntrustedComposeUrl));
   // We skip the dialog showing here, as there is no dialog required at this
   // URL.
   BindMojo();
@@ -1178,10 +1178,10 @@
                   ukm::builders::Compose_SessionProgress::kCanceledName, 1)));
 }
 
-// Tests that closing the session at chrome://compose does not crash the
-// browser, even though there is no dialog shown at that URL.
+// Tests that closing the session at chrome-untrusted://compose does not crash
+// the browser, even though there is no dialog shown at that URL.
 TEST_F(ChromeComposeClientTest, TestCloseUIAtChromeCompose) {
-  NavigateAndCommitActiveTab(GURL("chrome://compose"));
+  NavigateAndCommitActiveTab(GURL(chrome::kChromeUIUntrustedComposeUrl));
   // We skip the dialog showing here, as there is no dialog required at this
   // URL.
   BindMojo();
@@ -2981,11 +2981,12 @@
 }
 
 // Tests that the Compose client crashes the browser if a webcontents
-// sends any more messages after closing the dialog at chrome://contents.
+// sends any more messages after closing the dialog at
+// chrome-untrusted://compose.
 TEST_F(ChromeComposeClientTest,
        TestCannotSendMessagesAfterClosingDialogAtChromeCompose) {
   GTEST_FLAG_SET(death_test_style, "threadsafe");
-  NavigateAndCommitActiveTab(GURL("chrome://compose"));
+  NavigateAndCommitActiveTab(GURL(chrome::kChromeUIUntrustedComposeUrl));
   // We skip the dialog showing here, as there is no dialog required at this
   // URL.
   BindMojo();
diff --git a/chrome/browser/controlled_frame/controlled_frame_apitest.cc b/chrome/browser/controlled_frame/controlled_frame_apitest.cc
index 38692f3..66f92e88 100644
--- a/chrome/browser/controlled_frame/controlled_frame_apitest.cc
+++ b/chrome/browser/controlled_frame/controlled_frame_apitest.cc
@@ -195,6 +195,23 @@
 // TODO(odejesush): Add tests for the rest of the Promise API methods.
 const char* kControlledFramePromiseApiMethods[]{"back", "forward", "go"};
 
+[[nodiscard]] bool IsControlledFramePresent(
+    content::WebContents* web_contents) {
+  return ExecJs(web_contents, R"(
+    (async function() {
+      return await new Promise((resolve, reject) => {
+        const controlledframe = document.createElement('controlledframe');
+        if (('src' in controlledframe)) {
+          // Tag is defined.
+          resolve('SUCCESS');
+        } else {
+          reject('FAIL');
+        }
+      });
+    })();
+  )");
+}
+
 }  // namespace
 
 class ControlledFrameApiTest
@@ -885,15 +902,6 @@
   ControlledFrameAvailableChannelTest& operator=(
       const ControlledFrameAvailableChannelTest&) = delete;
 
-  void CheckIsAvailable() {
-    // Test if Controlled Frame is available.
-    const GURL& kOriginalControlledFrameUrl =
-        isolated_web_app_dev_server().GetURL("/controlled_frame.html");
-    ASSERT_TRUE(
-        CreateControlledFrame(app_contents(), kOriginalControlledFrameUrl));
-    EXPECT_EQ(kEvalSuccessStr, ExecuteScriptRedBackgroundFile(app_contents()));
-  }
-
  private:
   extensions::ScopedCurrentChannel channel_;
 };
@@ -907,11 +915,16 @@
                                          version_info::Channel::DEFAULT));
 
 IN_PROC_BROWSER_TEST_P(ControlledFrameAvailableChannelTest, Test) {
-  CheckIsAvailable();
+  // Test if Controlled Frame is available.
+  const GURL& kOriginalControlledFrameUrl =
+      isolated_web_app_dev_server().GetURL("/controlled_frame.html");
+  ASSERT_TRUE(
+      CreateControlledFrame(app_contents(), kOriginalControlledFrameUrl));
+  EXPECT_EQ(kEvalSuccessStr, ExecuteScriptRedBackgroundFile(app_contents()));
 }
 
 class ControlledFrameNotAvailableChannelTest
-    : public web_app::WebAppControllerBrowserTest,
+    : public ControlledFrameApiTest,
       public testing::WithParamInterface<version_info::Channel> {
  protected:
   ControlledFrameNotAvailableChannelTest() : channel_(GetParam()) {}
@@ -921,33 +934,6 @@
   ControlledFrameNotAvailableChannelTest& operator=(
       const ControlledFrameNotAvailableChannelTest&) = delete;
 
-  [[nodiscard]] bool IsControlledFramePresent(
-      content::WebContents* web_contents) {
-    return ExecJs(web_contents, R"(
-      (async function() {
-        return await new Promise((resolve, reject) => {
-          const controlledframe = document.createElement('controlledframe');
-          if (('src' in controlledframe)) {
-            // Tag is defined.
-            resolve('SUCCESS');
-          } else {
-            reject('FAIL');
-          }
-        });
-      })();
-    )");
-  }
-
-  void CheckIsNotAvailable() {
-    // Test if Controlled Frame is not available.
-    const GURL start_url("https://app.site.test/example/index");
-    const webapps::AppId app_id = InstallPWA(start_url);
-    content::WebContents* app_contents =
-        browser()->tab_strip_model()->GetActiveWebContents();
-
-    ASSERT_FALSE(IsControlledFramePresent(app_contents));
-  }
-
  private:
   extensions::ScopedCurrentChannel channel_;
 };
@@ -961,7 +947,38 @@
                                          version_info::Channel::DEFAULT));
 
 IN_PROC_BROWSER_TEST_P(ControlledFrameNotAvailableChannelTest, Test) {
-  CheckIsNotAvailable();
+  // Test if Controlled Frame is not available.
+  const GURL start_url("https://app.site.test/example/index");
+  const webapps::AppId app_id = InstallPWA(start_url);
+  content::WebContents* app_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  ASSERT_FALSE(IsControlledFramePresent(app_contents));
+}
+
+class ControlledFrameDisabledTest : public ControlledFrameApiTest {
+ public:
+  ControlledFrameDisabledTest(const ControlledFrameDisabledTest&) = delete;
+  ControlledFrameDisabledTest& operator=(const ControlledFrameDisabledTest&) =
+      delete;
+
+ protected:
+  ControlledFrameDisabledTest() {
+    feature_list.InitWithFeatures(
+        /*enabled_features=*/{},
+        /*disabled_features=*/{features::kControlledFrame});
+  }
+
+  ~ControlledFrameDisabledTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList feature_list;
+};
+
+IN_PROC_BROWSER_TEST_F(ControlledFrameDisabledTest, MissingFeature) {
+  const GURL& kOriginalControlledFrameUrl =
+      isolated_web_app_dev_server().GetURL("/controlled_frame.html");
+  ASSERT_FALSE(IsControlledFramePresent(app_contents()));
 }
 
 }  // namespace controlled_frame
diff --git a/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc b/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc
index f45444a..b9ac83ec 100644
--- a/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc
@@ -411,8 +411,15 @@
             current_key_pair);
 }
 
+// Flaky on Win. See http://crbug.com/324937427.
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_RemoteCommandKeyRotationFailure \
+  DISABLED_RemoteCommandKeyRotationFailure
+#else
+#define MAYBE_RemoteCommandKeyRotationFailure RemoteCommandKeyRotationFailure
+#endif
 IN_PROC_BROWSER_TEST_F(DeviceTrustKeyRotationBrowserTest,
-                       RemoteCommandKeyRotationFailure) {
+                       MAYBE_RemoteCommandKeyRotationFailure) {
   // Make sure key presents and stores its current value.
   std::vector<uint8_t> current_key_pair =
       device_trust_test_environment_win_->GetWrappedKey();
diff --git a/chrome/browser/extensions/events_apitest.cc b/chrome/browser/extensions/events_apitest.cc
index 58d8c1da6..4cad0c1 100644
--- a/chrome/browser/extensions/events_apitest.cc
+++ b/chrome/browser/extensions/events_apitest.cc
@@ -699,6 +699,83 @@
   EXPECT_EQ(extension_host->GetUnackedMessagesSizeForTesting(), 0UL);
 }
 
+// TODO(crbug.com/41493334): Refactor this test into the
+// DispatchToEventPage_Acks test since they are so similar.
+using PersistentBackgroundPageEventDispatchToSenderApiTest =
+    EventPageEventDispatchingApiTest;
+
+// Tests that persistent background pages will receive an event message (routed
+// through the EventRouter::DispatchToSender() flow) and properly track and
+// remove the unacked event message in ExtensionHost. Only persistent background
+// pages can use the webRequest API so event pages are not tested.
+IN_PROC_BROWSER_TEST_F(PersistentBackgroundPageEventDispatchToSenderApiTest,
+                       DispatchToPage_Acks) {
+  // Load an extension with a chrome.webRequest.onBeforeRequest
+  // (EventRouter::DispatchToSender()) listener and wait for the
+  // chrome.runtime.onInstalled listener to fire.
+  static constexpr char kManifest[] =
+      R"({
+       "name": "Persistent background page",
+       "version": "0.1",
+       "manifest_version": 2,
+       "background": {
+         "scripts": ["background.js"],
+         "persistent": true
+       },
+       "permissions": ["webRequest", "http://example.com/*"]
+     })";
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(kManifest);
+  constexpr char kBackgroundJs[] =
+      R"({
+        chrome.runtime.onInstalled.addListener((details) => {
+          // Asynchronously send the message that the listener fired so that the
+          // event is considered ack'd in the browser C++ code.
+          setTimeout(() => {
+            chrome.test.sendMessage('installed listener fired');
+          }, 0);
+        });
+
+        chrome.webRequest.onBeforeRequest.addListener(
+          (details) => {
+            setTimeout(() => {
+              chrome.test.sendMessage('listener fired');
+            }, 0);
+          },
+          {urls: ['<all_urls>'], types: ['main_frame']},
+          []
+        );
+      })";
+  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs);
+  ExtensionTestMessageListener extension_oninstall_listener_fired(
+      "installed listener fired");
+  const Extension* extension = LoadExtension(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+  // This ensures that we wait until the the browser receives the ack from the
+  // renderer. This prevents unexpected event state later when we check it.
+  ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());
+
+  // Confirm there are no unacked messages before we send the test event.
+  ProcessManager* process_manager = ProcessManager::Get(profile());
+  ExtensionHost* extension_host =
+      process_manager->GetBackgroundHostForExtension(extension->id());
+  ASSERT_EQ(extension_host->GetUnackedMessagesSizeForTesting(), 0UL);
+
+  ExtensionTestMessageListener extension_event_listener_fired("listener fired");
+
+  // Navigate somewhere to trigger webRequest.onBeforeRequest event to the
+  // extension listener.
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(),
+      embedded_test_server()->GetURL("example.com", "/simple.html")));
+
+  // Confirm that the listener in the persistent background page script fired.
+  EXPECT_TRUE(extension_event_listener_fired.WaitUntilSatisfied());
+  // TODO(crbug.com/1496093): Can we add an observer so that we know that an
+  // unacked message was added and then removed?
+  EXPECT_EQ(extension_host->GetUnackedMessagesSizeForTesting(), 0UL);
+}
+
 // Tests that an event targeted to a content script listener is not recorded
 // in unacked event messages in ExtensionHost.
 IN_PROC_BROWSER_TEST_F(EventPageEventDispatchingApiTest,
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 4504392..45d0bca 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2354,6 +2354,11 @@
     "expiry_milestone": 140
   },
   {
+    "name": "enable-controlled-frame",
+    "owners": [ "cmp@chromium.org", "odejesush@chromium.org" ],
+    "expiry_milestone": 140
+  },
+  {
     "name": "enable-cooperative-scheduling",
     "owners": [ "keishi@chromium.org" ],
     "expiry_milestone": 85
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index f555451..d96477c 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1242,6 +1242,12 @@
 const char kEnableIsolatedWebAppDevModeDescription[] =
     "Enables the installation of unverified Isolated Web Apps";
 
+const char kEnableControlledFrameName[] = "Enable Controlled Frame";
+const char kEnableControlledFrameDescription[] =
+    "Enables experimental support for Controlled Frame. See "
+    "https://github.com/WICG/controlled-frame/blob/main/EXPLAINER.md "
+    "for more information.";
+
 const char kEnableShortcutCustomizationAppName[] =
     "Enable shortcut customization app";
 const char kEnableShortcutCustomizationAppDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index f627879..a0e40fa9 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -805,6 +805,9 @@
 extern const char kEnableIsolatedWebAppDevModeName[];
 extern const char kEnableIsolatedWebAppDevModeDescription[];
 
+extern const char kEnableControlledFrameName[];
+extern const char kEnableControlledFrameDescription[];
+
 extern const char kEnableLensStandaloneFlagId[];
 extern const char kEnableLensStandaloneName[];
 extern const char kEnableLensStandaloneDescription[];
diff --git a/chrome/browser/metrics/structured/ash_event_storage.cc b/chrome/browser/metrics/structured/ash_event_storage.cc
index 2a2fe81..539eeb09 100644
--- a/chrome/browser/metrics/structured/ash_event_storage.cc
+++ b/chrome/browser/metrics/structured/ash_event_storage.cc
@@ -6,7 +6,7 @@
 
 #include "base/functional/callback_forward.h"
 #include "base/task/current_thread.h"
-#include "components/metrics/structured/histogram_util.h"
+#include "components/metrics/structured/lib/histogram_util.h"
 
 namespace metrics::structured {
 
diff --git a/chrome/browser/metrics/structured/chrome_structured_metrics_recorder_unittest.cc b/chrome/browser/metrics/structured/chrome_structured_metrics_recorder_unittest.cc
index 72c36ee7..ebb77f4 100644
--- a/chrome/browser/metrics/structured/chrome_structured_metrics_recorder_unittest.cc
+++ b/chrome/browser/metrics/structured/chrome_structured_metrics_recorder_unittest.cc
@@ -16,7 +16,7 @@
 #include "base/threading/scoped_blocking_call.h"
 #include "components/metrics/structured/event.h"
 #include "components/metrics/structured/key_data_prefs_delegate.h"
-#include "components/metrics/structured/key_util.h"
+#include "components/metrics/structured/lib/key_util.h"
 #include "components/metrics/structured/lib/proto/key.pb.h"
 #include "components/metrics/structured/proto/event_storage.pb.h"
 #include "components/metrics/structured/structured_events.h"
diff --git a/chrome/browser/metrics/structured/key_data_provider_ash.h b/chrome/browser/metrics/structured/key_data_provider_ash.h
index b0ca0117..f71d605 100644
--- a/chrome/browser/metrics/structured/key_data_provider_ash.h
+++ b/chrome/browser/metrics/structured/key_data_provider_ash.h
@@ -12,7 +12,7 @@
 #include "base/functional/callback_forward.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
-#include "components/metrics/structured/key_data_provider.h"
+#include "components/metrics/structured/lib/key_data_provider.h"
 
 namespace metrics::structured {
 
diff --git a/chrome/browser/metrics/structured/key_data_provider_ash_unittest.cc b/chrome/browser/metrics/structured/key_data_provider_ash_unittest.cc
index 5547425..88c5f4d 100644
--- a/chrome/browser/metrics/structured/key_data_provider_ash_unittest.cc
+++ b/chrome/browser/metrics/structured/key_data_provider_ash_unittest.cc
@@ -13,7 +13,7 @@
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
-#include "components/metrics/structured/key_data_provider.h"
+#include "components/metrics/structured/lib/key_data_provider.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/metrics/structured/key_data_provider_chrome.h b/chrome/browser/metrics/structured/key_data_provider_chrome.h
index 392c635..0edc0c0 100644
--- a/chrome/browser/metrics/structured/key_data_provider_chrome.h
+++ b/chrome/browser/metrics/structured/key_data_provider_chrome.h
@@ -7,8 +7,8 @@
 
 #include <string>
 
-#include "components/metrics/structured/key_data_provider.h"
 #include "components/metrics/structured/key_data_provider_prefs.h"
+#include "components/metrics/structured/lib/key_data_provider.h"
 #include "components/metrics/structured/lib/proto/key.pb.h"
 
 class PrefRegistrySimple;
diff --git a/chrome/browser/metrics/variations/chrome_variations_service_client.cc b/chrome/browser/metrics/variations/chrome_variations_service_client.cc
index c07d7b9..fa08d98f 100644
--- a/chrome/browser/metrics/variations/chrome_variations_service_client.cc
+++ b/chrome/browser/metrics/variations/chrome_variations_service_client.cc
@@ -91,8 +91,8 @@
   // branded Lacros build, see crbug.com/1474764.
   if (!g_browser_process->browser_policy_connector()->GetDeviceSettings()) {
     CHECK_IS_TEST();  // IN-TEST
-    CHECK(chromeos::BrowserParamsProxy::Get()
-              ->IsCrosapiDisabledForTesting());  // IN-TEST
+    CHECK(chromeos::BrowserParamsProxy::
+              IsCrosapiDisabledForTesting());  // IN-TEST
     return false;
   }
 
diff --git a/chrome/browser/notifications/mac/OWNERS b/chrome/browser/notifications/mac/OWNERS
index 14fce2ae..e69de29 100644
--- a/chrome/browser/notifications/mac/OWNERS
+++ b/chrome/browser/notifications/mac/OWNERS
@@ -1 +0,0 @@
-rsesek@chromium.org
diff --git a/chrome/browser/notifications/notification_permission_browsertest.cc b/chrome/browser/notifications/notification_permission_browsertest.cc
index 2d727d7..ed6e034f 100644
--- a/chrome/browser/notifications/notification_permission_browsertest.cc
+++ b/chrome/browser/notifications/notification_permission_browsertest.cc
@@ -3,20 +3,30 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/chrome_content_browser_client.h"
+#include "chrome/browser/notifications/non_persistent_notification_handler.h"
 #include "chrome/browser/notifications/notification_permission_context.h"
 #include "chrome/browser/profiles/profile.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/permissions/permission_request_manager.h"
+#include "components/permissions/permission_util.h"
+#include "components/permissions/request_type.h"
+#include "components/permissions/test/mock_permission_prompt_factory.h"
+#include "components/permissions/test/mock_permission_request.h"
+#include "components/ukm/test_ukm_recorder.h"
 #include "content/public/common/content_client.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_mock_cert_verifier.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
 
 namespace {
 
+using Permission = ukm::builders::Permission;
+
 const char kTestFilePath[] =
     "/notifications/notification_permission_checker.html";
 const char kTesterHost[] = "notification.com";
@@ -302,3 +312,66 @@
   histogram_tester.ExpectBucketCount(histogram_name, false, 2);
   histogram_tester.ExpectBucketCount(histogram_name, true, 1);
 }
+
+// Tests that non-persistent notifications (i.e. doesn't use
+// the Push API) records PermissionUsage and Notification UKMs.
+IN_PROC_BROWSER_TEST_F(NotificationPermissionBrowserTest,
+                       NonPersistentNotificationRecordsUkms) {
+  GrantNotificationPermissionForTest(TesterUrl());
+
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), TesterUrl()));
+  content::RenderFrameHost* main_frame =
+      GetActiveWebContents()->GetPrimaryMainFrame();
+
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  const std::string show_notification_js = R"(new Promise((resolve) => {
+     const notification = new Notification("done");
+     notification.onshow = () => {
+       const title = notification.title;
+       notification.close();
+       resolve(title);
+     };
+   });)";
+
+  EXPECT_EQ("done", EvalJs(main_frame, show_notification_js));
+  const auto usage_entries = ukm_recorder.GetEntriesByName("PermissionUsage");
+  ASSERT_EQ(1u, usage_entries.size());
+}
+
+IN_PROC_BROWSER_TEST_F(NotificationPermissionBrowserTest,
+                       DisablePermissionRecordsUkms) {
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  auto* manager = permissions::PermissionRequestManager::FromWebContents(
+      GetActiveWebContents());
+  std::unique_ptr<permissions::MockPermissionPromptFactory> bubble_factory =
+      std::make_unique<permissions::MockPermissionPromptFactory>(manager);
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), TesterUrl()));
+  permissions::MockPermissionRequest req(
+      permissions::RequestType::kNotifications);
+  manager->AddRequest(GetActiveWebContents()->GetPrimaryMainFrame(), &req);
+  bubble_factory->WaitForPermissionBubble();
+  manager->Accept();
+
+  GrantNotificationPermissionForTest(TesterUrl());
+
+  std::unique_ptr<NotificationHandler> handler =
+      std::make_unique<NonPersistentNotificationHandler>();
+  handler->DisableNotifications(browser()->profile(), TesterUrl());
+
+  const auto action_entries = ukm_recorder.GetEntriesByName("Permission");
+  ASSERT_EQ(2u, action_entries.size());
+
+  // The revocation event uses the notification source_id type
+  EXPECT_EQ(ukm::SourceIdType::NOTIFICATION_ID,
+            ukm::GetSourceIdType(action_entries[1]->source_id));
+
+  // Expect one GRANT and one REVOKE event
+  ukm_recorder.ExpectEntryMetric(
+      action_entries[0], Permission::kActionName,
+      static_cast<int>(permissions::PermissionAction::GRANTED));
+  ukm_recorder.ExpectEntryMetric(
+      action_entries[1], Permission::kActionName,
+      static_cast<int>(permissions::PermissionAction::REVOKED));
+}
diff --git a/chrome/browser/notifications/platform_notification_service_impl.cc b/chrome/browser/notifications/platform_notification_service_impl.cc
index b6f6c6a..371f6640 100644
--- a/chrome/browser/notifications/platform_notification_service_impl.cc
+++ b/chrome/browser/notifications/platform_notification_service_impl.cc
@@ -390,13 +390,11 @@
     return;
   }
 
-  // Check if this event can be recorded via UKM.
-  auto* ukm_background_service =
-      ukm::UkmBackgroundRecorderFactory::GetForProfile(profile_);
-  ukm_background_service->GetBackgroundSourceIdIfAllowed(
-      url::Origin::Create(data.origin),
-      base::BindOnce(&PlatformNotificationServiceImpl::DidGetBackgroundSourceId,
-                     std::move(ukm_recorded_closure_for_testing_), data));
+  ukm::SourceId source_id = ukm::UkmRecorder::GetSourceIdForNotificationEvent(
+      base::PassKey<PlatformNotificationServiceImpl>(), data.origin);
+
+  RecordNotificationUkmEventWithSourceId(
+      std::move(ukm_recorded_closure_for_testing_), data, source_id);
 }
 
 NotificationTriggerScheduler*
@@ -405,15 +403,11 @@
 }
 
 // static
-void PlatformNotificationServiceImpl::DidGetBackgroundSourceId(
+void PlatformNotificationServiceImpl::RecordNotificationUkmEventWithSourceId(
     base::OnceClosure recorded_closure,
     const content::NotificationDatabaseData& data,
-    std::optional<ukm::SourceId> source_id) {
-  // This background event did not meet the requirements for the UKM service.
-  if (!source_id)
-    return;
-
-  ukm::builders::Notification builder(*source_id);
+    ukm::SourceId source_id) {
+  ukm::builders::Notification builder(source_id);
 
   int64_t time_until_first_click_millis =
       data.time_until_first_click_millis.has_value()
diff --git a/chrome/browser/notifications/platform_notification_service_impl.h b/chrome/browser/notifications/platform_notification_service_impl.h
index 557b1dae..231a266 100644
--- a/chrome/browser/notifications/platform_notification_service_impl.h
+++ b/chrome/browser/notifications/platform_notification_service_impl.h
@@ -121,10 +121,10 @@
       const ContentSettingsPattern& secondary_pattern,
       ContentSettingsTypeSet content_type_set) override;
 
-  static void DidGetBackgroundSourceId(
+  static void RecordNotificationUkmEventWithSourceId(
       base::OnceClosure recorded_closure,
       const content::NotificationDatabaseData& data,
-      std::optional<ukm::SourceId> source_id);
+      ukm::SourceId source_id);
 
   // Creates a new Web Notification-based Notification object. Should only be
   // called when the notification is first shown. |web_app_hint_url| is used to
diff --git a/chrome/browser/permissions/chrome_permissions_client.cc b/chrome/browser/permissions/chrome_permissions_client.cc
index e1638042..bc5c31e 100644
--- a/chrome/browser/permissions/chrome_permissions_client.cc
+++ b/chrome/browser/permissions/chrome_permissions_client.cc
@@ -62,6 +62,7 @@
 #include "content/public/browser/web_contents.h"
 #include "extensions/buildflags/buildflags.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
 #include "url/origin.h"
 
 #if BUILDFLAG(IS_ANDROID)
@@ -232,6 +233,7 @@
 }
 
 void ChromePermissionsClient::GetUkmSourceId(
+    ContentSettingsType permission_type,
     content::BrowserContext* browser_context,
     content::WebContents* web_contents,
     const GURL& requesting_origin,
@@ -240,6 +242,11 @@
     ukm::SourceId source_id =
         web_contents->GetPrimaryMainFrame()->GetPageUkmSourceId();
     std::move(callback).Run(source_id);
+  } else if (permission_type == ContentSettingsType::NOTIFICATIONS) {
+    ukm::SourceId source_id =
+        ukm::UkmRecorder::GetSourceIdForNotificationPermission(
+            base::PassKey<ChromePermissionsClient>(), requesting_origin);
+    std::move(callback).Run(source_id);
   } else {
     // We only record a permission change if the origin is in the user's
     // history.
diff --git a/chrome/browser/permissions/chrome_permissions_client.h b/chrome/browser/permissions/chrome_permissions_client.h
index 006617fb..4a9a81e 100644
--- a/chrome/browser/permissions/chrome_permissions_client.h
+++ b/chrome/browser/permissions/chrome_permissions_client.h
@@ -46,7 +46,8 @@
       std::vector<std::pair<url::Origin, bool>>* urls) override;
   bool IsCookieDeletionDisabled(content::BrowserContext* browser_context,
                                 const GURL& origin) override;
-  void GetUkmSourceId(content::BrowserContext* browser_context,
+  void GetUkmSourceId(ContentSettingsType permission_type,
+                      content::BrowserContext* browser_context,
                       content::WebContents* web_contents,
                       const GURL& requesting_origin,
                       GetUkmSourceIdCallback callback) override;
diff --git a/chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_host.cc b/chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_host.cc
index 76a053ff..4533df37 100644
--- a/chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_host.cc
+++ b/chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_host.cc
@@ -7,6 +7,7 @@
 #include "chrome/browser/page_load_metrics/observers/lcp_critical_path_predictor_page_load_metrics_observer.h"
 #include "content/public/browser/render_frame_host.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/loader/lcp_critical_path_predictor_util.h"
 
 namespace {
 
@@ -56,7 +57,7 @@
 
 void LCPCriticalPathPredictorHost::SetLcpInfluencerScriptUrls(
     const std::vector<GURL>& lcp_influencer_scripts) {
-  if (!base::FeatureList::IsEnabled(blink::features::kLCPScriptObserver)) {
+  if (!blink::LcppScriptObserverEnabled()) {
     return;
   }
   if (auto* page_data =
diff --git a/chrome/browser/resources/ash/settings/os_people_page/fingerprint_list_subpage.ts b/chrome/browser/resources/ash/settings/os_people_page/fingerprint_list_subpage.ts
index 0931076..6c03cae1 100644
--- a/chrome/browser/resources/ash/settings/os_people_page/fingerprint_list_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_people_page/fingerprint_list_subpage.ts
@@ -144,7 +144,7 @@
     this.browserProxy_.removeEnrollment(e.model.index, this.authToken)
         .then(success => {
           if (success) {
-            recordSettingChange();
+            recordSettingChange(Setting.kRemoveFingerprintV2);
             this.updateFingerprintsList_();
           }
         });
diff --git a/chrome/browser/resources/ash/settings/os_people_page/setup_fingerprint_dialog.ts b/chrome/browser/resources/ash/settings/os_people_page/setup_fingerprint_dialog.ts
index 145f90fb..03e01c3a 100644
--- a/chrome/browser/resources/ash/settings/os_people_page/setup_fingerprint_dialog.ts
+++ b/chrome/browser/resources/ash/settings/os_people_page/setup_fingerprint_dialog.ts
@@ -9,14 +9,15 @@
 import 'chrome://resources/polymer/v3_0/iron-media-query/iron-media-query.js';
 import '../settings_shared.css.js';
 
-import {FingerprintProgressElement} from 'chrome://resources/ash/common/quick_unlock/fingerprint_progress.js';
 import {CrDialogElement} from 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
 import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
 import {WebUiListenerMixin} from 'chrome://resources/ash/common/cr_elements/web_ui_listener_mixin.js';
+import {FingerprintProgressElement} from 'chrome://resources/ash/common/quick_unlock/fingerprint_progress.js';
 import {assertNotReached} from 'chrome://resources/js/assert.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {recordSettingChange} from '../metrics_recorder.js';
+import {Setting} from '../mojom-webui/setting.mojom-webui.js';
 
 import {FingerprintBrowserProxy, FingerprintBrowserProxyImpl, FingerprintResultType, FingerprintScan} from './fingerprint_browser_proxy.js';
 import {getTemplate} from './setup_fingerprint_dialog.html.js';
@@ -308,7 +309,7 @@
     this.$.arc.reset();
     this.step_ = FingerprintSetupStep.MOVE_FINGER;
     this.browserProxy_.startEnroll(this.authToken);
-    recordSettingChange();
+    recordSettingChange(Setting.kAddFingerprintV2);
   }
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/common/automation_util.ts b/chrome/browser/resources/chromeos/accessibility/common/automation_util.ts
index c979d41f..bb340d1 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/automation_util.ts
+++ b/chrome/browser/resources/chromeos/accessibility/common/automation_util.ts
@@ -8,6 +8,7 @@
 
 import {AutomationPredicate} from './automation_predicate.js';
 import {constants} from './constants.js';
+import {TestImportManager} from './testing/test_import_manager.js';
 import {AutomationTreeWalker, AutomationTreeWalkerRestriction} from './tree_walker.js';
 
 type AutomationNode = chrome.automation.AutomationNode;
@@ -540,3 +541,5 @@
 
   return new AutomationTreeWalker(cur, dir, restrictions);
 }
+
+TestImportManager.exportForTesting(AutomationUtil);
diff --git a/chrome/browser/resources/chromeos/accessibility/common/paragraph_utils.ts b/chrome/browser/resources/chromeos/accessibility/common/paragraph_utils.ts
index 0e360fa..3746457 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/paragraph_utils.ts
+++ b/chrome/browser/resources/chromeos/accessibility/common/paragraph_utils.ts
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {TestImportManager} from './testing/test_import_manager.js';
 import {WordUtils} from './word_utils.js';
 
 type AutomationNode = chrome.automation.AutomationNode;
@@ -676,3 +677,5 @@
   }
 
 }
+
+TestImportManager.exportForTesting(ParagraphUtils);
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/prefs_manager.ts b/chrome/browser/resources/chromeos/accessibility/select_to_speak/prefs_manager.ts
index f3895ec..8d2ba3e3 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/prefs_manager.ts
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/prefs_manager.ts
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {TestImportManager} from '/common/testing/test_import_manager.js';
+
 import {SelectToSpeakConstants} from './select_to_speak_constants.js';
 
 /**
@@ -697,3 +699,5 @@
   export const WORD_HIGHLIGHT_KEY =
       'settings.a11y.select_to_speak_word_highlight';
 }
+
+TestImportManager.exportForTesting(PrefsManager);
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.ts b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.ts
index 14930ee..1aec9fbb 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.ts
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.ts
@@ -9,6 +9,7 @@
 import {NodeNavigationUtils} from '/common/node_navigation_utils.js';
 import {NodeUtils} from '/common/node_utils.js';
 import {ParagraphUtils} from '/common/paragraph_utils.js';
+import {TestImportManager} from '/common/testing/test_import_manager.js';
 import {WordUtils} from '/common/word_utils.js';
 
 import {InputHandler} from './input_handler.js';
@@ -1741,3 +1742,5 @@
     callback();
   }
 }
+
+TestImportManager.exportForTesting(getGSuiteAppRoot);
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_constants.ts b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_constants.ts
index 2b3bd659..c941066 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_constants.ts
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_constants.ts
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import {KeyCodeData} from '/common/key_code.js';
+import {TestImportManager} from '/common/testing/test_import_manager.js';
 
 export namespace SelectToSpeakConstants {
   export const SEARCH_KEY_CODE: number = KeyCodeData.SEARCH.code;
@@ -30,3 +31,6 @@
     useVoiceSwitching: boolean;
   }
 }
+
+TestImportManager.exportForTesting(
+    ['SelectToSpeakConstants', SelectToSpeakConstants]);
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_enhanced_voices_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_enhanced_voices_test.js
index ddde2e7..837016f 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_enhanced_voices_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_enhanced_voices_test.js
@@ -26,19 +26,6 @@
     };
   }
 
-  /** @override */
-  async setUpDeferred() {
-    await super.setUpDeferred();
-
-    await Promise.all([
-      importModule('selectToSpeak', '/select_to_speak/select_to_speak_main.js'),
-      importModule(
-          'SelectToSpeakConstants',
-          '/select_to_speak/select_to_speak_constants.js'),
-      importModule('PrefsManager', '/select_to_speak/prefs_manager.js'),
-    ]);
-  }
-
   // Sets the policy to allow or disallow the network voices.
   // Waits for the setting to propagate.
   async setEnhancedNetworkVoicesPolicy(allowed) {
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js
index 4f25ff82..b982c211f 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_keystroke_selection_test.js
@@ -20,14 +20,6 @@
   async setUpDeferred() {
     await super.setUpDeferred();
 
-    await Promise.all([
-      importModule('selectToSpeak', '/select_to_speak/select_to_speak_main.js'),
-      importModule(
-          'SelectToSpeakConstants',
-          '/select_to_speak/select_to_speak_constants.js'),
-      importModule('PrefsManager', '/select_to_speak/prefs_manager.js'),
-    ]);
-
     await new Promise(resolve => {
       chrome.settingsPrivate.setPref(
           PrefsManager.ENHANCED_VOICES_DIALOG_SHOWN_KEY, true,
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_main.ts b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_main.ts
index c7f3c37..b9d603af 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_main.ts
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_main.ts
@@ -2,9 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import '/common/testing/test_import_manager.js';
-
 import {InstanceChecker} from '/common/instance_checker.js';
+import {TestImportManager} from '/common/testing/test_import_manager.js';
 
 import {SelectToSpeak} from './select_to_speak.js';
 
@@ -12,4 +11,5 @@
 
 if (InstanceChecker.isActiveInstance()) {
   selectToSpeak = new SelectToSpeak();
+  TestImportManager.exportForTesting(['selectToSpeak', selectToSpeak]);
 }
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js
index 99590e3..4d960d0 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_mouse_selection_test.js
@@ -22,15 +22,6 @@
     window.EventType = chrome.automation.EventType;
     window.SelectToSpeakState = chrome.accessibilityPrivate.SelectToSpeakState;
 
-    await Promise.all([
-      importModule('selectToSpeak', '/select_to_speak/select_to_speak_main.js'),
-      importModule(
-          'SELECT_TO_SPEAK_TRAY_CLASS_NAME', '/select_to_speak/ui_manager.js'),
-      importModule(
-          'SelectToSpeakConstants',
-          '/select_to_speak/select_to_speak_constants.js'),
-      importModule('PrefsManager', '/select_to_speak/prefs_manager.js'),
-    ]);
     await new Promise(resolve => {
       chrome.settingsPrivate.setPref(
           PrefsManager.ENHANCED_VOICES_DIALOG_SHOWN_KEY, true,
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js
index 37bed5b..dbbf05d 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_navigation_control_test.js
@@ -29,15 +29,6 @@
     chrome.accessibilityPrivate.updateSelectToSpeakPanel =
         this.updateSelectToSpeakPanel;
 
-    await Promise.all([
-      importModule('selectToSpeak', '/select_to_speak/select_to_speak_main.js'),
-      importModule(
-          'SelectToSpeakConstants',
-          '/select_to_speak/select_to_speak_constants.js'),
-      importModule('AutomationUtil', '/common/automation_util.js'),
-      importModule('PrefsManager', '/select_to_speak/prefs_manager.js'),
-    ]);
-
     await new Promise(resolve => {
       chrome.settingsPrivate.setPref(
           PrefsManager.ENHANCED_VOICES_DIALOG_SHOWN_KEY, true,
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_prefs_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_prefs_test.js
index 790c50c3..c0f064b 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_prefs_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_prefs_test.js
@@ -25,14 +25,6 @@
   /** @override */
   async setUpDeferred() {
     await super.setUpDeferred();
-
-    await Promise.all([
-      importModule('selectToSpeak', '/select_to_speak/select_to_speak_main.js'),
-      importModule(
-          'SelectToSpeakConstants',
-          '/select_to_speak/select_to_speak_constants.js'),
-      importModule('PrefsManager', '/select_to_speak/prefs_manager.js'),
-    ]);
     await this.resetStorage();
   }
 
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_unittest.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_unittest.js
index b9a7bc3..7f272d1 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_unittest.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak_unittest.js
@@ -7,14 +7,7 @@
 /**
  * Test fixture for select_to_speak.js.
  */
-SelectToSpeakUnitTest = class extends SelectToSpeakE2ETest {
-  /** @override */
-  async setUpDeferred() {
-    await super.setUpDeferred();
-    await importModule(
-        'getGSuiteAppRoot', '/select_to_speak/select_to_speak.js');
-  }
-};
+SelectToSpeakUnitTest = class extends SelectToSpeakE2ETest {};
 
 AX_TEST_F('SelectToSpeakUnitTest', 'getGSuiteAppRoot', function() {
   const root = {url: 'https://docs.google.com/presentation/p/cats_r_awesome'};
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/tts_manager.ts b/chrome/browser/resources/chromeos/accessibility/select_to_speak/tts_manager.ts
index 2af10e8a..6793317 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/tts_manager.ts
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/tts_manager.ts
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {TestImportManager} from '/common/testing/test_import_manager.js';
+
 /**
  * The wrapper for Select-to-speak's text-to-speech features.
  */
@@ -254,3 +256,5 @@
     RESUME_WITH_EMPTY_CONTENT = 'Cannot resume with empty content.',
   }
 }
+
+TestImportManager.exportForTesting(TtsManager);
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/tts_manager_unittest.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/tts_manager_unittest.js
index b835f79..c9f3273 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/tts_manager_unittest.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/tts_manager_unittest.js
@@ -31,8 +31,6 @@
   /** @override */
   async setUpDeferred() {
     await super.setUpDeferred();
-    const module = await import('/select_to_speak/tts_manager.js');
-    window.TtsManager = module.TtsManager;
 
     this.mockTtsClient = new MockTtsClient();
     this.ttsManager = new TtsManager();
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager.ts b/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager.ts
index 8b01d91..431149f 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager.ts
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager.ts
@@ -4,6 +4,7 @@
 
 import {AutomationUtil} from '/common/automation_util.js';
 import {ParagraphUtils} from '/common/paragraph_utils.js';
+import {TestImportManager} from '/common/testing/test_import_manager.js';
 
 import {PrefsManager} from './prefs_manager.js';
 
@@ -400,3 +401,7 @@
     }) !== undefined;
   }
 }
+
+TestImportManager.exportForTesting(
+    UiManager,
+    ['SELECT_TO_SPEAK_TRAY_CLASS_NAME', SELECT_TO_SPEAK_TRAY_CLASS_NAME]);
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager_unittest.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager_unittest.js
index 25429e2b..d678042b 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager_unittest.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager_unittest.js
@@ -95,12 +95,6 @@
   async setUpDeferred() {
     await super.setUpDeferred();
 
-    await Promise.all([
-      importModule('ParagraphUtils', '/common/paragraph_utils.js'),
-      importModule('UiManager', '/select_to_speak/ui_manager.js'),
-      importModule('PrefsManager', '/select_to_speak/prefs_manager.js'),
-    ]);
-
     this.mockPrefsManager = new MockPrefsManager();
     this.mockListener = new MockUiListener();
     this.uiManager = new UiManager(this.mockPrefsManager, this.mockListener);
diff --git a/chrome/browser/resources/chromeos/arc_support/background.js b/chrome/browser/resources/chromeos/arc_support/background.js
index 821e739..6237563 100644
--- a/chrome/browser/resources/chromeos/arc_support/background.js
+++ b/chrome/browser/resources/chromeos/arc_support/background.js
@@ -350,7 +350,7 @@
 
   /** Called when the TermsOfService page is shown. */
   onShow() {
-    if (this.isManaged_ || this.state_ == LoadState.LOADED) {
+    if (this.isManaged_ || this.state_ === LoadState.LOADED) {
       // Note: in managed case, because it does not show the contents of terms
       // of service, it is ok to show the content container immediately.
       this.showContent_();
@@ -382,7 +382,7 @@
 
   /** Callback for getDropDown in showContext_. */
   focusOnLangZoneSelect_(results) {
-    if (results.length != 1) {
+    if (results.length !== 1) {
       console.error('unexpected return value of the script');
       return;
     }
@@ -425,7 +425,7 @@
 
   /** Starts to load the terms of service webview content. */
   startTermsViewLoading_() {
-    if (this.state_ == LoadState.LOADING) {
+    if (this.state_ === LoadState.LOADING) {
       // If there already is inflight loading task, do nothing.
       return;
     }
@@ -434,7 +434,7 @@
     if (this.termsView_.src) {
       // This is reloading the page, typically clicked RETRY on error page.
       this.fastLocation_ = undefined;
-      if (this.termsView_.src == defaultLocation) {
+      if (this.termsView_.src === defaultLocation) {
         this.termsView_.reload();
       } else {
         this.termsView_.src = defaultLocation;
@@ -478,7 +478,7 @@
     // In such a case, onTermsViewLoadAborted_() is called in advance, and
     // state_ is set to ABORTED. Here, switch the view only for the
     // successful loading case.
-    if (this.state_ == LoadState.LOADING) {
+    if (this.state_ === LoadState.LOADING) {
       const getToSContent = {code: 'getToSContent();'};
       termsPage.termsView_.executeScript(
           getToSContent, this.onGetToSContent_.bind(this));
@@ -487,8 +487,8 @@
 
   /** Callback for getToSContent. */
   onGetToSContent_(results) {
-    if (this.state_ == LoadState.LOADING) {
-      if (!results || results.length != 1 || typeof results[0] !== 'string') {
+    if (this.state_ === LoadState.LOADING) {
+      if (!results || results.length !== 1 || typeof results[0] !== 'string') {
         this.onTermsViewLoadAborted_('unable to get ToS content');
         return;
       }
@@ -524,7 +524,7 @@
 
   /** Called when the terms-view's load request is completed. */
   onTermsViewRequestCompleted_(details) {
-    if (this.state_ != LoadState.LOADING || details.statusCode == 200) {
+    if (this.state_ !== LoadState.LOADING || details.statusCode === 200) {
       return;
     }
 
@@ -636,25 +636,25 @@
     return;
   }
 
-  if (message.action == 'initialize') {
+  if (message.action === 'initialize') {
     initialize(message.data, message.deviceId);
-  } else if (message.action == 'setMetricsMode') {
+  } else if (message.action === 'setMetricsMode') {
     termsPage.onMetricsPreferenceChanged(message.enabled, message.managed);
-  } else if (message.action == 'setBackupAndRestoreMode') {
+  } else if (message.action === 'setBackupAndRestoreMode') {
     termsPage.onBackupRestorePreferenceChanged(
         message.enabled, message.managed);
-  } else if (message.action == 'setLocationServiceMode') {
+  } else if (message.action === 'setLocationServiceMode') {
     termsPage.onLocationServicePreferenceChanged(
         message.enabled, message.managed);
-  } else if (message.action == 'showPage') {
+  } else if (message.action === 'showPage') {
     showPage(message.page);
-  } else if (message.action == 'showErrorPage') {
+  } else if (message.action === 'showErrorPage') {
     showErrorPage(
         message.errorMessage, message.shouldShowSendFeedback,
         message.shouldShowNetworkTests);
-  } else if (message.action == 'closeWindow') {
+  } else if (message.action === 'closeWindow') {
     closeWindow();
-  } else if (message.action == 'setWindowBounds') {
+  } else if (message.action === 'setWindowBounds') {
     setWindowBounds(
         message.displayWorkareaX, message.displayWorkareaY,
         message.displayWorkareaWidth, message.displayWorkareaHeight);
@@ -686,17 +686,17 @@
 
   const pages = doc.getElementsByClassName('section');
   for (let i = 0; i < pages.length; i++) {
-    pages[i].hidden = pages[i].id != pageDivId;
+    pages[i].hidden = pages[i].id !== pageDivId;
   }
 
   appWindow.show();
-  if (pageDivId == 'terms') {
+  if (pageDivId === 'terms') {
     termsPage.onShow();
   }
 
   // Start progress bar animation for the page that has the dynamic progress
   // bar. 'error' page has the static progress bar that no need to be animated.
-  if (pageDivId == 'terms' || pageDivId == 'arc-loading') {
+  if (pageDivId === 'terms' || pageDivId === 'arc-loading') {
     appWindow.contentWindow.startProgressAnimation(pageDivId);
   }
 }
@@ -802,7 +802,7 @@
   }
   const details = {code: 'getPrivacyPolicyLink();'};
   termsPage.termsView_.executeScript(details, function(results) {
-    if (results && results.length == 1 && typeof results[0] == 'string') {
+    if (results && results.length === 1 && typeof results[0] === 'string') {
       showURLOverlay(results[0]);
     } else {
       showURLOverlay(defaultLink);
diff --git a/chrome/browser/resources/chromeos/arc_support/playstore.js b/chrome/browser/resources/chromeos/arc_support/playstore.js
index 563d53f..e8e57f9 100644
--- a/chrome/browser/resources/chromeos/arc_support/playstore.js
+++ b/chrome/browser/resources/chromeos/arc_support/playstore.js
@@ -8,11 +8,11 @@
  */
 function getPlayFooterElement() {
   const elements = document.getElementsByClassName('glue-footer');
-  if (!elements || elements.length == 0) {
+  if (!elements || elements.length === 0) {
     console.error('Failed to find play-footer element in ToS.');
     return null;
   }
-  if (elements.length != 1) {
+  if (elements.length !== 1) {
     console.error('Found more than one play-footer element in ToS.');
   }
   return elements[0];
@@ -28,11 +28,11 @@
   }
 
   const elements = footer.getElementsByTagName('select');
-  if (!elements || elements.length == 0) {
+  if (!elements || elements.length === 0) {
     console.error('Cannot find zone/language select select element');
     return null;
   }
-  if (elements.length != 1) {
+  if (elements.length !== 1) {
     console.error('Found more than one zone/language select element in ToS.');
   }
   return elements[0];
@@ -57,7 +57,7 @@
     const matchDefaultUs = null;
     if (window.location.href.startsWith(
             'https://play.google/intl/en_us/play-terms') &&
-        termsLang == 'en' && countryCode == 'us' &&
+        termsLang === 'en' && countryCode === 'us' &&
         selectLangZoneTerms.value.startsWith('/intl/en/play-terms')) {
       return true;
     }
@@ -84,7 +84,7 @@
     return true;
   }
   const langSegments = language.split('-');
-  if (langSegments.length == 2 && applyTermsForLangAndZone(langSegments[0])) {
+  if (langSegments.length === 2 && applyTermsForLangAndZone(langSegments[0])) {
     return true;
   }
 
@@ -117,7 +117,7 @@
 
   const matchByLang = '/intl/' + language + '_';
   let matchByLangShort = null;
-  if (langSegments.length == 2) {
+  if (langSegments.length === 2) {
     matchByLangShort = '/intl/' + langSegments[0] + '_';
   }
 
@@ -137,7 +137,7 @@
 
   for (let i = selectLangZoneTerms.options.length - 1; i >= 0; --i) {
     const option = selectLangZoneTerms.options[i];
-    if (selectLangZoneTerms.selectedIndex == i) {
+    if (selectLangZoneTerms.selectedIndex === i) {
       langMatch = option.value.startsWith(matchByLang) ||
           (matchByLangShort && option.value.startsWith(matchByLangShort));
       continue;
@@ -150,7 +150,7 @@
     option.hidden = !option.value.startsWith(matchByLang) &&
         !option.value.includes(matchByZone) &&
         !(matchByLangShort && option.value.startsWith(matchByLangShort)) &&
-        option.text != 'English';
+        option.text !== 'English';
   }
 
   if (initialLoad && !langMatch && defaultExist) {
diff --git a/chrome/browser/resources/chromeos/emoji_picker/store.ts b/chrome/browser/resources/chromeos/emoji_picker/store.ts
index c82d725..c0481da 100644
--- a/chrome/browser/resources/chromeos/emoji_picker/store.ts
+++ b/chrome/browser/resources/chromeos/emoji_picker/store.ts
@@ -4,9 +4,10 @@
 
 import {EmojiPickerApiProxy} from 'emoji_picker_api_proxy.js';
 
+import {EMOJI_PER_ROW} from './constants.js';
 import {CategoryEnum, Emoji, EmojiVariants, Gender, PreferenceMapping, Tone, VisualContent} from './types.js';
 
-const MAX_RECENTS = 10;
+const MAX_RECENTS = EMOJI_PER_ROW * 2;
 
 class Store<T> {
   data: T;
diff --git a/chrome/browser/resources/chromeos/launcher_internals/launcher_internals.ts b/chrome/browser/resources/chromeos/launcher_internals/launcher_internals.ts
index 4a01983..2cc2738 100644
--- a/chrome/browser/resources/chromeos/launcher_internals/launcher_internals.ts
+++ b/chrome/browser/resources/chromeos/launcher_internals/launcher_internals.ts
@@ -87,13 +87,13 @@
     }
 
     if (searchResults.length > 0) {
-      if (this.query != query) {
+      if (this.query !== query) {
         // Only reset search results if the query changes.
         this.$.searchResults.clearResults();
         this.query = query;
       }
 
-      if (this.keywords != keywords) {
+      if (this.keywords !== keywords) {
         this.keywords = keywords;
       }
 
diff --git a/chrome/browser/resources/chromeos/login/screens/oobe/demo_preferences.ts b/chrome/browser/resources/chromeos/login/screens/oobe/demo_preferences.ts
index bf7ba4e..59fa85a 100644
--- a/chrome/browser/resources/chromeos/login/screens/oobe/demo_preferences.ts
+++ b/chrome/browser/resources/chromeos/login/screens/oobe/demo_preferences.ts
@@ -26,6 +26,11 @@
 
 import {getTemplate} from './demo_preferences.html.js';
 
+// The retailer name input has the max length of 256 characters.
+const RETAILER_NAME_INPUT_MAX_LENGTH = 256;
+// The store number input has the max length of 256 characters.
+const STORE_NUMBER_INPUT_MAX_LENGTH = 256;
+
 const DemoPreferencesScreenBase =
     mixinBehaviors(
         [OobeI18nBehavior, OobeDialogHostBehavior, LoginScreenBehavior],
@@ -205,12 +210,26 @@
    * Based on the country, retailer name, and store number preferences being
    * correctly set.
    *
+   * We need to check all fields (parameters) are not undefined, not null and
+   * non-empty (console.log(!!"") => false) before checking their value or
+   * length.
+   *
+   * The retailer name must be a non-empty string in the max length of 256
+   * characters.
+   * The store number must be a non-empty numerical string in the max length
+   * of 256 characters.
+   *
+   * TODO(b/324086625): Add help text of the string length limit on the
+   * retailer name field and store number field.
    */
   private userCanContinue_(
       retailerNameInput: string, storeNumberInput: string,
       isCountrySelected: boolean): boolean {
-    return !!retailerNameInput && RegExp('^[0-9]+$').test(storeNumberInput) &&
-        isCountrySelected;
+    return !!retailerNameInput && !!isCountrySelected && !!storeNumberInput &&
+        isCountrySelected &&
+        storeNumberInput.length <= STORE_NUMBER_INPUT_MAX_LENGTH &&
+        retailerNameInput.length <= RETAILER_NAME_INPUT_MAX_LENGTH &&
+        RegExp('^[0-9]+$').test(storeNumberInput);
   }
 
   /**
@@ -218,6 +237,9 @@
    * only consider the input invalid if it's nonempty, thus the different
    * pattern than in {@link userCanContinue_}
    *
+   * TODO(b/324086625): Add help text of the string length limit on the
+   * retailer name field and store number field on the Demo Mode preferences
+   * screen.
    */
   private isStoreNumberInputInvalid_(storeNumberInput: string): boolean {
     return !RegExp('^[0-9]*$').test(storeNumberInput);
diff --git a/chrome/browser/resources/tab_search/BUILD.gn b/chrome/browser/resources/tab_search/BUILD.gn
index 5303430..90091769 100644
--- a/chrome/browser/resources/tab_search/BUILD.gn
+++ b/chrome/browser/resources/tab_search/BUILD.gn
@@ -43,7 +43,7 @@
     "tab_data.ts",
     "tab_group_color_helper.ts",
     "tab_search_api_proxy.ts",
-    "tab_search_sync_browser_proxy.ts",
+    "tab_search_sign_in_browser_proxy.ts",
     "tab_search.ts",
     "tab_search_utils.ts",
     "title_item.ts",
diff --git a/chrome/browser/resources/tab_search/tab_organization_not_started.html b/chrome/browser/resources/tab_search/tab_organization_not_started.html
index 06df4264..661879d 100644
--- a/chrome/browser/resources/tab_search/tab_organization_not_started.html
+++ b/chrome/browser/resources/tab_search/tab_organization_not_started.html
@@ -45,9 +45,9 @@
       [[getTitle_(showFre)]]
     </div>
     <div class="tab-organization-body">
-      [[getBody_(showFre, sync_, account_)]]
+      [[getBody_(showFre, signedIn_)]]
       <template is="dom-if"
-          if="[[shouldShowBodyLink_(showFre, sync_, account_)]]">
+          if="[[shouldShowBodyLink_(showFre, signedIn_)]]">
         <a class="tab-organization-link"
             role="link"
             tabindex="0"
@@ -58,22 +58,9 @@
       </template>
     </div>
   </div>
-  <template is="dom-if" if="[[shouldShowAccountInfo_(sync_, account_)]]">
-    <div class="account-row">
-      <!-- Decorative image, intentionally empty alt text -->
-      <img class="account-image" alt=""
-          src="[[getAccountImageSrc_(account_.avatarImage)]]">
-      <div class="account-text">
-        <div class="tab-organization-header">[[account_.name]]</div>
-        <div class="tab-organization-body account-email">
-          [[account_.email]]
-        </div>
-      </div>
-    </div>
-  </template>
   <cr-button class="action-button"
-      aria-label="[[getButtonAriaLabel_(sync_, account_)]]"
+      aria-label="[[getButtonAriaLabel_(showFre, signedIn_)]]"
       on-click="onButtonClick_">
-    [[getButtonText_(sync_, account_)]]
+    [[getButtonText_(showFre, signedIn_)]]
   </cr-button>
 </div>
diff --git a/chrome/browser/resources/tab_search/tab_organization_not_started.ts b/chrome/browser/resources/tab_search/tab_organization_not_started.ts
index e73f641..28b6a41 100644
--- a/chrome/browser/resources/tab_search/tab_organization_not_started.ts
+++ b/chrome/browser/resources/tab_search/tab_organization_not_started.ts
@@ -12,16 +12,8 @@
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './tab_organization_not_started.html.js';
-import type {AccountInfo, SyncInfo, TabSearchSyncBrowserProxy} from './tab_search_sync_browser_proxy.js';
-import {TabSearchSyncBrowserProxyImpl} from './tab_search_sync_browser_proxy.js';
-
-enum SyncState {
-  SIGNED_OUT,
-  UNSYNCED,
-  UNSYNCED_HISTORY,
-  SYNC_PAUSED,
-  SYNCED,
-}
+import type {TabSearchSignInBrowserProxy} from './tab_search_sign_in_browser_proxy.js';
+import {TabSearchSignInBrowserProxyImpl} from './tab_search_sign_in_browser_proxy.js';
 
 const TabOrganizationNotStartedElementBase = WebUiListenerMixin(PolymerElement);
 
@@ -41,17 +33,15 @@
   static get properties() {
     return {
       showFre: Boolean,
-      account_: Object,
-      sync_: Object,
+      signedIn_: Boolean,
     };
   }
 
   showFre: boolean;
 
-  private account_?: AccountInfo;
-  private sync_?: SyncInfo;
-  private syncBrowserProxy_: TabSearchSyncBrowserProxy =
-      TabSearchSyncBrowserProxyImpl.getInstance();
+  private signedIn_: boolean = false;
+  private signInBrowserProxy_: TabSearchSignInBrowserProxy =
+      TabSearchSignInBrowserProxyImpl.getInstance();
 
   static get template() {
     return getTemplate();
@@ -60,11 +50,9 @@
   override connectedCallback() {
     super.connectedCallback();
 
-    this.syncBrowserProxy_.getAccountInfo().then(this.setAccount_.bind(this));
-    this.addWebUiListener('account-info-changed', this.setAccount_.bind(this));
-
-    this.syncBrowserProxy_.getSyncInfo().then(this.setSync_.bind(this));
-    this.addWebUiListener('sync-info-changed', this.setSync_.bind(this));
+    this.signInBrowserProxy_.getSignInState().then(
+        this.setSignedIn_.bind(this));
+    this.addWebUiListener('has-account-changed', this.setSignedIn_.bind(this));
   }
 
   announceHeader() {
@@ -72,26 +60,8 @@
     this.$.header.textContent = this.getTitle_();
   }
 
-  private setAccount_(account: AccountInfo) {
-    this.account_ = account;
-  }
-
-  private setSync_(sync: SyncInfo) {
-    this.sync_ = sync;
-  }
-
-  private getSyncState_(): SyncState {
-    if (!this.account_) {
-      return SyncState.SIGNED_OUT;
-    } else if (!this.sync_?.syncing) {
-      return SyncState.UNSYNCED;
-    } else if (this.sync_.paused) {
-      return SyncState.SYNC_PAUSED;
-    } else if (!this.sync_.syncingHistory) {
-      return SyncState.UNSYNCED_HISTORY;
-    } else {
-      return SyncState.SYNCED;
-    }
+  private setSignedIn_(signedIn: boolean) {
+    this.signedIn_ = signedIn;
   }
 
   private getTitle_(): string {
@@ -103,101 +73,51 @@
   }
 
   private getBody_(): string {
-    switch (this.getSyncState_()) {
-      case SyncState.SIGNED_OUT:
-        return loadTimeData.getString('notStartedBodySignedOut');
-      case SyncState.UNSYNCED:
-        return loadTimeData.getString('notStartedBodyUnsynced');
-      case SyncState.SYNC_PAUSED:
-        return loadTimeData.getString('notStartedBodySyncPaused');
-      case SyncState.UNSYNCED_HISTORY:
-        return loadTimeData.getString('notStartedBodyUnsyncedHistory');
-      case SyncState.SYNCED: {
-        if (this.showFre) {
-          return loadTimeData.getString('notStartedBodyFRE');
-        } else {
-          return loadTimeData.getString('notStartedBody');
-        }
-      }
+    if (!this.signedIn_) {
+      return loadTimeData.getString('notStartedBodySignedOut');
+    } else if (this.showFre) {
+      return loadTimeData.getString('notStartedBodyFRE');
+    } else {
+      return loadTimeData.getString('notStartedBody');
     }
   }
 
   private shouldShowBodyLink_(): boolean {
-    return this.getSyncState_() === SyncState.SYNCED && this.showFre;
-  }
-
-  private shouldShowAccountInfo_(): boolean {
-    return !!this.account_ &&
-        (!this.sync_ || !this.sync_.syncing || this.sync_.paused ||
-         !this.sync_.syncingHistory);
-  }
-
-  private getAccountImageSrc_(image: string|null): string {
-    // image can be undefined if the account has not set an avatar photo.
-    return image || 'chrome://theme/IDR_PROFILE_AVATAR_PLACEHOLDER_LARGE';
+    return this.signedIn_ && this.showFre;
   }
 
   private getButtonAriaLabel_(): string {
-    switch (this.getSyncState_()) {
-      case SyncState.SIGNED_OUT:
-      case SyncState.UNSYNCED:
-        return loadTimeData.getString('notStartedButtonUnsyncedAriaLabel');
-      case SyncState.SYNC_PAUSED:
-        return loadTimeData.getString('notStartedButtonSyncPausedAriaLabel');
-      case SyncState.UNSYNCED_HISTORY:
-        return loadTimeData.getString(
-            'notStartedButtonUnsyncedHistoryAriaLabel');
-      case SyncState.SYNCED:
-        if (this.showFre) {
-          return loadTimeData.getString('notStartedButtonFREAriaLabel');
-        } else {
-          return loadTimeData.getString('notStartedButtonAriaLabel');
-        }
+    if (!this.signedIn_) {
+      return loadTimeData.getString('notStartedButtonSignedOutAriaLabel');
+    } else if (this.showFre) {
+      return loadTimeData.getString('notStartedButtonFREAriaLabel');
+    } else {
+      return loadTimeData.getString('notStartedButtonAriaLabel');
     }
   }
 
   private getButtonText_(): string {
-    switch (this.getSyncState_()) {
-      case SyncState.SIGNED_OUT:
-      case SyncState.UNSYNCED:
-        return loadTimeData.getString('notStartedButtonUnsynced');
-      case SyncState.SYNC_PAUSED:
-        return loadTimeData.getString('notStartedButtonSyncPaused');
-      case SyncState.UNSYNCED_HISTORY:
-        return loadTimeData.getString('notStartedButtonUnsyncedHistory');
-      case SyncState.SYNCED:
-        if (this.showFre) {
-          return loadTimeData.getString('notStartedButtonFRE');
-        } else {
-          return loadTimeData.getString('notStartedButton');
-        }
+    if (!this.signedIn_) {
+      return loadTimeData.getString('notStartedButtonSignedOut');
+    } else if (this.showFre) {
+      return loadTimeData.getString('notStartedButtonFRE');
+    } else {
+      return loadTimeData.getString('notStartedButton');
     }
   }
 
   private onButtonClick_() {
-    switch (this.getSyncState_()) {
-      case SyncState.SIGNED_OUT:
-      case SyncState.UNSYNCED:
-        this.dispatchEvent(
-            new CustomEvent('sync-click', {bubbles: true, composed: true}));
-        break;
-      case SyncState.SYNC_PAUSED:
-        this.dispatchEvent(
-            new CustomEvent('sign-in-click', {bubbles: true, composed: true}));
-        break;
-      case SyncState.UNSYNCED_HISTORY:
-        this.dispatchEvent(
-            new CustomEvent('settings-click', {bubbles: true, composed: true}));
-        break;
-      case SyncState.SYNCED:
-        // Start a tab organization
-        this.dispatchEvent(new CustomEvent(
-            'organize-tabs-click', {bubbles: true, composed: true}));
-        chrome.metricsPrivate.recordBoolean(
-            'Tab.Organization.AllEntrypoints.Clicked', true);
-        chrome.metricsPrivate.recordBoolean(
-            'Tab.Organization.TabSearch.Clicked', true);
-        break;
+    if (!this.signedIn_) {
+      this.dispatchEvent(
+          new CustomEvent('sign-in-click', {bubbles: true, composed: true}));
+    } else {
+      // Start a tab organization
+      this.dispatchEvent(new CustomEvent(
+          'organize-tabs-click', {bubbles: true, composed: true}));
+      chrome.metricsPrivate.recordBoolean(
+          'Tab.Organization.AllEntrypoints.Clicked', true);
+      chrome.metricsPrivate.recordBoolean(
+          'Tab.Organization.TabSearch.Clicked', true);
     }
   }
 
diff --git a/chrome/browser/resources/tab_search/tab_organization_page.html b/chrome/browser/resources/tab_search/tab_organization_page.html
index 7a2b69e..63ca696f 100644
--- a/chrome/browser/resources/tab_search/tab_organization_page.html
+++ b/chrome/browser/resources/tab_search/tab_organization_page.html
@@ -54,9 +54,7 @@
   <div id="body">
     <tab-organization-not-started id="notStarted"
         shown$="[[isState_(tabOrganizationStateEnum_.kNotStarted, state_)]]"
-        on-sync-click="onSyncClick_"
         on-sign-in-click="onSignInClick_"
-        on-settings-click="onSettingsClick_"
         on-organize-tabs-click="onOrganizeTabsClick_"
         on-learn-more-click="onLearnMoreClick_"
         show-fre="[[showFRE_]]">
diff --git a/chrome/browser/resources/tab_search/tab_organization_page.ts b/chrome/browser/resources/tab_search/tab_organization_page.ts
index 50e9e72..a688bfef 100644
--- a/chrome/browser/resources/tab_search/tab_organization_page.ts
+++ b/chrome/browser/resources/tab_search/tab_organization_page.ts
@@ -230,18 +230,10 @@
     return this.state_ === state;
   }
 
-  private onSyncClick_() {
-    this.apiProxy_.triggerSync();
-  }
-
   private onSignInClick_() {
     this.apiProxy_.triggerSignIn();
   }
 
-  private onSettingsClick_() {
-    this.apiProxy_.openSyncSettings();
-  }
-
   private onOrganizeTabsClick_() {
     this.apiProxy_.requestTabOrganization();
   }
diff --git a/chrome/browser/resources/tab_search/tab_search.ts b/chrome/browser/resources/tab_search/tab_search.ts
index d7d13d1..aab3cc9 100644
--- a/chrome/browser/resources/tab_search/tab_search.ts
+++ b/chrome/browser/resources/tab_search/tab_search.ts
@@ -17,6 +17,6 @@
 export {TabSearchGroupItem} from './tab_search_group_item.js';
 export {TabSearchItem} from './tab_search_item.js';
 export {TabSearchPageElement} from './tab_search_page.js';
-export {AccountInfo, SyncInfo, TabSearchSyncBrowserProxy, TabSearchSyncBrowserProxyImpl} from './tab_search_sync_browser_proxy.js';
+export {TabSearchSignInBrowserProxy, TabSearchSignInBrowserProxyImpl} from './tab_search_sign_in_browser_proxy.js';
 export {TabAlertState} from './tabs.mojom-webui.js';
 export {TitleItem} from './title_item.js';
diff --git a/chrome/browser/resources/tab_search/tab_search_api_proxy.ts b/chrome/browser/resources/tab_search/tab_search_api_proxy.ts
index f98597c..793683a 100644
--- a/chrome/browser/resources/tab_search/tab_search_api_proxy.ts
+++ b/chrome/browser/resources/tab_search/tab_search_api_proxy.ts
@@ -51,14 +51,10 @@
 
   triggerFeedback(sessionId: number): void;
 
-  triggerSync(): void;
-
   triggerSignIn(): void;
 
   openHelpPage(): void;
 
-  openSyncSettings(): void;
-
   setUserFeedback(
       sessionId: number, organizationId: number, feedback: UserFeedback): void;
 
@@ -151,10 +147,6 @@
     this.handler.triggerFeedback(sessionId);
   }
 
-  triggerSync() {
-    this.handler.triggerSync();
-  }
-
   triggerSignIn() {
     this.handler.triggerSignIn();
   }
@@ -163,10 +155,6 @@
     this.handler.openHelpPage();
   }
 
-  openSyncSettings() {
-    this.handler.openSyncSettings();
-  }
-
   setUserFeedback(
       sessionId: number, organizationId: number, feedback: UserFeedback) {
     this.handler.setUserFeedback(sessionId, organizationId, feedback);
diff --git a/chrome/browser/resources/tab_search/tab_search_sign_in_browser_proxy.ts b/chrome/browser/resources/tab_search/tab_search_sign_in_browser_proxy.ts
new file mode 100644
index 0000000..ee63733
--- /dev/null
+++ b/chrome/browser/resources/tab_search/tab_search_sign_in_browser_proxy.ts
@@ -0,0 +1,32 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {sendWithPromise} from 'chrome://resources/js/cr.js';
+
+/**
+ * @see chrome/browser/ui/webui/tab_search/tab_search_sign_in_handler.cc
+ */
+export interface TabSearchSignInBrowserProxy {
+  /**
+   * Returns whether the user is signed in
+   */
+  getSignInState(): Promise<boolean>;
+}
+
+export class TabSearchSignInBrowserProxyImpl implements
+    TabSearchSignInBrowserProxy {
+  getSignInState() {
+    return sendWithPromise('GetSignInState');
+  }
+
+  static getInstance(): TabSearchSignInBrowserProxy {
+    return instance || (instance = new TabSearchSignInBrowserProxyImpl());
+  }
+
+  static setInstance(obj: TabSearchSignInBrowserProxy) {
+    instance = obj;
+  }
+}
+
+let instance: TabSearchSignInBrowserProxy|null = null;
diff --git a/chrome/browser/resources/tab_search/tab_search_sync_browser_proxy.ts b/chrome/browser/resources/tab_search/tab_search_sync_browser_proxy.ts
deleted file mode 100644
index 7e5e1e33..0000000
--- a/chrome/browser/resources/tab_search/tab_search_sync_browser_proxy.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {sendWithPromise} from 'chrome://resources/js/cr.js';
-
-/**
- * @see chrome/browser/ui/webui/tab_search/tab_search_sync_handler.cc
- */
-export interface AccountInfo {
-  name: string;
-  email: string;
-  avatarImage?: string;
-}
-
-export interface SyncInfo {
-  syncing: boolean;
-  paused: boolean;
-  syncingHistory: boolean;
-}
-
-export interface TabSearchSyncBrowserProxy {
-  /**
-   * Gets the current sync info.
-   */
-  getSyncInfo(): Promise<SyncInfo>;
-
-  /**
-   * Gets the current account info.
-   */
-  getAccountInfo(): Promise<AccountInfo>;
-}
-
-export class TabSearchSyncBrowserProxyImpl implements
-    TabSearchSyncBrowserProxy {
-  getSyncInfo() {
-    return sendWithPromise('GetSyncInfo');
-  }
-
-  getAccountInfo() {
-    return sendWithPromise('GetAccountInfo');
-  }
-
-  static getInstance(): TabSearchSyncBrowserProxy {
-    return instance || (instance = new TabSearchSyncBrowserProxyImpl());
-  }
-
-  static setInstance(obj: TabSearchSyncBrowserProxy) {
-    instance = obj;
-  }
-}
-
-let instance: TabSearchSyncBrowserProxy|null = null;
diff --git a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
index 173954e..a1b9ec1 100644
--- a/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_blocking_page_test.cc
@@ -133,14 +133,6 @@
 #include "ui/events/test/test_event.h"
 #include "ui/views/controls/styled_label.h"
 
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-// Delayed warnings feature checks if the Suspicious Site Reporter extension
-// is installed. These includes are to fake-install this extension.
-#include "chrome/browser/extensions/crx_installer.h"
-#include "extensions/browser/test_extension_registry_observer.h"
-#include "extensions/common/extension.h"
-#endif
-
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker.h"
 #endif
@@ -2960,8 +2952,6 @@
     host_resolver()->AddRule("*", "127.0.0.1");
     content::SetupCrossSiteRedirector(embedded_test_server());
     ASSERT_TRUE(embedded_test_server()->Start());
-    SafeBrowsingUserInteractionObserver::
-        ResetSuspiciousSiteReporterExtensionIdForTesting();
   }
 
   void CreatedBrowserMainParts(
@@ -3106,41 +3096,6 @@
   }
 
  protected:
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-  // Installs an extension and returns its ID.
-  std::string InstallTestExtension() {
-    using extensions::CrxInstaller;
-    using extensions::CrxInstallError;
-    using extensions::ExtensionService;
-    using extensions::ExtensionSystem;
-
-    base::FilePath path = ui_test_utils::GetTestFilePath(
-        base::FilePath().AppendASCII("extensions"),
-        base::FilePath().AppendASCII("theme.crx"));
-    ExtensionService* service =
-        ExtensionSystem::Get(browser()->profile())->extension_service();
-    scoped_refptr<CrxInstaller> installer = CrxInstaller::CreateSilent(service);
-
-    installer->set_install_cause(extension_misc::INSTALL_CAUSE_AUTOMATION);
-    installer->set_install_immediately(true);
-    installer->set_allow_silent_install(true);
-    installer->set_off_store_install_allow_reason(
-        CrxInstaller::OffStoreInstallAllowedInTest);
-    installer->set_creation_flags(extensions::Extension::FROM_WEBSTORE);
-
-    base::test::TestFuture<std::optional<CrxInstallError>> done_future;
-    installer->AddInstallerCallback(
-        done_future.GetCallback<const std::optional<CrxInstallError>&>());
-
-    installer->InstallCrx(path);
-
-    auto optional_error = done_future.Get();
-    EXPECT_FALSE(optional_error.has_value());
-
-    return installer->extension()->id();
-  }
-#endif
-
   base::test::ScopedFeatureList scoped_feature_list_;
 
  private:
@@ -3213,61 +3168,6 @@
                 ->GetLastCommittedURL());
 }
 
-// Same as KeyPress_WarningShown, but user disabled URL elision by enabling
-// "Always Show Full URLs" option. A separate histogram must be recorded.
-IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
-                       KeyPress_WarningShown_UrlElisionDisabled) {
-  constexpr int kTimeOnPage = 10;
-  browser()->profile()->GetPrefs()->SetBoolean(
-      omnibox::kPreventUrlElisionsInOmnibox, true);
-
-  base::HistogramTester histograms;
-  NavigateAndAssertNoInterstitial();
-  content::WebContents* web_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-
-  // Inject a test clock to test the histogram that records the time on the
-  // delayed warning page before the warning shows or the user leaves the page.
-  base::SimpleTestClock clock;
-  SafeBrowsingUserInteractionObserver* observer =
-      SafeBrowsingUserInteractionObserver::FromWebContents(web_contents);
-  ASSERT_TRUE(observer);
-  clock.SetNow(observer->GetCreationTimeForTesting());
-  observer->SetClockForTesting(&clock);
-  clock.Advance(base::Seconds(kTimeOnPage));
-
-  // Type something. An interstitial should be shown.
-  EXPECT_TRUE(TypeAndWaitForInterstitial(browser()));
-
-  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "primary-button"));
-  AssertNoInterstitial(browser(), false);  // Assert the interstitial is gone
-  EXPECT_EQ(GURL(url::kAboutBlankURL),     // Back to "about:blank"
-            web_contents->GetLastCommittedURL());
-}
-
-// Same as KeyPress_WarningShown_UrlElisionDisabled, but user disabled URL
-// elision by installing Suspicious Site Reporter extension.
-IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
-                       KeyPress_WarningShown_UrlElisionDisabled_Extension) {
-  const std::string extension_id = InstallTestExtension();
-  SafeBrowsingUserInteractionObserver::
-      SetSuspiciousSiteReporterExtensionIdForTesting(extension_id.c_str());
-
-  base::HistogramTester histograms;
-  NavigateAndAssertNoInterstitial();
-
-  // Type something. An interstitial should be shown.
-  EXPECT_TRUE(TypeAndWaitForInterstitial(browser()));
-
-  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "primary-button"));
-  AssertNoInterstitial(browser(), false);  // Assert the interstitial is gone
-  EXPECT_EQ(GURL(url::kAboutBlankURL),     // Back to "about:blank"
-            browser()
-                ->tab_strip_model()
-                ->GetActiveWebContents()
-                ->GetLastCommittedURL());
-}
-
 IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                        KeyPress_ESC_WarningNotShown) {
   base::HistogramTester histograms;
diff --git a/chrome/browser/safe_browsing/user_interaction_observer.cc b/chrome/browser/safe_browsing/user_interaction_observer.cc
index cd6fe29..0a4e2d8 100644
--- a/chrome/browser/safe_browsing/user_interaction_observer.cc
+++ b/chrome/browser/safe_browsing/user_interaction_observer.cc
@@ -19,25 +19,12 @@
 #include "third_party/blink/public/common/input/web_mouse_event.h"
 #include "ui/events/keycodes/keyboard_codes.h"
 
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-#include "extensions/browser/extension_registry.h"
-#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
-
 using blink::WebInputEvent;
 
-namespace {
-// Id for extension that enables users to report sites to Safe Browsing.
-const char kPreventElisionExtensionId[] = "jknemblkbdhdcpllfgbfekkdciegfboi";
-}  // namespace
-
 namespace safe_browsing {
 
 WEB_CONTENTS_USER_DATA_KEY_IMPL(SafeBrowsingUserInteractionObserver);
 
-// static
-const char* SafeBrowsingUserInteractionObserver::
-    suspicious_site_reporter_extension_id_ = kPreventElisionExtensionId;
-
 SafeBrowsingUserInteractionObserver::SafeBrowsingUserInteractionObserver(
     content::WebContents* web_contents,
     const security_interstitials::UnsafeResource& resource,
@@ -236,18 +223,6 @@
   // DO NOT add code past this point. |this| is destroyed.
 }
 
-// static
-void SafeBrowsingUserInteractionObserver::
-    SetSuspiciousSiteReporterExtensionIdForTesting(const char* extension_id) {
-  suspicious_site_reporter_extension_id_ = extension_id;
-}
-
-// static
-void SafeBrowsingUserInteractionObserver::
-    ResetSuspiciousSiteReporterExtensionIdForTesting() {
-  suspicious_site_reporter_extension_id_ = kPreventElisionExtensionId;
-}
-
 void SafeBrowsingUserInteractionObserver::SetClockForTesting(
     base::Clock* clock) {
   clock_ = clock;
diff --git a/chrome/browser/safe_browsing/user_interaction_observer.h b/chrome/browser/safe_browsing/user_interaction_observer.h
index 49d08fa4..3ddb6b9 100644
--- a/chrome/browser/safe_browsing/user_interaction_observer.h
+++ b/chrome/browser/safe_browsing/user_interaction_observer.h
@@ -59,14 +59,6 @@
   kMaxValue = kWarningShownOnPaste,
 };
 
-// Name of the recorded histograms when the user did not disable URL elision via
-// "Always Show Full URLs" menu option or by installing Suspicious Site Reporter
-// extension.
-extern const char kDelayedWarningsTimeOnPageHistogram[];
-
-// Same as above but only recorded if the user disabled URL elision.
-extern const char kDelayedWarningsTimeOnPageWithElisionDisabledHistogram[];
-
 // Observes user interactions and shows an interstitial if necessary.
 // Only created when an interstitial was about to be displayed but was delayed
 // due to the Delayed Warnings experiment. Deleted once the interstitial is
@@ -112,10 +104,6 @@
   // a desktop capture. Shows the delayed interstitial immediately.
   void OnDesktopCaptureRequest();
 
-  static void SetSuspiciousSiteReporterExtensionIdForTesting(
-      const char* extension_id);
-  static void ResetSuspiciousSiteReporterExtensionIdForTesting();
-
   void SetClockForTesting(base::Clock* clock);
   base::Time GetCreationTimeForTesting() const;
 
@@ -153,9 +141,6 @@
   // it the first time the hook is called.
   bool initial_navigation_finished_ = false;
 
-  // Id of the Suspicious Site Reporter extension. Only set in tests.
-  static const char* suspicious_site_reporter_extension_id_;
-
   // The time that this observer was created. Used for recording histograms.
   base::Time creation_time_;
   // This clock is used to record the delta from |creation_time_| when the
diff --git a/chrome/browser/screen_ai/screen_ai_install_state.cc b/chrome/browser/screen_ai/screen_ai_install_state.cc
index 3d88d7aa..8817b21 100644
--- a/chrome/browser/screen_ai/screen_ai_install_state.cc
+++ b/chrome/browser/screen_ai/screen_ai_install_state.cc
@@ -29,7 +29,7 @@
 
 namespace {
 const int kScreenAICleanUpDelayInDays = 30;
-const char kMinExpectedVersion[] = "121.1";
+const char kMinExpectedVersion[] = "123.1";
 
 bool IsDeviceCompatible() {
   // Check if the CPU has the required instruction set to run the Screen AI
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 19270d52f..47f3dd6 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1932,8 +1932,8 @@
       "webui/tab_search/tab_search_page_handler.h",
       "webui/tab_search/tab_search_prefs.cc",
       "webui/tab_search/tab_search_prefs.h",
-      "webui/tab_search/tab_search_sync_handler.cc",
-      "webui/tab_search/tab_search_sync_handler.h",
+      "webui/tab_search/tab_search_sign_in_handler.cc",
+      "webui/tab_search/tab_search_sign_in_handler.h",
       "webui/tab_search/tab_search_ui.cc",
       "webui/tab_search/tab_search_ui.h",
       "webui/theme_handler.cc",
@@ -3689,6 +3689,7 @@
       "//ash/webui/personalization_app/proto",
       "//ash/webui/personalization_app/search:mojo_bindings",
       "//ash/webui/print_management",
+      "//ash/webui/print_preview_cros",
       "//ash/webui/projector_app",
       "//ash/webui/projector_app/public/mojom:projector_mojo_bindings",
       "//ash/webui/scanning",
@@ -6879,8 +6880,8 @@
 
   if (enable_compose) {
     sources += [
-      "webui/compose/compose_ui.cc",
-      "webui/compose/compose_ui.h",
+      "webui/compose/compose_untrusted_ui.cc",
+      "webui/compose/compose_untrusted_ui.h",
     ]
     deps += [
       "//chrome/common/compose:mojo_bindings",
diff --git a/chrome/browser/ui/ash/app_list/app_list_search_browsertest.cc b/chrome/browser/ui/ash/app_list/app_list_search_browsertest.cc
index 61076a0..86b0be1 100644
--- a/chrome/browser/ui/ash/app_list/app_list_search_browsertest.cc
+++ b/chrome/browser/ui/ash/app_list/app_list_search_browsertest.cc
@@ -7,12 +7,14 @@
 #include "ash/app_list/views/app_list_search_view.h"
 #include "ash/app_list/views/search_box_view.h"
 #include "ash/app_list/views/search_result_list_view.h"
+#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/app_list/app_list_types.h"
 #include "ash/public/cpp/test/app_list_test_api.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/shell.h"
 #include "ash/test/active_window_waiter.h"
 #include "ash/webui/os_feedback_ui/url_constants.h"
+#include "ash/webui/shortcut_customization_ui/url_constants.h"
 #include "base/run_loop.h"
 #include "chrome/browser/ash/app_list/app_list_client_impl.h"
 #include "chrome/browser/ash/app_list/search/test/app_list_search_test_helper.h"
@@ -74,13 +76,15 @@
     base::RunLoop().RunUntilIdle();
   }
 
-  void ClickTopSearchResult(aura::Window* primary_root_window,
-                            const std::u16string app_title) {
+  void ClickTopSearchResult(
+      aura::Window* primary_root_window,
+      const std::u16string app_title,
+      SearchResultListView::SearchResultListType list_type) {
     SearchResultListView* top_result_list =
         AppListTestApi().GetTopVisibleSearchResultListView();
     ASSERT_TRUE(top_result_list);
-    EXPECT_EQ(top_result_list->list_type_for_test(),
-              SearchResultListView::SearchResultListType::kApps);
+    EXPECT_EQ(top_result_list->list_type_for_test(), list_type);
+
     SearchResultView* top_result_view = top_result_list->GetResultViewAt(0);
     ASSERT_TRUE(top_result_view);
     ASSERT_TRUE(top_result_view->result());
@@ -101,6 +105,12 @@
       chromeos::features::kCrosWebAppShortcutUiUpdate};
 };
 
+class AppListSearchWithCustomizableShortcutsBrowserTest
+    : public AppListSearchBrowserTest {
+  base::test::ScopedFeatureList scoped_feature_list_{
+      features::kSearchCustomizableShortcutsInLauncher};
+};
+
 IN_PROC_BROWSER_TEST_F(AppListSearchBrowserTest, SearchBuiltInApps) {
   const std::string app_id = web_app::kOsSettingsAppId;
   aura::Window* const primary_root_window = Shell::GetPrimaryRootWindow();
@@ -109,7 +119,8 @@
 
   ActiveWindowWaiter window_waiter(primary_root_window);
 
-  ClickTopSearchResult(primary_root_window, u"Settings");
+  ClickTopSearchResult(primary_root_window, u"Settings",
+                       SearchResultListView::SearchResultListType::kApps);
 
   // Wait for the OS Settings window to activate.
   aura::Window* app_window = window_waiter.Wait();
@@ -128,7 +139,8 @@
   content::TestNavigationObserver navigation_observer(feedback_url);
   navigation_observer.StartWatchingNewWebContents();
 
-  ClickTopSearchResult(primary_root_window, u"Feedback");
+  ClickTopSearchResult(primary_root_window, u"Feedback",
+                       SearchResultListView::SearchResultListType::kApps);
 
   // Wait for the Feedback app to launch.
   navigation_observer.Wait();
@@ -137,6 +149,49 @@
   EXPECT_TRUE(feedback_browser);
 }
 
+IN_PROC_BROWSER_TEST_F(AppListSearchBrowserTest, OpenShortcutsApp) {
+  aura::Window* const primary_root_window = Shell::GetPrimaryRootWindow();
+  SearchForSystemApp(primary_root_window, u"Key Shortcuts",
+                     web_app::kShortcutCustomizationAppId);
+
+  GURL shortcut_customization_url = GURL(kChromeUIShortcutCustomizationAppURL);
+  content::TestNavigationObserver navigation_observer(
+      shortcut_customization_url);
+  navigation_observer.StartWatchingNewWebContents();
+
+  ClickTopSearchResult(primary_root_window, u"Key Shortcuts",
+                       SearchResultListView::SearchResultListType::kApps);
+
+  // Wait for the Shortcut Customization app to launch.
+  navigation_observer.Wait();
+  Browser* shortcut_customization_browser = FindSystemWebAppBrowser(
+      browser()->profile(), SystemWebAppType::SHORTCUT_CUSTOMIZATION);
+  EXPECT_TRUE(shortcut_customization_browser);
+}
+
+// Flaky. See http://crbug.com/324930012.
+IN_PROC_BROWSER_TEST_F(AppListSearchWithCustomizableShortcutsBrowserTest,
+                       DISABLED_OpenShortcutsAppFromShortcut) {
+  // Launch the app from the Launcher via searching for a shortcut
+  aura::Window* const primary_root_window = Shell::GetPrimaryRootWindow();
+  SearchForSystemApp(primary_root_window, u"Open notifications",
+                     web_app::kShortcutCustomizationAppId);
+
+  GURL shortcut_customization_url = GURL(kChromeUIShortcutCustomizationAppURL);
+  content::TestNavigationObserver navigation_observer(
+      shortcut_customization_url);
+  navigation_observer.StartWatchingNewWebContents();
+
+  ClickTopSearchResult(primary_root_window, u"Open notifications",
+                       SearchResultListView::SearchResultListType::kHelp);
+
+  // Wait for the Shortcut Customization app to launch.
+  navigation_observer.Wait();
+  Browser* shortcut_customization_browser = FindSystemWebAppBrowser(
+      browser()->profile(), SystemWebAppType::SHORTCUT_CUSTOMIZATION);
+  EXPECT_TRUE(shortcut_customization_browser);
+}
+
 IN_PROC_BROWSER_TEST_F(AppListSearchWithAppShortcutsBrowserTest,
                        SearchWebAppShortcut) {
   Profile* profile = ProfileManager::GetActiveUserProfile();
diff --git a/chrome/browser/ui/ash/faster_split_screen_browsertest.cc b/chrome/browser/ui/ash/faster_split_screen_browsertest.cc
index b3b2837..2571da6 100644
--- a/chrome/browser/ui/ash/faster_split_screen_browsertest.cc
+++ b/chrome/browser/ui/ash/faster_split_screen_browsertest.cc
@@ -65,8 +65,9 @@
   ash::SystemWebAppManager::GetForTest(browser()->profile())
       ->InstallSystemAppsForTesting();
 
-  // Snap the window to start partial overview.
+  // Create two browser windows and snap `window` to start partial overview.
   aura::Window* window = browser()->window()->GetNativeWindow();
+  CreateBrowser(browser()->profile());
   ash::WindowState* window_state = ash::WindowState::Get(window);
   const ash::WindowSnapWMEvent primary_snap_event(
       ash::WM_EVENT_SNAP_PRIMARY, ash::WindowSnapActionSource::kTest);
@@ -108,13 +109,15 @@
 // restore'd, partial overview auto-snaps the window. See b/314816288.
 IN_PROC_BROWSER_TEST_F(FasterSplitScreenBrowserTest,
                        AutoSnapWhileInSessionRestore) {
-  // Snap the window to start partial overview.
-  aura::Window* window = browser()->window()->GetNativeWindow();
-  ash::WindowState* window_state = ash::WindowState::Get(window);
+  // Create two browser windows and snap `window1` to start partial overview.
+  aura::Window* window1 = browser()->window()->GetNativeWindow();
+  ash::WindowState* window_state = ash::WindowState::Get(window1);
+  CreateBrowser(browser()->profile());
+
   const ash::WindowSnapWMEvent primary_snap_event(
       ash::WM_EVENT_SNAP_PRIMARY, ash::WindowSnapActionSource::kTest);
   window_state->OnWMEvent(&primary_snap_event);
-  ash::WaitForOverviewEnterAnimation();
+  ash::WaitForOverviewEntered();
   ASSERT_TRUE(ash::OverviewController::Get()->InOverviewSession());
 
   // Open a new browser window. Test it gets auto-snapped.
diff --git a/chrome/browser/ui/ash/picker/picker_client_impl.cc b/chrome/browser/ui/ash/picker/picker_client_impl.cc
index cb6acfd1..b99606607 100644
--- a/chrome/browser/ui/ash/picker/picker_client_impl.cc
+++ b/chrome/browser/ui/ash/picker/picker_client_impl.cc
@@ -148,7 +148,7 @@
         app_list_controller_delegate_.GetUrlForSearchResult(*result);
     if (result_url.has_value()) {
       picker_results.push_back(ash::PickerSearchResult::BrowsingHistory(
-          *result_url, result->icon().icon));
+          *result_url, result->title(), result->icon().icon));
     } else {
       picker_results.push_back(ash::PickerSearchResult::Text(result->title()));
     }
diff --git a/chrome/browser/ui/exclusive_access/fullscreen_controller.cc b/chrome/browser/ui/exclusive_access/fullscreen_controller.cc
index 580de814..81d7bb0 100644
--- a/chrome/browser/ui/exclusive_access/fullscreen_controller.cc
+++ b/chrome/browser/ui/exclusive_access/fullscreen_controller.cc
@@ -551,8 +551,8 @@
   if (chrome::IsRunningInAppMode())
     return;
 
-  CHECK(fullscreen_start_time_);
-  if (exclusive_access_tab()) {
+  // `fullscreen_start_time_` is null if a fullscreen tab moves to a new window.
+  if (fullscreen_start_time_ && exclusive_access_tab()) {
     ukm::SourceId source_id =
         exclusive_access_tab()->GetPrimaryMainFrame()->GetPageUkmSourceId();
     ukm::builders::Fullscreen_Exit(source_id)
diff --git a/chrome/browser/ui/tabs/organization/tab_organization_service.cc b/chrome/browser/ui/tabs/organization/tab_organization_service.cc
index 4a1be5a7..b506554 100644
--- a/chrome/browser/ui/tabs/organization/tab_organization_service.cc
+++ b/chrome/browser/ui/tabs/organization/tab_organization_service.cc
@@ -11,14 +11,14 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/flag_descriptions.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/sync/sync_service_factory.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/organization/request_factory.h"
 #include "chrome/browser/ui/tabs/organization/tab_organization_session.h"
 #include "chrome/browser/ui/tabs/organization/tab_sensitivity_cache.h"
 #include "chrome/browser/ui/tabs/organization/trigger_policies.h"
 #include "chrome/browser/ui/webui/tab_search/tab_search_prefs.h"
-#include "components/sync/service/sync_user_settings.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "base/system/sys_info.h"
@@ -118,29 +118,18 @@
 }
 
 bool TabOrganizationService::CanStartRequest() const {
-  const syncer::SyncService* const sync_service =
-      SyncServiceFactory::GetForProfile(profile_);
-  if (!sync_service) {
-    return false;
-  }
-
-  // Sync must be enabled.
-  if (!sync_service->IsSyncFeatureEnabled()) {
-    return false;
-  }
-
-  // Sync must not be paused.
-  if (!sync_service->IsSyncFeatureActive()) {
-    return false;
-  }
-
-  // History Sync must be enabled.
-  if (!sync_service->GetUserSettings()->GetSelectedTypes().Has(
-          syncer::UserSelectableType::kHistory)) {
-    return false;
-  }
-
+// The signin flow is not used on ChromeOS.
+#if !BUILDFLAG(IS_CHROMEOS)
+  const signin::IdentityManager* const identity_manager(
+      IdentityManagerFactory::GetInstance()->GetForProfile(profile_));
+  const auto primary_account_info =
+      identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
+  const auto extended_account_info =
+      identity_manager->FindExtendedAccountInfo(primary_account_info);
+  return !extended_account_info.IsEmpty();
+#else
   return true;
+#endif  // !BUILDFLAG(IS_CHROMEOS)
 }
 
 void TabOrganizationService::StartRequestIfNotFRE(const Browser* browser) {
diff --git a/chrome/browser/ui/tabs/organization/tab_organization_service_unittest.cc b/chrome/browser/ui/tabs/organization/tab_organization_service_unittest.cc
index 5b692b4..21691920 100644
--- a/chrome/browser/ui/tabs/organization/tab_organization_service_unittest.cc
+++ b/chrome/browser/ui/tabs/organization/tab_organization_service_unittest.cc
@@ -5,7 +5,7 @@
 #include <memory>
 
 #include "base/test/scoped_feature_list.h"
-#include "chrome/browser/sync/sync_service_factory.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/organization/tab_organization_observer.h"
 #include "chrome/browser/ui/tabs/organization/tab_organization_service.h"
@@ -15,8 +15,7 @@
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
-#include "components/sync/test/test_sync_service.h"
+#include "components/signin/public/identity_manager/identity_test_utils.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_renderer_host.h"
 #include "content/public/test/web_contents_tester.h"
@@ -30,21 +29,11 @@
 constexpr char kValidURL[] = "http://zombo.com";
 constexpr char kInvalidURL[] = "chrome://page";
 
-std::unique_ptr<KeyedService> CreateSyncService(
-    content::BrowserContext* context) {
-  return std::make_unique<syncer::TestSyncService>();
-}
-
 }  // anonymous namespace
 
 class TabOrganizationServiceTest : public BrowserWithTestWindowTest {
  public:
-  TabOrganizationServiceTest()
-      : dependency_manager_subscription_(
-            BrowserContextDependencyManager::GetInstance()
-                ->RegisterCreateServicesCallbackForTesting(base::BindRepeating(
-                    &TabOrganizationServiceTest::SetTestingFactories,
-                    base::Unretained(this)))) {}
+  TabOrganizationServiceTest() {}
   TabOrganizationServiceTest(const TabOrganizationServiceTest&) = delete;
   TabOrganizationServiceTest& operator=(const TabOrganizationServiceTest&) =
       delete;
@@ -86,15 +75,20 @@
 
   TestingProfile* profile() { return profile_.get(); }
   TabOrganizationService* service() { return service_.get(); }
-  syncer::TestSyncService* sync_service() { return sync_service_; }
 
  private:
   void SetUp() override {
     feature_list_.InitWithFeatures({features::kTabOrganization}, {});
     profile_ = std::make_unique<TestingProfile>();
     service_ = std::make_unique<TabOrganizationService>(profile_.get());
-    sync_service_ = static_cast<syncer::TestSyncService*>(
-        SyncServiceFactory::GetInstance()->GetForProfile(profile_.get()));
+// The signin flow is not used on ChromeOS.
+#if !BUILDFLAG(IS_CHROMEOS)
+    signin::IdentityManager* identity_manager =
+        IdentityManagerFactory::GetForProfile(profile());
+    signin::MakeAccountAvailable(identity_manager, "test@example.com");
+    signin::SetPrimaryAccount(identity_manager, "test@example.com",
+                              signin::ConsentLevel::kSignin);
+#endif  // !BUILDFLAG(IS_CHROMEOS)
   }
   void TearDown() override {
     for (auto& browser : browsers_) {
@@ -102,19 +96,11 @@
     }
   }
 
-  void SetTestingFactories(content::BrowserContext* context) {
-    SyncServiceFactory::GetInstance()->SetTestingFactory(
-        context, base::BindRepeating(&CreateSyncService));
-  }
-
   content::RenderViewHostTestEnabler rvh_test_enabler_;
   std::unique_ptr<TestingProfile> profile_;
   std::unique_ptr<TabOrganizationService> service_;
   std::vector<std::unique_ptr<Browser>> browsers_;
   base::test::ScopedFeatureList feature_list_;
-
-  raw_ptr<syncer::TestSyncService> sync_service_;
-  base::CallbackListSubscription dependency_manager_subscription_;
 };
 
 class MockTabOrganizationObserver : public TabOrganizationObserver {
@@ -295,39 +281,23 @@
   EXPECT_NE(session->request()->base_tab_id(), std::nullopt);
 }
 
+// The signin flow is not used on ChromeOS.
+#if !BUILDFLAG(IS_CHROMEOS)
 TEST_F(TabOrganizationServiceTest, CanStartRequest) {
-  // // Not Synced
-  sync_service()->SetDisableReasons(
-      {syncer::SyncService::DISABLE_REASON_NOT_SIGNED_IN});
-  EXPECT_FALSE(service()->CanStartRequest());
-  sync_service()->SetDisableReasons({});
-
-  // Sync Paused
-  sync_service()->SetPersistentAuthError();
-  EXPECT_FALSE(service()->CanStartRequest());
-  sync_service()->ClearAuthError();
-
-  // Sync History not enabled
-  ASSERT_TRUE(sync_service()->GetActiveDataTypes().HasAll({syncer::HISTORY}));
-  sync_service()->GetUserSettings()->SetSelectedTypes(false, {});
-  ASSERT_FALSE(sync_service()->GetActiveDataTypes().HasAll({syncer::HISTORY}));
+  // Not signed in
+  signin::IdentityManager* identity_manager =
+      IdentityManagerFactory::GetForProfile(profile());
+  signin::SetPrimaryAccount(identity_manager, "unavailable@example.com",
+                            signin::ConsentLevel::kSignin);
   EXPECT_FALSE(service()->CanStartRequest());
 
-  sync_service()->GetUserSettings()->SetSelectedTypes(
-      false, {syncer::UserSelectableType::kHistory});
-  EXPECT_TRUE(service()->CanStartRequest());
-
-  // Should return true if everything is enabled.
-  sync_service()->GetUserSettings()->SetSelectedTypes(true, {});
+  // Signed in
+  signin::MakeAccountAvailable(identity_manager, "available@example.com");
+  signin::SetPrimaryAccount(identity_manager, "available@example.com",
+                            signin::ConsentLevel::kSignin);
   EXPECT_TRUE(service()->CanStartRequest());
 }
-
-TEST_F(TabOrganizationServiceTest, EnterpriseDisabledPolicy) {
-  EXPECT_TRUE(service()->CanStartRequest());
-  sync_service()->SetDisableReasons(
-      {syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY});
-  EXPECT_FALSE(service()->CanStartRequest());
-}
+#endif  // !BUILDFLAG(IS_CHROMEOS)
 
 TEST_F(TabOrganizationServiceTest, TabStripAddRemoveDestroysSession) {
   Browser* browser1 = AddBrowser();
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_model.cc b/chrome/browser/ui/toolbar/toolbar_actions_model.cc
index bb14cc5..18da142 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_model.cc
+++ b/chrome/browser/ui/toolbar/toolbar_actions_model.cc
@@ -239,6 +239,40 @@
   });
 }
 
+bool ToolbarActionsModel::IsPolicyBlockedHost(const GURL& url) const {
+  extensions::ManagementPolicy* policy =
+      extensions::ExtensionSystem::Get(profile_)->management_policy();
+  auto is_enterprise_extension =
+      [policy](const extensions::Extension& extension) {
+        return !policy->UserMayModifySettings(&extension, nullptr) ||
+               policy->MustRemainInstalled(&extension, nullptr);
+      };
+
+  // `url` is NOT a policy-blockedsite when there are no extensions installed.
+  if (action_ids().empty()) {
+    return false;
+  }
+
+  for (auto& action_id : action_ids()) {
+    // Skip enterprise extensions since they could still access policy-blocked
+    // sites.
+    const extensions::Extension* extension = GetExtensionById(action_id);
+    if (is_enterprise_extension(*extension)) {
+      continue;
+    }
+
+    // `url` is NOT a policy-blocked sit when it's allowed for any
+    // non-enterprise extension.
+    if (!extension->permissions_data()->IsPolicyBlockedHost(url)) {
+      return false;
+    }
+  }
+
+  // `url` is a policy-blocked site when it's blocked for every non-enterprise
+  // extension.
+  return true;
+}
+
 bool ToolbarActionsModel::IsActionPinned(const ActionId& action_id) const {
   return base::Contains(pinned_action_ids_, action_id);
 }
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_model.h b/chrome/browser/ui/toolbar/toolbar_actions_model.h
index d41ee2ef..550caa2 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_model.h
+++ b/chrome/browser/ui/toolbar/toolbar_actions_model.h
@@ -109,10 +109,14 @@
   // Returns true if `action_id` is in the toolbar model.
   bool HasAction(const ActionId& action_id) const;
 
-  // Returns true if `url` is restricted for all extensions with actions in the
-  // toolbar.ß
+  // Returns if `url` is restricted for all extensions with actions in the
+  // toolbar.
   bool IsRestrictedUrl(const GURL& url) const;
 
+  // Returns if `url` is a policy-blocked url for all non-enterprise extensions
+  // with actions in the toolbar.
+  bool IsPolicyBlockedHost(const GURL& url) const;
+
   // Returns true if the action is pinned to the toolbar.
   bool IsActionPinned(const ActionId& action_id) const;
 
diff --git a/chrome/browser/ui/views/compose/chrome_compose_dialog_controller.cc b/chrome/browser/ui/views/compose/chrome_compose_dialog_controller.cc
index 9605acc..d554e95 100644
--- a/chrome/browser/ui/views/compose/chrome_compose_dialog_controller.cc
+++ b/chrome/browser/ui/views/compose/chrome_compose_dialog_controller.cc
@@ -14,14 +14,6 @@
 #include "ui/views/bubble/bubble_border.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 
-// Default size from Figma spec. The size of the view will follow the requested
-// size of the WebUI, once these are connected.
-namespace {
-
-const char kComposeURL[] = "chrome://compose/";
-
-}
-
 namespace chrome {
 
 std::unique_ptr<compose::ComposeDialogController> ShowComposeDialog(
@@ -56,8 +48,10 @@
 
   Profile* profile =
       Profile::FromBrowserContext(web_contents_->GetBrowserContext());
-  auto bubble_wrapper = std::make_unique<WebUIContentsWrapperT<ComposeUI>>(
-      GURL(kComposeURL), profile, IDS_COMPOSE_DIALOG_TITLE);
+  auto bubble_wrapper =
+      std::make_unique<WebUIContentsWrapperT<ComposeUntrustedUI>>(
+          GURL(chrome::kChromeUIUntrustedComposeUrl), profile,
+          IDS_COMPOSE_DIALOG_TITLE);
   bubble_wrapper->ReloadWebContents();
 
   // This WebUI needs to know the calling BrowserContents so that the compose
@@ -102,7 +96,7 @@
   }
 }
 
-WebUIContentsWrapperT<ComposeUI>*
+WebUIContentsWrapperT<ComposeUntrustedUI>*
 ChromeComposeDialogController::GetBubbleWrapper() const {
   if (bubble_) {
     return bubble_->bubble_wrapper();
diff --git a/chrome/browser/ui/views/compose/chrome_compose_dialog_controller.h b/chrome/browser/ui/views/compose/chrome_compose_dialog_controller.h
index 71f1048..733ebe9c 100644
--- a/chrome/browser/ui/views/compose/chrome_compose_dialog_controller.h
+++ b/chrome/browser/ui/views/compose/chrome_compose_dialog_controller.h
@@ -9,7 +9,7 @@
 #include "base/scoped_observation.h"
 #include "chrome/browser/ui/views/bubble/webui_bubble_dialog_view.h"
 #include "chrome/browser/ui/views/compose/compose_dialog_view.h"
-#include "chrome/browser/ui/webui/compose/compose_ui.h"
+#include "chrome/browser/ui/webui/compose/compose_untrusted_ui.h"
 #include "chrome/browser/ui/webui/top_chrome/webui_contents_wrapper.h"
 #include "components/compose/core/browser/compose_dialog_controller.h"
 #include "components/strings/grit/components_strings.h"
@@ -39,7 +39,7 @@
 
   // Returns the currently shown compose dialog. Returns nullptr if the dialog
   // is not currently shown.
-  WebUIContentsWrapperT<ComposeUI>* GetBubbleWrapper() const;
+  WebUIContentsWrapperT<ComposeUntrustedUI>* GetBubbleWrapper() const;
 
   // Shows the current dialog view, if there is one.
   void ShowUI() override;
diff --git a/chrome/browser/ui/views/compose/compose_dialog_view.cc b/chrome/browser/ui/views/compose/compose_dialog_view.cc
index ef18e50..5ed7130 100644
--- a/chrome/browser/ui/views/compose/compose_dialog_view.cc
+++ b/chrome/browser/ui/views/compose/compose_dialog_view.cc
@@ -141,7 +141,7 @@
 
 ComposeDialogView::ComposeDialogView(
     View* anchor_view,
-    std::unique_ptr<WebUIContentsWrapperT<ComposeUI>> bubble_wrapper,
+    std::unique_ptr<WebUIContentsWrapperT<ComposeUntrustedUI>> bubble_wrapper,
     const gfx::Rect& anchor_bounds,
     views::BubbleBorder::Arrow anchor_position)
     : WebUIBubbleDialogView(anchor_view,
diff --git a/chrome/browser/ui/views/compose/compose_dialog_view.h b/chrome/browser/ui/views/compose/compose_dialog_view.h
index 3efde30..d58cb12b 100644
--- a/chrome/browser/ui/views/compose/compose_dialog_view.h
+++ b/chrome/browser/ui/views/compose/compose_dialog_view.h
@@ -6,7 +6,7 @@
 #define CHROME_BROWSER_UI_VIEWS_COMPOSE_COMPOSE_DIALOG_VIEW_H_
 
 #include "chrome/browser/ui/views/bubble/webui_bubble_dialog_view.h"
-#include "chrome/browser/ui/webui/compose/compose_ui.h"
+#include "chrome/browser/ui/webui/compose/compose_untrusted_ui.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/view.h"
@@ -34,7 +34,7 @@
 
   explicit ComposeDialogView(
       View* anchor_view,
-      std::unique_ptr<WebUIContentsWrapperT<ComposeUI>> bubble_wrapper,
+      std::unique_ptr<WebUIContentsWrapperT<ComposeUntrustedUI>> bubble_wrapper,
       const gfx::Rect& anchor_bounds,
       views::BubbleBorder::Arrow anchor_position);
 
@@ -47,7 +47,7 @@
   bool HandleContextMenu(content::RenderFrameHost& render_frame_host,
                          const content::ContextMenuParams& params) override;
 
-  WebUIContentsWrapperT<ComposeUI>* bubble_wrapper() {
+  WebUIContentsWrapperT<ComposeUntrustedUI>* bubble_wrapper() {
     return bubble_wrapper_.get();
   }
 
@@ -55,7 +55,7 @@
 
  private:
   gfx::Rect anchor_bounds_;
-  std::unique_ptr<WebUIContentsWrapperT<ComposeUI>> bubble_wrapper_;
+  std::unique_ptr<WebUIContentsWrapperT<ComposeUntrustedUI>> bubble_wrapper_;
   base::WeakPtrFactory<ComposeDialogView> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
index 0fe8840..54beb79 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
@@ -328,6 +328,13 @@
       label_id = IDS_EXTENSIONS_MENU_MESSAGE_SECTION_RESTRICTED_ACCESS_TEXT;
       show_label_tooltip = false;
       break;
+    case ExtensionsMenuMainPageView::MessageSectionState::kPolicyBlockedAccess:
+      container_type = ContainerType::kTextContainer;
+      label_id = IDS_EXTENSIONS_MENU_MESSAGE_SECTION_POLICY_BLOCKED_ACCESS_TEXT;
+      // Tooltip can only be visible on this state, and if there are any
+      // enterprise extensions installed.
+      show_label_tooltip = has_enterprise_extensions;
+      break;
     case ExtensionsMenuMainPageView::MessageSectionState::kUserCustomizedAccess:
       container_type = ContainerType::kRequestsAccessContainer;
       // This state has a static label, thus we don't need to pass a label id.
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h
index 732a880..3f42856 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h
+++ b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h
@@ -40,6 +40,8 @@
   enum class MessageSectionState {
     // Site is restricted to all extensions.
     kRestrictedAccess,
+    // Site is restricted all non-enterprise extensions by policy.
+    kPolicyBlockedAccess,
     // User can customize each extension's access to the site.
     kUserCustomizedAccess,
     // User can customize each extension's access to the site, but a page
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc
index f31372e..4cf3be7 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc
@@ -1025,8 +1025,8 @@
   EXPECT_EQ(menu_items().size(), 0u);
 }
 
-// Tests that extension's site permission button is always hidden when site is
-// restricted.
+// Tests that the site setting toggle, and extensions' site access toggle and
+// site permission button are always hidden when site is restricted.
 TEST_F(ExtensionsMenuMainPageViewUnitTest, RestrictedSite) {
   constexpr char kExtension[] = "Extension";
   constexpr char kEnterpriseExtension[] = "Enterprise extension";
@@ -1046,71 +1046,104 @@
   ShowMenu();
   ASSERT_EQ(menu_items().size(), 2u);
 
-  // Both extension's site permissions button should be hidden.
-  EXPECT_FALSE(
-      menu_items()[0]->site_permissions_button_for_testing()->GetVisible());
-  EXPECT_FALSE(
-      menu_items()[1]->site_permissions_button_for_testing()->GetVisible());
+  // Verify site setting toggle is not visible, since no extension can customize
+  // a restricted site.
+  EXPECT_FALSE(main_page()->GetSiteSettingsToggleForTesting()->GetVisible());
 
-  // Change site settings to "block all extensions".
-  extensions::PermissionsManagerWaiter waiter(
-      PermissionsManager::Get(browser()->profile()));
-  permissions_manager->UpdateUserSiteSetting(
-      restricted_origin,
-      PermissionsManager::UserSiteSetting::kBlockAllExtensions);
-  waiter.WaitForUserPermissionsSettingsChange();
-
-  // Both extension's site permission button should still be hidden (restricted
-  // sites have priority over enterprise extensions).
+  // Verify both extensions':
+  //   - site access toggle is hidden, since site access cannot be changed
+  //   - site permission button is hidden, since restricted sites have priority
+  //   over enterprise extensions.
+  EXPECT_FALSE(menu_items()[0]->site_access_toggle_for_testing()->GetVisible());
+  EXPECT_FALSE(menu_items()[1]->site_access_toggle_for_testing()->GetVisible());
   EXPECT_FALSE(
       menu_items()[0]->site_permissions_button_for_testing()->GetVisible());
   EXPECT_FALSE(
       menu_items()[1]->site_permissions_button_for_testing()->GetVisible());
 }
 
-// Tests that the extension's site access toggle is always hidden and site
-// permissions button is visible and disabled when site is blocked by policy.
+// Tests that the site setting toggle and extension's site access toggle is
+// always hidden, and extensions' site permissions button is visible and
+// disabled when site is blocked by policy.
 TEST_F(ExtensionsMenuMainPageViewUnitTest, PolicyBlockedSite) {
-  // Add a policy blocked site.
-  extensions::URLPatternSet default_blocked_hosts;
+  URLPattern default_policy_blocked_pattern =
+      URLPattern(URLPattern::SCHEME_ALL, "*://*.policy-blocked.com/*");
+
+  // Add a policy-blocked site.
   extensions::URLPatternSet default_allowed_hosts;
-  default_blocked_hosts.AddPattern(
-      URLPattern(URLPattern::SCHEME_ALL, "*://*.policy-blocked.com/*"));
+  extensions::URLPatternSet default_blocked_hosts;
+  default_blocked_hosts.AddPattern(default_policy_blocked_pattern);
   extensions::PermissionsData::SetDefaultPolicyHostRestrictions(
       extensions::util::GetBrowserContextId(browser()->profile()),
       default_blocked_hosts, default_allowed_hosts);
 
-  // Install extensions that request host permissions.
-  InstallExtensionWithHostPermissions("Extension", {"<all_urls>"});
+  // Install extensions requesting host permissions.
+  auto extension =
+      InstallExtensionWithHostPermissions("Extension", {"<all_urls>"});
+  auto enterprise_extension =
+      InstallEnterpriseExtension("Enterprise extension",
+                                 /*host_permissions=*/{"<all_urls>"});
 
-  InstallEnterpriseExtension("Enterprise extension",
-                             /*host_permissions=*/{"<all_urls>"});
+  // Allow enterprise extension access to policy-blocked site.
+  extensions::URLPatternSet allowed_hosts;
+  extensions::URLPatternSet blocked_hosts;
+  allowed_hosts.AddPattern(default_policy_blocked_pattern);
+  enterprise_extension->permissions_data()->SetPolicyHostRestrictions(
+      blocked_hosts, allowed_hosts);
 
   // Navigate to the policy-blocked site.
   const GURL policy_blocked_url("https://www.policy-blocked.com");
   auto restricted_origin = url::Origin::Create(policy_blocked_url);
   web_contents_tester()->NavigateAndCommit(policy_blocked_url);
 
-  // By default, site settings is set to "customize by extension".
-  PermissionsManager* permissions_manager = PermissionsManager::Get(profile());
-  EXPECT_EQ(permissions_manager->GetUserSiteSetting(restricted_origin),
-            PermissionsManager::UserSiteSetting::kCustomizeByExtension);
-
   ShowMenu();
   ASSERT_EQ(menu_items().size(), 2u);
 
-  // Both extensions' site access toggle should be hidden, since site access
-  // cannot be changed.
-  EXPECT_FALSE(menu_items()[0]->site_access_toggle_for_testing()->GetVisible());
-  EXPECT_FALSE(menu_items()[1]->site_access_toggle_for_testing()->GetVisible());
+  // Verify site setting toggle is not visible, since no extension can customize
+  // a policy-blocked site.
+  EXPECT_FALSE(main_page()->GetSiteSettingsToggleForTesting()->GetVisible());
 
-  // Both extension's site permissions button should be visible and disabled. We
-  // leave them visible because enterprise extensions can still have access to
-  // the site, but disabled because site access cannot be changed.
+  // Retrieve menu items.
+  ExtensionMenuItemView* enterprise_extension_item = menu_items()[0];
+  ExtensionMenuItemView* extension_item = menu_items()[1];
+  ASSERT_EQ(enterprise_extension_item->primary_action_button_for_testing()
+                ->label_text_for_testing(),
+            u"Enterprise extension");
+  ASSERT_EQ(extension_item->primary_action_button_for_testing()
+                ->label_text_for_testing(),
+            u"Extension");
+
+  // Verify both extensions':
+  //   - site access toggle is hidden, since site access cannot be changed
+  //   - site permissions button is visible and disabled. We leave them visible
+  //     because enterprise extensions can still have access to the site, but
+  //     disabled because site access cannot be changed.
+  EXPECT_FALSE(extension_item->site_access_toggle_for_testing()->GetVisible());
+  EXPECT_FALSE(enterprise_extension_item->site_access_toggle_for_testing()
+                   ->GetVisible());
+  EXPECT_TRUE(
+      extension_item->site_permissions_button_for_testing()->GetVisible());
+  EXPECT_TRUE(enterprise_extension_item->site_permissions_button_for_testing()
+                  ->GetVisible());
   EXPECT_FALSE(
-      menu_items()[0]->site_permissions_button_for_testing()->GetEnabled());
-  EXPECT_FALSE(
-      menu_items()[1]->site_permissions_button_for_testing()->GetEnabled());
+      extension_item->site_permissions_button_for_testing()->GetEnabled());
+  EXPECT_FALSE(enterprise_extension_item->site_permissions_button_for_testing()
+                   ->GetEnabled());
+
+  // Verify site permission button text for:
+  //   - extension is "none", since site is blocked by policy
+  //   - enterprise extension is "on all sites", since site is allowed to the
+  //     extension by policy.
+  EXPECT_EQ(
+      extension_item->site_permissions_button_for_testing()->title()->GetText(),
+      l10n_util::GetStringUTF16(
+          IDS_EXTENSIONS_MENU_MAIN_PAGE_EXTENSION_SITE_ACCESS_NONE));
+  EXPECT_EQ(
+      enterprise_extension_item->site_permissions_button_for_testing()
+          ->title()
+          ->GetText(),
+      l10n_util::GetStringUTF16(
+          IDS_EXTENSIONS_MENU_MAIN_PAGE_EXTENSION_SITE_ACCESS_ON_ALL_SITES));
 }
 
 // Tests that the message section only displays the text container when the
@@ -1139,6 +1172,44 @@
 }
 
 // Tests that the message section only displays the text container when the
+// site has policy-blocked access to all non-enterprise extensions.
+TEST_F(ExtensionsMenuMainPageViewUnitTest, MessageSection_PolicyBlockedAccess) {
+  // Add a policy blocked site.
+  extensions::URLPatternSet default_blocked_hosts;
+  extensions::URLPatternSet default_allowed_hosts;
+  default_blocked_hosts.AddPattern(
+      URLPattern(URLPattern::SCHEME_ALL, "*://*.policy-blocked.com/*"));
+  extensions::PermissionsData::SetDefaultPolicyHostRestrictions(
+      extensions::util::GetBrowserContextId(browser()->profile()),
+      default_blocked_hosts, default_allowed_hosts);
+
+  InstallExtensionWithHostPermissions("Extension", {"<all_urls>"});
+  InstallEnterpriseExtension("Enterprise extension", {"<all_urls>"});
+
+  // Navigate to the policy-blocked site.
+  const GURL policy_blocked_url("https://www.policy-blocked.com");
+  web_contents_tester()->NavigateAndCommit(policy_blocked_url);
+
+  ShowMenu();
+  views::View* text_container = main_page()->GetTextContainerForTesting();
+  views::View* reload_container = main_page()->GetReloadContainerForTesting();
+  views::View* requests_access_container =
+      main_page()->GetRequestsAccessContainerForTesting();
+
+  // Only the text container is displayed with policy blocked site message and
+  // tooltip, when site access is blocked to all non-enterprise extensions.
+  EXPECT_TRUE(text_container->GetVisible());
+  EXPECT_FALSE(reload_container->GetVisible());
+  EXPECT_FALSE(requests_access_container->GetVisible());
+  EXPECT_EQ(
+      views::AsViewClass<views::Label>(text_container->children()[0])
+          ->GetText(),
+      l10n_util::GetStringUTF16(
+          IDS_EXTENSIONS_MENU_MESSAGE_SECTION_POLICY_BLOCKED_ACCESS_TEXT));
+  EXPECT_TRUE(text_container->children()[1]->GetVisible());
+}
+
+// Tests that the message section only displays the text container when the
 // user has blocked all extensions on the site.
 TEST_F(ExtensionsMenuMainPageViewUnitTest, MessageSection_UserBlockedAccess) {
   InstallExtensionWithHostPermissions("Extension", {"<all_urls>"});
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_view_controller.cc b/chrome/browser/ui/views/extensions/extensions_menu_view_controller.cc
index 0738d611..baa5fbd 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_view_controller.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_view_controller.cc
@@ -102,7 +102,9 @@
 // Returns whether the site setting toggle for `web_contents` should be visible.
 bool IsSiteSettingsToggleVisible(const ToolbarActionsModel& toolbar_model,
                                  content::WebContents* web_contents) {
-  return !toolbar_model.IsRestrictedUrl(web_contents->GetLastCommittedURL());
+  const GURL& url = web_contents->GetLastCommittedURL();
+  return !toolbar_model.IsRestrictedUrl(url) &&
+         !toolbar_model.IsPolicyBlockedHost(url);
 }
 
 // Returns whether the site settings toggle for `web_contents` should be on.
@@ -259,10 +261,16 @@
     Profile& profile,
     const ToolbarActionsModel& toolbar_model,
     content::WebContents& web_contents) {
-  if (toolbar_model.IsRestrictedUrl(web_contents.GetLastCommittedURL())) {
+  const GURL& url = web_contents.GetLastCommittedURL();
+  if (toolbar_model.IsRestrictedUrl(url)) {
     return ExtensionsMenuMainPageView::MessageSectionState::kRestrictedAccess;
   }
 
+  if (toolbar_model.IsPolicyBlockedHost(url)) {
+    return ExtensionsMenuMainPageView::MessageSectionState::
+        kPolicyBlockedAccess;
+  }
+
   PermissionsManager::UserSiteSetting site_setting =
       PermissionsManager::Get(&profile)->GetUserSiteSetting(
           web_contents.GetPrimaryMainFrame()->GetLastCommittedOrigin());
@@ -586,10 +594,12 @@
       GetMessageSectionState(*browser_->profile(), *toolbar_model_,
                              *web_contents);
   bool has_enterprise_extensions = false;
-  // Only kUserBlockedAccess state cares whether there are any extensions
-  // installed by enterprise.
+  // Only kUserBlockedAccess or kPolicyBlockedAccess states care whether there
+  // are any extensions installed by enterprise.
   if (message_section_state ==
-      ExtensionsMenuMainPageView::MessageSectionState::kUserBlockedAccess) {
+          ExtensionsMenuMainPageView::MessageSectionState::kUserBlockedAccess ||
+      message_section_state == ExtensionsMenuMainPageView::MessageSectionState::
+                                   kPolicyBlockedAccess) {
     has_enterprise_extensions = std::any_of(
         toolbar_model_->action_ids().begin(),
         toolbar_model_->action_ids().end(),
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
index bd44a6d..c6c9e0b1 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
@@ -31,6 +31,7 @@
 #include "chrome/browser/ui/views/extensions/extensions_menu_view.h"
 #include "chrome/browser/ui/views/extensions/extensions_request_access_button.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
+#include "chrome/browser/ui/views/extensions/extensions_toolbar_container_view_controller.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_action_hover_card_controller.h"
@@ -58,11 +59,6 @@
 
 using ::ui::mojom::DragOperation;
 
-// Flex behavior precedence for the container's views.
-constexpr int kFlexOrderExtensionsButton = 1;
-constexpr int kFlexOrderRequestAccessButton = 2;
-constexpr int kFlexOrderActionView = 3;
-
 base::OnceClosure& GetOnVisibleCallbackForTesting() {
   static base::NoDestructor<base::OnceClosure> callback;
   return *callback;
@@ -194,11 +190,15 @@
       // order preference.
       extensions_button_->SetProperty(
           views::kFlexBehaviorKey,
-          hide_icon_flex_specification.WithOrder(kFlexOrderExtensionsButton));
+          hide_icon_flex_specification.WithOrder(
+              ExtensionsToolbarContainerViewController::
+                  kFlexOrderExtensionsButton));
       if (request_access_button_) {
         request_access_button_->SetProperty(
-            views::kFlexBehaviorKey, hide_icon_flex_specification.WithOrder(
-                                         kFlexOrderRequestAccessButton));
+            views::kFlexBehaviorKey,
+            hide_icon_flex_specification.WithOrder(
+                ExtensionsToolbarContainerViewController::
+                    kFlexOrderRequestAccessButton));
       }
       break;
   }
@@ -505,7 +505,8 @@
             views::FlexSpecification(min_flex_rule,
                                      views::MaximumFlexSizeRule::kPreferred)
                 .WithWeight(0)
-                .WithOrder(kFlexOrderActionView));
+                .WithOrder(ExtensionsToolbarContainerViewController::
+                               kFlexOrderActionView));
         break;
     }
   } else {
@@ -972,27 +973,6 @@
   UpdateContainerVisibility();
 }
 
-void ExtensionsToolbarContainer::WindowControlsOverlayEnabledChanged(
-    bool enabled) {
-  if (!main_item())
-    return;
-
-  UpdateContainerVisibility();
-
-  main_item()->ClearProperty(views::kFlexBehaviorKey);
-  views::MinimumFlexSizeRule min_flex_rule =
-      views::MinimumFlexSizeRule::kPreferredSnapToZero;
-
-  if (enabled)
-    min_flex_rule = views::MinimumFlexSizeRule::kPreferred;
-
-  main_item()->SetProperty(
-      views::kFlexBehaviorKey,
-      views::FlexSpecification(min_flex_rule,
-                               views::MaximumFlexSizeRule::kPreferred)
-          .WithOrder(kFlexOrderExtensionsButton));
-}
-
 void ExtensionsToolbarContainer::UpdateSidePanelState(bool is_active) {
   close_side_panel_button_->SetVisible(is_active);
   if (is_active) {
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
index f95c241..b63652b 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
@@ -114,6 +114,9 @@
       extensions::PermissionsManager::UserSiteSetting site_setting,
       content::WebContents* web_contents);
 
+  // Updates the container visibility and animation as needed.
+  void UpdateContainerVisibility();
+
   // Updates the controls visibility.
   void UpdateControlsVisibility();
 
@@ -163,13 +166,6 @@
     return close_side_panel_button_;
   }
 
-  // Updates the flex layout rules for the extension toolbar container to have
-  // views::MinimumFlexSizeRule::kPreferred when WindowControlsOverlay (WCO) is
-  // toggled on for PWAs. Otherwise the extensions icon does not stay visible as
-  // it is not considered for during the calculation of the preferred size of
-  // it's parent (in the case of WCO PWAs, WebAppFrameToolbarView).
-  void WindowControlsOverlayEnabledChanged(bool enabled);
-
   // Called when the side panel state has changed for an extensions side panel
   // to pop out button reflecting the side panel being open.
   void UpdateSidePanelState(bool is_active);
@@ -281,9 +277,6 @@
   void SetExtensionIconVisibility(ToolbarActionsModel::ActionId id,
                                   bool visible);
 
-  // Calls SetVisible() with ShouldContainerBeVisible().
-  void UpdateContainerVisibility();
-
   // Returns whether the contianer should be showing, e.g. not if there are no
   // extensions installed, nor if the container is inactive in kAutoHide mode.
   bool ShouldContainerBeVisible() const;
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container_view_controller.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container_view_controller.cc
index 9074553..e08bb43 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container_view_controller.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container_view_controller.cc
@@ -31,6 +31,26 @@
   permissions_manager_observation_.Reset();
 }
 
+void ExtensionsToolbarContainerViewController::
+    WindowControlsOverlayEnabledChanged(bool enabled) {
+  if (!extensions_container_->main_item()) {
+    return;
+  }
+
+  extensions_container_->UpdateContainerVisibility();
+  extensions_container_->main_item()->ClearProperty(views::kFlexBehaviorKey);
+
+  views::MinimumFlexSizeRule min_flex_rule =
+      enabled ? views::MinimumFlexSizeRule::kPreferred
+              : views::MinimumFlexSizeRule::kPreferredSnapToZero;
+  extensions_container_->main_item()->SetProperty(
+      views::kFlexBehaviorKey,
+      views::FlexSpecification(min_flex_rule,
+                               views::MaximumFlexSizeRule::kPreferred)
+          .WithOrder(ExtensionsToolbarContainerViewController::
+                         kFlexOrderExtensionsButton));
+}
+
 void ExtensionsToolbarContainerViewController::MaybeShowIPH() {
   // IPH is only shown for the kExtensionsMenuAccessControl feature.
   if (!base::FeatureList::IsEnabled(
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container_view_controller.h b/chrome/browser/ui/views/extensions/extensions_toolbar_container_view_controller.h
index 9a626e5..e88b5b2 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container_view_controller.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container_view_controller.h
@@ -15,6 +15,11 @@
       public ToolbarActionsModel::Observer,
       public extensions::PermissionsManager::Observer {
  public:
+  // Flex behavior precedence for the container's views.
+  static constexpr int kFlexOrderExtensionsButton = 1;
+  static constexpr int kFlexOrderRequestAccessButton = 2;
+  static constexpr int kFlexOrderActionView = 3;
+
   ExtensionsToolbarContainerViewController(
       Browser* browser,
       ExtensionsToolbarContainer* extensions_container);
@@ -24,6 +29,13 @@
       const ExtensionsToolbarContainerViewController&) = delete;
   ~ExtensionsToolbarContainerViewController() override;
 
+  // Updates the flex layout rules for the extension toolbar container to have
+  // views::MinimumFlexSizeRule::kPreferred when WindowControlsOverlay (WCO) is
+  // toggled on for PWAs. Otherwise the extensions icon does not stay visible as
+  // it is not considered for during the calculation of the preferred size of
+  // it's parent (in the case of WCO PWAs, WebAppFrameToolbarView).
+  void WindowControlsOverlayEnabledChanged(bool enabled);
+
  private:
   // Maybe displays the In-Product-Help with a specific priority order.
   void MaybeShowIPH();
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_coordinator.h b/chrome/browser/ui/views/extensions/extensions_toolbar_coordinator.h
index 1d81c54c..a17dcdd 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_coordinator.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_coordinator.h
@@ -21,6 +21,11 @@
       const ExtensionsToolbarCoordinator&) = delete;
   ~ExtensionsToolbarCoordinator();
 
+  ExtensionsToolbarContainerViewController*
+  GetExtensionsContainerViewController() {
+    return extensions_container_controller_.get();
+  }
+
  private:
   void ResetCoordinatorState();
 
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc
index 14587e0..ba02a15 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc
@@ -8,8 +8,11 @@
 
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
+#include "base/test/run_until.h"
+#include "base/test/test_future.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
+#include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/apps/app_service/app_registry_cache_waiter.h"
 #include "chrome/browser/prefs/session_startup_pref.h"
 #include "chrome/browser/sessions/session_restore_test_helper.h"
@@ -17,76 +20,22 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_list.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/view_ids.h"
-#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
-#include "chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_test_utils.h"
-#include "chrome/browser/ui/views/frame/browser_view.h"
-#include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
-#include "chrome/browser/ui/views/frame/immersive_mode_tester.h"
-#include "chrome/browser/web_applications/test/prevent_close_test_base.h"
-#include "chrome/browser/web_applications/web_app_id_constants.h"
-#include "chrome/test/base/in_process_browser_test.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "chromeos/constants/chromeos_features.h"
-#include "chromeos/ui/base/window_properties.h"
-#include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h"
-#include "chromeos/ui/frame/caption_buttons/frame_size_button.h"
-#include "components/keep_alive_registry/keep_alive_types.h"
-#include "components/keep_alive_registry/scoped_keep_alive.h"
-#include "components/services/app_service/public/cpp/app_update.h"
-#include "content/public/test/browser_test.h"
-#include "third_party/skia/include/core/SkColor.h"
-#include "ui/aura/client/aura_constants.h"
-#include "ui/aura/window.h"
-#include "ui/gfx/geometry/size.h"
-#include "ui/views/test/views_test_utils.h"
-#include "ui/views/widget/widget.h"
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "ash/constants/ash_switches.h"
-#include "ash/public/cpp/shelf_test_api.h"
-#include "ash/public/cpp/split_view_test_api.h"
-#include "ash/public/cpp/test/shell_test_api.h"
-#include "ash/shell.h"
-#include "ash/wm/overview/overview_controller.h"
-#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
-#include "ash/wm/tablet_mode/tablet_mode_multitask_menu_controller.h"
-#include "ash/wm/tablet_mode/tablet_mode_window_manager.h"
-#include "base/functional/callback_helpers.h"
-#include "base/ranges/algorithm.h"
-#include "base/run_loop.h"
-#include "base/scoped_observation.h"
-#include "base/test/test_future.h"
-#include "chrome/app/chrome_command_ids.h"
-#include "chrome/app/vector_icons/vector_icons.h"
-#include "chrome/browser/ash/login/app_mode/test/web_kiosk_base_test.h"
-#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
-#include "chrome/browser/ash/system_web_apps/test_support/test_system_web_app_installation.h"
-#include "chrome/browser/command_updater.h"
-#include "chrome/browser/profiles/profile_avatar_icon_util.h"
-#include "chrome/browser/profiles/profile_io_data.h"
-#include "chrome/browser/sessions/session_service_factory.h"
-#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
-#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
-#include "chrome/browser/ui/ash/multi_user/test_multi_user_window_manager.h"
-#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
-#include "chrome/browser/ui/browser_command_controller.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
-#include "chrome/browser/ui/browser_tabstrip.h"
-#include "chrome/browser/ui/chromeos/window_pin_util.h"
-#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
-#include "chrome/browser/ui/exclusive_access/exclusive_access_test.h"
-#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/chromeos/test_util.h"
+#include "chrome/browser/ui/lacros/window_utility.h"
 #include "chrome/browser/ui/passwords/passwords_client_ui_delegate.h"
-#include "chrome/browser/ui/settings_window_manager_chromeos.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/ui_features.h"
+#include "chrome/browser/ui/view_ids.h"
+#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
+#include "chrome/browser/ui/views/frame/app_menu_button.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
 #include "chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.h"
-#include "chrome/browser/ui/views/frame/tab_strip_region_view.h"
+#include "chrome/browser/ui/views/frame/immersive_mode_tester.h"
 #include "chrome/browser/ui/views/frame/webui_tab_strip_container_view.h"
-#include "chrome/browser/ui/views/fullscreen_control/fullscreen_control_host.h"
 #include "chrome/browser/ui/views/location_bar/content_setting_image_view.h"
 #include "chrome/browser/ui/views/location_bar/custom_tab_bar_view.h"
 #include "chrome/browser/ui/views/location_bar/zoom_bubble_view.h"
@@ -102,55 +51,103 @@
 #include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_toolbar_button_container.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
+#include "chrome/browser/web_applications/test/prevent_close_test_base.h"
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
+#include "chrome/browser/web_applications/web_app_id_constants.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
-#include "chrome/test/base/interactive_test_utils.h"
+#include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "chromeos/constants/chromeos_features.h"
+#include "chromeos/crosapi/mojom/test_controller.mojom-test-utils.h"
+#include "chromeos/crosapi/mojom/test_controller.mojom.h"
+#include "chromeos/lacros/lacros_service.h"
 #include "chromeos/ui/base/chromeos_ui_constants.h"
-#include "chromeos/ui/base/window_pin_type.h"
+#include "chromeos/ui/base/window_properties.h"
+#include "chromeos/ui/base/window_state_type.h"
+#include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h"
+#include "chromeos/ui/frame/caption_buttons/frame_size_button.h"
 #include "chromeos/ui/frame/default_frame_header.h"
 #include "chromeos/ui/frame/frame_header.h"
+#include "chromeos/ui/frame/multitask_menu/float_controller_base.h"
 #include "components/account_id/account_id.h"
+#include "components/keep_alive_registry/keep_alive_types.h"
+#include "components/keep_alive_registry/scoped_keep_alive.h"
 #include "components/password_manager/core/browser/password_form.h"
+#include "components/services/app_service/public/cpp/app_update.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/render_widget_host.h"
-#include "content/public/test/background_color_change_waiter.h"
+#include "content/public/test/browser_test.h"
 #include "content/public/test/content_mock_cert_verifier.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "net/dns/mock_host_resolver.h"
 #include "third_party/blink/public/common/page/page_zoom.h"
-#include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
-#include "ui/aura/test/env_test_helper.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/aura/client/aura_constants.h"
+#include "ui/aura/window.h"
 #include "ui/base/class_property.h"
 #include "ui/base/hit_test.h"
 #include "ui/base/page_transition_types.h"
 #include "ui/base/pointer/touch_ui_controller.h"
-#include "ui/base/window_open_disposition.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
+#include "ui/display/screen.h"
 #include "ui/events/base_event_utils.h"
 #include "ui/events/event.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/gfx/color_palette.h"
+#include "ui/gfx/geometry/size.h"
 #include "ui/gfx/vector_icon_types.h"
 #include "ui/views/focus/focus_manager.h"
-#include "ui/views/view_observer.h"
+#include "ui/views/test/views_test_utils.h"
+#include "ui/views/test/widget_test.h"
+#include "ui/views/widget/widget.h"
 #include "ui/views/window/caption_button_layout_constants.h"
 #include "ui/views/window/frame_caption_button.h"
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "ash/public/cpp/test/shell_test_api.h"
+#include "ash/shell.h"
+#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
+#include "ash/wm/tablet_mode/tablet_mode_multitask_menu_controller.h"
+#include "ash/wm/tablet_mode/tablet_mode_window_manager.h"
+#include "chrome/browser/ash/login/app_mode/test/web_kiosk_base_test.h"
+#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
+#include "chrome/browser/ash/system_web_apps/test_support/test_system_web_app_installation.h"
+#include "chrome/browser/profiles/profile_avatar_icon_util.h"
+#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
+#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
+#include "chrome/browser/ui/ash/multi_user/test_multi_user_window_manager.h"
+#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
+#include "chrome/browser/ui/settings_window_manager_chromeos.h"
+#include "content/public/test/background_color_change_waiter.h"
+#else  // BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chrome/browser/lacros/browser_test_util.h"
+#include "chrome/browser/ui/lacros/window_properties.h"
 #include "chrome/browser/web_applications/app_service/test/loopback_crosapi_app_service_proxy.h"
-#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "ui/views/widget/desktop_aura/desktop_window_tree_host_lacros.h"
+#endif
 
-using views::Widget;
+namespace {
 
-// TODO(crbug.com/1235203): Identify tests that should also run under Lacros.
+bool WaitForFocus(bool expected, views::View* view) {
+  return base::test::RunUntil([&]() { return view->HasFocus() == expected; });
+}
+
+bool WaitForVisible(bool expected, views::View* view) {
+  return base::test::RunUntil([&]() { return view->GetVisible() == expected; });
+}
+
+bool WaitForPaintAsActive(bool expected, views::FrameCaptionButton* button) {
+  return base::test::RunUntil(
+      [&]() { return button->GetPaintAsActive() == expected; });
+}
+
+}  // namespace
 
 using BrowserNonClientFrameViewChromeOSTest =
-    TopChromeMdParamTest<InProcessBrowserTest>;
+    TopChromeMdParamTest<ChromeOSBrowserUITest>;
 using BrowserNonClientFrameViewChromeOSTestNoWebUiTabStrip =
     WebUiTabStripOverrideTest<false, BrowserNonClientFrameViewChromeOSTest>;
 using BrowserNonClientFrameViewChromeOSTestWithWebUiTabStrip =
@@ -181,19 +178,12 @@
   const raw_ptr<BrowserNonClientFrameViewChromeOS> frame_view_;
 };
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-using BrowserNonClientFrameViewChromeOSTouchTest =
-    TopChromeTouchTest<InProcessBrowserTest>;
-using BrowserNonClientFrameViewChromeOSTouchTestWithWebUiTabStrip =
-    WebUiTabStripOverrideTest<true, BrowserNonClientFrameViewChromeOSTouchTest>;
-
-
 // This test does not make sense for the webUI tabstrip, since the window layout
 // is different in that case.
 IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewChromeOSTestNoWebUiTabStrip,
                        NonClientHitTest) {
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
-  Widget* widget = browser_view->GetWidget();
+  views::Widget* widget = browser_view->GetWidget();
   BrowserNonClientFrameViewChromeOS* frame_view =
       GetFrameViewChromeOS(browser_view);
 
@@ -210,47 +200,79 @@
 
   // Click in the top edge of a maximized window now hits the client area,
   // because we want it to fall through to the tab strip and select a tab.
-  widget->Maximize();
-  int expected_value = HTCLIENT;
-  EXPECT_EQ(expected_value, frame_view->NonClientHitTest(top_edge));
+  {
+    gfx::Rect old_bounds = frame_view->bounds();
+    widget->Maximize();
+    auto* window = widget->GetNativeWindow();
+    ASSERT_TRUE(base::test::RunUntil([&]() {
+      return window->GetProperty(chromeos::kWindowStateTypeKey) ==
+             chromeos::WindowStateType::kMaximized;
+    }));
+    // TODO(crbug.com/1466385): Remove waiting for bounds change when the bug is
+    // fixed.
+    ASSERT_TRUE(base::test::RunUntil(
+        [&]() { return frame_view->bounds() != old_bounds; }));
+  }
+  EXPECT_EQ(HTCLIENT, frame_view->NonClientHitTest(top_edge));
 }
 
+using BrowserNonClientFrameViewChromeOSTouchTest =
+    TopChromeTouchTest<ChromeOSBrowserUITest>;
+
+using BrowserNonClientFrameViewChromeOSTouchTestWithWebUiTabStrip =
+    WebUiTabStripOverrideTest<true, BrowserNonClientFrameViewChromeOSTouchTest>;
+
 IN_PROC_BROWSER_TEST_F(
     BrowserNonClientFrameViewChromeOSTouchTestWithWebUiTabStrip,
     TabletSplitViewNonClientHitTest) {
+  if (!IsSnapWindowSupported()) {
+    GTEST_SKIP() << "Ash is too old.";
+  }
+
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
   BrowserNonClientFrameViewChromeOS* frame_view =
       GetFrameViewChromeOS(browser_view);
+  views::Widget* widget = browser_view->GetWidget();
+  aura::Window* window = widget->GetNativeWindow();
+
   const int expect_y =
       frame_view->GetBorder() ? frame_view->GetBorder()->GetInsets().top() : 0;
   EXPECT_EQ(expect_y, frame_view->GetBoundsForClientView().y());
 
-  Widget* widget = browser_view->GetWidget();
-  ASSERT_NO_FATAL_FAILURE(
-      ash::ShellTestApi().SetTabletModeEnabledForTest(true));
-  ash::SplitViewTestApi().SnapWindow(widget->GetNativeWindow(),
-                                     ash::SnapPosition::kPrimary);
+  EnterTabletMode();
+  SnapWindow(window, crosapi::mojom::SnapPosition::kPrimary);
 
   // Touch on the top of the window is interpreted as client hit.
   gfx::Point top_point(widget->GetWindowBoundsInScreen().width() / 2, 0);
   EXPECT_EQ(HTCLIENT, frame_view->NonClientHitTest(top_point));
 }
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+// TODO(crbug.com/1494785): Find out why the test fails and fix it. May require
+// ui_controls for global touch events.
+#define MAYBE_TabletSplitViewSwipeDownFromEdgeOpensWebUiTabStrip \
+  DISABLED_TabletSplitViewSwipeDownFromEdgeOpensWebUiTabStrip
+#else
+#define MAYBE_TabletSplitViewSwipeDownFromEdgeOpensWebUiTabStrip \
+  TabletSplitViewSwipeDownFromEdgeOpensWebUiTabStrip
+#endif
 IN_PROC_BROWSER_TEST_F(
     BrowserNonClientFrameViewChromeOSTouchTestWithWebUiTabStrip,
-    TabletSplitViewSwipeDownFromEdgeOpensWebUiTabStrip) {
+    MAYBE_TabletSplitViewSwipeDownFromEdgeOpensWebUiTabStrip) {
+  if (!IsSnapWindowSupported()) {
+    GTEST_SKIP() << "Ash is too old.";
+  }
+
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
   BrowserNonClientFrameViewChromeOS* frame_view =
       GetFrameViewChromeOS(browser_view);
   const int expect_y =
       frame_view->GetBorder() ? frame_view->GetBorder()->GetInsets().top() : 0;
   EXPECT_EQ(expect_y, frame_view->GetBoundsForClientView().y());
+  views::Widget* widget = browser_view->GetWidget();
 
-  Widget* widget = browser_view->GetWidget();
-  ASSERT_NO_FATAL_FAILURE(
-      ash::ShellTestApi().SetTabletModeEnabledForTest(true));
-  ash::SplitViewTestApi().SnapWindow(widget->GetNativeWindow(),
-                                     ash::SnapPosition::kPrimary);
+  EnterTabletMode();
+  SnapWindow(widget->GetNativeWindow(), crosapi::mojom::SnapPosition::kPrimary);
 
   // A point above the window, but not in the center horizontally, as a swipe
   // down from the top center will show the chromeos tablet mode multitask menu.
@@ -263,16 +285,21 @@
   event_generator.PressTouch(edge_point);
   event_generator.MoveTouchBy(0, 100);
   event_generator.ReleaseTouch();
-  ASSERT_TRUE(browser_view->webui_tab_strip()->GetVisible());
+  ASSERT_TRUE(WaitForVisible(true, browser_view->webui_tab_strip()));
 }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Test that the frame view does not do any painting in non-immersive
 // fullscreen.
 // This test does not make sense for the webUI tabstrip, since the frame is not
 // painted in that case.
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+// TODO(crbug.com/1235203): Find out why the test is flaky.
+#define MAYBE_NonImmersiveFullscreen DISABLED_NonImmersiveFullscreen
+#else
+#define MAYBE_NonImmersiveFullscreen NonImmersiveFullscreen
+#endif
 IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewChromeOSTestNoWebUiTabStrip,
-                       NonImmersiveFullscreen) {
+                       MAYBE_NonImmersiveFullscreen) {
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
   content::WebContents* web_contents = browser_view->GetActiveWebContents();
   BrowserNonClientFrameViewChromeOS* frame_view =
@@ -284,7 +311,7 @@
 
   // No painting should occur in non-immersive fullscreen. (We enter into tab
   // fullscreen here because tab fullscreen is non-immersive even on ChromeOS).
-  EnterFullscreenModeForTabAndWait(browser(), web_contents);
+  EnterTabFullscreenMode(browser(), web_contents);
   EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
   EXPECT_TRUE(browser_view->IsFullscreen());
   EXPECT_FALSE(test_api.GetShouldPaint());
@@ -294,15 +321,23 @@
 
   // The frame should be painted again when fullscreen is exited and the caption
   // buttons should be visible.
-  ui_test_utils::ToggleFullscreenModeAndWait(browser());
+  ExitTabFullscreenMode(browser(), web_contents);
   EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
   EXPECT_FALSE(browser_view->IsFullscreen());
   EXPECT_TRUE(test_api.GetShouldPaint());
 }
 
 // Tests that caption buttons are hidden when entering tab fullscreen.
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+// TODO(crbug.com/1235203): Find out why the test is flaky.
+#define MAYBE_CaptionButtonsHiddenNonImmersiveFullscreen \
+  DISABLED_CaptionButtonsHiddenNonImmersiveFullscreen
+#else
+#define MAYBE_CaptionButtonsHiddenNonImmersiveFullscreen \
+  CaptionButtonsHiddenNonImmersiveFullscreen
+#endif
 IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewChromeOSTestNoWebUiTabStrip,
-                       CaptionButtonsHiddenNonImmersiveFullscreen) {
+                       MAYBE_CaptionButtonsHiddenNonImmersiveFullscreen) {
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
   content::WebContents* web_contents = browser_view->GetActiveWebContents();
   BrowserNonClientFrameViewChromeOS* frame_view =
@@ -310,7 +345,7 @@
 
   EXPECT_TRUE(frame_view->caption_button_container()->GetVisible());
 
-  EnterFullscreenModeForTabAndWait(browser(), web_contents);
+  EnterTabFullscreenMode(browser(), web_contents);
   EXPECT_TRUE(browser_view->IsFullscreen());
   EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
   // Caption buttons are hidden.
@@ -318,15 +353,13 @@
 
   // The frame should be painted again when fullscreen is exited and the caption
   // buttons should be visible.
-  ui_test_utils::ToggleFullscreenModeAndWait(browser());
+  ExitTabFullscreenMode(browser(), web_contents);
   EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
   EXPECT_FALSE(browser_view->IsFullscreen());
   // Caption button container visible again.
   EXPECT_TRUE(frame_view->caption_button_container()->GetVisible());
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-
 // There should be no top inset when using the WebUI tab strip since the frame
 // is invisible. Regression test for crbug.com/1076675
 IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewChromeOSTestWithWebUiTabStrip,
@@ -340,13 +373,12 @@
   BrowserView* const browser_view =
       BrowserView::GetBrowserViewForBrowser(browser());
 
-  StartOverview();
   EXPECT_EQ(0, GetFrameViewChromeOS(browser_view)->GetTopInset(false));
-
-  EndOverview();
+  EnterOverviewMode();
+  EXPECT_EQ(0, GetFrameViewChromeOS(browser_view)->GetTopInset(false));
+  ExitOverviewMode();
   EXPECT_EQ(0, GetFrameViewChromeOS(browser_view)->GetTopInset(false));
 }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Tests to ensure caption buttons are not painted when the WebUI tab strip is
 // present for the browser window (crbug.com/1362731).
@@ -421,7 +453,7 @@
   // Check that a layout occurs.
   BrowserView* browser_view =
       BrowserView::GetBrowserViewForBrowser(new_browser);
-  Widget* widget = browser_view->GetWidget();
+  views::Widget* widget = browser_view->GetWidget();
 
   BrowserNonClientFrameViewChromeOS* frame_view =
       static_cast<BrowserNonClientFrameViewChromeOS*>(
@@ -432,55 +464,17 @@
   EXPECT_TRUE(test.size_button()->icon_definition_for_test());
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
 namespace {
 
-class ViewVisibilityWaiter : public views::ViewObserver {
+class WebAppNonClientFrameViewChromeOSTest
+    : public TopChromeMdParamTest<ChromeOSBrowserUITest> {
  public:
-  ViewVisibilityWaiter(views::View* observed_view, bool expected_visible)
-      : view_(observed_view), expected_visible_(expected_visible) {
-    observation_.Observe(view_.get());
-  }
-  ViewVisibilityWaiter(const ViewVisibilityWaiter&) = delete;
-  ViewVisibilityWaiter& operator=(const ViewVisibilityWaiter&) = delete;
-  ~ViewVisibilityWaiter() override = default;
-
-  // Wait for changes to occur, or return immediately if view already has
-  // expected visibility.
-  bool Wait() {
-    if (expected_visible_ == view_->GetVisible()) {
-      return true;
-    }
-    return future_.Wait();
-  }
-
- private:
-  // views::ViewObserver:
-  void OnViewVisibilityChanged(views::View* observed_view,
-                               views::View* starting_view) override {
-    bool visible = observed_view->GetVisible();
-    if (visible == expected_visible_) {
-      future_.SetValue();
-    }
-  }
-
-  raw_ptr<views::View> view_;
-  const bool expected_visible_;
-  base::test::TestFuture<void> future_;
-  base::ScopedObservation<views::View, views::ViewObserver> observation_{this};
-};
-
-class WebAppNonClientFrameViewAshTest
-    : public TopChromeMdParamTest<InProcessBrowserTest> {
- public:
-  WebAppNonClientFrameViewAshTest() = default;
-
-  WebAppNonClientFrameViewAshTest(const WebAppNonClientFrameViewAshTest&) =
-      delete;
-  WebAppNonClientFrameViewAshTest& operator=(
-      const WebAppNonClientFrameViewAshTest&) = delete;
-
-  ~WebAppNonClientFrameViewAshTest() override = default;
+  WebAppNonClientFrameViewChromeOSTest() = default;
+  WebAppNonClientFrameViewChromeOSTest(
+      const WebAppNonClientFrameViewChromeOSTest&) = delete;
+  WebAppNonClientFrameViewChromeOSTest& operator=(
+      const WebAppNonClientFrameViewChromeOSTest&) = delete;
+  ~WebAppNonClientFrameViewChromeOSTest() override = default;
 
   GURL GetAppURL() const {
     return https_server_.GetURL("app.com", "/ssl/google.html");
@@ -501,24 +495,24 @@
   raw_ptr<AppMenuButton, DanglingUntriaged> web_app_menu_button_ = nullptr;
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    TopChromeMdParamTest<InProcessBrowserTest>::SetUpCommandLine(command_line);
+    TopChromeMdParamTest<ChromeOSBrowserUITest>::SetUpCommandLine(command_line);
     cert_verifier_.SetUpCommandLine(command_line);
   }
 
   void SetUpInProcessBrowserTestFixture() override {
     TopChromeMdParamTest<
-        InProcessBrowserTest>::SetUpInProcessBrowserTestFixture();
+        ChromeOSBrowserUITest>::SetUpInProcessBrowserTestFixture();
     cert_verifier_.SetUpInProcessBrowserTestFixture();
   }
 
   void TearDownInProcessBrowserTestFixture() override {
     cert_verifier_.TearDownInProcessBrowserTestFixture();
     TopChromeMdParamTest<
-        InProcessBrowserTest>::TearDownInProcessBrowserTestFixture();
+        ChromeOSBrowserUITest>::TearDownInProcessBrowserTestFixture();
   }
 
   void SetUpOnMainThread() override {
-    TopChromeMdParamTest<InProcessBrowserTest>::SetUpOnMainThread();
+    TopChromeMdParamTest<ChromeOSBrowserUITest>::SetUpOnMainThread();
 
     WebAppToolbarButtonContainer::DisableAnimationForTesting(true);
 
@@ -545,6 +539,14 @@
     navigation_observer.StartWatchingNewWebContents();
     app_browser_ = web_app::LaunchWebAppBrowser(browser()->profile(), app_id);
     navigation_observer.WaitForNavigationFinished();
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+    {
+      aura::Window* window = app_browser_->window()->GetNativeWindow();
+      std::string id =
+          lacros_window_utility::GetRootWindowUniqueId(window->GetRootWindow());
+      ASSERT_TRUE(browser_test_util::WaitForWindowCreation(id));
+    }
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
 
     browser_view_ = BrowserView::GetBrowserViewForBrowser(app_browser_);
     BrowserNonClientFrameViewChromeOS* frame_view =
@@ -612,9 +614,10 @@
 // Tests that the page info dialog doesn't anchor in a way that puts it outside
 // of web-app windows. This is important as some platforms don't support bubble
 // anchor adjustment (see |BubbleDialogDelegateView::CreateBubble()|).
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest,
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
                        PageInfoBubblePosition) {
   SetUpWebApp();
+
   // Resize app window to only take up the left half of the screen.
   views::Widget* widget = browser_view_->GetWidget();
   gfx::Size screen_size =
@@ -637,29 +640,31 @@
   EXPECT_TRUE(widget->GetWindowBoundsInScreen().Contains(page_info_bounds));
 }
 
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest, FocusableViews) {
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest, FocusableViews) {
   SetUpWebApp();
-  EXPECT_TRUE(browser_view_->contents_web_view()->HasFocus());
+  ASSERT_TRUE(WaitForFocus(true, browser_view_->contents_web_view()));
   browser_view_->GetFocusManager()->AdvanceFocus(false);
-  EXPECT_TRUE(web_app_menu_button_->HasFocus());
+  ASSERT_TRUE(WaitForFocus(true, web_app_menu_button_));
   browser_view_->GetFocusManager()->AdvanceFocus(false);
-  EXPECT_TRUE(browser_view_->contents_web_view()->HasFocus());
+  ASSERT_TRUE(WaitForFocus(true, browser_view_->contents_web_view()));
 }
 
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest,
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
                        ButtonVisibilityInOverviewMode) {
   SetUpWebApp();
-  EXPECT_TRUE(web_app_frame_toolbar_->GetVisible());
+  ASSERT_TRUE(WaitForVisible(true, web_app_frame_toolbar_));
 
-  StartOverview();
+  EnterOverviewMode();
   views::test::RunScheduledLayout(browser_view_);
-  EXPECT_FALSE(web_app_frame_toolbar_->GetVisible());
-  EndOverview();
+  ASSERT_TRUE(WaitForVisible(false, web_app_frame_toolbar_));
+
+  ExitOverviewMode();
   views::test::RunScheduledLayout(browser_view_);
-  EXPECT_TRUE(web_app_frame_toolbar_->GetVisible());
+  ASSERT_TRUE(WaitForVisible(true, web_app_frame_toolbar_));
 }
 
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest, FrameThemeColorIsSet) {
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
+                       FrameThemeColorIsSet) {
   SetUpWebApp();
   aura::Window* window = browser_view_->GetWidget()->GetNativeWindow();
   EXPECT_EQ(GetThemeColor(),
@@ -671,7 +676,7 @@
 
 // Make sure that for web apps, the height of the frame doesn't exceed the
 // height of the caption buttons.
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest, FrameSize) {
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest, FrameSize) {
   SetUpWebApp();
   const int inset = GetFrameViewChromeOS(browser_view_)->GetTopInset(false);
   EXPECT_EQ(inset, views::GetCaptionButtonLayoutSize(
@@ -681,13 +686,13 @@
   EXPECT_GE(inset, web_app_frame_toolbar_->size().height());
 }
 
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest,
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
                        IsToolbarButtonProvider) {
   SetUpWebApp();
   EXPECT_EQ(browser_view_->toolbar_button_provider(), web_app_frame_toolbar_);
 }
 
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest,
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
                        ShowManagePasswordsIcon) {
   SetUpWebApp();
   content::WebContents* web_contents =
@@ -706,12 +711,10 @@
       ->OnPasswordAutofilled({&password_form},
                              url::Origin::Create(password_form.url), nullptr);
   chrome::ManagePasswordsForPage(app_browser_);
-  // Wait for manage_passwords_icon to become visible.
-  ViewVisibilityWaiter waiter(manage_passwords_icon, true);
-  ASSERT_TRUE(waiter.Wait());
+  ASSERT_TRUE(WaitForVisible(true, manage_passwords_icon));
 }
 
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest, ShowZoomIcon) {
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest, ShowZoomIcon) {
   SetUpWebApp();
   content::WebContents* web_contents =
       app_browser_->tab_strip_model()->GetActiveWebContents();
@@ -724,13 +727,11 @@
   EXPECT_FALSE(ZoomBubbleView::GetZoomBubble());
 
   zoom_controller->SetZoomLevel(blink::PageZoomFactorToZoomLevel(1.5));
-  // Wait for zoom_icon to become visible.
-  ViewVisibilityWaiter waiter(zoom_icon, true);
-  ASSERT_TRUE(waiter.Wait());
+  ASSERT_TRUE(WaitForVisible(true, zoom_icon));
   EXPECT_TRUE(ZoomBubbleView::GetZoomBubble());
 }
 
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest, ShowFindIcon) {
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest, ShowFindIcon) {
   SetUpWebApp();
   PageActionIconView* find_icon = GetPageActionIcon(PageActionIconType::kFind);
 
@@ -739,10 +740,11 @@
 
   chrome::Find(app_browser_);
 
-  EXPECT_TRUE(find_icon->GetVisible());
+  ASSERT_TRUE(WaitForVisible(true, find_icon));
 }
 
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest, ShowTranslateIcon) {
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
+                       ShowTranslateIcon) {
   SetUpWebApp();
   PageActionIconView* translate_icon =
       GetPageActionIcon(PageActionIconType::kTranslate);
@@ -756,42 +758,45 @@
                                      "en", "fr",
                                      translate::TranslateErrors::NONE, true);
 
-  EXPECT_TRUE(translate_icon->GetVisible());
+  ASSERT_TRUE(WaitForVisible(true, translate_icon));
 }
 
 // Tests that the focus toolbar command focuses the app menu button in web-app
 // windows.
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest,
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
                        BrowserCommandFocusToolbarAppMenu) {
   SetUpWebApp();
+  ASSERT_TRUE(WaitForFocus(true, browser_view_->contents_web_view()));
+
   EXPECT_FALSE(web_app_menu_button_->HasFocus());
   chrome::ExecuteCommand(app_browser_, IDC_FOCUS_TOOLBAR);
-  EXPECT_TRUE(web_app_menu_button_->HasFocus());
+  ASSERT_TRUE(WaitForFocus(true, web_app_menu_button_));
 }
 
-// TODO(): Flaky crash on Chrome OS debug.
 // Tests that the focus toolbar command focuses content settings icons before
 // the app menu button when present in web-app windows.
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest,
-                       DISABLED_BrowserCommandFocusToolbarGeolocation) {
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
+                       BrowserCommandFocusToolbarGeolocation) {
   SetUpWebApp();
+
   ContentSettingImageView* geolocation_icon = GrantGeolocationPermission();
 
   // In order to receive focus, the geo icon must be laid out (and be both
   // visible and nonzero size).
   RunScheduledLayouts();
 
+  ASSERT_TRUE(WaitForFocus(true, browser_view_->contents_web_view()));
   EXPECT_FALSE(web_app_menu_button_->HasFocus());
   EXPECT_FALSE(geolocation_icon->HasFocus());
 
   chrome::ExecuteCommand(app_browser_, IDC_FOCUS_TOOLBAR);
 
+  ASSERT_TRUE(WaitForFocus(true, geolocation_icon));
   EXPECT_FALSE(web_app_menu_button_->HasFocus());
-  EXPECT_TRUE(geolocation_icon->HasFocus());
 }
 
 // Tests that the show app menu command opens the app menu for web-app windows.
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest,
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
                        BrowserCommandShowAppMenu) {
   SetUpWebApp();
   EXPECT_EQ(nullptr, GetAppMenu());
@@ -801,16 +806,18 @@
 
 // Tests that the focus next pane command focuses the app menu for web-app
 // windows.
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest,
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
                        BrowserCommandFocusNextPane) {
   SetUpWebApp();
+  ASSERT_TRUE(WaitForFocus(true, browser_view_->contents_web_view()));
   EXPECT_FALSE(web_app_menu_button_->HasFocus());
   chrome::ExecuteCommand(app_browser_, IDC_FOCUS_NEXT_PANE);
-  EXPECT_TRUE(web_app_menu_button_->HasFocus());
+  ASSERT_TRUE(WaitForFocus(true, web_app_menu_button_));
 }
 
 // Tests the app icon and title are not shown.
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest, IconAndTitleNotShown) {
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
+                       IconAndTitleNotShown) {
   SetUpWebApp();
   auto* browser_view = BrowserView::GetBrowserViewForBrowser(app_browser_);
   EXPECT_FALSE(browser_view->ShouldShowWindowIcon());
@@ -818,9 +825,10 @@
 }
 
 // Tests that the custom tab bar is focusable from the keyboard.
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest,
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
                        CustomTabBarIsFocusable) {
   SetUpWebApp();
+  ASSERT_TRUE(WaitForFocus(true, browser_view_->contents_web_view()));
 
   auto* browser_view = BrowserView::GetBrowserViewForBrowser(app_browser_);
 
@@ -831,25 +839,27 @@
   auto* custom_tab_bar = browser_view->toolbar()->custom_tab_bar();
 
   chrome::ExecuteCommand(app_browser_, IDC_FOCUS_NEXT_PANE);
-  ASSERT_TRUE(web_app_menu_button_->HasFocus());
+  ASSERT_TRUE(WaitForFocus(true, web_app_menu_button_));
 
   EXPECT_FALSE(custom_tab_bar->close_button_for_testing()->HasFocus());
   chrome::ExecuteCommand(app_browser_, IDC_FOCUS_NEXT_PANE);
-  EXPECT_TRUE(custom_tab_bar->close_button_for_testing()->HasFocus());
+  ASSERT_TRUE(WaitForFocus(true, custom_tab_bar->close_button_for_testing()));
 }
 
 // Tests that the focus previous pane command focuses the app menu for web-app
 // windows.
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest,
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
                        BrowserCommandFocusPreviousPane) {
   SetUpWebApp();
+  ASSERT_TRUE(WaitForFocus(true, browser_view_->contents_web_view()));
   EXPECT_FALSE(web_app_menu_button_->HasFocus());
   chrome::ExecuteCommand(app_browser_, IDC_FOCUS_PREVIOUS_PANE);
-  EXPECT_TRUE(web_app_menu_button_->HasFocus());
+  ASSERT_TRUE(WaitForFocus(true, web_app_menu_button_));
 }
 
 // Tests that a web app's content settings icons can be interacted with.
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest, ContentSettingIcons) {
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
+                       ContentSettingIcons) {
   SetUpWebApp();
   for (ContentSettingImageView* view : *content_setting_views_) {
     EXPECT_FALSE(view->GetVisible());
@@ -872,20 +882,29 @@
 }
 
 // Regression test for https://crbug.com/839955
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest,
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
                        ActiveStateOfButtonMatchesWidget) {
   SetUpWebApp();
   chromeos::FrameCaptionButtonContainerView::TestApi test(
       GetFrameViewChromeOS(browser_view_)->caption_button_container());
-  EXPECT_TRUE(test.size_button()->GetPaintAsActive());
+
+  EXPECT_TRUE(WaitForPaintAsActive(true, test.size_button()));
   EXPECT_TRUE(GetPaintingAsActive());
 
-  browser_view_->GetWidget()->Deactivate();
-  EXPECT_FALSE(test.size_button()->GetPaintAsActive());
+  DeactivateWidget(browser_view_->GetWidget());
+  EXPECT_TRUE(WaitForPaintAsActive(false, test.size_button()));
   EXPECT_FALSE(GetPaintingAsActive());
 }
 
-IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewAshTest, PopupHasNoToolbar) {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+// TODO(crbug.com/1507735): Reenable the test once the GetLastActive() race
+// condition is fixed.
+#define MAYBE_PopupHasNoToolbar DISABLED_PopupHasNoToolbar
+#else
+#define MAYBE_PopupHasNoToolbar PopupHasNoToolbar
+#endif
+IN_PROC_BROWSER_TEST_P(WebAppNonClientFrameViewChromeOSTest,
+                       MAYBE_PopupHasNoToolbar) {
   SetUpWebApp();
   {
     NavigateParams navigate_params(app_browser_, GetAppURL(),
@@ -904,7 +923,6 @@
   EXPECT_FALSE(browser_view->web_app_frame_toolbar_for_testing() &&
                browser_view->web_app_frame_toolbar_for_testing()->GetVisible());
 }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Test the normal type browser's kTopViewInset is always 0.
 IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewChromeOSTest, TopViewInset) {
@@ -912,12 +930,10 @@
   ImmersiveModeController* immersive_mode_controller =
       browser_view->immersive_mode_controller();
   aura::Window* window = browser()->window()->GetNativeWindow();
-  EXPECT_FALSE(immersive_mode_controller->IsEnabled());
   EXPECT_EQ(0, window->GetProperty(aura::client::kTopViewInset));
 
   // The kTopViewInset should be 0 when in immersive mode.
-  ui_test_utils::ToggleFullscreenModeAndWait(browser());
-  EXPECT_TRUE(immersive_mode_controller->IsEnabled());
+  EnterImmersiveFullscreenMode(browser());
   EXPECT_EQ(0, window->GetProperty(aura::client::kTopViewInset));
 
   // An immersive reveal shows the top of the frame.
@@ -930,18 +946,20 @@
   // End the reveal and exit immersive mode.
   // The kTopViewInset should be 0 when immersive mode is exited.
   revealed_lock.reset();
-  ui_test_utils::ToggleFullscreenModeAndWait(browser());
-  EXPECT_FALSE(immersive_mode_controller->IsEnabled());
+  ExitImmersiveFullscreenMode(browser());
   EXPECT_EQ(0, window->GetProperty(aura::client::kTopViewInset));
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
 // Test that for a browser window, its caption buttons are always hidden in
 // tablet mode.
 IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewChromeOSTest,
                        BrowserHeaderVisibilityInTabletModeTest) {
+  if (!IsSnapWindowSupported()) {
+    GTEST_SKIP() << "Ash is too old.";
+  }
+
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
-  Widget* widget = browser_view->GetWidget();
+  views::Widget* widget = browser_view->GetWidget();
   BrowserNonClientFrameViewChromeOS* frame_view =
       GetFrameViewChromeOS(browser_view);
 
@@ -957,9 +975,9 @@
     EXPECT_TRUE(frame_view->caption_button_container()->GetVisible());
   }
 
-  StartOverview();
+  EnterOverviewMode();
   EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());
-  EndOverview();
+  ExitOverviewMode();
 
   // Caption buttons are not supported when using the WebUI tab strip.
   if (browser_view->webui_tab_strip()) {
@@ -968,32 +986,24 @@
     EXPECT_TRUE(frame_view->caption_button_container()->GetVisible());
   }
 
-  ASSERT_NO_FATAL_FAILURE(
-      ash::ShellTestApi().SetTabletModeEnabledForTest(true));
+  EnterTabletMode();
   EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());
-  StartOverview();
+  EnterOverviewMode();
   EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());
-  EndOverview();
+  ExitOverviewMode();
   EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());
-  ash::SplitViewTestApi().SnapWindow(widget->GetNativeWindow(),
-                                     ash::SnapPosition::kPrimary);
+  SnapWindow(widget->GetNativeWindow(), crosapi::mojom::SnapPosition::kPrimary);
   EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());
 }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Regression test for https://crbug.com/879851.
 // Tests that we don't accidentally change the color of app frame title bars.
 // Update expectation if change is intentional.
 IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewChromeOSTest, AppFrameColor) {
-  browser()->window()->Close();
+  Browser* app_browser =
+      CreateBrowserForApp("test_browser_app", browser()->profile());
 
-  // Open a new app window.
-  Browser* app_browser = Browser::Create(Browser::CreateParams::CreateForApp(
-      "test_browser_app", true /* trusted_source */, gfx::Rect(),
-      browser()->profile(), true /* user_gesture */));
   aura::Window* window = app_browser->window()->GetNativeWindow();
-  window->Show();
-
   SkColor active_frame_color =
       window->GetProperty(chromeos::kFrameActiveColorKey);
 
@@ -1061,7 +1071,8 @@
 
   views::Button* GetWindowCloseButton(Browser* browser) {
     auto* const browser_view = BrowserView::GetBrowserViewForBrowser(browser);
-    auto* const frame_view = GetFrameViewChromeOS(browser_view);
+    auto* const frame_view =
+        ChromeOSBrowserUITest::GetFrameViewChromeOS(browser_view);
 
     chromeos::FrameCaptionButtonContainerView::TestApi test_api(
         frame_view->caption_button_container());
@@ -1123,28 +1134,20 @@
   ClearWebAppSettings();
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
 IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewChromeOSTest,
                        ImmersiveModeTopViewInset) {
-  browser()->window()->Close();
+  Browser* app_browser =
+      CreateBrowserForApp("test_browser_app", browser()->profile());
 
-  // Open a new app window.
-  Browser::CreateParams params = Browser::CreateParams::CreateForApp(
-      "test_browser_app", true /* trusted_source */, gfx::Rect(),
-      browser()->profile(), true);
-  params.initial_show_state = ui::SHOW_STATE_DEFAULT;
-  Browser* browser = Browser::Create(params);
-  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
+  BrowserView* browser_view =
+      BrowserView::GetBrowserViewForBrowser(app_browser);
   ImmersiveModeController* immersive_mode_controller =
       browser_view->immersive_mode_controller();
-  aura::Window* window = browser->window()->GetNativeWindow();
-  window->Show();
-  EXPECT_FALSE(immersive_mode_controller->IsEnabled());
+  aura::Window* window = app_browser->window()->GetNativeWindow();
   EXPECT_LT(0, window->GetProperty(aura::client::kTopViewInset));
 
   // The kTopViewInset should be 0 when in immersive mode.
-  ui_test_utils::ToggleFullscreenModeAndWait(browser);
-  EXPECT_TRUE(immersive_mode_controller->IsEnabled());
+  EnterImmersiveFullscreenMode(app_browser);
   EXPECT_EQ(0, window->GetProperty(aura::client::kTopViewInset));
 
   // An immersive reveal shows the top of the frame.
@@ -1158,13 +1161,12 @@
   // The kTopViewInset should be larger than 0 again when immersive mode is
   // exited.
   revealed_lock.reset();
-  ui_test_utils::ToggleFullscreenModeAndWait(browser);
-  EXPECT_FALSE(immersive_mode_controller->IsEnabled());
+  ExitImmersiveFullscreenMode(app_browser);
   EXPECT_LT(0, window->GetProperty(aura::client::kTopViewInset));
 
   // The kTopViewInset is the same as in overview mode.
   const int inset_normal = window->GetProperty(aura::client::kTopViewInset);
-  StartOverview();
+  EnterOverviewMode();
   const int inset_in_overview_mode =
       window->GetProperty(aura::client::kTopViewInset);
   EXPECT_EQ(inset_normal, inset_in_overview_mode);
@@ -1175,19 +1177,12 @@
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
   ImmersiveModeController* immersive_mode_controller =
       browser_view->immersive_mode_controller();
-  ASSERT_FALSE(immersive_mode_controller->IsEnabled());
-  ASSERT_FALSE(browser_view->IsFullscreen());
 
-  // Enter immersive mode.
-  ui_test_utils::ToggleFullscreenModeAndWait(browser());
-  ASSERT_TRUE(immersive_mode_controller->IsEnabled());
-  ASSERT_TRUE(browser_view->IsFullscreen());
-
-  // Enable tablet mode.
-  ASSERT_NO_FATAL_FAILURE(
-      ash::ShellTestApi().SetTabletModeEnabledForTest(true));
+  EnterImmersiveFullscreenMode(browser());
 
   // Should exit immersive mode + fullscreen when tablet mode is enabled.
+  EnterTabletMode();
+  ImmersiveModeTester(browser()).WaitForFullscreenToExit();
   EXPECT_FALSE(immersive_mode_controller->IsEnabled());
   EXPECT_FALSE(browser_view->IsFullscreen());
 }
@@ -1200,43 +1195,38 @@
   ASSERT_FALSE(immersive_mode_controller->IsEnabled());
   ASSERT_FALSE(browser_view->IsFullscreen());
 
-  // Enter tablet mode.
-  ASSERT_NO_FATAL_FAILURE(
-      ash::ShellTestApi().SetTabletModeEnabledForTest(true));
+  EnterTabletMode();
 
-  // Enter immersive mode.
-  ui_test_utils::ToggleFullscreenModeAndWait(browser());
   // Should be able to enter immersive mode even when the tablet mode is
   // enabled.
-  ASSERT_TRUE(immersive_mode_controller->IsEnabled());
-  ASSERT_TRUE(browser_view->IsFullscreen());
+  EnterImmersiveFullscreenMode(browser());
 }
 
 // TODO(b/270175923): Consider using WebUiTabStripOverrideTest, since it
 // makes sense for it to always be enabled.
 using FloatBrowserNonClientFrameViewChromeOSTest =
-    TopChromeMdParamTest<InProcessBrowserTest>;
+    TopChromeMdParamTest<ChromeOSBrowserUITest>;
 
+// TODO(crbug.com/1494785): Port this test to Lacros.
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 IN_PROC_BROWSER_TEST_P(FloatBrowserNonClientFrameViewChromeOSTest,
                        TabletModeMultitaskMenu) {
   ui::ScopedAnimationDurationScaleMode test_duration_mode(
       ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
-  ASSERT_NO_FATAL_FAILURE(
-      ash::ShellTestApi().SetTabletModeEnabledForTest(true));
+  EnterTabletMode();
 
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
-  Widget* widget = browser_view->GetWidget();
+  views::Widget* widget = browser_view->GetWidget();
   aura::Window* window = widget->GetNativeWindow();
   ui::test::EventGenerator event_generator(window->GetRootWindow());
 
   // A normal tap on the top center of the window and in the omnibox
   // bounds will focus the omnibox.
-  const gfx::Rect omnibox_bounds =
-      browser_view->GetViewByID(VIEW_ID_OMNIBOX)->GetBoundsInScreen();
+  views::View* omnibox = browser_view->GetViewByID(VIEW_ID_OMNIBOX);
+  const gfx::Rect omnibox_bounds = omnibox->GetBoundsInScreen();
   ASSERT_NO_FATAL_FAILURE(
       event_generator.GestureTapAt(omnibox_bounds.top_center()));
-  ASSERT_NO_FATAL_FAILURE(
-      ui_test_utils::WaitForViewFocus(browser(), VIEW_ID_OMNIBOX, true));
+  ASSERT_TRUE(WaitForFocus(true, omnibox));
 
   // Swipe down from the top center opens the multitask menu.
   event_generator.SetTouchRadius(10, 5);
@@ -1244,8 +1234,7 @@
   event_generator.PressTouch(top_center);
   event_generator.MoveTouchBy(0, 100);
   event_generator.ReleaseTouch();
-  ASSERT_NO_FATAL_FAILURE(
-      ui_test_utils::WaitForViewFocus(browser(), VIEW_ID_OMNIBOX, false));
+  ASSERT_TRUE(WaitForFocus(false, omnibox));
   auto* multitask_menu_event_handler =
       ash::TabletModeControllerTestApi()
           .tablet_mode_window_manager()
@@ -1260,53 +1249,60 @@
   // Tap on the omnibox outside the menu takes focus and closes the menu.
   ASSERT_NO_FATAL_FAILURE(
       event_generator.GestureTapAt(omnibox_bounds.left_center()));
-  ASSERT_NO_FATAL_FAILURE(
-      ui_test_utils::WaitForViewFocus(browser(), VIEW_ID_OMNIBOX, true));
+  ASSERT_TRUE(WaitForFocus(true, omnibox));
   EXPECT_FALSE(multitask_menu_event_handler->multitask_menu());
 }
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 IN_PROC_BROWSER_TEST_P(FloatBrowserNonClientFrameViewChromeOSTest,
                        BrowserHeaderVisibilityInTabletModeTest) {
+  if (!IsSnapWindowSupported()) {
+    GTEST_SKIP() << "Ash is too old.";
+  }
+
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
   BrowserNonClientFrameViewChromeOS* frame_view =
       GetFrameViewChromeOS(browser_view);
 
-  ASSERT_NO_FATAL_FAILURE(
-      ash::ShellTestApi().SetTabletModeEnabledForTest(true));
-  EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());
+  EnterTabletMode();
+  ASSERT_TRUE(WaitForVisible(false, frame_view->caption_button_container()));
 
-  Widget* widget = browser_view->GetWidget();
+  aura::Window* window = browser_view->GetWidget()->GetNativeWindow();
   auto* immersive_controller = chromeos::ImmersiveFullscreenController::Get(
-      views::Widget::GetWidgetForNativeView(widget->GetNativeWindow()));
+      views::Widget::GetWidgetForNativeView(window));
 
-  // Snap a window. No immersive mode from regular browsers.
-  ash::SplitViewTestApi().SnapWindow(widget->GetNativeWindow(),
-                                     ash::SnapPosition::kSecondary);
-  EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());
+  // Snap the window. No immersive mode from regular browsers.
+  SnapWindow(window, crosapi::mojom::SnapPosition::kSecondary);
+  ASSERT_TRUE(WaitForVisible(false, frame_view->caption_button_container()));
   EXPECT_FALSE(immersive_controller->IsEnabled());
 
-  // Float the window; the title bar is visible.
-  ui::test::EventGenerator event_generator(
-      widget->GetNativeWindow()->GetRootWindow());
-  event_generator.PressAndReleaseKeyAndModifierKeys(
-      ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
-  EXPECT_TRUE(frame_view->caption_button_container()->GetVisible());
+  // Float the window; the title bar becomes visible.
+  chromeos::FloatControllerBase::Get()->SetFloat(
+      window, chromeos::FloatStartLocation::kBottomRight);
+  ASSERT_TRUE(WaitForVisible(true, frame_view->caption_button_container()));
   EXPECT_FALSE(immersive_controller->IsEnabled());
 }
 
 // Test that for a browser app window, its caption buttons may or may not hide
 // in tablet mode.
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+// TODO(crbug.com/1505656): Finish porting to Lacros when the bug is fixed.
+#define MAYBE_BrowserAppHeaderVisibilityInTabletModeTest \
+  DISABLED_BrowserAppHeaderVisibilityInTabletModeTest
+#else
+#define MAYBE_BrowserAppHeaderVisibilityInTabletModeTest \
+  BrowserAppHeaderVisibilityInTabletModeTest
+#endif
 IN_PROC_BROWSER_TEST_P(FloatBrowserNonClientFrameViewChromeOSTest,
-                       BrowserAppHeaderVisibilityInTabletModeTest) {
-  // Create a browser app window.
-  Browser::CreateParams params = Browser::CreateParams::CreateForApp(
-      "test_browser_app", /*trusted_source=*/true, gfx::Rect(),
-      browser()->profile(), true);
-  params.initial_show_state = ui::SHOW_STATE_DEFAULT;
-  Browser* browser2 = Browser::Create(params);
-  AddBlankTabAndShow(browser2);
+                       MAYBE_BrowserAppHeaderVisibilityInTabletModeTest) {
+  if (!IsSnapWindowSupported()) {
+    GTEST_SKIP() << "Ash is too old.";
+  }
+
+  Browser* browser2 =
+      CreateBrowserForApp("test_browser_app", browser()->profile());
   BrowserView* browser_view2 = BrowserView::GetBrowserViewForBrowser(browser2);
-  Widget* widget2 = browser_view2->GetWidget();
+  views::Widget* widget2 = browser_view2->GetWidget();
   BrowserNonClientFrameViewChromeOS* frame_view2 =
       GetFrameViewChromeOS(browser_view2);
   widget2->GetNativeWindow()->SetProperty(
@@ -1314,25 +1310,24 @@
       aura::client::kResizeBehaviorCanMaximize |
           aura::client::kResizeBehaviorCanResize);
 
-  StartOverview();
+  EnterOverviewMode();
   EXPECT_FALSE(frame_view2->caption_button_container()->GetVisible());
-  EndOverview();
+  ExitOverviewMode();
   EXPECT_TRUE(frame_view2->caption_button_container()->GetVisible());
 
-  ASSERT_NO_FATAL_FAILURE(
-      ash::ShellTestApi().SetTabletModeEnabledForTest(true));
-  StartOverview();
+  EnterTabletMode();
+  EnterOverviewMode();
   EXPECT_FALSE(frame_view2->caption_button_container()->GetVisible());
-
-  EndOverview();
+  ExitOverviewMode();
   EXPECT_TRUE(frame_view2->caption_button_container()->GetVisible());
 
   auto* immersive_controller = chromeos::ImmersiveFullscreenController::Get(
       views::Widget::GetWidgetForNativeView(widget2->GetNativeWindow()));
+  EXPECT_TRUE(immersive_controller->IsEnabled());
 
   // Snap a window. Immersive mode is enabled so its title bar is not visible.
-  ash::SplitViewTestApi().SnapWindow(widget2->GetNativeWindow(),
-                                     ash::SnapPosition::kSecondary);
+  SnapWindow(widget2->GetNativeWindow(),
+             crosapi::mojom::SnapPosition::kSecondary);
   EXPECT_TRUE(frame_view2->caption_button_container()->GetVisible());
   EXPECT_TRUE(immersive_controller->IsEnabled());
 
@@ -1362,37 +1357,18 @@
       browser_view->GetWidget()->GetNativeWindow()->GetRootWindow());
   event_generator.PressAndReleaseKeyAndModifierKeys(ui::VKEY_Z,
                                                     ui::EF_COMMAND_DOWN);
-  ASSERT_TRUE(size_button->IsMultitaskMenuShown());
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return size_button->IsMultitaskMenuShown(); }));
 
   // Pressing accelerator a second time should close the menu.
   event_generator.PressAndReleaseKeyAndModifierKeys(ui::VKEY_Z,
                                                     ui::EF_COMMAND_DOWN);
-  ASSERT_FALSE(size_button->IsMultitaskMenuShown());
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return !size_button->IsMultitaskMenuShown(); }));
 }
 
-namespace {
-
-class HomeLauncherBrowserNonClientFrameViewChromeOSTest
-    : public TopChromeMdParamTest<InProcessBrowserTest> {
- public:
-  HomeLauncherBrowserNonClientFrameViewChromeOSTest() = default;
-
-  HomeLauncherBrowserNonClientFrameViewChromeOSTest(
-      const HomeLauncherBrowserNonClientFrameViewChromeOSTest&) = delete;
-  HomeLauncherBrowserNonClientFrameViewChromeOSTest& operator=(
-      const HomeLauncherBrowserNonClientFrameViewChromeOSTest&) = delete;
-
-  ~HomeLauncherBrowserNonClientFrameViewChromeOSTest() override = default;
-
-  void SetUpDefaultCommandLine(base::CommandLine* command_line) override {
-    TopChromeMdParamTest<InProcessBrowserTest>::SetUpDefaultCommandLine(
-        command_line);
-
-    command_line->AppendSwitch(ash::switches::kAshEnableTabletMode);
-  }
-};
-
-}  // namespace
+using HomeLauncherBrowserNonClientFrameViewChromeOSTest =
+    BrowserNonClientFrameViewChromeOSTest;
 
 IN_PROC_BROWSER_TEST_P(HomeLauncherBrowserNonClientFrameViewChromeOSTest,
                        TabletModeBrowserCaptionButtonVisibility) {
@@ -1407,17 +1383,15 @@
     EXPECT_TRUE(frame_view->caption_button_container()->GetVisible());
   }
 
-  ASSERT_NO_FATAL_FAILURE(
-      ash::ShellTestApi().SetTabletModeEnabledForTest(true));
+  EnterTabletMode();
   EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());
 
-  StartOverview();
+  EnterOverviewMode();
   EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());
-  EndOverview();
+  ExitOverviewMode();
   EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());
 
-  ASSERT_NO_FATAL_FAILURE(
-      ash::ShellTestApi().SetTabletModeEnabledForTest(false));
+  ExitTabletMode();
 
   // Caption buttons are not supported when using the WebUI tab strip.
   if (browser_view->webui_tab_strip()) {
@@ -1434,27 +1408,27 @@
 // |BrowserNonClientFrameViewChromeOS|.
 IN_PROC_BROWSER_TEST_P(HomeLauncherBrowserNonClientFrameViewChromeOSTest,
                        CaptionButtonVisibilityForBrowserLaunchedInTabletMode) {
-  ASSERT_NO_FATAL_FAILURE(
-      ash::ShellTestApi().SetTabletModeEnabledForTest(true));
-  auto* frame_view = GetFrameViewChromeOS(BrowserView::GetBrowserViewForBrowser(
-      CreateBrowser(browser()->profile())));
+  EnterTabletMode();
+  auto* new_browser = CreateBrowser(browser()->profile());
+  auto* frame_view =
+      GetFrameViewChromeOS(BrowserView::GetBrowserViewForBrowser(new_browser));
   EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());
 }
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+// TODO(crbug.com/1505656): Finish porting to Lacros when the bug is fixed.
+#define MAYBE_TabletModeAppCaptionButtonVisibility \
+  DISABLED_TabletModeAppCaptionButtonVisibility
+#else
+#define MAYBE_TabletModeAppCaptionButtonVisibility \
+  TabletModeAppCaptionButtonVisibility
+#endif
 IN_PROC_BROWSER_TEST_P(HomeLauncherBrowserNonClientFrameViewChromeOSTest,
-                       TabletModeAppCaptionButtonVisibility) {
-  browser()->window()->Close();
-
-  // Open a new app window.
-  Browser::CreateParams params = Browser::CreateParams::CreateForApp(
-      "test_browser_app", true /* trusted_source */, gfx::Rect(),
-      browser()->profile(), true);
-  params.initial_show_state = ui::SHOW_STATE_DEFAULT;
-  Browser* browser = Browser::Create(params);
-  ASSERT_TRUE(browser->is_type_app());
-  browser->window()->Show();
-
-  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
+                       MAYBE_TabletModeAppCaptionButtonVisibility) {
+  Browser* app_browser =
+      CreateBrowserForApp("test_browser_app", browser()->profile());
+  BrowserView* browser_view =
+      BrowserView::GetBrowserViewForBrowser(app_browser);
   BrowserNonClientFrameViewChromeOS* frame_view =
       GetFrameViewChromeOS(browser_view);
   EXPECT_TRUE(frame_view->caption_button_container()->GetVisible());
@@ -1463,20 +1437,18 @@
   EXPECT_FALSE(immersive_mode_controller->IsEnabled());
 
   // Tablet mode doesn't affect app's caption button's visibility.
-  ASSERT_NO_FATAL_FAILURE(
-      ash::ShellTestApi().SetTabletModeEnabledForTest(true));
+  EnterTabletMode();
   EXPECT_TRUE(frame_view->caption_button_container()->GetVisible());
   EXPECT_FALSE(browser_view->IsFullscreen());
   EXPECT_TRUE(immersive_mode_controller->IsEnabled());
 
   // However, overview mode does.
-  StartOverview();
+  EnterOverviewMode();
   EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());
-  EndOverview();
+  ExitOverviewMode();
   EXPECT_TRUE(frame_view->caption_button_container()->GetVisible());
 
-  ASSERT_NO_FATAL_FAILURE(
-      ash::ShellTestApi().SetTabletModeEnabledForTest(false));
+  ExitTabletMode();
   EXPECT_TRUE(frame_view->caption_button_container()->GetVisible());
   EXPECT_FALSE(browser_view->IsFullscreen());
   EXPECT_FALSE(immersive_mode_controller->IsEnabled());
@@ -1485,7 +1457,7 @@
 namespace {
 
 class TabSearchFrameCaptionButtonTest
-    : public TopChromeMdParamTest<InProcessBrowserTest> {
+    : public TopChromeMdParamTest<ChromeOSBrowserUITest> {
  public:
   TabSearchFrameCaptionButtonTest() = default;
   TabSearchFrameCaptionButtonTest(const TabSearchFrameCaptionButtonTest&) =
@@ -1497,7 +1469,7 @@
   void SetUp() override {
     scoped_feature_list_.InitAndEnableFeature(
         features::kChromeOSTabSearchCaptionButton);
-    TopChromeMdParamTest<InProcessBrowserTest>::SetUp();
+    TopChromeMdParamTest<ChromeOSBrowserUITest>::SetUp();
   }
 
  private:
@@ -1520,6 +1492,8 @@
             test.custom_button());
 }
 
+// TODO(crbug.com/1494785): Port this kiosk test to Lacros?
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 namespace {
 
 class KioskBrowserNonClientFrameViewChromeOSTest
@@ -1542,7 +1516,7 @@
   EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
   auto* browser_view =
       BrowserView::GetBrowserViewForBrowser(BrowserList::GetInstance()->get(0));
-  auto* frame_view = GetFrameViewChromeOS(browser_view);
+  auto* frame_view = ChromeOSBrowserUITest::GetFrameViewChromeOS(browser_view);
   EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());
 
   auto* widget = browser_view->GetWidget();
@@ -1557,25 +1531,20 @@
   EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());
   EXPECT_FALSE(immersive_controller->IsEnabled());
 }
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-namespace {
-
-class LockedFullscreenBrowserNonClientFrameViewChromeOSTest
-    : public TopChromeMdParamTest<InProcessBrowserTest> {
- public:
-  LockedFullscreenBrowserNonClientFrameViewChromeOSTest() = default;
-  LockedFullscreenBrowserNonClientFrameViewChromeOSTest(
-      const LockedFullscreenBrowserNonClientFrameViewChromeOSTest&) = delete;
-  LockedFullscreenBrowserNonClientFrameViewChromeOSTest& operator=(
-      const LockedFullscreenBrowserNonClientFrameViewChromeOSTest&) = delete;
-  ~LockedFullscreenBrowserNonClientFrameViewChromeOSTest() override = default;
-};
-
-}  // namespace
+using LockedFullscreenBrowserNonClientFrameViewChromeOSTest =
+    TopChromeMdParamTest<ChromeOSBrowserUITest>;
 
 IN_PROC_BROWSER_TEST_P(LockedFullscreenBrowserNonClientFrameViewChromeOSTest,
                        ToggleTabletMode) {
+  if (!IsIsShelfVisibleSupported()) {
+    GTEST_SKIP() << "Ash is too old.";
+  }
+
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
+  EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
+
   // Set locked fullscreen state.
   PinWindow(browser_view->GetWidget()->GetNativeWindow(), /*trusted=*/true);
 
@@ -1583,24 +1552,25 @@
   // we're at it, also make sure that the shelf is hidden.
   EXPECT_TRUE(browser_view->GetWidget()->IsFullscreen());
   EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  // TODO(crbug.com/1466385): Enable this assertion once the bug is fixed (at
+  // the moment PinWindow returns too early).
+#else
   EXPECT_FALSE(IsShelfVisible());
+#endif
 
   auto* widget = browser_view->GetWidget();
   auto* immersive_controller = chromeos::ImmersiveFullscreenController::Get(
       views::Widget::GetWidgetForNativeView(widget->GetNativeWindow()));
   EXPECT_FALSE(immersive_controller->IsEnabled());
 
-  // Enter tablet mode.
-  ASSERT_NO_FATAL_FAILURE(
-      ash::ShellTestApi().SetTabletModeEnabledForTest(true));
+  EnterTabletMode();
 
   EXPECT_TRUE(browser_view->GetWidget()->IsFullscreen());
   EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
   EXPECT_FALSE(IsShelfVisible());
 }
 
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
 // The remaining tests make sense only for Ash, not for Lacros.
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 
@@ -1896,14 +1866,14 @@
 INSTANTIATE_TEST_SUITE(BrowserNonClientFrameViewChromeOSTest);
 INSTANTIATE_TEST_SUITE(BrowserNonClientFrameViewChromeOSTestNoWebUiTabStrip);
 INSTANTIATE_TEST_SUITE(BrowserNonClientFrameViewChromeOSTestWithWebUiTabStrip);
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-INSTANTIATE_TEST_SUITE(BrowserNonClientFrameViewAshTest);
-INSTANTIATE_TEST_SUITE(BrowserNonClientFrameViewAshTestNoWebUiTabStrip);
 INSTANTIATE_TEST_SUITE(FloatBrowserNonClientFrameViewChromeOSTest);
 INSTANTIATE_TEST_SUITE(HomeLauncherBrowserNonClientFrameViewChromeOSTest);
-INSTANTIATE_TEST_SUITE(KioskBrowserNonClientFrameViewChromeOSTest);
 INSTANTIATE_TEST_SUITE(LockedFullscreenBrowserNonClientFrameViewChromeOSTest);
 INSTANTIATE_TEST_SUITE(TabSearchFrameCaptionButtonTest);
-INSTANTIATE_TEST_SUITE(WebAppNonClientFrameViewAshTest);
+INSTANTIATE_TEST_SUITE(WebAppNonClientFrameViewChromeOSTest);
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+INSTANTIATE_TEST_SUITE(BrowserNonClientFrameViewAshTestNoWebUiTabStrip);
+INSTANTIATE_TEST_SUITE(BrowserNonClientFrameViewAshTest);
+INSTANTIATE_TEST_SUITE(KioskBrowserNonClientFrameViewChromeOSTest);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_test_utils.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_test_utils.cc
deleted file mode 100644
index fdeaaf6..0000000
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_test_utils.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// 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.
-
-#include "chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_test_utils.h"
-
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_commands.h"
-#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
-#include "chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.h"
-#include "chrome/browser/ui/views/frame/browser_view.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_contents_delegate.h"
-#include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
-#include "ui/views/widget/widget.h"
-#include "ui/views/window/non_client_view.h"
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "ash/public/cpp/shelf_test_api.h"
-#include "ash/shell.h"
-#include "ash/wm/overview/overview_controller.h"
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
-// Enters fullscreen mode for tab and waits for the notification.
-void EnterFullscreenModeForTabAndWait(Browser* browser,
-                                      content::WebContents* web_contents) {
-  ui_test_utils::FullscreenWaiter waiter(browser, {.tab_fullscreen = true});
-  static_cast<content::WebContentsDelegate*>(browser)
-      ->EnterFullscreenModeForTab(web_contents->GetPrimaryMainFrame(), {});
-  waiter.Wait();
-}
-
-// Exits fullscreen mode for tab and waits for the notification.
-void ExitFullscreenModeForTabAndWait(Browser* browser,
-                                     content::WebContents* web_contents) {
-  ui_test_utils::FullscreenWaiter waiter(browser, {.tab_fullscreen = false});
-  browser->exclusive_access_manager()
-      ->fullscreen_controller()
-      ->ExitFullscreenModeForTab(web_contents);
-  waiter.Wait();
-}
-
-BrowserNonClientFrameViewChromeOS* GetFrameViewChromeOS(
-    BrowserView* browser_view) {
-  // We know we're using ChromeOS, so static cast.
-  auto* frame_view = static_cast<BrowserNonClientFrameViewChromeOS*>(
-      browser_view->GetWidget()->non_client_view()->frame_view());
-  DCHECK(frame_view);
-  return frame_view;
-}
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-void StartOverview() {
-  ash::Shell::Get()->overview_controller()->StartOverview(
-      ash::OverviewStartAction::kTests);
-}
-
-void EndOverview() {
-  ash::Shell::Get()->overview_controller()->EndOverview(
-      ash::OverviewEndAction::kTests);
-}
-
-bool IsShelfVisible() {
-  return ash::ShelfTestApi().IsVisible();
-}
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_test_utils.h b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_test_utils.h
deleted file mode 100644
index 58b83ce..0000000
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_test_utils.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// 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.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_NON_CLIENT_FRAME_VIEW_CHROMEOS_TEST_UTILS_H_
-#define CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_NON_CLIENT_FRAME_VIEW_CHROMEOS_TEST_UTILS_H_
-
-#include "build/chromeos_buildflags.h"
-
-class Browser;
-class BrowserView;
-class BrowserNonClientFrameViewChromeOS;
-
-namespace content {
-class WebContents;
-}
-
-// Enters fullscreen mode for tab and waits for the notification.
-void EnterFullscreenModeForTabAndWait(Browser* browser,
-                                      content::WebContents* web_contents);
-
-// Exits fullscreen mode for tab and waits for the notification.
-void ExitFullscreenModeForTabAndWait(Browser* browser,
-                                     content::WebContents* web_contents);
-
-// Returns the non client frame view for |browser_view|.
-BrowserNonClientFrameViewChromeOS* GetFrameViewChromeOS(
-    BrowserView* browser_view);
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-// Starts overview session which displays an overview of all windows.
-void StartOverview();
-
-// Ends overview session.
-void EndOverview();
-
-// Returns true if the shelf is visible (e.g. not auto-hidden).
-bool IsShelfVisible();
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
-#endif  // CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_NON_CLIENT_FRAME_VIEW_CHROMEOS_TEST_UTILS_H_
diff --git a/chrome/browser/ui/views/frame/immersive_mode_browser_view_test.cc b/chrome/browser/ui/views/frame/immersive_mode_browser_view_test.cc
index 2313d2a..7b80390 100644
--- a/chrome/browser/ui/views/frame/immersive_mode_browser_view_test.cc
+++ b/chrome/browser/ui/views/frame/immersive_mode_browser_view_test.cc
@@ -7,9 +7,8 @@
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
-#include "chrome/browser/ui/chromeos/window_pin_util.h"
+#include "chrome/browser/ui/chromeos/test_util.h"
 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.h"
-#include "chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_test_utils.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
 #include "chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.h"
@@ -36,7 +35,7 @@
 namespace {
 
 class ImmersiveModeBrowserViewTest
-    : public TopChromeMdParamTest<InProcessBrowserTest> {
+    : public TopChromeMdParamTest<ChromeOSBrowserUITest> {
  public:
   ImmersiveModeBrowserViewTest() = default;
   ImmersiveModeBrowserViewTest(const ImmersiveModeBrowserViewTest&) = delete;
@@ -44,7 +43,7 @@
       delete;
   ~ImmersiveModeBrowserViewTest() override = default;
 
-  // TopChromeMdParamTest<InProcessBrowserTest>:
+  // TopChromeMdParamTest<ChromeOSBrowserUITest>:
   void PreRunTestOnMainThread() override {
     InProcessBrowserTest::PreRunTestOnMainThread();
 
@@ -66,8 +65,15 @@
 
 // This test does not make sense for the webUI tabstrip, since the frame is not
 // painted in that case.
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+// TODO(crbug.com/1255304): Reveal does not end until mouse is moved. Find out
+// if this is a product or test issue and fix it.
+#define MAYBE_ImmersiveFullscreen DISABLED_ImmersiveFullscreen
+#else
+#define MAYBE_ImmersiveFullscreen ImmersiveFullscreen
+#endif
 IN_PROC_BROWSER_TEST_P(ImmersiveModeBrowserViewTestNoWebUiTabStrip,
-                       ImmersiveFullscreen) {
+                       MAYBE_ImmersiveFullscreen) {
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
   content::WebContents* web_contents = browser_view->GetActiveWebContents();
   BrowserNonClientFrameViewChromeOS* frame_view =
@@ -90,7 +96,7 @@
   // Enter both browser fullscreen and tab fullscreen. Entering browser
   // fullscreen should enable immersive fullscreen.
   ui_test_utils::ToggleFullscreenModeAndWait(browser());
-  EnterFullscreenModeForTabAndWait(browser(), web_contents);
+  EnterTabFullscreenMode(browser(), web_contents);
   EXPECT_TRUE(immersive_mode_controller->IsEnabled());
   // Caption button container is hidden.
   EXPECT_FALSE(frame_view->caption_button_container_->GetVisible());
@@ -116,7 +122,7 @@
   EXPECT_FALSE(frame_view->caption_button_container()->GetVisible());
 
   // Repeat test but without tab fullscreen.
-  ExitFullscreenModeForTabAndWait(browser(), web_contents);
+  EnterTabFullscreenMode(browser(), web_contents);
 
   // Immersive reveal should have same behavior as before.
   revealed_lock = immersive_mode_controller->GetRevealedLock(
@@ -161,6 +167,7 @@
 IN_PROC_BROWSER_TEST_P(ImmersiveModeBrowserViewTest,
                        TabNavigationAcceleratorsFullscreenBrowser) {
   ImmersiveModeTester tester(browser());
+
   // Make sure that the focus is on the webcontents rather than on the omnibox,
   // because if the focus is on the omnibox, the tab strip will remain revealed
   // in the immersive fullscreen mode and will interfere with this test waiting
@@ -176,10 +183,8 @@
   ASSERT_TRUE(AddTabAtIndex(0, about_blank, ui::PAGE_TRANSITION_TYPED));
   browser()->tab_strip_model()->GetActiveWebContents()->Focus();
 
-  // Toggle fullscreen mode.
-  chrome::ToggleFullscreenMode(browser());
-  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
-  EXPECT_TRUE(browser_view->immersive_mode_controller()->IsEnabled());
+  EnterImmersiveFullscreenMode(browser());
+
   // Wait for the end of the initial reveal which results from adding the new
   // tabs and changing the focused tab.
   tester.VerifyTabIndexAfterReveal(0);
@@ -210,11 +215,7 @@
   // for the revealer to be dismissed.
   browser()->tab_strip_model()->GetActiveWebContents()->Focus();
 
-  // Toggle fullscreen mode.
-  chrome::ToggleFullscreenMode(browser());
-  EXPECT_TRUE(browser_view->immersive_mode_controller()->IsEnabled());
-
-  EXPECT_TRUE(browser()->window()->IsFullscreen());
+  EnterImmersiveFullscreenMode(browser());
   EXPECT_FALSE(browser()->window()->IsMaximized());
   EXPECT_FALSE(browser_view->immersive_mode_controller()->IsRevealed());
 
@@ -223,8 +224,6 @@
           ImmersiveModeController::ANIMATE_REVEAL_NO);
   EXPECT_TRUE(browser_view->immersive_mode_controller()->IsRevealed());
 
-  ImmersiveModeTester tester(browser());
-
   // Clicking the "restore" caption button should exit the immersive mode.
   aura::Window* window = browser()->window()->GetNativeWindow();
   ui::test::EventGenerator event_generator(window->GetRootWindow());
@@ -237,84 +236,82 @@
   event_generator.MoveMouseTo(point_in_restore_button);
   EXPECT_TRUE(browser_view->immersive_mode_controller()->IsRevealed());
   event_generator.ClickLeftButton();
-  tester.WaitForFullscreenToExit();
-
+  ImmersiveModeTester(browser()).WaitForFullscreenToExit();
   EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
   EXPECT_FALSE(browser()->window()->IsFullscreen());
 }
 
 IN_PROC_BROWSER_TEST_P(ImmersiveModeBrowserViewTest,
                        TestCaptionButtonsReceiveEventsInAppImmersiveMode) {
-  browser()->window()->Close();
-
   // Open a new app window.
-  Browser::CreateParams params = Browser::CreateParams::CreateForApp(
-      "test_browser_app", true /* trusted_source */, gfx::Rect(0, 0, 300, 300),
-      browser()->profile(), true);
-  params.initial_show_state = ui::SHOW_STATE_DEFAULT;
-  Browser* browser = Browser::Create(params);
-  ASSERT_TRUE(browser->is_type_app());
-  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
+  Browser* app_browser =
+      CreateBrowserForApp("test_browser_app", browser()->profile());
 
+  BrowserView* app_view = BrowserView::GetBrowserViewForBrowser(app_browser);
   chromeos::ImmersiveFullscreenControllerTestApi(
       static_cast<ImmersiveModeControllerChromeos*>(
-          browser_view->immersive_mode_controller())
+          app_view->immersive_mode_controller())
           ->controller())
       .SetupForTest();
 
-  // Toggle fullscreen mode.
-  chrome::ToggleFullscreenMode(browser);
-  EXPECT_TRUE(browser_view->immersive_mode_controller()->IsEnabled());
-  EXPECT_FALSE(browser_view->GetTabStripVisible());
-
-  EXPECT_TRUE(browser->window()->IsFullscreen());
-  EXPECT_FALSE(browser->window()->IsMaximized());
-  EXPECT_FALSE(browser_view->immersive_mode_controller()->IsRevealed());
+  EnterImmersiveFullscreenMode(app_browser);
+  EXPECT_TRUE(app_browser->window()->IsFullscreen());
+  EXPECT_FALSE(app_browser->window()->IsMaximized());
+  EXPECT_FALSE(app_view->GetTabStripVisible());
+  EXPECT_FALSE(app_view->immersive_mode_controller()->IsRevealed());
 
   std::unique_ptr<ImmersiveRevealedLock> revealed_lock =
-      browser_view->immersive_mode_controller()->GetRevealedLock(
+      app_view->immersive_mode_controller()->GetRevealedLock(
           ImmersiveModeController::ANIMATE_REVEAL_NO);
-  EXPECT_TRUE(browser_view->immersive_mode_controller()->IsRevealed());
+  EXPECT_TRUE(app_view->immersive_mode_controller()->IsRevealed());
 
-  ImmersiveModeTester tester(browser);
-  AddBlankTabAndShow(browser);
+  AddBlankTabAndShow(app_browser);
 
   // Clicking the "restore" caption button should exit the immersive mode.
-  aura::Window* window = browser->window()->GetNativeWindow();
-  ui::test::EventGenerator event_generator(window->GetRootWindow(), window);
+  aura::Window* app_window = app_browser->window()->GetNativeWindow();
+  ui::test::EventGenerator event_generator(app_window->GetRootWindow(),
+                                           app_window);
   gfx::Size button_size = views::GetCaptionButtonLayoutSize(
       views::CaptionButtonLayoutSize::kBrowserCaptionMaximized);
   gfx::Point point_in_restore_button(
-      window->GetBoundsInRootWindow().top_right());
+      app_window->GetBoundsInRootWindow().top_right());
   point_in_restore_button.Offset(-2 * button_size.width(),
                                  button_size.height() / 2);
 
   event_generator.MoveMouseTo(point_in_restore_button);
-  EXPECT_TRUE(browser_view->immersive_mode_controller()->IsRevealed());
+  EXPECT_TRUE(app_view->immersive_mode_controller()->IsRevealed());
   event_generator.ClickLeftButton();
-  tester.WaitForFullscreenToExit();
-
-  EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
-  EXPECT_FALSE(browser->window()->IsFullscreen());
+  ImmersiveModeTester(app_browser).WaitForFullscreenToExit();
+  EXPECT_FALSE(app_view->immersive_mode_controller()->IsEnabled());
+  EXPECT_FALSE(app_browser->window()->IsFullscreen());
 }
 
 // Regression test for crbug.com/796171.  Make sure that going from regular
 // fullscreen to locked fullscreen does not cause a crash.
 // Also test that the immersive mode is disabled afterwards (and the shelf is
 // hidden, and the fullscreen control popup doesn't show up).
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+// TODO(crbug.com/1508249): Reenable test when bug is fixed.
+#define MAYBE_RegularToLockedFullscreenDisablesImmersive \
+  DISABLED_RegularToLockedFullscreenDisablesImmersive
+#else
+#define MAYBE_RegularToLockedFullscreenDisablesImmersive \
+  RegularToLockedFullscreenDisablesImmersive
+#endif
 IN_PROC_BROWSER_TEST_P(ImmersiveModeBrowserViewTest,
-                       RegularToLockedFullscreenDisablesImmersive) {
-  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
+                       MAYBE_RegularToLockedFullscreenDisablesImmersive) {
+  if (!IsIsShelfVisibleSupported()) {
+    GTEST_SKIP() << "Ash is too old.";
+  }
 
-  // Toggle fullscreen mode.
-  chrome::ToggleFullscreenMode(browser());
-  EXPECT_TRUE(browser_view->immersive_mode_controller()->IsEnabled());
+  EnterImmersiveFullscreenMode(browser());
 
   // Set locked fullscreen state.
   PinWindow(browser()->window()->GetNativeWindow(), /*trusted=*/true);
 
   // We're fullscreen, immersive is disabled in locked fullscreen, and while
   // we're at it, also make sure that the shelf is hidden.
+  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
   EXPECT_TRUE(browser_view->GetWidget()->IsFullscreen());
   EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
   EXPECT_FALSE(IsShelfVisible());
@@ -332,6 +329,10 @@
 // fullscreen control popup doesn't show up).
 IN_PROC_BROWSER_TEST_P(ImmersiveModeBrowserViewTest,
                        LockedFullscreenDisablesImmersive) {
+  if (!IsIsShelfVisibleSupported()) {
+    GTEST_SKIP() << "Ash is too old.";
+  }
+
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
   EXPECT_FALSE(browser_view->GetWidget()->IsFullscreen());
 
@@ -342,7 +343,12 @@
   // we're at it, also make sure that the shelf is hidden.
   EXPECT_TRUE(browser_view->GetWidget()->IsFullscreen());
   EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  // TODO(crbug.com/1466385): Enable this assertion once the bug is fixed (at
+  // the moment PinWindow returns too early).
+#else
   EXPECT_FALSE(IsShelfVisible());
+#endif
 
   // Make sure the fullscreen control popup doesn't show up.
   ui::MouseEvent mouse_move(ui::ET_MOUSE_MOVED, gfx::Point(1, 1), gfx::Point(),
@@ -355,6 +361,10 @@
 // Test the shelf visibility affected by entering and exiting tab fullscreen and
 // immersive fullscreen.
 IN_PROC_BROWSER_TEST_P(ImmersiveModeBrowserViewTest, TabAndBrowserFullscreen) {
+  if (!IsIsShelfVisibleSupported()) {
+    GTEST_SKIP() << "Ash is too old.";
+  }
+
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
 
   ASSERT_TRUE(
@@ -365,25 +375,23 @@
 
   // 1) Test that entering tab fullscreen from immersive fullscreen hides the
   // shelf.
-  chrome::ToggleFullscreenMode(browser());
-  ASSERT_TRUE(browser_view->immersive_mode_controller()->IsEnabled());
+  EnterImmersiveFullscreenMode(browser());
   EXPECT_FALSE(IsShelfVisible());
-
   content::WebContents* web_contents = browser_view->GetActiveWebContents();
-  EnterFullscreenModeForTabAndWait(browser(), web_contents);
+  EnterTabFullscreenMode(browser(), web_contents);
   ASSERT_TRUE(browser_view->immersive_mode_controller()->IsEnabled());
   EXPECT_FALSE(IsShelfVisible());
 
   // 2) Test that exiting tab fullscreen autohides the shelf.
-  ExitFullscreenModeForTabAndWait(browser(), web_contents);
+  ExitTabFullscreenMode(browser(), web_contents);
   ASSERT_TRUE(browser_view->immersive_mode_controller()->IsEnabled());
   EXPECT_FALSE(IsShelfVisible());
 
   // 3) Test that exiting tab fullscreen and immersive fullscreen correctly
   // updates the shelf visibility.
-  EnterFullscreenModeForTabAndWait(browser(), web_contents);
+  EnterTabFullscreenMode(browser(), web_contents);
   ASSERT_TRUE(browser_view->immersive_mode_controller()->IsEnabled());
-  chrome::ToggleFullscreenMode(browser());
+  ui_test_utils::ToggleFullscreenModeAndWait(browser());
   ASSERT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
   EXPECT_TRUE(IsShelfVisible());
 }
diff --git a/chrome/browser/ui/views/page_info/page_info_bubble_view_unittest.cc b/chrome/browser/ui/views/page_info/page_info_bubble_view_unittest.cc
index bf8824c1..3dbf5d6 100644
--- a/chrome/browser/ui/views/page_info/page_info_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/page_info/page_info_bubble_view_unittest.cc
@@ -499,16 +499,7 @@
 
 TEST_F(PageInfoBubbleViewTest, NotificationPermissionRevokeUkm) {
   GURL origin_url = GURL(kUrl).DeprecatedGetOriginAsURL();
-  TestingProfile* profile =
-      static_cast<TestingProfile*>(web_contents_helper_->profile());
   ukm::TestAutoSetUkmRecorder ukm_recorder;
-  auto* history_service = HistoryServiceFactory::GetForProfile(
-      profile, ServiceAccessType::EXPLICIT_ACCESS);
-  history_service->AddPage(origin_url, base::Time::Now(),
-                           history::SOURCE_BROWSED);
-  base::RunLoop origin_queried_waiter;
-  history_service->set_origin_queried_closure_for_testing(
-      origin_queried_waiter.QuitClosure());
 
   PermissionInfoList list(1);
   list.back().type = ContentSettingsType::NOTIFICATIONS;
@@ -519,8 +510,6 @@
   list.back().setting = CONTENT_SETTING_BLOCK;
   api_->SetPermissionInfo(list);
 
-  origin_queried_waiter.Run();
-
   auto entries = ukm_recorder.GetEntriesByName("Permission");
   EXPECT_EQ(1u, entries.size());
   auto* entry = entries.front().get();
diff --git a/chrome/browser/ui/views/permissions/embedded_permission_prompt_interactive_uitest.cc b/chrome/browser/ui/views/permissions/embedded_permission_prompt_interactive_uitest.cc
index fb4d40c..f6789fa 100644
--- a/chrome/browser/ui/views/permissions/embedded_permission_prompt_interactive_uitest.cc
+++ b/chrome/browser/ui/views/permissions/embedded_permission_prompt_interactive_uitest.cc
@@ -273,7 +273,7 @@
   TestAskBlockAllowFlow(
       "microphone", {ContentSettingsType::MEDIASTREAM_MIC},
       std::queue<std::u16string>(
-          {u"Use your microphone",
+          {u"Use your microphones",
            u"You have allowed microphone on a.test:" +
                base::UTF8ToUTF16(GetOrigin().port()),
            u"You previously didn't allow microphone on a.test:" +
@@ -284,7 +284,7 @@
                        MAYBE_BasicFlowCamera) {
   TestAskBlockAllowFlow("camera", {ContentSettingsType::MEDIASTREAM_CAMERA},
                         std::queue<std::u16string>(
-                            {u"Use your camera",
+                            {u"Use your cameras",
                              u"You have allowed camera on a.test:" +
                                  base::UTF8ToUTF16(GetOrigin().port()),
                              u"You previously didn't allow camera on a.test:" +
@@ -298,12 +298,12 @@
       {ContentSettingsType::MEDIASTREAM_CAMERA,
        ContentSettingsType::MEDIASTREAM_MIC},
       std::queue<std::u16string>(
-          {u"Use your camera",
+          {u"Use your cameras",
            u"You have allowed camera and microphone on a.test:" +
                base::UTF8ToUTF16(GetOrigin().port()),
            u"You previously didn't allow camera and microphone on a.test:" +
                base::UTF8ToUTF16(GetOrigin().port())}),
-      std::queue<std::u16string>({u"Use your microphone"}));
+      std::queue<std::u16string>({u"Use your microphones"}));
 }
 
 IN_PROC_BROWSER_TEST_F(EmbeddedPermissionPromptInteractiveTest,
@@ -312,9 +312,9 @@
                   NavigateWebContents(kWebContentsElementId, GetURL()));
 
   TestPartialPermissionsLabel(CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK,
-                              u"Use your microphone");
+                              u"Use your microphones");
   TestPartialPermissionsLabel(CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW,
-                              u"Use your camera");
+                              u"Use your cameras");
 
   TestPartialPermissionsLabel(
       CONTENT_SETTING_BLOCK, CONTENT_SETTING_ASK,
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
index e66f6ec..d33f92c 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
@@ -117,6 +117,8 @@
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
 #include "chrome/browser/ui/views/frame/desktop_browser_frame_lacros.h"
+#include "ui/aura/window_tree_host_platform.h"
+#include "ui/platform_window/extensions/wayland_extension.h"
 #define DESKTOP_BROWSER_FRAME_AURA DesktopBrowserFrameLacros
 #elif BUILDFLAG(IS_LINUX)
 #include "chrome/browser/ui/views/frame/desktop_browser_frame_aura_linux.h"
@@ -1894,8 +1896,15 @@
 
 // Drags from browser that has a web dialog to separate window.
 // The dialog should follow the new browser window.
+// TODO(crbug.com/40934892): Expectations are sometimes off by one pixel on
+// Windows. Reenable once deflaked.
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_DetachToOwnWindowWithDialog DISABLED_DetachToOwnWindowWithDialog
+#else
+#define MAYBE_DetachToOwnWindowWithDialog DetachToOwnWindowWithDialog
+#endif
 IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
-                       DetachToOwnWindowWithDialog) {
+                       MAYBE_DetachToOwnWindowWithDialog) {
   const gfx::Rect initial_bounds(browser()->window()->GetBounds());
   AddTabsAndResetBrowser(browser(), 1);
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
@@ -2494,7 +2503,14 @@
 }  // namespace
 
 // Selects multiple tabs and starts dragging the window.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest, DragAll) {
+// TODO(crbug.com/40934892): Expectations are sometimes off by one pixel on
+// Windows. Reenable once deflaked.
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_DragAll DISABLED_DragAll
+#else
+#define MAYBE_DragAll DragAll
+#endif
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest, MAYBE_DragAll) {
   AddTabsAndResetBrowser(browser(), 1);
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   browser()->tab_strip_model()->ToggleSelectionAt(0);
@@ -4047,6 +4063,9 @@
   EXPECT_EQ("1", IDString(app_browser1->tab_strip_model()));
 }
 
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+#if BUILDFLAG(IS_CHROMEOS)
 // Subclass of DetachToBrowserTabDragControllerTest that
 // creates multiple displays.
 class DetachToBrowserInSeparateDisplayTabDragControllerTest
@@ -4065,10 +4084,18 @@
     // 1280x800 is the default resolution for the main display in tests.
     // We stick to it, as opposed to a smaller one, to avoid the browser
     // window being shrunk and maximized when calling UpdateDisplay.
+    const std::string display_specs = "1280x800,1280x800";
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+    ui_controls::UpdateDisplaySync(display_specs);
+#else
     display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
-        .UpdateDisplay("1280x800,1280x800");
+        .UpdateDisplay(display_specs);
+#endif
   }
 };
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 
 namespace {
 
@@ -4143,8 +4170,25 @@
   EXPECT_FALSE(new_browser->window()->IsMaximized());
 }
 
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+#if BUILDFLAG(IS_CHROMEOS)
+
 namespace {
 
+void SetBoundsSync(BrowserWindow* window, const gfx::Rect& bounds) {
+  window->SetBounds(bounds);
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  // Wait for a Wayland roundtrip to ensure all side effects have been
+  // processed.
+  auto* host = static_cast<aura::WindowTreeHostPlatform*>(
+      window->GetNativeWindow()->GetHost());
+  auto* wayland_extension = ui::GetWaylandExtension(*host->platform_window());
+  wayland_extension->RoundTripQueue();
+#endif
+}
+
 // Invoked from the nested run loop.
 void DragTabToWindowInSeparateDisplayStep2(
     DetachToBrowserTabDragControllerTest* test,
@@ -4180,7 +4224,7 @@
   // Move the second browser to the second display.
   display::Screen* screen = display::Screen::GetScreen();
   Display second_display = ui_test_utils::GetSecondaryDisplay(screen);
-  browser2->window()->SetBounds(second_display.work_area());
+  SetBoundsSync(browser2->window(), second_display.work_area());
   EXPECT_EQ(
       second_display.id(),
       screen->GetDisplayNearestWindow(browser2->window()->GetNativeWindow())
@@ -4210,6 +4254,10 @@
   EXPECT_FALSE(browser2->window()->IsMaximized());
 }
 
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+
 // Crashes on ChromeOS. crbug.com/1003288
 IN_PROC_BROWSER_TEST_P(
     DetachToBrowserInSeparateDisplayTabDragControllerTest,
@@ -4953,8 +5001,7 @@
                        ::testing::Values("mouse")));
 #endif
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-// TODO(crbug.com/1488094): Enable Multi Display Test on lacros
+#if BUILDFLAG(IS_CHROMEOS)
 INSTANTIATE_TEST_SUITE_P(
     TabDragging,
     DetachToBrowserInSeparateDisplayTabDragControllerTest,
@@ -4962,6 +5009,9 @@
         ::testing::Bool(),
         /*kTearOffWebAppTabOpensWebAppWindow=*/::testing::Values(false),
         ::testing::Values("mouse")));
+#endif  // BUILDFLAG(IS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// TODO(crbug.com/1488094): Enable Multi Display Test on lacros
 INSTANTIATE_TEST_SUITE_P(
     TabDragging,
     DifferentDeviceScaleFactorDisplayTabDragControllerTest,
@@ -4989,7 +5039,7 @@
         ::testing::Bool(),
         /*kTearOffWebAppTabOpensWebAppWindow=*/::testing::Values(false),
         ::testing::Values("mouse", "touch")));
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(IS_CHROMEOS)
 INSTANTIATE_TEST_SUITE_P(
diff --git a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.cc b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.cc
index e5f9c4a..415a30f4 100644
--- a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.cc
+++ b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.cc
@@ -10,6 +10,8 @@
 #include "chrome/browser/ui/view_ids.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
+#include "chrome/browser/ui/views/extensions/extensions_toolbar_container_view_controller.h"
+#include "chrome/browser/ui/views/extensions/extensions_toolbar_coordinator.h"
 #include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/page_action/page_action_icon_controller.h"
@@ -305,8 +307,10 @@
     DestroyLayer();
     views::SetHitTestComponent(this, static_cast<int>(HTNOWHERE));
   }
-  right_container_->extensions_container()->WindowControlsOverlayEnabledChanged(
-      browser_view_->IsWindowControlsOverlayEnabled());
+  right_container_->extensions_toolbar_coordinator()
+      ->GetExtensionsContainerViewController()
+      ->WindowControlsOverlayEnabledChanged(
+          browser_view_->IsWindowControlsOverlayEnabled());
 }
 
 void WebAppFrameToolbarView::UpdateBorderlessModeEnabled() {
diff --git a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.h b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.h
index 7cb0187..cfea08e 100644
--- a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.h
+++ b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.h
@@ -18,7 +18,7 @@
 #include "ui/views/view_targeter_delegate.h"
 
 namespace {
-class WebAppNonClientFrameViewAshTest;
+class WebAppNonClientFrameViewChromeOSTest;
 }
 
 namespace views {
@@ -111,7 +111,7 @@
  private:
   friend class ImmersiveModeControllerChromeosWebAppBrowserTest;
   friend class WebAppAshInteractiveUITest;
-  friend class WebAppNonClientFrameViewAshTest;
+  friend class WebAppNonClientFrameViewChromeOSTest;
 
   views::View* GetContentSettingContainerForTesting();
 
diff --git a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_toolbar_button_container.h b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_toolbar_button_container.h
index 46ff5d4..0773c62 100644
--- a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_toolbar_button_container.h
+++ b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_toolbar_button_container.h
@@ -77,6 +77,10 @@
     return extensions_container_;
   }
 
+  ExtensionsToolbarCoordinator* extensions_toolbar_coordinator() {
+    return extensions_toolbar_coordinator_.get();
+  }
+
   DownloadToolbarButtonView* download_button() {
     return download_button_.get();
   }
diff --git a/chrome/browser/ui/web_applications/web_app_controller_browsertest.cc b/chrome/browser/ui/web_applications/web_app_controller_browsertest.cc
index 50bc46b..c684b4b3 100644
--- a/chrome/browser/ui/web_applications/web_app_controller_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_controller_browsertest.cc
@@ -51,6 +51,10 @@
 #include "chromeos/ash/components/standalone_browser/feature_refs.h"
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chromeos/startup/browser_params_proxy.h"
+#endif
+
 namespace web_app {
 
 WebAppControllerBrowserTest::WebAppControllerBrowserTest()
@@ -271,7 +275,7 @@
     test::LogDebugInfoToConsole(profile_manager->GetLoadedProfiles(), log_time);
   }
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-  if (IsCrosapiEnabled()) {
+  if (!chromeos::BrowserParamsProxy::IsCrosapiDisabledForTesting()) {
     // Make sure all ash browser UI are closed before the test tears down.
     CloseAllAshBrowserWindows();
   }
@@ -288,7 +292,7 @@
 
 void WebAppControllerBrowserTest::SetUpOnMainThread() {
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-  if (IsCrosapiEnabled()) {
+  if (!chromeos::BrowserParamsProxy::IsCrosapiDisabledForTesting()) {
     CHECK(IsWebAppsCrosapiEnabled());
   }
 #endif
diff --git a/chrome/browser/ui/webui/BUILD.gn b/chrome/browser/ui/webui/BUILD.gn
index 5782fe7e..babb595 100644
--- a/chrome/browser/ui/webui/BUILD.gn
+++ b/chrome/browser/ui/webui/BUILD.gn
@@ -16,6 +16,7 @@
 
   deps = [
     "//chrome/browser/ui:ui",
+    "//components/compose:buildflags",
     "//components/lens:buildflags",
     "//content/public/browser",
     "//extensions/buildflags:buildflags",
@@ -48,6 +49,7 @@
       "//ash/webui/help_app_ui",
       "//ash/webui/os_feedback_ui",
       "//ash/webui/print_management",
+      "//ash/webui/print_preview_cros",
       "//ash/webui/scanning",
       "//ash/webui/shimless_rma",
       "//ash/webui/shortcut_customization_ui",
diff --git a/chrome/browser/ui/webui/ash/chrome_web_ui_configs_chromeos.cc b/chrome/browser/ui/webui/ash/chrome_web_ui_configs_chromeos.cc
index 0b289fd..d9c7a3d 100644
--- a/chrome/browser/ui/webui/ash/chrome_web_ui_configs_chromeos.cc
+++ b/chrome/browser/ui/webui/ash/chrome_web_ui_configs_chromeos.cc
@@ -30,6 +30,7 @@
 #include "ash/webui/os_feedback_ui/os_feedback_ui.h"
 #include "ash/webui/personalization_app/personalization_app_ui.h"
 #include "ash/webui/print_management/print_management_ui.h"
+#include "ash/webui/print_preview_cros/print_preview_cros_ui.h"
 #include "ash/webui/scanning/scanning_ui.h"
 #include "ash/webui/shimless_rma/shimless_rma.h"
 #include "ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h"
@@ -281,6 +282,8 @@
           base::BindRepeating(
               &printing::print_management::PrintingManagerFactory::
                   CreatePrintManagementUIController)));
+  map.AddWebUIConfig(
+      std::make_unique<printing::print_preview::PrintPreviewCrosUIConfig>());
   map.AddWebUIConfig(std::make_unique<multidevice::ProximityAuthUIConfig>());
   map.AddWebUIConfig(std::make_unique<RemoteMaintenanceCurtainUIConfig>());
   map.AddWebUIConfig(
diff --git a/chrome/browser/ui/webui/ash/settings/services/metrics/settings_user_action_tracker_unittest.cc b/chrome/browser/ui/webui/ash/settings/services/metrics/settings_user_action_tracker_unittest.cc
index eff1ad7b..6f486a8 100644
--- a/chrome/browser/ui/webui/ash/settings/services/metrics/settings_user_action_tracker_unittest.cc
+++ b/chrome/browser/ui/webui/ash/settings/services/metrics/settings_user_action_tracker_unittest.cc
@@ -101,8 +101,8 @@
                                         mojom::Setting::kTouchpadSpeed);
     fake_hierarchy_->AddSettingMetadata(mojom::Section::kPeople,
                                         mojom::Setting::kAddAccount);
-    fake_hierarchy_->AddSettingMetadata(mojom::Section::kPrinting,
-                                        mojom::Setting::kScanningApp);
+    fake_hierarchy_->AddSettingMetadata(mojom::Section::kPersonalization,
+                                        mojom::Setting::kOpenWallpaper);
     fake_hierarchy_->AddSettingMetadata(mojom::Section::kNetwork,
                                         mojom::Setting::kWifiAddNetwork);
 
@@ -262,24 +262,24 @@
 }
 
 TEST_F(SettingsUserActionTrackerTest, TestRecordSettingChangedNullValue) {
-  // Record that the Scan app is opened.
-  tracker_->RecordSettingChangeWithDetails(mojom::Setting::kScanningApp,
+  // Record that the Wallpaper app is opened.
+  tracker_->RecordSettingChangeWithDetails(mojom::Setting::kOpenWallpaper,
                                            nullptr);
 
   // The umbrella metric for which setting was changed should be updated. Note
-  // that kScanningApp has enum value of 1403.
+  // that kOpenWallpaper has enum value of 500.
   histogram_tester_.ExpectTotalCount("ChromeOS.Settings.SettingChanged",
                                      /*count=*/1);
   histogram_tester_.ExpectBucketCount("ChromeOS.Settings.SettingChanged",
-                                      /*sample=*/1403,
+                                      /*sample=*/500,
                                       /*count=*/1);
 
-  // The LogMetric fn in the Printing section should have been called.
-  const FakeOsSettingsSection* printing_section =
+  // The LogMetric fn in the Personalization section should have been called.
+  const FakeOsSettingsSection* personalization_section =
       static_cast<const FakeOsSettingsSection*>(
-          fake_sections_->GetSection(mojom::Section::kPrinting));
-  EXPECT_TRUE(printing_section->logged_metrics().back() ==
-              mojom::Setting::kScanningApp);
+          fake_sections_->GetSection(mojom::Section::kPersonalization));
+  EXPECT_TRUE(personalization_section->logged_metrics().back() ==
+              mojom::Setting::kOpenWallpaper);
 }
 
 }  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/chrome_untrusted_web_ui_configs.cc b/chrome/browser/ui/webui/chrome_untrusted_web_ui_configs.cc
index fb95b5dff..dfd38cc 100644
--- a/chrome/browser/ui/webui/chrome_untrusted_web_ui_configs.cc
+++ b/chrome/browser/ui/webui/chrome_untrusted_web_ui_configs.cc
@@ -15,7 +15,11 @@
 #include "chrome/browser/ui/webui/hats/hats_ui.h"
 #include "chrome/browser/ui/webui/side_panel/companion/companion_side_panel_untrusted_ui.h"
 #include "chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_ui.h"
+#include "components/compose/buildflags.h"
 #include "components/lens/buildflags.h"
+#if BUILDFLAG(ENABLE_COMPOSE)
+#include "chrome/browser/ui/webui/compose/compose_untrusted_ui.h"
+#endif  // BUILDFLAG(ENABLE_COMPOSE)
 #if BUILDFLAG(ENABLE_LENS_DESKTOP_GOOGLE_BRANDED_FEATURES)
 #include "chrome/browser/ui/webui/lens/lens_untrusted_ui_config.h"
 #endif  // BUILDFLAG(ENABLE_LENS_DESKTOP_GOOGLE_BRANDED_FEATURES)
@@ -48,6 +52,10 @@
       std::make_unique<ReadAnythingUIUntrustedConfig>());
   map.AddUntrustedWebUIConfig(std::make_unique<HatsUIConfig>());
 
+#if BUILDFLAG(ENABLE_COMPOSE)
+  map.AddUntrustedWebUIConfig(std::make_unique<ComposeUIUntrustedConfig>());
+#endif  // BUILDFLAG(ENABLE_COMPOSE)
+
 #if BUILDFLAG(ENABLE_LENS_DESKTOP_GOOGLE_BRANDED_FEATURES)
   map.AddUntrustedWebUIConfig(std::make_unique<lens::LensUntrustedUIConfig>());
 #endif  // BUILDFLAG(ENABLE_LENS_DESKTOP_GOOGLE_BRANDED_FEATURES)
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index 58ba4c2c..ea7cd03 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -71,7 +71,6 @@
 #include "chrome/common/webui_url_constants.h"
 #include "components/commerce/content/browser/commerce_internals_ui.h"
 #include "components/commerce/core/commerce_constants.h"
-#include "components/compose/buildflags.h"
 #include "components/favicon/core/favicon_service.h"
 #include "components/favicon_base/favicon_util.h"
 #include "components/favicon_base/select_favicon_frames.h"
@@ -160,7 +159,6 @@
 #include "chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.h"
 #include "chrome/browser/ui/webui/side_panel/history_clusters/history_clusters_side_panel_ui.h"
 #include "chrome/browser/ui/webui/side_panel/performance_controls/performance_side_panel_ui.h"
-#include "chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_ui.h"
 #include "chrome/browser/ui/webui/side_panel/reading_list/reading_list_ui.h"
 #include "chrome/browser/ui/webui/side_panel/user_notes/user_notes_side_panel_ui.h"
 #include "chrome/browser/ui/webui/signin/sync_confirmation_ui.h"
@@ -184,6 +182,7 @@
 #include "ash/webui/files_internals/url_constants.h"
 #include "ash/webui/help_app_ui/url_constants.h"
 #include "ash/webui/multidevice_debug/url_constants.h"
+#include "ash/webui/print_preview_cros/url_constants.h"
 #include "ash/webui/vc_background_ui/url_constants.h"
 #include "chrome/browser/ash/extensions/url_constants.h"
 #include "chrome/browser/extensions/extension_keeplist_chromeos.h"
@@ -286,12 +285,6 @@
 #include "chrome/browser/ui/webui/lens/lens_ui.h"
 #endif
 
-#if BUILDFLAG(ENABLE_COMPOSE)
-#include "chrome/browser/compose/compose_enabling.h"
-#include "chrome/browser/ui/webui/compose/compose_ui.h"
-#include "components/compose/core/browser/compose_features.h"
-#endif
-
 using content::WebUI;
 using content::WebUIController;
 using ui::WebDialogUI;
@@ -726,12 +719,6 @@
   if (url.host_piece() == chrome::kChromeUIWebuiGalleryHost) {
     return &NewWebUI<WebuiGalleryUI>;
   }
-#if BUILDFLAG(ENABLE_COMPOSE)
-  if (url.host_piece() == chrome::kChromeUIComposeHost &&
-      ComposeEnabling::IsEnabledForProfile(profile)) {
-    return &NewWebUI<ComposeUI>;
-  }
-#endif
   if (url.host_piece() == chrome::kChromeUIWhatsNewHost &&
       whats_new::IsEnabled()) {
     return &NewWebUI<WhatsNewUI>;
@@ -1095,6 +1082,7 @@
     GURL(ash::kChromeUICameraAppURL),
     GURL(ash::kChromeUIFilesInternalsURL),
     GURL(ash::kChromeUIHelpAppURL),
+    GURL(ash::kChromeUIPrintPreviewCrosURL),
     GURL(ash::multidevice::kChromeUIProximityAuthURL),
     GURL(ash::vc_background_ui::kChromeUIVcBackgroundURL),
     GURL(chrome::kChromeUIAccountManagerErrorURL),
diff --git a/chrome/browser/ui/webui/compose/compose_ui.h b/chrome/browser/ui/webui/compose/compose_ui.h
deleted file mode 100644
index c6d6afe2..0000000
--- a/chrome/browser/ui/webui/compose/compose_ui.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2023 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_UI_WEBUI_COMPOSE_COMPOSE_UI_H_
-#define CHROME_BROWSER_UI_WEBUI_COMPOSE_COMPOSE_UI_H_
-
-#include <memory>
-
-#include "base/memory/weak_ptr.h"
-#include "chrome/common/compose/compose.mojom.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_ui_controller.h"
-#include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
-#include "mojo/public/cpp/bindings/receiver.h"
-#include "ui/webui/mojo_bubble_web_ui_controller.h"
-#include "ui/webui/resources/cr_components/color_change_listener/color_change_listener.mojom.h"
-
-namespace ui {
-class ColorChangeHandler;
-}
-
-class ComposeUI : public ui::MojoBubbleWebUIController,
-                  public compose::mojom::ComposeSessionPageHandlerFactory {
- public:
-  explicit ComposeUI(content::WebUI* web_ui);
-
-  ComposeUI(const ComposeUI&) = delete;
-  ComposeUI& operator=(const ComposeUI&) = delete;
-  ~ComposeUI() override;
-  void BindInterface(
-      mojo::PendingReceiver<compose::mojom::ComposeSessionPageHandlerFactory>
-          factory);
-
-  void BindInterface(
-      mojo::PendingReceiver<color_change_listener::mojom::PageHandler>
-          pending_receiver);
-
-  void set_triggering_web_contents(content::WebContents* web_contents) {
-    triggering_web_contents_ = web_contents->GetWeakPtr();
-  }
-
-  static constexpr std::string GetWebUIName() { return "Compose"; }
-
- private:
-  void CreateComposeSessionPageHandler(
-      mojo::PendingReceiver<compose::mojom::ComposeClientPageHandler>
-          close_handler,
-      mojo::PendingReceiver<compose::mojom::ComposeSessionPageHandler> handler,
-      mojo::PendingRemote<compose::mojom::ComposeDialog> dialog) override;
-  mojo::Receiver<compose::mojom::ComposeSessionPageHandlerFactory>
-      session_handler_factory_{this};
-
-  std::unique_ptr<ui::ColorChangeHandler> color_provider_handler_;
-  base::WeakPtr<content::WebContents> triggering_web_contents_;
-
-  WEB_UI_CONTROLLER_TYPE_DECL();
-};
-
-#endif  // CHROME_BROWSER_UI_WEBUI_COMPOSE_COMPOSE_UI_H_
diff --git a/chrome/browser/ui/webui/compose/compose_ui.cc b/chrome/browser/ui/webui/compose/compose_untrusted_ui.cc
similarity index 77%
rename from chrome/browser/ui/webui/compose/compose_ui.cc
rename to chrome/browser/ui/webui/compose/compose_untrusted_ui.cc
index dbfe00b..a3b470bc 100644
--- a/chrome/browser/ui/webui/compose/compose_ui.cc
+++ b/chrome/browser/ui/webui/compose/compose_untrusted_ui.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/webui/compose/compose_ui.h"
+#include "chrome/browser/ui/webui/compose/compose_untrusted_ui.h"
 
 #include <string>
 #include <utility>
@@ -12,8 +12,8 @@
 #include "base/strings/strcat.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/compose/chrome_compose_client.h"
+#include "chrome/browser/ui/webui/theme_source.h"
 #include "chrome/browser/ui/webui/webui_util.h"
-#include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/compose_resources.h"
 #include "chrome/grit/compose_resources_map.h"
 #include "chrome/grit/generated_resources.h"
@@ -24,14 +24,21 @@
 #include "content/public/browser/web_ui.h"
 #include "content/public/browser/web_ui_data_source.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/resources/grit/webui_resources.h"
 #include "ui/webui/color_change_listener/color_change_handler.h"
 
-ComposeUI::ComposeUI(content::WebUI* web_ui)
-    : ui::MojoBubbleWebUIController(web_ui) {
+bool ComposeUIUntrustedConfig::IsWebUIEnabled(
+    content::BrowserContext* browser_context) {
+  return ComposeEnabling::IsEnabledForProfile(
+      Profile::FromBrowserContext(browser_context));
+}
+
+ComposeUntrustedUI::ComposeUntrustedUI(content::WebUI* web_ui)
+    : ui::UntrustedBubbleWebUIController(web_ui) {
   content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
       web_ui->GetWebContents()->GetBrowserContext(),
-      chrome::kChromeUIComposeHost);
-  webui::SetupWebUIDataSource(
+      chrome::kChromeUIUntrustedComposeUrl);
+webui::SetupWebUIDataSource(
       source, base::make_span(kComposeResources, kComposeResourcesSize),
       IDR_COMPOSE_COMPOSE_HTML);
   webui::SetupChromeRefresh2023(source);
@@ -100,18 +107,35 @@
   const compose::Config& config = compose::GetComposeConfig();
   source->AddInteger("savedStateTimeoutInMilliseconds",
                      config.saved_state_timeout_milliseconds);
+
+  source->OverrideContentSecurityPolicy(
+      network::mojom::CSPDirectiveName::StyleSrc,
+      "style-src 'self' chrome-untrusted://resources chrome-untrusted://theme "
+      "'unsafe-inline';");
+  source->OverrideContentSecurityPolicy(
+      network::mojom::CSPDirectiveName::FontSrc,
+      "font-src 'self' chrome-untrusted://resources;");
+  source->OverrideContentSecurityPolicy(
+      network::mojom::CSPDirectiveName::ImgSrc,
+      "img-src 'self' chrome-untrusted://resources;");
+
+  // If the ThemeSource isn't added here, since Compose is chrome-untrusted,
+  // it will be unable to load stylesheets until a new tab is opened.
+  raw_ptr<Profile> profile = Profile::FromWebUI(web_ui);
+  content::URLDataSource::Add(profile, std::make_unique<ThemeSource>(
+                                           profile, /*serve_untrusted=*/true));
 }
 
-ComposeUI::~ComposeUI() = default;
+ComposeUntrustedUI::~ComposeUntrustedUI() = default;
 
-void ComposeUI::BindInterface(
+void ComposeUntrustedUI::BindInterface(
     mojo::PendingReceiver<color_change_listener::mojom::PageHandler>
         pending_receiver) {
   color_provider_handler_ = std::make_unique<ui::ColorChangeHandler>(
       web_ui()->GetWebContents(), std::move(pending_receiver));
 }
 
-void ComposeUI::BindInterface(
+void ComposeUntrustedUI::BindInterface(
     mojo::PendingReceiver<compose::mojom::ComposeSessionPageHandlerFactory>
         factory) {
   if (session_handler_factory_.is_bound()) {
@@ -120,7 +144,7 @@
   session_handler_factory_.Bind(std::move(factory));
 }
 
-void ComposeUI::CreateComposeSessionPageHandler(
+void ComposeUntrustedUI::CreateComposeSessionPageHandler(
     mojo::PendingReceiver<compose::mojom::ComposeClientPageHandler>
         close_handler,
     mojo::PendingReceiver<compose::mojom::ComposeSessionPageHandler> handler,
@@ -138,4 +162,4 @@
   }
 }
 
-WEB_UI_CONTROLLER_TYPE_IMPL(ComposeUI)
+WEB_UI_CONTROLLER_TYPE_IMPL(ComposeUntrustedUI)
diff --git a/chrome/browser/ui/webui/compose/compose_untrusted_ui.h b/chrome/browser/ui/webui/compose/compose_untrusted_ui.h
new file mode 100644
index 0000000..cdf8a7d
--- /dev/null
+++ b/chrome/browser/ui/webui/compose/compose_untrusted_ui.h
@@ -0,0 +1,78 @@
+// Copyright 2023 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_UI_WEBUI_COMPOSE_COMPOSE_UNTRUSTED_UI_H_
+#define CHROME_BROWSER_UI_WEBUI_COMPOSE_COMPOSE_UNTRUSTED_UI_H_
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/common/compose/compose.mojom.h"
+#include "chrome/common/webui_url_constants.h"
+#include "content/public/browser/url_data_source.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui_controller.h"
+#include "content/public/browser/webui_config.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "ui/webui/resources/cr_components/color_change_listener/color_change_listener.mojom.h"
+#include "ui/webui/untrusted_bubble_web_ui_controller.h"
+
+namespace ui {
+class ColorChangeHandler;
+}
+
+class ComposeUntrustedUI;
+
+class ComposeUIUntrustedConfig
+    : public content::DefaultWebUIConfig<ComposeUntrustedUI> {
+ public:
+  ComposeUIUntrustedConfig()
+      : DefaultWebUIConfig(content::kChromeUIUntrustedScheme,
+                           chrome::kChromeUIUntrustedComposeHost) {}
+
+  bool IsWebUIEnabled(content::BrowserContext* browser_context) override;
+};
+
+// TODO(b/317056725): update mojom to reflect that the page is untrusted.
+class ComposeUntrustedUI
+    : public ui::UntrustedBubbleWebUIController,
+      public compose::mojom::ComposeSessionPageHandlerFactory {
+ public:
+  explicit ComposeUntrustedUI(content::WebUI* web_ui);
+
+  ComposeUntrustedUI(const ComposeUntrustedUI&) = delete;
+  ComposeUntrustedUI& operator=(const ComposeUntrustedUI&) = delete;
+  ~ComposeUntrustedUI() override;
+  void BindInterface(
+      mojo::PendingReceiver<compose::mojom::ComposeSessionPageHandlerFactory>
+          factory);
+
+  void BindInterface(
+      mojo::PendingReceiver<color_change_listener::mojom::PageHandler>
+          pending_receiver);
+
+  void set_triggering_web_contents(content::WebContents* web_contents) {
+    triggering_web_contents_ = web_contents->GetWeakPtr();
+  }
+
+  static constexpr std::string GetWebUIName() { return "Compose"; }
+
+ private:
+  void CreateComposeSessionPageHandler(
+      mojo::PendingReceiver<compose::mojom::ComposeClientPageHandler>
+          close_handler,
+      mojo::PendingReceiver<compose::mojom::ComposeSessionPageHandler> handler,
+      mojo::PendingRemote<compose::mojom::ComposeDialog> dialog) override;
+  mojo::Receiver<compose::mojom::ComposeSessionPageHandlerFactory>
+      session_handler_factory_{this};
+
+  std::unique_ptr<ui::ColorChangeHandler> color_provider_handler_;
+  base::WeakPtr<content::WebContents> triggering_web_contents_;
+
+  WEB_UI_CONTROLLER_TYPE_DECL();
+};
+
+#endif  // CHROME_BROWSER_UI_WEBUI_COMPOSE_COMPOSE_UNTRUSTED_UI_H_
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
index fa71e16..f8c3bd0 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
@@ -2435,14 +2435,6 @@
 TEST_P(SiteSettingsHandlerTest, NotificationPermissionRevokeUkm) {
   const std::string google("https://www.google.com");
   ukm::TestAutoSetUkmRecorder ukm_recorder;
-  auto* history_service = HistoryServiceFactory::GetForProfile(
-      profile(), ServiceAccessType::EXPLICIT_ACCESS);
-  history_service->AddPage(GURL(google), base::Time::Now(),
-                           history::SOURCE_BROWSED);
-  base::RunLoop origin_queried_waiter;
-  history_service->set_origin_queried_closure_for_testing(
-      origin_queried_waiter.QuitClosure());
-
   {
     base::Value::List set_notification_origin_args;
     set_notification_origin_args.Append(google);
@@ -2467,8 +2459,6 @@
         set_notification_origin_args);
   }
 
-  origin_queried_waiter.Run();
-
   auto entries = ukm_recorder.GetEntriesByName("Permission");
   EXPECT_EQ(1u, entries.size());
   auto* entry = entries.front().get();
diff --git a/chrome/browser/ui/webui/tab_search/tab_search.mojom b/chrome/browser/ui/webui/tab_search/tab_search.mojom
index b961f16b..02ad38e3 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search.mojom
+++ b/chrome/browser/ui/webui/tab_search/tab_search.mojom
@@ -247,18 +247,12 @@
   // Initiate the feedback flow.
   TriggerFeedback(int32 session_id);
 
-  // Initiate the sync flow.
-  TriggerSync();
-
   // Initiate the sign in flow.
   TriggerSignIn();
 
   // Open the help page associated with tab organization.
   OpenHelpPage();
 
-  // Open the user's sync settings in a new tab.
-  OpenSyncSettings();
-
   // Store the user feedback supplied by the user.
   SetUserFeedback(int32 session_id,
                   int32 organization_id,
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
index e6c7994..625878e 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
+++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
@@ -582,18 +582,9 @@
       /*autofill_metadata=*/base::Value::Dict(), std::move(feedback_metadata));
 }
 
-void TabSearchPageHandler::TriggerSync() {
-  Profile* profile = chrome::FindLastActive()->profile();
-  signin_ui_util::EnableSyncFromSingleAccountPromo(
-      profile,
-      IdentityManagerFactory::GetForProfile(profile)->GetPrimaryAccountInfo(
-          signin::ConsentLevel::kSignin),
-      signin_metrics::AccessPoint::ACCESS_POINT_TAB_ORGANIZATION);
-}
-
 void TabSearchPageHandler::TriggerSignIn() {
   Profile* profile = chrome::FindLastActive()->profile();
-  signin_ui_util::ShowReauthForPrimaryAccountWithAuthError(
+  signin_ui_util::ShowSigninPromptFromPromo(
       profile, signin_metrics::AccessPoint::ACCESS_POINT_TAB_ORGANIZATION);
 }
 
@@ -606,15 +597,6 @@
   Navigate(&params);
 }
 
-void TabSearchPageHandler::OpenSyncSettings() {
-  Browser* browser = chrome::FindLastActive();
-  GURL settings_url("chrome://settings/syncSetup/advanced");
-  NavigateParams params(browser, settings_url,
-                        ui::PageTransition::PAGE_TRANSITION_LINK);
-  params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
-  Navigate(&params);
-}
-
 void TabSearchPageHandler::SetUserFeedback(
     int32_t session_id,
     int32_t organization_id,
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.h b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.h
index 571e915..3c8cd91 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.h
+++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.h
@@ -91,10 +91,8 @@
   void SetTabIndex(int32_t index) override;
   void StartTabGroupTutorial() override;
   void TriggerFeedback(int32_t session_id) override;
-  void TriggerSync() override;
   void TriggerSignIn() override;
   void OpenHelpPage() override;
-  void OpenSyncSettings() override;
   void SetUserFeedback(int32_t session_id,
                        int32_t organization_id,
                        tab_search::mojom::UserFeedback feedback) override;
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_sign_in_handler.cc b/chrome/browser/ui/webui/tab_search/tab_search_sign_in_handler.cc
new file mode 100644
index 0000000..ee584851
--- /dev/null
+++ b/chrome/browser/ui/webui/tab_search/tab_search_sign_in_handler.cc
@@ -0,0 +1,61 @@
+// Copyright 2023 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/ui/webui/tab_search/tab_search_sign_in_handler.h"
+
+#include "base/values.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/webui/web_ui_util.h"
+
+TabSearchSignInHandler::TabSearchSignInHandler(Profile* profile)
+    : profile_(profile) {}
+
+TabSearchSignInHandler::~TabSearchSignInHandler() = default;
+
+void TabSearchSignInHandler::RegisterMessages() {
+  web_ui()->RegisterMessageCallback(
+      "GetSignInState",
+      base::BindRepeating(&TabSearchSignInHandler::HandleGetSignInState,
+                          base::Unretained(this)));
+}
+
+void TabSearchSignInHandler::OnJavascriptAllowed() {
+  signin::IdentityManager* identity_manager(
+      IdentityManagerFactory::GetInstance()->GetForProfile(profile_));
+  if (identity_manager && !identity_manager_observation_.IsObserving()) {
+    identity_manager_observation_.Observe(identity_manager);
+  }
+}
+
+void TabSearchSignInHandler::OnJavascriptDisallowed() {
+  identity_manager_observation_.Reset();
+}
+
+bool TabSearchSignInHandler::GetSignInState() const {
+  const signin::IdentityManager* const identity_manager(
+      IdentityManagerFactory::GetInstance()->GetForProfile(profile_));
+  const auto stored_account = identity_manager->FindExtendedAccountInfo(
+      identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin));
+  return stored_account.IsValid();
+}
+
+void TabSearchSignInHandler::HandleGetSignInState(const base::Value::List& args) {
+  AllowJavascript();
+  CHECK_EQ(1U, args.size());
+  const base::Value& callback_id = args[0];
+
+  ResolveJavascriptCallback(callback_id, GetSignInState());
+}
+
+void TabSearchSignInHandler::OnExtendedAccountInfoUpdated(
+    const AccountInfo& info) {
+  FireWebUIListener("account-info-changed", GetSignInState());
+}
+
+void TabSearchSignInHandler::OnExtendedAccountInfoRemoved(
+    const AccountInfo& info) {
+  FireWebUIListener("account-info-changed", GetSignInState());
+}
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_sign_in_handler.h b/chrome/browser/ui/webui/tab_search/tab_search_sign_in_handler.h
new file mode 100644
index 0000000..0f04346
--- /dev/null
+++ b/chrome/browser/ui/webui/tab_search/tab_search_sign_in_handler.h
@@ -0,0 +1,51 @@
+// Copyright 2023 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_UI_WEBUI_TAB_SEARCH_TAB_SEARCH_SIGN_IN_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_TAB_SEARCH_TAB_SEARCH_SIGN_IN_HANDLER_H_
+
+#include "base/memory/raw_ptr.h"
+#include "base/scoped_observation.h"
+#include "base/values.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "content/public/browser/web_ui_message_handler.h"
+
+class Profile;
+
+// A class allowing TabSearch WebUI to interact with the identity manager and
+// observing and propagating relevant events to the WebUI.
+class TabSearchSignInHandler : public content::WebUIMessageHandler,
+                             public signin::IdentityManager::Observer {
+ public:
+  explicit TabSearchSignInHandler(Profile* profile);
+
+  TabSearchSignInHandler(const TabSearchSignInHandler&) = delete;
+  TabSearchSignInHandler& operator=(const TabSearchSignInHandler&) = delete;
+
+  ~TabSearchSignInHandler() override;
+
+ private:
+  // WebUIMessageHandler:
+  void RegisterMessages() override;
+  void OnJavascriptAllowed() override;
+  void OnJavascriptDisallowed() override;
+
+  // Returns whether or not the user is currently signed in.
+  bool GetSignInState() const;
+  // Handles the request for the sign in state.
+  void HandleGetSignInState(const base::Value::List& args);
+
+  // IdentityManager::Observer implementation.
+  void OnExtendedAccountInfoUpdated(const AccountInfo& info) override;
+  void OnExtendedAccountInfoRemoved(const AccountInfo& info) override;
+
+  // Weak pointer.
+  raw_ptr<Profile, DanglingUntriaged> profile_;
+
+  base::ScopedObservation<signin::IdentityManager,
+                          signin::IdentityManager::Observer>
+      identity_manager_observation_{this};
+};
+
+#endif  // CHROME_BROWSER_UI_WEBUI_TAB_SEARCH_TAB_SEARCH_SIGN_IN_HANDLER_H_
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_sync_handler.cc b/chrome/browser/ui/webui/tab_search/tab_search_sync_handler.cc
deleted file mode 100644
index 81f79756..0000000
--- a/chrome/browser/ui/webui/tab_search/tab_search_sync_handler.cc
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright 2023 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/ui/webui/tab_search/tab_search_sync_handler.h"
-
-#include "base/values.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/signin/identity_manager_factory.h"
-#include "chrome/browser/sync/sync_service_factory.h"
-#include "chrome/browser/sync/sync_ui_util.h"
-#include "components/sync/service/sync_service.h"
-#include "components/sync/service/sync_service_utils.h"
-#include "components/sync/service/sync_user_settings.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/base/webui/web_ui_util.h"
-
-TabSearchSyncHandler::TabSearchSyncHandler(Profile* profile)
-    : profile_(profile) {}
-
-TabSearchSyncHandler::~TabSearchSyncHandler() = default;
-
-void TabSearchSyncHandler::RegisterMessages() {
-  web_ui()->RegisterMessageCallback(
-      "GetAccountInfo",
-      base::BindRepeating(&TabSearchSyncHandler::HandleGetAccountInfo,
-                          base::Unretained(this)));
-  web_ui()->RegisterMessageCallback(
-      "GetSyncInfo",
-      base::BindRepeating(&TabSearchSyncHandler::HandleGetSyncInfo,
-                          base::Unretained(this)));
-}
-
-void TabSearchSyncHandler::OnJavascriptAllowed() {
-  syncer::SyncService* sync_service = GetSyncService();
-  if (sync_service && !sync_service_observation_.IsObserving()) {
-    sync_service_observation_.Observe(sync_service);
-  }
-
-  signin::IdentityManager* identity_manager(
-      IdentityManagerFactory::GetInstance()->GetForProfile(profile_));
-  if (identity_manager && !identity_manager_observation_.IsObserving()) {
-    identity_manager_observation_.Observe(identity_manager);
-  }
-}
-
-void TabSearchSyncHandler::OnJavascriptDisallowed() {
-  sync_service_observation_.Reset();
-  identity_manager_observation_.Reset();
-}
-
-base::Value::Dict TabSearchSyncHandler::GetSyncInfo() const {
-  base::Value::Dict dict;
-  bool syncing = false;
-  bool syncingHistory = false;
-  bool paused = true;
-
-  const syncer::SyncService* const sync_service = GetSyncService();
-  // sync_service might be nullptr if SyncServiceFactory::IsSyncAllowed is
-  // false.
-  if (sync_service) {
-    syncing = sync_service->IsSyncFeatureEnabled();
-    paused = !sync_service->IsSyncFeatureActive();
-    syncer::UserSelectableTypeSet types =
-        sync_service->GetUserSettings()->GetSelectedTypes();
-    syncingHistory = sync_service->IsSyncFeatureEnabled() &&
-                     types.Has(syncer::UserSelectableType::kHistory);
-  }
-
-  dict.Set("syncing", syncing);
-  dict.Set("paused", paused);
-  dict.Set("syncingHistory", syncingHistory);
-
-  return dict;
-}
-
-void TabSearchSyncHandler::HandleGetSyncInfo(const base::Value::List& args) {
-  AllowJavascript();
-
-  CHECK_EQ(1U, args.size());
-  const base::Value& callback_id = args[0];
-
-  ResolveJavascriptCallback(callback_id, GetSyncInfo());
-}
-
-base::Value::Dict TabSearchSyncHandler::GetAccountInfo() const {
-  const signin::IdentityManager* const identity_manager(
-      IdentityManagerFactory::GetInstance()->GetForProfile(profile_));
-  const auto stored_account = identity_manager->FindExtendedAccountInfo(
-      identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin));
-
-  base::Value::Dict dict;
-  dict.Set("name", stored_account.full_name);
-  dict.Set("email", stored_account.email);
-  const auto& avatar_image = stored_account.account_image;
-  if (!avatar_image.IsEmpty()) {
-    dict.Set("avatarImage", webui::GetBitmapDataUrl(avatar_image.AsBitmap()));
-  }
-  return dict;
-}
-
-void TabSearchSyncHandler::HandleGetAccountInfo(const base::Value::List& args) {
-  AllowJavascript();
-  CHECK_EQ(1U, args.size());
-  const base::Value& callback_id = args[0];
-
-  ResolveJavascriptCallback(callback_id, GetAccountInfo());
-}
-
-void TabSearchSyncHandler::OnStateChanged(syncer::SyncService* sync_service) {
-  FireWebUIListener("sync-info-changed", GetSyncInfo());
-}
-
-void TabSearchSyncHandler::OnExtendedAccountInfoUpdated(
-    const AccountInfo& info) {
-  FireWebUIListener("account-info-changed", GetAccountInfo());
-}
-
-void TabSearchSyncHandler::OnExtendedAccountInfoRemoved(
-    const AccountInfo& info) {
-  FireWebUIListener("account-info-changed", GetAccountInfo());
-}
-
-syncer::SyncService* TabSearchSyncHandler::GetSyncService() const {
-  return SyncServiceFactory::IsSyncAllowed(profile_)
-             ? SyncServiceFactory::GetForProfile(profile_)
-             : nullptr;
-}
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_sync_handler.h b/chrome/browser/ui/webui/tab_search/tab_search_sync_handler.h
deleted file mode 100644
index fd593360..0000000
--- a/chrome/browser/ui/webui/tab_search/tab_search_sync_handler.h
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2023 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_UI_WEBUI_TAB_SEARCH_TAB_SEARCH_SYNC_HANDLER_H_
-#define CHROME_BROWSER_UI_WEBUI_TAB_SEARCH_TAB_SEARCH_SYNC_HANDLER_H_
-
-#include "base/memory/raw_ptr.h"
-#include "base/scoped_observation.h"
-#include "base/values.h"
-#include "components/signin/public/identity_manager/identity_manager.h"
-#include "components/sync/service/sync_service.h"
-#include "components/sync/service/sync_service_observer.h"
-#include "content/public/browser/web_ui_message_handler.h"
-
-class Profile;
-
-// A class allowing TabSearch WebUI to interact with the sync
-// service and identity manager and observing and propagating relevant
-// events to the WebUI.
-class TabSearchSyncHandler : public content::WebUIMessageHandler,
-                             public signin::IdentityManager::Observer,
-                             public syncer::SyncServiceObserver {
- public:
-  explicit TabSearchSyncHandler(Profile* profile);
-
-  TabSearchSyncHandler(const TabSearchSyncHandler&) = delete;
-  TabSearchSyncHandler& operator=(const TabSearchSyncHandler&) = delete;
-
-  ~TabSearchSyncHandler() override;
-
- private:
-  // WebUIMessageHandler:
-  void RegisterMessages() override;
-  void OnJavascriptAllowed() override;
-  void OnJavascriptDisallowed() override;
-
-  // Retrieves sync related information from the SyncService.
-  base::Value::Dict GetSyncInfo() const;
-  // Handles the request for sync information.
-  void HandleGetSyncInfo(const base::Value::List& args);
-
-  // Retrieves information about the primary account.
-  base::Value::Dict GetAccountInfo() const;
-  // Handles the request for the primary account information.
-  void HandleGetAccountInfo(const base::Value::List& args);
-
-  // syncer::SyncServiceObserver implementation.
-  void OnStateChanged(syncer::SyncService* sync_service) override;
-
-  // IdentityManager::Observer implementation.
-  void OnExtendedAccountInfoUpdated(const AccountInfo& info) override;
-  void OnExtendedAccountInfoRemoved(const AccountInfo& info) override;
-
-  syncer::SyncService* GetSyncService() const;
-
-  // Weak pointer.
-  raw_ptr<Profile, DanglingUntriaged> profile_;
-
-  base::ScopedObservation<syncer::SyncService, syncer::SyncServiceObserver>
-      sync_service_observation_{this};
-  base::ScopedObservation<signin::IdentityManager,
-                          signin::IdentityManager::Observer>
-      identity_manager_observation_{this};
-};
-
-#endif  // CHROME_BROWSER_UI_WEBUI_TAB_SEARCH_TAB_SEARCH_SYNC_HANDLER_H_
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_ui.cc b/chrome/browser/ui/webui/tab_search/tab_search_ui.cc
index c43ee5c..3d2c3f30 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search_ui.cc
+++ b/chrome/browser/ui/webui/tab_search/tab_search_ui.cc
@@ -15,7 +15,7 @@
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/webui/favicon_source.h"
 #include "chrome/browser/ui/webui/tab_search/tab_search_prefs.h"
-#include "chrome/browser/ui/webui/tab_search/tab_search_sync_handler.h"
+#include "chrome/browser/ui/webui/tab_search/tab_search_sign_in_handler.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
@@ -98,30 +98,16 @@
       {"notStartedBodyLinkFRE", IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_LINK_FRE},
       {"notStartedBodySignedOut",
        IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_SIGNED_OUT},
-      {"notStartedBodySyncPaused",
-       IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_SYNC_PAUSED},
-      {"notStartedBodyUnsynced",
-       IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_UNSYNCED},
-      {"notStartedBodyUnsyncedHistory",
-       IDS_TAB_ORGANIZATION_NOT_STARTED_BODY_UNSYNCED_HISTORY},
       {"notStartedButton", IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON},
       {"notStartedButtonAriaLabel",
        IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_ARIA_LABEL},
       {"notStartedButtonFRE", IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_FRE},
       {"notStartedButtonFREAriaLabel",
        IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_FRE_ARIA_LABEL},
-      {"notStartedButtonSyncPaused",
-       IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_SYNC_PAUSED},
-      {"notStartedButtonSyncPausedAriaLabel",
-       IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_SYNC_PAUSED_ARIA_LABEL},
-      {"notStartedButtonUnsynced",
-       IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_UNSYNCED},
-      {"notStartedButtonUnsyncedAriaLabel",
-       IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_UNSYNCED_ARIA_LABEL},
-      {"notStartedButtonUnsyncedHistory",
-       IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_UNSYNCED_HISTORY},
-      {"notStartedButtonUnsyncedHistoryAriaLabel",
-       IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_UNSYNCED_HISTORY_ARIA_LABEL},
+      {"notStartedButtonSignedOut",
+       IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_SIGNED_OUT},
+      {"notStartedButtonSignedOutAriaLabel",
+       IDS_TAB_ORGANIZATION_NOT_STARTED_BUTTON_SIGNED_OUT_ARIA_LABEL},
       {"notStartedTitle", IDS_TAB_ORGANIZATION_NOT_STARTED_TITLE},
       {"notStartedTitleFRE", IDS_TAB_ORGANIZATION_NOT_STARTED_TITLE_FRE},
       {"rejectSuggestion", IDS_TAB_ORGANIZATION_REJECT_SUGGESTION},
@@ -202,7 +188,7 @@
       profile, std::make_unique<FaviconSource>(
                    profile, chrome::FaviconUrlFormat::kFavicon2));
 
-  web_ui->AddMessageHandler(std::make_unique<TabSearchSyncHandler>(profile));
+  web_ui->AddMessageHandler(std::make_unique<TabSearchSignInHandler>(profile));
 
   page_handler_timer_ = base::ElapsedTimer();
   TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(
diff --git a/chrome/browser/web_applications/app_service/lacros_browser_shortcuts_controller.cc b/chrome/browser/web_applications/app_service/lacros_browser_shortcuts_controller.cc
index 4d3a6419..3b3fc4d 100644
--- a/chrome/browser/web_applications/app_service/lacros_browser_shortcuts_controller.cc
+++ b/chrome/browser/web_applications/app_service/lacros_browser_shortcuts_controller.cc
@@ -145,7 +145,7 @@
   if (service->GetInterfaceVersion<crosapi::mojom::AppShortcutPublisher>() <
           int{crosapi::mojom::AppShortcutPublisher::MethodMinVersions::
                   kRegisterAppShortcutControllerMinVersion} &&
-      !chromeos::BrowserParamsProxy::Get()->IsCrosapiDisabledForTesting()) {
+      !chromeos::BrowserParamsProxy::IsCrosapiDisabledForTesting()) {
     LOG(WARNING)
         << "Ash AppShortcutPublisher version "
         << service->GetInterfaceVersion<crosapi::mojom::AppShortcutPublisher>()
@@ -184,7 +184,7 @@
   if (service->GetInterfaceVersion<crosapi::mojom::AppShortcutPublisher>() <
           int{crosapi::mojom::AppShortcutPublisher::MethodMinVersions::
                   kPublishShortcutsMinVersion} &&
-      !chromeos::BrowserParamsProxy::Get()->IsCrosapiDisabledForTesting()) {
+      !chromeos::BrowserParamsProxy::IsCrosapiDisabledForTesting()) {
     LOG(WARNING)
         << "Ash AppShortcutPublisher version "
         << service->GetInterfaceVersion<crosapi::mojom::AppShortcutPublisher>()
@@ -289,7 +289,7 @@
   if (service->GetInterfaceVersion<crosapi::mojom::AppShortcutPublisher>() <
           int{crosapi::mojom::AppShortcutPublisher::MethodMinVersions::
                   kShortcutRemovedMinVersion} &&
-      !chromeos::BrowserParamsProxy::Get()->IsCrosapiDisabledForTesting()) {
+      !chromeos::BrowserParamsProxy::IsCrosapiDisabledForTesting()) {
     LOG(WARNING)
         << "Ash AppShortcutPublisher version "
         << service->GetInterfaceVersion<crosapi::mojom::AppShortcutPublisher>()
diff --git a/chrome/build/lacros64.pgo.txt b/chrome/build/lacros64.pgo.txt
index a97088958..65a4a572 100644
--- a/chrome/build/lacros64.pgo.txt
+++ b/chrome/build/lacros64.pgo.txt
@@ -1 +1 @@
-chrome-chromeos-amd64-generic-main-1707738626-1696f986a8c29d92d622edf3bc95aa794f72931b.profdata
+chrome-chromeos-amd64-generic-main-1707782597-603ab6846224efb8e9e965aefd96ae0f7d6982b7.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index e34ef01f..1a097ad0 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1707760725-ca04eea28cc9e7d4d347d4846c67142fe5b1f167.profdata
+chrome-linux-main-1707782361-1958ba0884472c0f61b1c19b3291e3b70c003a68.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 49110b3..d79e576 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1707775072-2fdd2f085faa2b907188fab88c4f91a4d606dc33.profdata
+chrome-mac-arm-main-1707803882-48776b9a5e69a7b1f5492b9fb4fb42f12f67060e.profdata
diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni
index ade7406..a6c3ac16 100644
--- a/chrome/chrome_paks.gni
+++ b/chrome/chrome_paks.gni
@@ -242,6 +242,7 @@
         "$root_gen_dir/ash/webui/ash_os_feedback_untrusted_resources.pak",
         "$root_gen_dir/ash/webui/ash_personalization_app_resources.pak",
         "$root_gen_dir/ash/webui/ash_print_management_resources.pak",
+        "$root_gen_dir/ash/webui/ash_print_preview_cros_app_resources.pak",
         "$root_gen_dir/ash/webui/ash_projector_annotator_untrusted_resources.pak",
         "$root_gen_dir/ash/webui/ash_projector_app_untrusted_resources.pak",
         "$root_gen_dir/ash/webui/ash_projector_common_resources.pak",
@@ -319,6 +320,7 @@
         "//ash/webui/os_feedback_ui/untrusted_resources:resources",
         "//ash/webui/personalization_app/resources:resources",
         "//ash/webui/print_management/resources:resources",
+        "//ash/webui/print_preview_cros/resources:resources",
         "//ash/webui/resources:camera_app_resources",
         "//ash/webui/resources:demo_mode_app_resources",
         "//ash/webui/resources:eche_app_resources",
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 8302c091..6aa2d7f 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -284,6 +284,17 @@
              "ChromeStructuredMetrics",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Kill switch for Controlled Frame. This is enabled by default but is only
+// tested to ensure it's enabled before proceeding with the rest of the feature
+// checks like testing for IWA support or Kiosk mode. If this feature flag is
+// disabled, such as via Finch, then Controlled Frame will no longer be
+// available in IWA or Kiosk mode.
+// See https://github.com/WICG/controlled-frame/blob/main/README.md for more
+// info.
+BASE_FEATURE(kControlledFrame,
+             "ControlledFrame",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // When installing default installed PWAs, we wait for service workers
 // to cache resources.
 BASE_FEATURE(kDesktopPWAsCacheDuringDefaultInstall,
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 47c9846..21005c3c 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -186,6 +186,8 @@
 COMPONENT_EXPORT(CHROME_FEATURES)
 BASE_DECLARE_FEATURE(kChromeStructuredMetrics);
 
+COMPONENT_EXPORT(CHROME_FEATURES) BASE_DECLARE_FEATURE(kControlledFrame);
+
 COMPONENT_EXPORT(CHROME_FEATURES)
 BASE_DECLARE_FEATURE(kDesktopPWAsCacheDuringDefaultInstall);
 
diff --git a/chrome/common/controlled_frame/controlled_frame.cc b/chrome/common/controlled_frame/controlled_frame.cc
index 3559ed5..6954019 100644
--- a/chrome/common/controlled_frame/controlled_frame.cc
+++ b/chrome/common/controlled_frame/controlled_frame.cc
@@ -7,15 +7,14 @@
 #include <string>
 
 #include "base/containers/contains.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/initialize_extensions_client.h"
-#include "content/public/common/content_features.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/features/feature.h"
 #include "extensions/common/mojom/context_type.mojom.h"
 
 #if BUILDFLAG(IS_CHROMEOS)
 #include "base/command_line.h"
-#include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
 #include "url/url_constants.h"
 #endif
@@ -60,6 +59,10 @@
                        int context_id,
                        bool check_developer_mode,
                        const extensions::ContextData& context_data) {
+  if (!base::FeatureList::IsEnabled(features::kControlledFrame)) {
+    return false;
+  }
+
   bool is_allowed_for_scheme = url.SchemeIs("isolated-app");
 
 #if BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index 4fa070dc..83314a9 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -53,7 +53,6 @@
 const char kChromeUIChromeURLsURL[] = "chrome://chrome-urls/";
 const char kChromeUIComponentsHost[] = "components";
 const char kChromeUIComponentsUrl[] = "chrome://components";
-const char kChromeUIComposeHost[] = "compose";
 const char kChromeUIConflictsHost[] = "conflicts";
 const char kChromeUIConstrainedHTMLTestURL[] = "chrome://constrained-test/";
 const char kChromeUICookieSettingsURL[] = "chrome://settings/cookies";
@@ -237,6 +236,8 @@
 const char kChromeUIThemeURL[] = "chrome://theme/";
 const char kChromeUITranslateInternalsHost[] = "translate-internals";
 const char kChromeUITopChromeDomain[] = "top-chrome";
+const char kChromeUIUntrustedComposeHost[] = "compose";
+const char kChromeUIUntrustedComposeUrl[] = "chrome-untrusted://compose/";
 #if !BUILDFLAG(IS_ANDROID)
 const char kChromeUIUntrustedHatsHost[] = "hats";
 const char kChromeUIUntrustedHatsURL[] = "chrome-untrusted://hats/";
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index 45e4d66..7606110 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -52,7 +52,6 @@
 extern const char kChromeUIChromeURLsURL[];
 extern const char kChromeUIComponentsHost[];
 extern const char kChromeUIComponentsUrl[];
-extern const char kChromeUIComposeHost[];
 extern const char kChromeUIConflictsHost[];
 extern const char kChromeUIConstrainedHTMLTestURL[];
 extern const char kChromeUICookieSettingsURL[];
@@ -226,6 +225,8 @@
 extern const char kChromeUIThemeURL[];
 extern const char kChromeUITopChromeDomain[];
 extern const char kChromeUITranslateInternalsHost[];
+extern const char kChromeUIUntrustedComposeHost[];
+extern const char kChromeUIUntrustedComposeUrl[];
 #if !BUILDFLAG(IS_ANDROID)
 extern const char kChromeUIUntrustedHatsHost[];
 extern const char kChromeUIUntrustedHatsURL[];
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index c02bb8c..3317e9fca 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -734,8 +734,6 @@
       "../browser/extensions/policy_test_utils.h",
       "../browser/ui/chromeos/test_util.cc",
       "../browser/ui/chromeos/test_util.h",
-      "../browser/ui/views/frame/browser_non_client_frame_view_chromeos_test_utils.cc",
-      "../browser/ui/views/frame/browser_non_client_frame_view_chromeos_test_utils.h",
       "../browser/ui/views/frame/immersive_mode_tester.cc",
       "../browser/ui/views/frame/immersive_mode_tester.h",
       "base/chromeos/crosier/annotations.cc",
@@ -822,6 +820,7 @@
       "//ash/public/cpp:test_support",
       "//ash/webui/firmware_update_ui:url_constants",
       "//ash/webui/personalization_app:test_support",
+      "//ash/webui/print_preview_cros:url_constants",
       "//ash/webui/scanning:url_constants",
       "//chrome/browser/ash",
       "//chrome/browser/ash/crosapi:fake_device_ownership_waiter",
@@ -3182,7 +3181,6 @@
         "../browser/chromeos/video_conference/video_conference_media_listener_browsertest.cc",
         "../browser/download/notification/download_notification_browsertest.cc",
         "../browser/file_system_access/cloud_identifier/cloud_identifier_util_cros_browsertest.cc",
-        "../browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc",
       ]
     }
 
@@ -4859,6 +4857,7 @@
         "../browser/ui/views/editor_menu/editor_menu_browsertest.cc",
         "../browser/ui/views/extensions/extension_dialog_bounds_browsertest.cc",
         "../browser/ui/views/frame/browser_frame_ash_browsertest.cc",
+        "../browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc",
         "../browser/ui/views/frame/immersive_mode_browser_view_test.cc",
         "../browser/ui/views/frame/immersive_mode_controller_chromeos_browsertest.cc",
         "../browser/ui/views/frame/system_menu_model_builder_browsertest_chromeos.cc",
@@ -5589,6 +5588,8 @@
       "../browser/ui/lacros/screen_capture_notification_ui_lacros_browsertest.cc",
       "../browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_lacros_browsertest.cc",
       "../browser/ui/startup/first_run_service_browsertest.cc",
+      "../browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc",
+      "../browser/ui/views/frame/immersive_mode_browser_view_test.cc",
       "../browser/ui/views/intent_picker_bubble_view_browsertest.cc",
       "../browser/ui/views/location_bar/intent_chip_button_browsertest.cc",
       "../browser/ui/views/profiles/profile_picker_view_browsertest.cc",
@@ -5640,7 +5641,9 @@
       "//chrome/browser/policy:test_support",
       "//chrome/browser/ui/web_applications:app_service_browser_tests",
       "//chrome/browser/web_applications:app_service_browser_tests",
+      "//chrome/browser/web_applications:prevent_close_test_support",
       "//chrome/browser/web_applications:web_applications_test_support",
+      "//chrome/browser/web_applications/app_service:test_support",
       "//chrome/test/media_router/access_code_cast:access_code_cast_integration_base",
       "//chromeos/components/kcer",
       "//chromeos/crosapi/cpp:cpp",
@@ -5655,6 +5658,7 @@
       "//chromeos/startup",
       "//chromeos/ui/base",
       "//chromeos/ui/frame",
+      "//chromeos/ui/frame:test_support",
       "//components/account_manager_core:test_support",
       "//components/captive_portal/content:content",
       "//components/captive_portal/core:buildflags",
@@ -5678,6 +5682,7 @@
       "//components/webapps/browser",
       "//components/webapps/common",
       "//services/preferences/public/cpp:cpp",
+      "//ui/aura:test_support",
       "//ui/display:test_support",
       "//ui/events:test_support",
       "//ui/gfx/codec",
@@ -8630,6 +8635,7 @@
       "//components/metrics/structured:structured_events",
       "//components/metrics/structured:structured_metrics_features",
       "//components/metrics/structured:test_support",
+      "//components/metrics/structured/lib",
       "//components/omnibox/browser:vector_icons",
       "//components/services/app_service",
       "//components/session_manager/core",
@@ -10165,6 +10171,7 @@
       "//components/metrics/structured:structured_events",
       "//components/metrics/structured:structured_metrics_validator",
       "//components/metrics/structured:test_support",
+      "//components/metrics/structured/lib",
     ]
   }
 
@@ -10492,6 +10499,7 @@
         ":lacros_test_support_ui",
         "//chromeos/lacros",
         "//chromeos/lacros:test_support",
+        "//chromeos/startup:startup",
         "//components/account_manager_core",
         "//components/account_manager_core:test_support",
       ]
diff --git a/chrome/test/base/in_process_browser_test.cc b/chrome/test/base/in_process_browser_test.cc
index 7fa234cb..32212a34 100644
--- a/chrome/test/base/in_process_browser_test.cc
+++ b/chrome/test/base/in_process_browser_test.cc
@@ -161,6 +161,7 @@
 #include "chromeos/crosapi/mojom/crosapi.mojom.h"
 #include "chromeos/crosapi/mojom/test_controller.mojom-test-utils.h"
 #include "chromeos/lacros/lacros_service.h"
+#include "chromeos/startup/browser_params_proxy.h"
 #include "components/account_manager_core/chromeos/account_manager.h"
 #include "components/account_manager_core/chromeos/account_manager_facade_factory.h"  // nogncheck
 #include "components/account_manager_core/chromeos/fake_account_manager_ui.h"  // nogncheck
@@ -291,18 +292,14 @@
 // Try to find a better name.
 bool WaitForWindowCreation(Browser* browser) {
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-  // TODO(crbug.com/1508245): Get rid of the IsTestControllerAvailable condition
-  // by making it always true (when crosapi is enabled). Moreover, propagate the
-  // WaitForWindowCreation return value. This requires fixing some tests that
-  // improperly override init params.
-  if (InProcessBrowserTest::IsCrosapiEnabled() && IsTestControllerAvailable()) {
+  if (!chromeos::BrowserParamsProxy::IsCrosapiDisabledForTesting()) {
+    CHECK(IsTestControllerAvailable());
     // Wait for window creation to complete in Ash in order to avoid
     // wayland-crosapi race conditions in subsequent test steps.
     aura::Window* window = browser->window()->GetNativeWindow();
     std::string id =
         lacros_window_utility::GetRootWindowUniqueId(window->GetRootWindow());
-    std::ignore = browser_test_util::WaitForWindowCreation(id);
-    return true;
+    return browser_test_util::WaitForWindowCreation(id);
   }
 #endif
   return true;
@@ -355,12 +352,6 @@
       MaybeGetAshAccountManagerUIForTests());
 }
 
-bool InProcessBrowserTest::IsCrosapiEnabled() {
-  return !base::CommandLine::ForCurrentProcess()
-              ->GetSwitchValuePath("lacros-mojo-socket-for-testing")
-              .empty();
-}
-
 base::Version InProcessBrowserTest::GetAshChromeVersion() {
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
   base::FilePath ash_chrome_path =
@@ -934,7 +925,8 @@
   content::NetworkConnectionChangeSimulator network_change_simulator;
   network_change_simulator.InitializeChromeosConnectionType();
 
-  if (IsCrosapiEnabled() && IsTestControllerAvailable()) {
+  if (!chromeos::BrowserParamsProxy::IsCrosapiDisabledForTesting()) {
+    CHECK(IsTestControllerAvailable());
     // There should NOT be any open ash browser window UI at this point.
     VerifyNoAshBrowserWindowOpenRightNow();
   }
@@ -1010,7 +1002,8 @@
   CHECK(BrowserList::GetInstance()->empty());
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-  if (IsCrosapiEnabled() && IsTestControllerAvailable()) {
+  if (!chromeos::BrowserParamsProxy::IsCrosapiDisabledForTesting()) {
+    CHECK(IsTestControllerAvailable());
     // At this point, there should NOT be any ash browser UIs(e.g. SWA, etc)
     // open; otherwise, the tests running after the current one could be
     // polluted if the tests are running against the shared Ash (by default).
@@ -1079,10 +1072,10 @@
     const std::vector<std::string>& additional_cmdline_switches,
     const std::string& bug_number_and_reason) {
   DCHECK(!bug_number_and_reason.empty());
-  base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
-  CHECK(IsCrosapiEnabled())
+  CHECK(!chromeos::BrowserParamsProxy::IsCrosapiDisabledForTesting())
       << "You can only start unique ash chrome when crosapi is enabled. "
       << "It should not be necessary otherwise.";
+  base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
   base::FilePath ash_dir_holder = cmdline->GetSwitchValuePath("unique-ash-dir");
   CHECK(!ash_dir_holder.empty());
   CHECK(unique_ash_user_data_dir_.CreateUniqueTempDirUnderPath(ash_dir_holder));
diff --git a/chrome/test/base/in_process_browser_test.h b/chrome/test/base/in_process_browser_test.h
index e2a7a8d..1e524c7c 100644
--- a/chrome/test/base/in_process_browser_test.h
+++ b/chrome/test/base/in_process_browser_test.h
@@ -240,9 +240,6 @@
       const std::string& bug_number_and_reason);
 #endif
 
-  // Returns true if crosapi is enabled for the test.
-  static bool IsCrosapiEnabled();
-
  protected:
   // Closes the given browser and waits for it to release all its resources.
   void CloseBrowserSynchronously(Browser* browser);
diff --git a/chrome/test/base/run_all_unittests.cc b/chrome/test/base/run_all_unittests.cc
index 0c6e47b..0ffedec 100644
--- a/chrome/test/base/run_all_unittests.cc
+++ b/chrome/test/base/run_all_unittests.cc
@@ -57,10 +57,6 @@
 int main(int argc, char** argv) {
   base::PlatformThread::SetName("MainThread");
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  chromeos::ScopedDisableCrosapiForTesting disable_crosapi;
-#endif
-
   content::UnitTestTestSuite test_suite(
       new ChromeUnitTestSuite(argc, argv),
       base::BindRepeating(CreateContentClients));
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index 3fb854e..d265a916 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -101,6 +101,7 @@
       "chromeos/inline_login/inline_login_browsertest.cc",
       "chromeos/personalization_app/personalization_app_browsertest.cc",
       "chromeos/personalization_app/personalization_app_sea_pen_browsertest.cc",
+      "chromeos/print_preview_cros/print_preview_cros_browsertest.cc",
       "chromeos/scanning/scanning_app_browsertest.cc",
       "chromeos/vc_background_ui/vc_background_ui_browsertest.cc",
       "settings/chromeos/os_settings_browsertest.cc",
@@ -494,6 +495,7 @@
       "chromeos/parent_access:build_grdp",
       "chromeos/personalization_app:build_grdp",
       "chromeos/print_management:build_grdp",
+      "chromeos/print_preview_cros:build_grdp",
       "chromeos/scanning:build_grdp",
       "chromeos/shortcut_customization:build_grdp",
       "chromeos/vc_background_ui:build_grdp",
@@ -519,6 +521,7 @@
       "$target_gen_dir/chromeos/parent_access/resources.grdp",
       "$target_gen_dir/chromeos/personalization_app/resources.grdp",
       "$target_gen_dir/chromeos/print_management/resources.grdp",
+      "$target_gen_dir/chromeos/print_preview_cros/resources.grdp",
       "$target_gen_dir/chromeos/resources.grdp",
       "$target_gen_dir/chromeos/vc_background_ui/resources.grdp",
       "$target_gen_dir/chromeos/scanning/resources.grdp",
diff --git a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_controller_test.ts b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_controller_test.ts
index c62c3da..e7cd50e 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_controller_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_controller_test.ts
@@ -61,10 +61,7 @@
                 recentImageData: {},
                 recentImages: false,
                 thumbnails: true,
-                selected: {
-                  image: false,
-                  attribution: false,
-                },
+                currentSelected: false,
                 setImage: 0,
               },
               recentImageData: {},
@@ -82,10 +79,7 @@
                 recentImageData: {},
                 recentImages: false,
                 thumbnails: true,
-                selected: {
-                  image: false,
-                  attribution: false,
-                },
+                currentSelected: false,
                 setImage: 0,
               },
               recentImageData: {},
@@ -103,10 +97,7 @@
                 recentImageData: {},
                 recentImages: false,
                 thumbnails: false,
-                selected: {
-                  image: false,
-                  attribution: false,
-                },
+                currentSelected: false,
                 setImage: 0,
               },
               recentImageData: {},
diff --git a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_recent_wallpapers_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_recent_wallpapers_element_test.ts
index 2bc03133..4400f68 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/sea_pen_recent_wallpapers_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/sea_pen_recent_wallpapers_element_test.ts
@@ -63,10 +63,7 @@
       },
       recentImages: false,
       thumbnails: false,
-      selected: {
-        image: false,
-        attribution: false,
-      },
+      currentSelected: false,
       setImage: 0,
     };
 
@@ -140,10 +137,7 @@
         recentImageData: {},
         recentImages: false,
         thumbnails: false,
-        selected: {
-          image: false,
-          attribution: false,
-        },
+        currentSelected: false,
         setImage: 0,
       },
       recentImages: seaPenProvider.recentImages,
@@ -173,10 +167,7 @@
         },
         recentImages: false,
         thumbnails: false,
-        selected: {
-          image: false,
-          attribution: false,
-        },
+        currentSelected: false,
         setImage: 0,
       },
     };
@@ -197,10 +188,7 @@
         },
         recentImages: false,
         thumbnails: false,
-        selected: {
-          image: false,
-          attribution: false,
-        },
+        currentSelected: false,
         setImage: 0,
       },
     };
@@ -219,10 +207,7 @@
         recentImageData: {},
         recentImages: false,
         thumbnails: false,
-        selected: {
-          image: false,
-          attribution: false,
-        },
+        currentSelected: false,
         setImage: 0,
       },
       recentImageData: seaPenProvider.recentImageData,
@@ -247,10 +232,7 @@
         },
         recentImages: false,
         thumbnails: false,
-        selected: {
-          image: false,
-          attribution: false,
-        },
+        currentSelected: false,
         setImage: 0,
       },
     };
@@ -281,10 +263,7 @@
         },
         recentImages: false,
         thumbnails: false,
-        selected: {
-          image: false,
-          attribution: false,
-        },
+        currentSelected: false,
         setImage: 0,
       },
     };
@@ -422,10 +401,7 @@
         '/sea_pen/333.jpg': false,
       },
       thumbnails: false,
-      selected: {
-        image: false,
-        attribution: false,
-      },
+      currentSelected: false,
       setImage: 0,
     };
 
@@ -494,10 +470,7 @@
       },
       recentImages: false,
       thumbnails: false,
-      selected: {
-        image: false,
-        attribution: false,
-      },
+      currentSelected: false,
       setImage: 0,
     };
 
@@ -530,10 +503,7 @@
         },
         recentImages: false,
         thumbnails: false,
-        selected: {
-          image: false,
-          attribution: false,
-        },
+        currentSelected: false,
         setImage: 0,
       },
       recentImages: seaPenProvider.recentImages,
diff --git a/chrome/test/data/webui/chromeos/personalization_app/wallpaper_selected_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/wallpaper_selected_element_test.ts
index 2479a5f..897c57b 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/wallpaper_selected_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/wallpaper_selected_element_test.ts
@@ -149,7 +149,7 @@
       async () => {
         personalizationStore.data.wallpaper.seaPen.loading = {
           ...personalizationStore.data.wallpaper.seaPen.loading,
-          selected: {image: true, attribution: false},
+          currentSelected: true,
           setImage: 0,
         };
         wallpaperSelectedElement = initElement(WallpaperSelectedElement);
@@ -168,28 +168,12 @@
         assertTrue(
             !!placeholder, 'image is loading, the placeholder should display');
 
-        // Attribution still loading, loading placeholder should be shown.
-        personalizationStore.data.wallpaper.seaPen = {
-          ...personalizationStore.data.wallpaper.seaPen,
-          loading: {
-            ...personalizationStore.data.wallpaper.seaPen.loading,
-            selected: {image: false, attribution: true},
-            setImage: 0,
-          },
-        };
-        personalizationStore.notifyObservers();
-        await waitAfterNextRender(wallpaperSelectedElement);
-
-        assertEquals(
-            '', placeholder.style.display,
-            'attribution is loading, loading placeholder should display');
-
         // Loading placeholder should be hidden.
         personalizationStore.data.wallpaper.seaPen = {
           ...personalizationStore.data.wallpaper.seaPen,
           loading: {
             ...personalizationStore.data.wallpaper.seaPen.loading,
-            selected: {image: false, attribution: false},
+            currentSelected: false,
             setImage: 0,
           },
         };
@@ -213,7 +197,7 @@
           ...personalizationStore.data.wallpaper.seaPen,
           loading: {
             ...personalizationStore.data.wallpaper.seaPen.loading,
-            selected: {image: false, attribution: false},
+            currentSelected: false,
             setImage: 1,
           },
         };
diff --git a/chrome/test/data/webui/chromeos/print_preview_cros/BUILD.gn b/chrome/test/data/webui/chromeos/print_preview_cros/BUILD.gn
new file mode 100644
index 0000000..7e8c8d763
--- /dev/null
+++ b/chrome/test/data/webui/chromeos/print_preview_cros/BUILD.gn
@@ -0,0 +1,27 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("../../build_webui_tests.gni")
+
+build_webui_tests("build_webui_tests") {
+  ts_path_mappings = [
+    "chrome://os-print/*|" + rebase_path(
+            "$root_gen_dir/ash/webui/print_preview_cros/resources/tsc/*",
+            target_gen_dir),
+    "chrome://webui-test/chromeos/*|" +
+        rebase_path("$root_gen_dir/chrome/test/data/webui/chromeos/tsc/*",
+                    target_gen_dir),
+  ]
+
+  files = [ "print_preview_cros_app_test.ts" ]
+
+  ts_deps = [
+    "//ash/webui/common/resources:build_ts",
+    "//ash/webui/print_preview_cros/resources:build_ts",
+    "//chrome/test/data/webui/chromeos:build_ts",
+    "//third_party/polymer/v3_0:library",
+    "//ui/webui/resources/js:build_ts",
+    "//ui/webui/resources/mojo:build_ts",
+  ]
+}
diff --git a/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_app_test.ts b/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_app_test.ts
new file mode 100644
index 0000000..287c8b3
--- /dev/null
+++ b/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_app_test.ts
@@ -0,0 +1,24 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://os-print/js/print_preview_cros_app.js';
+
+import {PrintPreviewCrosAppElement} from 'chrome://os-print/js/print_preview_cros_app.js';
+import {assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
+import {isVisible} from 'chrome://webui-test/test_util.js';
+
+suite('PrintPreviewCrosApp', () => {
+  setup(() => {
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+  });
+
+  // Verify the print-preview-cros-app element can be rendered.
+  test('element renders', () => {
+    const element = document.createElement(PrintPreviewCrosAppElement.is) as
+        PrintPreviewCrosAppElement;
+    assertTrue(!!element);
+    document.body.append(element);
+    assertTrue(isVisible(element));
+  });
+});
diff --git a/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_browsertest.cc b/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_browsertest.cc
new file mode 100644
index 0000000..77b90bf
--- /dev/null
+++ b/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_browsertest.cc
@@ -0,0 +1,38 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/constants/ash_features.h"
+#include "ash/webui/print_preview_cros/url_constants.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/test/base/web_ui_mocha_browser_test.h"
+#include "content/public/test/browser_test.h"
+
+/**
+ * @fileoverview Test suite for chrome://os-print.
+ */
+namespace ash {
+
+namespace {
+
+class PrintPreviewCrosBrowserTest : public WebUIMochaBrowserTest {
+ public:
+  PrintPreviewCrosBrowserTest() {
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kPrintPreviewCrosApp},
+        /*disabled_features=*/{});
+    set_test_loader_host(::ash::kChromeUIPrintPreviewCrosHost);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(PrintPreviewCrosBrowserTest, PrintPreviewCrosAppTest) {
+  RunTest("chromeos/print_preview_cros/print_preview_cros_app_test.js",
+          "mocha.run()");
+}
+
+}  // namespace
+
+}  // namespace ash
diff --git a/chrome/test/data/webui/compose/BUILD.gn b/chrome/test/data/webui/compose/BUILD.gn
index 31eae3c..88576510 100644
--- a/chrome/test/data/webui/compose/BUILD.gn
+++ b/chrome/test/data/webui/compose/BUILD.gn
@@ -5,6 +5,7 @@
 import("../build_webui_tests.gni")
 
 build_webui_tests("build") {
+  is_chrome_untrusted = true
   files = [
     "compose_animator_test.ts",
     "compose_app_test.ts",
@@ -16,7 +17,7 @@
   ]
 
   ts_path_mappings =
-      [ "chrome://compose/*|" +
+      [ "chrome-untrusted://compose/*|" +
         rebase_path("$root_gen_dir/chrome/browser/resources/compose/tsc/*",
                     target_gen_dir) ]
   ts_deps = [
diff --git a/chrome/test/data/webui/compose/compose_animator_test.ts b/chrome/test/data/webui/compose/compose_animator_test.ts
index 0fa7838..ea64ffb 100644
--- a/chrome/test/data/webui/compose/compose_animator_test.ts
+++ b/chrome/test/data/webui/compose/compose_animator_test.ts
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://compose/animations/animator.js';
+import 'chrome-untrusted://compose/animations/animator.js';
 
 import {getTrustedHTML} from '//resources/js/static_types.js';
-import {Animator} from 'chrome://compose/animations/animator.js';
-import {assertEquals} from 'chrome://webui-test/chai_assert.js';
+import {Animator} from 'chrome-untrusted://compose/animations/animator.js';
+import {assertEquals} from 'chrome-untrusted://webui-test/chai_assert.js';
 
 suite('ComposeAnimator', () => {
   let animator: Animator;
diff --git a/chrome/test/data/webui/compose/compose_app_focus_test.ts b/chrome/test/data/webui/compose/compose_app_focus_test.ts
index 836fa44..d2472257 100644
--- a/chrome/test/data/webui/compose/compose_app_focus_test.ts
+++ b/chrome/test/data/webui/compose/compose_app_focus_test.ts
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://compose/app.js';
+import 'chrome-untrusted://compose/app.js';
 
-import type {ComposeAppElement} from 'chrome://compose/app.js';
-import {Length, Tone, UserFeedback} from 'chrome://compose/compose.mojom-webui.js';
-import {ComposeApiProxyImpl} from 'chrome://compose/compose_api_proxy.js';
-import {ComposeStatus} from 'chrome://compose/compose_enums.mojom-webui.js';
-import {assertEquals} from 'chrome://webui-test/chai_assert.js';
-import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
+import type {ComposeAppElement} from 'chrome-untrusted://compose/app.js';
+import {Length, Tone, UserFeedback} from 'chrome-untrusted://compose/compose.mojom-webui.js';
+import {ComposeApiProxyImpl} from 'chrome-untrusted://compose/compose_api_proxy.js';
+import {ComposeStatus} from 'chrome-untrusted://compose/compose_enums.mojom-webui.js';
+import {assertEquals} from 'chrome-untrusted://webui-test/chai_assert.js';
+import {flushTasks} from 'chrome-untrusted://webui-test/polymer_test_util.js';
 
 import {TestComposeApiProxy} from './test_compose_api_proxy.js';
 
diff --git a/chrome/test/data/webui/compose/compose_app_test.ts b/chrome/test/data/webui/compose/compose_app_test.ts
index 7e258c7..1e59070 100644
--- a/chrome/test/data/webui/compose/compose_app_test.ts
+++ b/chrome/test/data/webui/compose/compose_app_test.ts
@@ -2,18 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://compose/app.js';
+import 'chrome-untrusted://compose/app.js';
 
 import {CrFeedbackOption} from '//resources/cr_elements/cr_feedback_buttons/cr_feedback_buttons.js';
 import {loadTimeData} from '//resources/js/load_time_data.js';
-import type {ComposeAppElement, ComposeAppState} from 'chrome://compose/app.js';
-import type {ComposeState} from 'chrome://compose/compose.mojom-webui.js';
-import {CloseReason, Length, Tone, UserFeedback} from 'chrome://compose/compose.mojom-webui.js';
-import {ComposeApiProxyImpl} from 'chrome://compose/compose_api_proxy.js';
-import {ComposeStatus} from 'chrome://compose/compose_enums.mojom-webui.js';
-import {assertDeepEquals, assertEquals, assertFalse, assertStringContains, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
-import {isVisible, whenCheck} from 'chrome://webui-test/test_util.js';
+import type {ComposeAppElement, ComposeAppState} from 'chrome-untrusted://compose/app.js';
+import type {ComposeState} from 'chrome-untrusted://compose/compose.mojom-webui.js';
+import {CloseReason, Length, Tone, UserFeedback} from 'chrome-untrusted://compose/compose.mojom-webui.js';
+import {ComposeApiProxyImpl} from 'chrome-untrusted://compose/compose_api_proxy.js';
+import {ComposeStatus} from 'chrome-untrusted://compose/compose_enums.mojom-webui.js';
+import {assertDeepEquals, assertEquals, assertFalse, assertStringContains, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';
+import {flushTasks} from 'chrome-untrusted://webui-test/polymer_test_util.js';
+import {isVisible, whenCheck} from 'chrome-untrusted://webui-test/test_util.js';
 
 import {TestComposeApiProxy} from './test_compose_api_proxy.js';
 
diff --git a/chrome/test/data/webui/compose/compose_browsertest.cc b/chrome/test/data/webui/compose/compose_browsertest.cc
index d639f1f7..3437f1e 100644
--- a/chrome/test/data/webui/compose/compose_browsertest.cc
+++ b/chrome/test/data/webui/compose/compose_browsertest.cc
@@ -11,7 +11,8 @@
 class ComposeTest : public WebUIMochaBrowserTest {
  protected:
   ComposeTest() {
-    set_test_loader_host(chrome::kChromeUIComposeHost);
+    set_test_loader_host(chrome::kChromeUIUntrustedComposeHost);
+    set_test_loader_scheme(content::kChromeUIUntrustedScheme);
     scoped_compose_enabled_ = ComposeEnabling::ScopedEnableComposeForTesting();
   }
 
diff --git a/chrome/test/data/webui/compose/compose_focus_test.cc b/chrome/test/data/webui/compose/compose_focus_test.cc
index e0b859d..534bcc5d 100644
--- a/chrome/test/data/webui/compose/compose_focus_test.cc
+++ b/chrome/test/data/webui/compose/compose_focus_test.cc
@@ -11,7 +11,8 @@
 class ComposeFocusTest : public WebUIMochaFocusTest {
  protected:
   ComposeFocusTest() {
-    set_test_loader_host(chrome::kChromeUIComposeHost);
+    set_test_loader_host(chrome::kChromeUIUntrustedComposeHost);
+    set_test_loader_scheme(content::kChromeUIUntrustedScheme);
     scoped_compose_enabled_ = ComposeEnabling::ScopedEnableComposeForTesting();
   }
 
diff --git a/chrome/test/data/webui/compose/compose_textarea_test.ts b/chrome/test/data/webui/compose/compose_textarea_test.ts
index b94b2e17..8016716 100644
--- a/chrome/test/data/webui/compose/compose_textarea_test.ts
+++ b/chrome/test/data/webui/compose/compose_textarea_test.ts
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://compose/textarea.js';
+import 'chrome-untrusted://compose/textarea.js';
 
-import type {ComposeTextareaElement} from 'chrome://compose/textarea.js';
-import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';
+import type {ComposeTextareaElement} from 'chrome-untrusted://compose/textarea.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';
+import {eventToPromise, isVisible} from 'chrome-untrusted://webui-test/test_util.js';
 
 suite('ComposeTextarea', () => {
   let textarea: ComposeTextareaElement;
diff --git a/chrome/test/data/webui/compose/result_text_test.ts b/chrome/test/data/webui/compose/result_text_test.ts
index 84dcd81..f8169a2e 100644
--- a/chrome/test/data/webui/compose/result_text_test.ts
+++ b/chrome/test/data/webui/compose/result_text_test.ts
@@ -2,12 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://compose/result_text.js';
+import 'chrome-untrusted://compose/result_text.js';
 
-import type {ComposeResultTextElement} from 'chrome://compose/result_text.js';
-// import {assertEquals, assertFalse, assertStringEquals, assertTrue} from
-import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
+import type {ComposeResultTextElement} from 'chrome-untrusted://compose/result_text.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';
+import {flushTasks} from 'chrome-untrusted://webui-test/polymer_test_util.js';
 
 suite('ResultText', () => {
   let resultText: ComposeResultTextElement;
diff --git a/chrome/test/data/webui/compose/test_compose_api_proxy.ts b/chrome/test/data/webui/compose/test_compose_api_proxy.ts
index 29930468..23da4186 100644
--- a/chrome/test/data/webui/compose/test_compose_api_proxy.ts
+++ b/chrome/test/data/webui/compose/test_compose_api_proxy.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.
 
-import type {CloseReason, ComposeState, OpenMetadata, StyleModifiers} from 'chrome://compose/compose.mojom-webui.js';
-import {ComposeDialogCallbackRouter, UserFeedback} from 'chrome://compose/compose.mojom-webui.js';
-import type {ComposeApiProxy} from 'chrome://compose/compose_api_proxy.js';
-import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
+import type {CloseReason, ComposeState, OpenMetadata, StyleModifiers} from 'chrome-untrusted://compose/compose.mojom-webui.js';
+import {ComposeDialogCallbackRouter, UserFeedback} from 'chrome-untrusted://compose/compose.mojom-webui.js';
+import type {ComposeApiProxy} from 'chrome-untrusted://compose/compose_api_proxy.js';
+import {TestBrowserProxy} from 'chrome-untrusted://webui-test/test_browser_proxy.js';
 
 function getDefaultComposeState(): ComposeState {
   return {
diff --git a/chrome/test/data/webui/compose/word_streamer_test.ts b/chrome/test/data/webui/compose/word_streamer_test.ts
index 4cce1e25..b69c020 100644
--- a/chrome/test/data/webui/compose/word_streamer_test.ts
+++ b/chrome/test/data/webui/compose/word_streamer_test.ts
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://compose/word_streamer.js';
+import 'chrome-untrusted://compose/word_streamer.js';
 
-import {WordStreamer} from 'chrome://compose/word_streamer.js';
-import {assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {MockTimer} from 'chrome://webui-test/mock_timer.js';
+import {WordStreamer} from 'chrome-untrusted://compose/word_streamer.js';
+import {assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';
+import {MockTimer} from 'chrome-untrusted://webui-test/mock_timer.js';
 
 interface Update {
   words: string[];
diff --git a/chrome/test/data/webui/tab_search/BUILD.gn b/chrome/test/data/webui/tab_search/BUILD.gn
index 4afdaac..694079a8 100644
--- a/chrome/test/data/webui/tab_search/BUILD.gn
+++ b/chrome/test/data/webui/tab_search/BUILD.gn
@@ -18,7 +18,7 @@
     "tab_search_test_data.ts",
     "tab_search_test_helper.ts",
     "test_tab_search_api_proxy.ts",
-    "test_tab_search_sync_browser_proxy.ts",
+    "test_tab_search_sign_in_browser_proxy.ts",
   ]
 
   ts_path_mappings = [
diff --git a/chrome/test/data/webui/tab_search/tab_organization_page_test.ts b/chrome/test/data/webui/tab_search/tab_organization_page_test.ts
index 0299818..a87419e 100644
--- a/chrome/test/data/webui/tab_search/tab_organization_page_test.ts
+++ b/chrome/test/data/webui/tab_search/tab_organization_page_test.ts
@@ -2,37 +2,31 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {mojoString16ToString, stringToMojoString16} from 'chrome://resources/js/mojo_type_util.js';
-import type {SyncInfo, Tab, TabOrganizationPageElement, TabOrganizationResultsElement, TabOrganizationSession} from 'chrome://tab-search.top-chrome/tab_search.js';
-import {TabOrganizationError, TabOrganizationState, TabSearchApiProxyImpl, TabSearchSyncBrowserProxyImpl} from 'chrome://tab-search.top-chrome/tab_search.js';
+import type {Tab, TabOrganizationPageElement, TabOrganizationResultsElement, TabOrganizationSession} from 'chrome://tab-search.top-chrome/tab_search.js';
+import {TabOrganizationError, TabOrganizationState, TabSearchApiProxyImpl, TabSearchSignInBrowserProxyImpl} from 'chrome://tab-search.top-chrome/tab_search.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 import {isVisible} from 'chrome://webui-test/test_util.js';
 
 import {TestTabSearchApiProxy} from './test_tab_search_api_proxy.js';
-import {TestTabSearchSyncBrowserProxy} from './test_tab_search_sync_browser_proxy.js';
+import {TestTabSearchSignInBrowserProxy} from './test_tab_search_sign_in_browser_proxy.js';
 
 suite('TabOrganizationPageTest', () => {
   let tabOrganizationPage: TabOrganizationPageElement;
   let tabOrganizationResults: TabOrganizationResultsElement;
   let testApiProxy: TestTabSearchApiProxy;
-  let testSyncProxy: TestTabSearchSyncBrowserProxy;
+  let testSignInProxy: TestTabSearchSignInBrowserProxy;
 
-  async function tabOrganizationPageSetup(syncInfo: SyncInfo = {
-    syncing: true,
-    syncingHistory: true,
-    paused: false,
-  }) {
+  async function tabOrganizationPageSetup() {
     testApiProxy = new TestTabSearchApiProxy();
     const session = createSession();
     testApiProxy.setSession(session);
     TabSearchApiProxyImpl.setInstance(testApiProxy);
 
-    testSyncProxy = new TestTabSearchSyncBrowserProxy();
-    testSyncProxy.syncInfo = syncInfo;
-    TabSearchSyncBrowserProxyImpl.setInstance(testSyncProxy);
+    testSignInProxy = new TestTabSearchSignInBrowserProxy();
+    TabSearchSignInBrowserProxyImpl.setInstance(testSignInProxy);
 
     tabOrganizationPage = document.createElement('tab-organization-page');
 
@@ -48,8 +42,8 @@
     testApiProxy.setSession(session);
     TabSearchApiProxyImpl.setInstance(testApiProxy);
 
-    testSyncProxy = new TestTabSearchSyncBrowserProxy();
-    TabSearchSyncBrowserProxyImpl.setInstance(testSyncProxy);
+    testSignInProxy = new TestTabSearchSignInBrowserProxy();
+    TabSearchSignInBrowserProxyImpl.setInstance(testSignInProxy);
 
     tabOrganizationResults = document.createElement('tab-organization-results');
     tabOrganizationResults.name =
@@ -319,113 +313,6 @@
         assertTrue(refreshButton.innerHTML.includes(rejectSuggestion));
       });
 
-  test('Sync required for organization', async () => {
-    const syncInfo: SyncInfo = {
-      syncing: false,
-      syncingHistory: false,
-      paused: false,
-    };
-    await tabOrganizationPageSetup(syncInfo);
-
-    const notStarted = tabOrganizationPage.shadowRoot!.querySelector(
-        'tab-organization-not-started');
-    assertTrue(!!notStarted);
-    assertTrue(isVisible(notStarted));
-
-    const actionButton = notStarted.shadowRoot!.querySelector('cr-button');
-    assertTrue(!!actionButton);
-    actionButton.click();
-
-    // The action button should not request tab organization if the user is in
-    // an invalid sync state.
-    assertEquals(0, testApiProxy.getCallCount('requestTabOrganization'));
-  });
-
-  test('Triggers sync when not syncing', async () => {
-    const syncInfo: SyncInfo = {
-      syncing: false,
-      syncingHistory: true,
-      paused: false,
-    };
-    await tabOrganizationPageSetup(syncInfo);
-
-    const notStarted = tabOrganizationPage.shadowRoot!.querySelector(
-        'tab-organization-not-started');
-    assertTrue(!!notStarted);
-    assertTrue(isVisible(notStarted));
-
-    const actionButton = notStarted.shadowRoot!.querySelector('cr-button');
-    assertTrue(!!actionButton);
-    actionButton.click();
-
-    assertEquals(1, testApiProxy.getCallCount('triggerSync'));
-  });
-
-  test('Triggers sign in when paused', async () => {
-    const syncInfo: SyncInfo = {
-      syncing: true,
-      syncingHistory: true,
-      paused: true,
-    };
-    await tabOrganizationPageSetup(syncInfo);
-
-    const notStarted = tabOrganizationPage.shadowRoot!.querySelector(
-        'tab-organization-not-started');
-    assertTrue(!!notStarted);
-    assertTrue(isVisible(notStarted));
-
-    const actionButton = notStarted.shadowRoot!.querySelector('cr-button');
-    assertTrue(!!actionButton);
-    actionButton.click();
-
-    assertEquals(1, testApiProxy.getCallCount('triggerSignIn'));
-  });
-
-  test('Opens settings when not syncing history', async () => {
-    const syncInfo: SyncInfo = {
-      syncing: true,
-      syncingHistory: false,
-      paused: false,
-    };
-    await tabOrganizationPageSetup(syncInfo);
-
-    const notStarted = tabOrganizationPage.shadowRoot!.querySelector(
-        'tab-organization-not-started');
-    assertTrue(!!notStarted);
-    assertTrue(isVisible(notStarted));
-
-    const actionButton = notStarted.shadowRoot!.querySelector('cr-button');
-    assertTrue(!!actionButton);
-    actionButton.click();
-
-    assertEquals(1, testApiProxy.getCallCount('openSyncSettings'));
-  });
-
-  test('Updates with sync changes', async () => {
-    await tabOrganizationPageSetup();
-
-    const notStarted = tabOrganizationPage.shadowRoot!.querySelector(
-        'tab-organization-not-started');
-    assertTrue(!!notStarted);
-    assertTrue(isVisible(notStarted));
-
-    const accountRowSynced =
-        notStarted.shadowRoot!.querySelector('.account-row');
-    assertFalse(!!accountRowSynced);
-
-    testSyncProxy.syncInfo = {
-      syncing: false,
-      syncingHistory: false,
-      paused: false,
-    };
-    webUIListenerCallback('sync-info-changed', testSyncProxy.syncInfo);
-    await testSyncProxy.whenCalled('getSyncInfo');
-
-    const accountRowUnsynced =
-        notStarted.shadowRoot!.querySelector('.account-row');
-    assertTrue(!!accountRowUnsynced);
-  });
-
   test('Check now action activates on Enter', async () => {
     await tabOrganizationPageSetup();
 
diff --git a/chrome/test/data/webui/tab_search/test_tab_search_api_proxy.ts b/chrome/test/data/webui/tab_search/test_tab_search_api_proxy.ts
index 9609b68..2570988 100644
--- a/chrome/test/data/webui/tab_search/test_tab_search_api_proxy.ts
+++ b/chrome/test/data/webui/tab_search/test_tab_search_api_proxy.ts
@@ -29,10 +29,8 @@
       'setTabIndex',
       'startTabGroupTutorial',
       'triggerFeedback',
-      'triggerSync',
       'triggerSignIn',
       'openHelpPage',
-      'openSyncSettings',
       'setUserFeedback',
       'showUi',
     ]);
@@ -108,10 +106,6 @@
     this.methodCalled('triggerFeedback', [sessionId]);
   }
 
-  triggerSync() {
-    this.methodCalled('triggerSync');
-  }
-
   triggerSignIn() {
     this.methodCalled('triggerSignIn');
   }
@@ -120,10 +114,6 @@
     this.methodCalled('openHelpPage');
   }
 
-  openSyncSettings() {
-    this.methodCalled('openSyncSettings');
-  }
-
   setUserFeedback(feedback: UserFeedback) {
     this.methodCalled('setUserFeedback', [feedback]);
   }
diff --git a/chrome/test/data/webui/tab_search/test_tab_search_sign_in_browser_proxy.ts b/chrome/test/data/webui/tab_search/test_tab_search_sign_in_browser_proxy.ts
new file mode 100644
index 0000000..4bb0a08
--- /dev/null
+++ b/chrome/test/data/webui/tab_search/test_tab_search_sign_in_browser_proxy.ts
@@ -0,0 +1,20 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import type {TabSearchSignInBrowserProxy} from 'chrome://tab-search.top-chrome/tab_search.js';
+import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
+
+export class TestTabSearchSignInBrowserProxy extends TestBrowserProxy implements
+    TabSearchSignInBrowserProxy {
+  constructor() {
+    super([
+      'getSignInState',
+    ]);
+  }
+
+  getSignInState() {
+    this.methodCalled('getSignInState');
+    return Promise.resolve(true);
+  }
+}
diff --git a/chrome/test/data/webui/tab_search/test_tab_search_sync_browser_proxy.ts b/chrome/test/data/webui/tab_search/test_tab_search_sync_browser_proxy.ts
deleted file mode 100644
index 1d545bf..0000000
--- a/chrome/test/data/webui/tab_search/test_tab_search_sync_browser_proxy.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import type {AccountInfo, SyncInfo, TabSearchSyncBrowserProxy} from 'chrome://tab-search.top-chrome/tab_search.js';
-import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
-
-export class TestTabSearchSyncBrowserProxy extends TestBrowserProxy implements
-    TabSearchSyncBrowserProxy {
-  accountInfo: AccountInfo;
-  syncInfo: SyncInfo;
-
-  constructor() {
-    super([
-      'getSyncInfo',
-      'getAccountInfo',
-    ]);
-
-    this.accountInfo = {
-      name: 'Jane Doe',
-      email: 'testemail@gmail.com',
-    };
-    this.syncInfo = {
-      syncing: true,
-      syncingHistory: true,
-      paused: false,
-    };
-  }
-
-  getSyncInfo() {
-    this.methodCalled('getSyncInfo');
-    return Promise.resolve(this.syncInfo);
-  }
-
-  getAccountInfo() {
-    this.methodCalled('getAccountInfo');
-    return Promise.resolve(this.accountInfo);
-  }
-}
diff --git a/chromeos/constants/devicetype_unittest.cc b/chromeos/constants/devicetype_unittest.cc
index 3c133311..b9d8dd6 100644
--- a/chromeos/constants/devicetype_unittest.cc
+++ b/chromeos/constants/devicetype_unittest.cc
@@ -67,7 +67,6 @@
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
 TEST(DeviceTypeTest, GetDeviceTypeLacros) {
   base::test::TaskEnvironment task_environment;
-  chromeos::ScopedDisableCrosapiForTesting disable_crosapi;
   {
     auto params = crosapi::mojom::BrowserInitParams::New();
     params->device_type =
diff --git a/chromeos/lacros/lacros_service.cc b/chromeos/lacros/lacros_service.cc
index 2c81066..db896c15 100644
--- a/chromeos/lacros/lacros_service.cc
+++ b/chromeos/lacros/lacros_service.cc
@@ -854,9 +854,6 @@
 }
 
 std::optional<uint32_t> LacrosService::CrosapiVersion() const {
-  if (chromeos::BrowserParamsProxy::Get()->IsCrosapiDisabledForTesting()) {
-    return std::nullopt;
-  }
   DCHECK(did_bind_receiver_);
   return chromeos::BrowserParamsProxy::Get()->CrosapiVersion();
 }
@@ -880,9 +877,6 @@
 }
 
 int LacrosService::GetInterfaceVersionImpl(base::Token interface_uuid) const {
-  if (chromeos::BrowserParamsProxy::Get()->IsCrosapiDisabledForTesting()) {
-    return -1;
-  }
   if (!chromeos::BrowserParamsProxy::Get()->InterfaceVersions()) {
     return -1;
   }
diff --git a/chromeos/lacros/lacros_test_helper.cc b/chromeos/lacros/lacros_test_helper.cc
index ea4f517..9c1d68a 100644
--- a/chromeos/lacros/lacros_test_helper.cc
+++ b/chromeos/lacros/lacros_test_helper.cc
@@ -7,7 +7,7 @@
 #include "base/check.h"
 #include "base/test/test_future.h"
 #include "chromeos/crosapi/mojom/test_controller.mojom-test-utils.h"
-#include "chromeos/startup/browser_init_params.h"
+#include "chromeos/startup/browser_params_proxy.h"
 
 namespace chromeos {
 namespace {
@@ -28,20 +28,10 @@
 }
 }  // namespace
 
-ScopedDisableCrosapiForTesting::ScopedDisableCrosapiForTesting()
-    : disable_crosapi_resetter_(
-          &BrowserInitParams::is_crosapi_disabled_for_testing_,
-          true) {
-  // Ensure that no instance exist, to prevent interference.
-  CHECK(!LacrosService::Get());
+ScopedLacrosServiceTestHelper::ScopedLacrosServiceTestHelper() {
+  CHECK(BrowserParamsProxy::IsCrosapiDisabledForTesting());
 }
 
-// TODO(crbug.com/1196314): Ensure that no instance exist on destruction, too.
-// Currently, browser_tests' shutdown is an exception.
-ScopedDisableCrosapiForTesting::~ScopedDisableCrosapiForTesting() = default;
-
-ScopedLacrosServiceTestHelper::ScopedLacrosServiceTestHelper() = default;
-
 ScopedLacrosServiceTestHelper::~ScopedLacrosServiceTestHelper() = default;
 
 bool IsAshVersionAtLeastForTesting(base::Version required_version) {
diff --git a/chromeos/lacros/lacros_test_helper.h b/chromeos/lacros/lacros_test_helper.h
index 200cf90..143aa216 100644
--- a/chromeos/lacros/lacros_test_helper.h
+++ b/chromeos/lacros/lacros_test_helper.h
@@ -11,22 +11,6 @@
 
 namespace chromeos {
 
-// Disables crosapi while this instance is alive.
-// This must be instantiate before LacrosService is instantiated.
-// Used only for testing purposes.
-class ScopedDisableCrosapiForTesting {
- public:
-  ScopedDisableCrosapiForTesting();
-  ScopedDisableCrosapiForTesting(const ScopedDisableCrosapiForTesting&) =
-      delete;
-  ScopedDisableCrosapiForTesting& operator=(
-      const ScopedDisableCrosapiForTesting&) = delete;
-  ~ScopedDisableCrosapiForTesting();
-
- private:
-  base::AutoReset<bool> disable_crosapi_resetter_;
-};
-
 // Helper for tests to instantiate LacrosService. This should only be
 // used for unit tests, not browser tests.
 // Instantiated LacrosService is expected to be accessed via
@@ -40,7 +24,6 @@
   ~ScopedLacrosServiceTestHelper();
 
  private:
-  ScopedDisableCrosapiForTesting disable_crosapi_;
   LacrosService lacros_service_;
 };
 
diff --git a/chromeos/startup/README.md b/chromeos/startup/README.md
index 58a4cad..97f5cf5 100644
--- a/chromeos/startup/README.md
+++ b/chromeos/startup/README.md
@@ -14,3 +14,34 @@
 directly. Instead, Lacros code should use `BrowserParamsProxy` which
 dispatches to either `BrowserInitParams` and `BrowserPostLoginParams`
 based on whether Lacros was prelaunched at login screen or not.
+
+
+### `BrowserInitParams` in production vs. testing
+
+In production, Lacros is invoked by Ash with the
+`--crosapi-mojo-platform-channel-handle` command line switch pointing to a file
+descriptor. This causes Lacros to populate `BrowserInitParams` with Ash-provided
+data read from the file. This happens whenever the `BrowserInitParams` are
+queried for the first time.
+
+In testing where Lacros is not invoked by Ash (e.g. Lacros unit and browser
+tests) there are two scenarios:
+
+1) Lacros is invoked without crosapi command line switch. Then
+   `BrowserInitParams` is created empty. This is the case for example for
+   `unit_tests` and `browser_tests`. Crosapi is thereby effectively disabled.
+
+2) Lacros is invoked with crosapi command line switch. Then `BrowserInitParams`
+   are read from Ash just as in production *unless* there's a call to
+   `DisableCrosapiForTesting`, in which case the behavior follows that in (1).
+   (An assertion ensures that `DisableCrosapiForTesting` is called before the
+   first query of `BrowserInitParams`, if at all).
+
+   Detail: For technical reasons, the command line switch here is not
+   `--crosapi-mojo-platform-channel-handle` but
+   `--lacros-mojo-socket-for-testing`. The test runner passes a socket through
+   which `BrowserTestBase` then requests the
+   `--crosapi-mojo-platform-channel-handle` file descriptor from Ash.
+
+Only `lacros_chrome_browsertests` and Lacros `interactive_ui_tests` follow (2),
+with only very few uses of `DisableCrosapiForTesting`.
diff --git a/chromeos/startup/browser_init_params.cc b/chromeos/startup/browser_init_params.cc
index f9ac18a9..e7ac76f 100644
--- a/chromeos/startup/browser_init_params.cc
+++ b/chromeos/startup/browser_init_params.cc
@@ -7,6 +7,8 @@
 #include <optional>
 #include <string>
 
+#include "base/check_is_test.h"
+#include "base/command_line.h"
 #include "chromeos/startup/startup.h"
 
 namespace chromeos {
@@ -32,17 +34,49 @@
 
 }  // namespace
 
+std::optional<bool> BrowserInitParams::is_crosapi_enabled_;
+
+bool BrowserInitParams::IsCrosapiDisabledForTesting() {
+  return !IsCrosapiEnabled();
+}
+
+bool BrowserInitParams::IsCrosapiEnabled() {
+  if (is_crosapi_enabled_.has_value()) {
+    return *is_crosapi_enabled_;
+  }
+
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
+  const bool enabled =
+      command_line->HasSwitch("crosapi-mojo-platform-channel-handle") ||
+      command_line->HasSwitch("lacros-mojo-socket-for-testing");
+  if (!enabled) {
+    CHECK_IS_TEST();
+  }
+  is_crosapi_enabled_ = enabled;
+  return enabled;
+}
+
+void BrowserInitParams::DisableCrosapiForTesting() {
+  CHECK_IS_TEST();
+  // TODO(crbug.com/324508902): Strengthen this CHECK condition to
+  // `!is_crosapi_enabled_.has_value()` when the bug is fixed.
+  CHECK(!is_crosapi_enabled_.value_or(false))
+      << "You are calling DisableCrosapiForTesting too late.";
+  is_crosapi_enabled_ = false;
+}
+
 BrowserInitParams::BrowserInitParams()
-    : init_params_(is_crosapi_disabled_for_testing_
-                       ? crosapi::mojom::BrowserInitParams::New()
-                       : ReadStartupBrowserInitParams()) {
-  if (!init_params_) {
-    LOG(WARNING) << "BrowserInitParams is not set. "
-                 << "This message should not appear except for testing. "
-                 << "For testing, consider setting "
-                 << "BrowserInitParams::is_crosapi_disabled_for_testing_ "
-                 << "to true if crosapi is not required.";
-    init_params_ = crosapi::mojom::BrowserInitParams::New();
+    : init_params_(IsCrosapiEnabled()
+                       ? ReadStartupBrowserInitParams()
+                       : crosapi::mojom::BrowserInitParams::New()) {
+  if (IsCrosapiEnabled()) {
+    CHECK(init_params_) << "crosapi is enabled but BrowserInitParams could not "
+                           "be read. You are probably trying to get or set "
+                           "the BrowserInitParams too early.";
+    CHECK(init_params_->ash_chrome_version);
+  } else {
+    CHECK(init_params_);
   }
 }
 
@@ -59,6 +93,15 @@
 // static
 void BrowserInitParams::SetInitParamsForTests(
     crosapi::mojom::BrowserInitParamsPtr init_params) {
+  CHECK_IS_TEST();
+  if (IsCrosapiEnabled()) {
+    CHECK(init_params);
+    CHECK(init_params->ash_chrome_version)
+        << "crosapi is enabled but the given BrowserInitParams is missing "
+           "essential data. Make sure to use "
+           "BrowserInitParams::GetForTests()->Clone() and customize that "
+           "instead of starting with an empty one";
+  }
   GetInstance()->init_params_ = std::move(init_params);
 }
 
@@ -75,7 +118,4 @@
   return browser_init_params.get();
 }
 
-// static
-bool BrowserInitParams::is_crosapi_disabled_for_testing_ = false;
-
 }  // namespace chromeos
diff --git a/chromeos/startup/browser_init_params.h b/chromeos/startup/browser_init_params.h
index a2ab0f8..1163c98 100644
--- a/chromeos/startup/browser_init_params.h
+++ b/chromeos/startup/browser_init_params.h
@@ -32,16 +32,23 @@
   // Create Mem FD from `init_params_`.
   static base::ScopedFD CreateStartupData();
 
-  static bool is_crosapi_disabled_for_testing() {
-    return is_crosapi_disabled_for_testing_;
-  }
+  // This will always be false in production.
+  static bool IsCrosapiDisabledForTesting();
+
+  // Use sparingly. This should be needed only in exceptional cases. In
+  // particular, Lacros unit_tests and browser_tests have crosapi disabled by
+  // default and don't need to call this.
+  //
+  // This action cannot be undone, so it must be used only by tests that run in
+  // separate processes. (However, crosapi is only enabled in such tests
+  // anyways, hence this is not really a restriction.)
+  //
+  // See also README.md.
+  static void DisableCrosapiForTesting();
 
  private:
   friend base::NoDestructor<BrowserInitParams>;
 
-  // Needs to access |is_crosapi_disabled_for_testing_|.
-  friend class ScopedDisableCrosapiForTesting;
-
   // Needs to access |Get()|.
   friend class BrowserParamsProxy;
 
@@ -58,13 +65,11 @@
   BrowserInitParams();
   ~BrowserInitParams();
 
-  // Tests will set this to |true| which will make all crosapi functionality
-  // unavailable. Should be set from ScopedDisableCrosapiForTesting always.
-  // TODO(https://crbug.com/1131722): Ideally we could stub this out or make
-  // this functional for tests without modifying production code
-  static bool is_crosapi_disabled_for_testing_;
+  static bool IsCrosapiEnabled();
 
-  // Parameters passed from ash-chrome.
+  static std::optional<bool> is_crosapi_enabled_;
+
+  // Parameters passed from ash-chrome (unless crosapi is disabled).
   crosapi::mojom::BrowserInitParamsPtr init_params_;
 };
 
diff --git a/chromeos/startup/browser_params_proxy.cc b/chromeos/startup/browser_params_proxy.cc
index e9ab362..f82aae8 100644
--- a/chromeos/startup/browser_params_proxy.cc
+++ b/chromeos/startup/browser_params_proxy.cc
@@ -26,8 +26,12 @@
   return BrowserPostLoginParams::IsLoggedIn();
 }
 
-bool BrowserParamsProxy::IsCrosapiDisabledForTesting() const {
-  return BrowserInitParams::is_crosapi_disabled_for_testing();
+bool BrowserParamsProxy::IsCrosapiDisabledForTesting() {
+  return BrowserInitParams::IsCrosapiDisabledForTesting();
+}
+
+void BrowserParamsProxy::DisableCrosapiForTesting() {
+  return BrowserInitParams::DisableCrosapiForTesting();
 }
 
 uint32_t BrowserParamsProxy::CrosapiVersion() const {
diff --git a/chromeos/startup/browser_params_proxy.h b/chromeos/startup/browser_params_proxy.h
index 171d87f..961e542 100644
--- a/chromeos/startup/browser_params_proxy.h
+++ b/chromeos/startup/browser_params_proxy.h
@@ -25,8 +25,11 @@
   // Returns true if the user has logged in, false if not.
   static bool IsLoggedIn();
 
+  // See documentation in browser_init_params.h.
+  static bool IsCrosapiDisabledForTesting();
+  static void DisableCrosapiForTesting();
+
   // Init and post-login parameters' accessors are listed starting from here.
-  bool IsCrosapiDisabledForTesting() const;
 
   uint32_t CrosapiVersion() const;
 
diff --git a/chromeos/tast_control.gni b/chromeos/tast_control.gni
index 35a4319..62758f42 100644
--- a/chromeos/tast_control.gni
+++ b/chromeos/tast_control.gni
@@ -115,6 +115,9 @@
   "inputs.VirtualKeyboardHandwriting.floating_lacros@jacuzzi",
   "inputs.VirtualKeyboardHandwriting.floating_lacros@octopus",
 
+  # Failed on LKGM 15770.0.0, fixed on LKGM 15771.0.0 and later.
+  "filemanager.DrivefsUI",
+
   # READ COMMENT AT TOP BEFORE ADDING NEW TESTS HERE.
 ]
 
diff --git a/clank b/clank
index 2c1a38e..2d0a791 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 2c1a38e990de645f3a7871f5e3f44558d9d0ad17
+Subproject commit 2d0a791e87f12aa439b145774e2efe96e6d82225
diff --git a/components/metrics/structured/BUILD.gn b/components/metrics/structured/BUILD.gn
index 13e696c2..a04e6e97 100644
--- a/components/metrics/structured/BUILD.gn
+++ b/components/metrics/structured/BUILD.gn
@@ -12,20 +12,12 @@
   sources = [
     "event_storage.cc",
     "event_storage.h",
-    "key_data.cc",
-    "key_data.h",
-    "key_data_file_delegate.cc",
-    "key_data_file_delegate.h",
     "key_data_prefs_delegate.cc",
     "key_data_prefs_delegate.h",
-    "key_data_provider.cc",
-    "key_data_provider.h",
     "key_data_provider_file.cc",
     "key_data_provider_file.h",
     "key_data_provider_prefs.cc",
     "key_data_provider_prefs.h",
-    "key_util.cc",
-    "key_util.h",
     "reporting/structured_metrics_log_metrics.cc",
     "reporting/structured_metrics_log_metrics.h",
     "reporting/structured_metrics_reporting_service.cc",
@@ -127,6 +119,7 @@
     ":proto",
     ":structured_metrics_validator",
     "//base",
+    "//components/metrics/structured/lib",
     "//components/prefs",
     "//third_party/metrics_proto",
   ]
@@ -241,13 +234,13 @@
     "//base",
     "//base/test:test_support",
     "//components/metrics:metrics",
+    "//components/metrics/structured/lib",
   ]
 }
 
 source_set("unit_tests") {
   testonly = true
   sources = [
-    "key_data_file_delegate_unittest.cc",
     "key_data_prefs_delegate_unittest.cc",
     "structured_metrics_provider_unittest.cc",
     "structured_metrics_recorder_unittest.cc",
@@ -265,6 +258,7 @@
     "//base/test:test_support",
     "//components/metrics",
     "//components/metrics:test_support",
+    "//components/metrics/structured/lib",
     "//components/metrics/structured/lib:proto",
     "//components/prefs",
     "//components/prefs:test_support",
diff --git a/components/metrics/structured/histogram_util.cc b/components/metrics/structured/histogram_util.cc
index 381d5df..44ae450 100644
--- a/components/metrics/structured/histogram_util.cc
+++ b/components/metrics/structured/histogram_util.cc
@@ -10,10 +10,6 @@
 
 namespace metrics::structured {
 
-void LogInternalError(StructuredMetricsError error) {
-  UMA_HISTOGRAM_ENUMERATION("UMA.StructuredMetrics.InternalError2", error);
-}
-
 void LogEventRecordingState(EventRecordingState state) {
   UMA_HISTOGRAM_ENUMERATION("UMA.StructuredMetrics.EventRecordingState2",
                             state);
@@ -24,10 +20,6 @@
                             num_events);
 }
 
-void LogKeyValidation(KeyValidationState state) {
-  UMA_HISTOGRAM_ENUMERATION("UMA.StructuredMetrics.KeyValidationState", state);
-}
-
 void LogNumEventsRecordedBeforeInit(int num_events) {
   UMA_HISTOGRAM_COUNTS_100("UMA.StructuredMetrics.EventsRecordedBeforeInit",
                            num_events);
diff --git a/components/metrics/structured/histogram_util.h b/components/metrics/structured/histogram_util.h
index e0b3679..159f214 100644
--- a/components/metrics/structured/histogram_util.h
+++ b/components/metrics/structured/histogram_util.h
@@ -11,29 +11,6 @@
 
 namespace metrics::structured {
 
-// Possible internal errors of the structured metrics system. These are events
-// we expect to never see, so only the absolute counts should be looked at, the
-// bucket proportion doesn't make sense. These values are persisted to logs.
-// Entries should not be renumbered and numeric values should never be reused.
-enum class StructuredMetricsError {
-  kMissingKey = 0,
-  kWrongKeyLength = 1,
-  kMissingLastRotation = 2,
-  kMissingRotationPeriod = 3,
-  kFailedUintConversion = 4,
-  kKeyReadError = 5,
-  kKeyParseError = 6,
-  kKeyWriteError = 7,
-  kKeySerializationError = 8,
-  kEventReadError = 9,
-  kEventParseError = 10,
-  kEventWriteError = 11,
-  kEventSerializationError = 12,
-  kUninitializedClient = 13,
-  kInvalidEventParsed = 14,
-  kMaxValue = kInvalidEventParsed,
-};
-
 // Whether a single event was recorded correctly, or otherwise what error state
 // occurred. These values are persisted to logs. Entries should not be
 // renumbered and numeric values should never be reused.
@@ -47,24 +24,8 @@
   kMaxValue = kLogSizeExceeded,
 };
 
-// Describes the action taken by KeyData::ValidateAndGetKey on a particular user
-// event key. A key can either be valid with no action taken, missing and so
-// created, or out of its rotation period and so re-created. These values are
-// persisted to logs. Entries should not be renumbered and numeric values should
-// never be reused.
-enum class KeyValidationState {
-  kValid = 0,
-  kCreated = 1,
-  kRotated = 2,
-  kMaxValue = kRotated,
-};
-
-void LogInternalError(StructuredMetricsError error);
-
 void LogEventRecordingState(EventRecordingState state);
 
-void LogKeyValidation(KeyValidationState state);
-
 // Log how many structured metrics events were contained in a call to
 // ProvideCurrentSessionData.
 void LogNumEventsInUpload(int num_events);
diff --git a/components/metrics/structured/key_data_prefs_delegate.cc b/components/metrics/structured/key_data_prefs_delegate.cc
index 802482e..541253e0 100644
--- a/components/metrics/structured/key_data_prefs_delegate.cc
+++ b/components/metrics/structured/key_data_prefs_delegate.cc
@@ -10,8 +10,8 @@
 #include "base/check.h"
 #include "base/logging.h"
 #include "base/values.h"
-#include "components/metrics/structured/key_data.h"
-#include "components/metrics/structured/key_util.h"
+#include "components/metrics/structured/lib/key_data.h"
+#include "components/metrics/structured/lib/key_util.h"
 #include "components/metrics/structured/lib/proto/key.pb.h"
 #include "components/metrics/structured/project_validator.h"
 #include "components/metrics/structured/structured_metrics_validator.h"
diff --git a/components/metrics/structured/key_data_prefs_delegate.h b/components/metrics/structured/key_data_prefs_delegate.h
index 1eb27c9..2837476 100644
--- a/components/metrics/structured/key_data_prefs_delegate.h
+++ b/components/metrics/structured/key_data_prefs_delegate.h
@@ -13,7 +13,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/time/time.h"
-#include "components/metrics/structured/key_data.h"
+#include "components/metrics/structured/lib/key_data.h"
 #include "components/prefs/pref_service.h"
 
 namespace metrics::structured {
diff --git a/components/metrics/structured/key_data_prefs_delegate_unittest.cc b/components/metrics/structured/key_data_prefs_delegate_unittest.cc
index 55840c0..2184e34 100644
--- a/components/metrics/structured/key_data_prefs_delegate_unittest.cc
+++ b/components/metrics/structured/key_data_prefs_delegate_unittest.cc
@@ -14,8 +14,9 @@
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "components/metrics/structured/histogram_util.h"
-#include "components/metrics/structured/key_data.h"
-#include "components/metrics/structured/key_util.h"
+#include "components/metrics/structured/lib/histogram_util.h"
+#include "components/metrics/structured/lib/key_data.h"
+#include "components/metrics/structured/lib/key_util.h"
 #include "components/metrics/structured/lib/proto/key.pb.h"
 #include "components/metrics/structured/structured_metrics_validator.h"
 #include "components/prefs/pref_registry_simple.h"
diff --git a/components/metrics/structured/key_data_provider_file.cc b/components/metrics/structured/key_data_provider_file.cc
index 9fbeac1..96cc7ad 100644
--- a/components/metrics/structured/key_data_provider_file.cc
+++ b/components/metrics/structured/key_data_provider_file.cc
@@ -8,8 +8,8 @@
 
 #include "base/functional/bind.h"
 #include "base/logging.h"
-#include "components/metrics/structured/key_data.h"
-#include "components/metrics/structured/key_data_file_delegate.h"
+#include "components/metrics/structured/lib/key_data.h"
+#include "components/metrics/structured/lib/key_data_file_delegate.h"
 #include "components/metrics/structured/recorder.h"
 #include "components/metrics/structured/structured_metrics_validator.h"
 
diff --git a/components/metrics/structured/key_data_provider_file.h b/components/metrics/structured/key_data_provider_file.h
index 054239b..7f5c49e7 100644
--- a/components/metrics/structured/key_data_provider_file.h
+++ b/components/metrics/structured/key_data_provider_file.h
@@ -8,10 +8,11 @@
 #include <memory>
 #include <optional>
 
+#include "base/barrier_closure.h"
 #include "base/files/file_path.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
-#include "components/metrics/structured/key_data_provider.h"
+#include "components/metrics/structured/lib/key_data_provider.h"
 
 namespace metrics::structured {
 
diff --git a/components/metrics/structured/key_data_provider_prefs.h b/components/metrics/structured/key_data_provider_prefs.h
index be0cf88..b16adde71 100644
--- a/components/metrics/structured/key_data_provider_prefs.h
+++ b/components/metrics/structured/key_data_provider_prefs.h
@@ -8,8 +8,8 @@
 #include <optional>
 #include <string_view>
 
-#include "components/metrics/structured/key_data.h"
-#include "components/metrics/structured/key_data_provider.h"
+#include "components/metrics/structured/lib/key_data.h"
+#include "components/metrics/structured/lib/key_data_provider.h"
 #include "components/prefs/pref_service.h"
 
 namespace metrics::structured {
diff --git a/components/metrics/structured/lib/BUILD.gn b/components/metrics/structured/lib/BUILD.gn
index c928a7e8..5bff1e20 100644
--- a/components/metrics/structured/lib/BUILD.gn
+++ b/components/metrics/structured/lib/BUILD.gn
@@ -9,6 +9,16 @@
 # See README.md for more details.
 source_set("lib") {
   sources = [
+    "histogram_util.cc",
+    "histogram_util.h",
+    "key_data.cc",
+    "key_data.h",
+    "key_data_file_delegate.cc",
+    "key_data_file_delegate.h",
+    "key_data_provider.cc",
+    "key_data_provider.h",
+    "key_util.cc",
+    "key_util.h",
     "persistent_proto.h",
     "persistent_proto_internal.cc",
     "persistent_proto_internal.h",
@@ -18,6 +28,7 @@
 
   deps = [
     "//base",
+    "//crypto",
     "//third_party/protobuf:protobuf_lite",
   ]
 }
@@ -31,7 +42,10 @@
 
 source_set("unit_tests") {
   testonly = true
-  sources = [ "persistent_proto_unittest.cc" ]
+  sources = [
+    "key_data_file_delegate_unittest.cc",
+    "persistent_proto_unittest.cc",
+  ]
   deps = [
     ":lib",
     ":proto",
diff --git a/components/metrics/structured/lib/DEPS b/components/metrics/structured/lib/DEPS
index 136b7dc..4185ed6 100644
--- a/components/metrics/structured/lib/DEPS
+++ b/components/metrics/structured/lib/DEPS
@@ -9,6 +9,7 @@
 
 include_rules = [
     "+base",
+    "+crypto",
     "+third_party/protobuf",
     "+testing/gtest",
 ]
diff --git a/components/metrics/structured/lib/histogram_util.cc b/components/metrics/structured/lib/histogram_util.cc
new file mode 100644
index 0000000..83c18a7c
--- /dev/null
+++ b/components/metrics/structured/lib/histogram_util.cc
@@ -0,0 +1,19 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/structured/lib/histogram_util.h"
+
+#include "base/metrics/histogram_macros.h"
+
+namespace metrics::structured {
+
+void LogInternalError(StructuredMetricsError error) {
+  UMA_HISTOGRAM_ENUMERATION("UMA.StructuredMetrics.InternalError2", error);
+}
+
+void LogKeyValidation(KeyValidationState state) {
+  UMA_HISTOGRAM_ENUMERATION("UMA.StructuredMetrics.KeyValidationState", state);
+}
+
+}  // namespace metrics::structured
diff --git a/components/metrics/structured/lib/histogram_util.h b/components/metrics/structured/lib/histogram_util.h
new file mode 100644
index 0000000..9ae8dfe
--- /dev/null
+++ b/components/metrics/structured/lib/histogram_util.h
@@ -0,0 +1,54 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_STRUCTURED_LIB_HISTOGRAM_UTIL_H_
+#define COMPONENTS_METRICS_STRUCTURED_LIB_HISTOGRAM_UTIL_H_
+
+namespace metrics::structured {
+
+// Describes the action taken by KeyData::ValidateAndGetKey on a particular user
+// event key. A key can either be valid with no action taken, missing and so
+// created, or out of its rotation period and so re-created. These values are
+// persisted to logs. Entries should not be renumbered and numeric values should
+// never be reused.
+enum class KeyValidationState {
+  kValid = 0,
+  kCreated = 1,
+  kRotated = 2,
+  kMaxValue = kRotated,
+};
+
+// Possible internal errors of the structured metrics system. These are events
+// we expect to never see, so only the absolute counts should be looked at, the
+// bucket proportion doesn't make sense. These values are persisted to logs.
+// Entries should not be renumbered and numeric values should never be reused.
+enum class StructuredMetricsError {
+  kMissingKey = 0,
+  kWrongKeyLength = 1,
+  kMissingLastRotation = 2,
+  kMissingRotationPeriod = 3,
+  kFailedUintConversion = 4,
+  kKeyReadError = 5,
+  kKeyParseError = 6,
+  kKeyWriteError = 7,
+  kKeySerializationError = 8,
+  kEventReadError = 9,
+  kEventParseError = 10,
+  kEventWriteError = 11,
+  kEventSerializationError = 12,
+  kUninitializedClient = 13,
+  kInvalidEventParsed = 14,
+  kMaxValue = kInvalidEventParsed,
+};
+
+// Logs an error state in Structured metrics.
+void LogInternalError(StructuredMetricsError error);
+
+// Logs the key validation state. This captures the key state when keys are
+// requested to be validated.
+void LogKeyValidation(KeyValidationState state);
+
+}  // namespace metrics::structured
+
+#endif  // COMPONENTS_METRICS_STRUCTURED_LIB_HISTOGRAM_UTIL_H_
diff --git a/components/metrics/structured/key_data.cc b/components/metrics/structured/lib/key_data.cc
similarity index 95%
rename from components/metrics/structured/key_data.cc
rename to components/metrics/structured/lib/key_data.cc
index 0afae2e..f7af6e7b 100644
--- a/components/metrics/structured/key_data.cc
+++ b/components/metrics/structured/lib/key_data.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/metrics/structured/key_data.h"
+#include "components/metrics/structured/lib/key_data.h"
 
 #include <memory>
 #include <utility>
@@ -15,8 +15,8 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/time/time.h"
 #include "base/unguessable_token.h"
-#include "components/metrics/structured/histogram_util.h"
-#include "components/metrics/structured/key_util.h"
+#include "components/metrics/structured/lib/histogram_util.h"
+#include "components/metrics/structured/lib/key_util.h"
 #include "crypto/hmac.h"
 #include "crypto/sha2.h"
 
@@ -157,7 +157,6 @@
   // Return the key unless it's the wrong size, in which case return nullopt.
   const std::string_view key_string = key->key();
   if (key_string.size() != kKeySize) {
-    LogInternalError(StructuredMetricsError::kWrongKeyLength);
     return std::nullopt;
   }
   return key_string;
diff --git a/components/metrics/structured/key_data.h b/components/metrics/structured/lib/key_data.h
similarity index 97%
rename from components/metrics/structured/key_data.h
rename to components/metrics/structured/lib/key_data.h
index b6b3b497..d5599160 100644
--- a/components/metrics/structured/key_data.h
+++ b/components/metrics/structured/lib/key_data.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_METRICS_STRUCTURED_KEY_DATA_H_
-#define COMPONENTS_METRICS_STRUCTURED_KEY_DATA_H_
+#ifndef COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_H_
+#define COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_H_
 
 #include <memory>
 #include <optional>
@@ -156,4 +156,4 @@
 
 }  // namespace metrics::structured
 
-#endif  // COMPONENTS_METRICS_STRUCTURED_KEY_DATA_H_
+#endif  // COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_H_
diff --git a/components/metrics/structured/key_data_file_delegate.cc b/components/metrics/structured/lib/key_data_file_delegate.cc
similarity index 93%
rename from components/metrics/structured/key_data_file_delegate.cc
rename to components/metrics/structured/lib/key_data_file_delegate.cc
index 58a354a..6ccf553 100644
--- a/components/metrics/structured/key_data_file_delegate.cc
+++ b/components/metrics/structured/lib/key_data_file_delegate.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/metrics/structured/key_data_file_delegate.h"
+#include "components/metrics/structured/lib/key_data_file_delegate.h"
 
 #include <utility>
 
@@ -13,8 +13,8 @@
 #include "base/task/bind_post_task.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/time/time.h"
-#include "components/metrics/structured/histogram_util.h"
-#include "components/metrics/structured/key_util.h"
+#include "components/metrics/structured/lib/histogram_util.h"
+#include "components/metrics/structured/lib/key_util.h"
 #include "components/metrics/structured/lib/persistent_proto.h"
 #include "components/metrics/structured/lib/proto/key.pb.h"
 
diff --git a/components/metrics/structured/key_data_file_delegate.h b/components/metrics/structured/lib/key_data_file_delegate.h
similarity index 89%
rename from components/metrics/structured/key_data_file_delegate.h
rename to components/metrics/structured/lib/key_data_file_delegate.h
index 5ce597bb..a600659c 100644
--- a/components/metrics/structured/key_data_file_delegate.h
+++ b/components/metrics/structured/lib/key_data_file_delegate.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_METRICS_STRUCTURED_KEY_DATA_FILE_DELEGATE_H_
-#define COMPONENTS_METRICS_STRUCTURED_KEY_DATA_FILE_DELEGATE_H_
+#ifndef COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_FILE_DELEGATE_H_
+#define COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_FILE_DELEGATE_H_
 
 #include <memory>
 #include <optional>
@@ -13,7 +13,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
-#include "components/metrics/structured/key_data.h"
+#include "components/metrics/structured/lib/key_data.h"
 #include "components/metrics/structured/lib/persistent_proto.h"
 #include "components/metrics/structured/lib/proto/key.pb.h"
 
@@ -74,4 +74,4 @@
 
 }  // namespace metrics::structured
 
-#endif  // COMPONENTS_METRICS_STRUCTURED_KEY_DATA_FILE_DELEGATE_H_
+#endif  // COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_FILE_DELEGATE_H_
diff --git a/components/metrics/structured/key_data_file_delegate_unittest.cc b/components/metrics/structured/lib/key_data_file_delegate_unittest.cc
similarity index 98%
rename from components/metrics/structured/key_data_file_delegate_unittest.cc
rename to components/metrics/structured/lib/key_data_file_delegate_unittest.cc
index 615cfa46..9d1c85de 100644
--- a/components/metrics/structured/key_data_file_delegate_unittest.cc
+++ b/components/metrics/structured/lib/key_data_file_delegate_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/metrics/structured/key_data_file_delegate.h"
+#include "components/metrics/structured/lib/key_data_file_delegate.h"
 
 #include <memory>
 #include <string>
@@ -19,10 +19,8 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "base/values.h"
-#include "components/metrics/structured/histogram_util.h"
+#include "components/metrics/structured/lib/histogram_util.h"
 #include "components/metrics/structured/lib/proto/key.pb.h"
-#include "components/metrics/structured/recorder.h"
-#include "components/prefs/persistent_pref_store.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace metrics::structured {
diff --git a/components/metrics/structured/key_data_provider.cc b/components/metrics/structured/lib/key_data_provider.cc
similarity index 87%
rename from components/metrics/structured/key_data_provider.cc
rename to components/metrics/structured/lib/key_data_provider.cc
index c50790a..246dcb1e 100644
--- a/components/metrics/structured/key_data_provider.cc
+++ b/components/metrics/structured/lib/key_data_provider.cc
@@ -2,7 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/metrics/structured/key_data_provider.h"
+#include <optional>
+#include <string>
+
+#include "components/metrics/structured/lib/key_data_provider.h"
 
 namespace metrics::structured {
 
diff --git a/components/metrics/structured/key_data_provider.h b/components/metrics/structured/lib/key_data_provider.h
similarity index 93%
rename from components/metrics/structured/key_data_provider.h
rename to components/metrics/structured/lib/key_data_provider.h
index 43a2465..8c6b813 100644
--- a/components/metrics/structured/key_data_provider.h
+++ b/components/metrics/structured/lib/key_data_provider.h
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_METRICS_STRUCTURED_KEY_DATA_PROVIDER_H_
-#define COMPONENTS_METRICS_STRUCTURED_KEY_DATA_PROVIDER_H_
+#ifndef COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_PROVIDER_H_
+#define COMPONENTS_METRICS_STRUCTURED_LIB_KEY_DATA_PROVIDER_H_
 
 #include <optional>
 
 #include "base/functional/callback_forward.h"
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
-#include "components/metrics/structured/key_data.h"
+#include "components/metrics/structured/lib/key_data.h"
 
 namespace base {
 class FilePath;
diff --git a/components/metrics/structured/key_util.cc b/components/metrics/structured/lib/key_util.cc
similarity index 96%
rename from components/metrics/structured/key_util.cc
rename to components/metrics/structured/lib/key_util.cc
index 148addd9..0157b86 100644
--- a/components/metrics/structured/key_util.cc
+++ b/components/metrics/structured/lib/key_util.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/metrics/structured/key_util.h"
+#include "components/metrics/structured/lib/key_util.h"
 
 #include "base/check_op.h"
 #include "base/unguessable_token.h"
diff --git a/components/metrics/structured/key_util.h b/components/metrics/structured/lib/key_util.h
similarity index 83%
rename from components/metrics/structured/key_util.h
rename to components/metrics/structured/lib/key_util.h
index e55aa211..e22e1d5c 100644
--- a/components/metrics/structured/key_util.h
+++ b/components/metrics/structured/lib/key_util.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_METRICS_STRUCTURED_KEY_UTIL_H_
-#define COMPONENTS_METRICS_STRUCTURED_KEY_UTIL_H_
+#ifndef COMPONENTS_METRICS_STRUCTURED_LIB_KEY_UTIL_H_
+#define COMPONENTS_METRICS_STRUCTURED_LIB_KEY_UTIL_H_
 
 #include <optional>
 #include <string>
@@ -29,4 +29,4 @@
 }  // namespace util
 }  // namespace metrics::structured
 
-#endif  // COMPONENTS_METRICS_STRUCTURED_KEY_UTIL_H_
+#endif  // COMPONENTS_METRICS_STRUCTURED_LIB_KEY_UTIL_H_
diff --git a/components/metrics/structured/recorder.cc b/components/metrics/structured/recorder.cc
index eb268bd..1d355d6 100644
--- a/components/metrics/structured/recorder.cc
+++ b/components/metrics/structured/recorder.cc
@@ -10,6 +10,7 @@
 #include "base/task/current_thread.h"
 #include "base/task/sequenced_task_runner.h"
 #include "components/metrics/structured/histogram_util.h"
+#include "components/metrics/structured/lib/histogram_util.h"
 #include "components/metrics/structured/structured_metrics_features.h"
 
 namespace metrics::structured {
diff --git a/components/metrics/structured/structured_metrics_recorder.h b/components/metrics/structured/structured_metrics_recorder.h
index 6f3d1a3..2783d61 100644
--- a/components/metrics/structured/structured_metrics_recorder.h
+++ b/components/metrics/structured/structured_metrics_recorder.h
@@ -16,8 +16,8 @@
 #include "components/metrics/metrics_provider.h"
 #include "components/metrics/structured/event.h"
 #include "components/metrics/structured/event_storage.h"
-#include "components/metrics/structured/key_data.h"
-#include "components/metrics/structured/key_data_provider.h"
+#include "components/metrics/structured/lib/key_data.h"
+#include "components/metrics/structured/lib/key_data_provider.h"
 #include "components/metrics/structured/project_validator.h"
 #include "components/metrics/structured/recorder.h"
 
diff --git a/components/metrics/structured/test/test_key_data_provider.h b/components/metrics/structured/test/test_key_data_provider.h
index 887061380..71bdc37 100644
--- a/components/metrics/structured/test/test_key_data_provider.h
+++ b/components/metrics/structured/test/test_key_data_provider.h
@@ -10,7 +10,7 @@
 #include <string>
 
 #include "base/functional/callback_forward.h"
-#include "components/metrics/structured/key_data_provider.h"
+#include "components/metrics/structured/lib/key_data_provider.h"
 
 namespace base {
 class FilePath;
diff --git a/components/omnibox/browser/autocomplete_controller.cc b/components/omnibox/browser/autocomplete_controller.cc
index 96e7e4f..1f27eb87 100644
--- a/components/omnibox/browser/autocomplete_controller.cc
+++ b/components/omnibox/browser/autocomplete_controller.cc
@@ -1423,17 +1423,20 @@
     auto subtypes = match->subtypes;
     ExtendMatchSubtypes(*match, &subtypes);
 
-    // Count any suggestions that constitute zero-prefix suggestions.
-    if (subtypes.contains(omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_HISTORY) ||
-        subtypes.contains(omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_URLS) ||
-        subtypes.contains(
-            omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_QUERIES) ||
-        subtypes.contains(omnibox::SUBTYPE_ZERO_PREFIX) ||
-        subtypes.contains(omnibox::SUBTYPE_CLIPBOARD_IMAGE) ||
-        subtypes.contains(omnibox::SUBTYPE_CLIPBOARD_TEXT) ||
-        subtypes.contains(omnibox::SUBTYPE_CLIPBOARD_URL) ||
-        subtypes.contains(omnibox::SUBTYPE_ZERO_PREFIX_QUERY_TILE)) {
-      num_zero_prefix_suggestions_shown++;
+    if (input_.IsZeroSuggest()) {
+      result->set_zero_prefix_enabled_in_session(true);
+      // Count any suggestions that constitute zero-prefix suggestions.
+      if (subtypes.contains(omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_HISTORY) ||
+          subtypes.contains(omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_URLS) ||
+          subtypes.contains(
+              omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_QUERIES) ||
+          subtypes.contains(omnibox::SUBTYPE_ZERO_PREFIX) ||
+          subtypes.contains(omnibox::SUBTYPE_CLIPBOARD_IMAGE) ||
+          subtypes.contains(omnibox::SUBTYPE_CLIPBOARD_TEXT) ||
+          subtypes.contains(omnibox::SUBTYPE_CLIPBOARD_URL) ||
+          subtypes.contains(omnibox::SUBTYPE_ZERO_PREFIX_QUERY_TILE)) {
+        num_zero_prefix_suggestions_shown++;
+      }
     }
 
     auto* available_suggestion = searchbox_stats.add_available_suggestions();
@@ -1473,7 +1476,9 @@
           ? result->num_zero_prefix_suggestions_shown_in_session()
           : num_zero_prefix_suggestions_shown);
   searchbox_stats.set_zero_prefix_enabled(
-      searchbox_stats.num_zero_prefix_suggestions_shown() > 0);
+      omnibox_feature_configs::ReportNumZPSInSession::Get().enabled
+          ? result->zero_prefix_enabled_in_session()
+          : searchbox_stats.num_zero_prefix_suggestions_shown() > 0);
 
   // Go over all matches and set searchbox stats if the match supports it.
   for (size_t index = 0; index < result->size(); ++index) {
diff --git a/components/omnibox/browser/autocomplete_controller_unittest.cc b/components/omnibox/browser/autocomplete_controller_unittest.cc
index 3582975..f01d3ca 100644
--- a/components/omnibox/browser/autocomplete_controller_unittest.cc
+++ b/components/omnibox/browser/autocomplete_controller_unittest.cc
@@ -100,6 +100,7 @@
         name, AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED, false, false,
         traditional_relevance, std::nullopt);
     match.keyword = u"keyword";
+    match.suggestion_group_id = omnibox::GROUP_PERSONALIZED_ZERO_SUGGEST;
     match.subtypes.emplace(omnibox::SUBTYPE_PERSONAL);
     match.subtypes.emplace(omnibox::SUBTYPE_ZERO_PREFIX);
     return match;
@@ -627,7 +628,10 @@
 #endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
 }
 
-TEST_F(AutocompleteControllerTest, UpdateResult_NumZPSShownInSession) {
+TEST_F(AutocompleteControllerTest, UpdateResult_ZPSEnabledAndShownInSession) {
+  auto zps_input = FakeAutocompleteController::CreateInput(u"");
+  zps_input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_CLOBBER);
+
   {
     SCOPED_TRACE("Zero-prefix suggestions are offered synchronously");
     EXPECT_THAT(controller_.SimulateAutocompletePass(
@@ -636,11 +640,13 @@
                         CreatePersonalizedZeroPrefixMatch("zps_1", 1450),
                         CreatePersonalizedZeroPrefixMatch("zps_2", 1449),
                     },
-                    FakeAutocompleteController::CreateInput(u"")),
+                    zps_input),
                 testing::ElementsAreArray({
                     "zps_1",
                     "zps_2",
                 }));
+    // Whether zero-prefix suggestions were enabled in the session is updated.
+    EXPECT_TRUE(controller_.published_result_.zero_prefix_enabled_in_session());
     // The count of zero-prefix suggestions offered in the session is updated.
     EXPECT_EQ(controller_.published_result_
                   .num_zero_prefix_suggestions_shown_in_session(),
@@ -656,13 +662,14 @@
                         CreatePersonalizedZeroPrefixMatch("zps_3", 1448),
                         CreatePersonalizedZeroPrefixMatch("zps_4", 1447),
                     },
-                    FakeAutocompleteController::CreateInput(u"")),
+                    zps_input),
                 testing::ElementsAreArray({
                     "zps_1",
                     "zps_2",
                     "zps_3",
                     "zps_4",
                 }));
+    EXPECT_TRUE(controller_.published_result_.zero_prefix_enabled_in_session());
     // If zero-prefix suggestions are offered multiple times in the session, the
     // most recent count is logged.
     EXPECT_EQ(controller_.published_result_
@@ -675,6 +682,8 @@
     // Stop with clear_result=false does not clear the result set or notify
     // `OnResultChanged()`.
     EXPECT_FALSE(controller_.published_result_.empty());
+    // Whether zero-prefix suggestions were enabled in the session is unchanged.
+    EXPECT_TRUE(controller_.published_result_.zero_prefix_enabled_in_session());
     // The count of zero-prefix suggestions offered in the session is unchanged.
     EXPECT_EQ(controller_.published_result_
                   .num_zero_prefix_suggestions_shown_in_session(),
@@ -690,6 +699,8 @@
                 testing::ElementsAreArray({
                     "search_1",
                 }));
+    // Whether zero-prefix suggestions were enabled in the session is unchanged.
+    EXPECT_TRUE(controller_.published_result_.zero_prefix_enabled_in_session());
     // The count of zero-prefix suggestions offered in the session is unchanged.
     EXPECT_EQ(controller_.published_result_
                   .num_zero_prefix_suggestions_shown_in_session(),
@@ -699,9 +710,11 @@
     SCOPED_TRACE("Stop with clear_result=true is called due to popup closing");
     controller_.Stop(/*clear_result=*/true);
     // Stop with clear_result=true clears the result set and notifies
-    // `OnResultChanged()`. The count of zero-prefix suggestions offered in the
-    // session is also reset.
+    // `OnResultChanged()`. Whether zero-prefix suggestions were enabled and the
+    // count of zero-prefix suggestions offered in the session is also reset.
     EXPECT_TRUE(controller_.published_result_.empty());
+    EXPECT_FALSE(
+        controller_.published_result_.zero_prefix_enabled_in_session());
     EXPECT_EQ(controller_.published_result_
                   .num_zero_prefix_suggestions_shown_in_session(),
               0u);
diff --git a/components/omnibox/browser/autocomplete_provider_unittest.cc b/components/omnibox/browser/autocomplete_provider_unittest.cc
index ffcaec2..87286ade 100644
--- a/components/omnibox/browser/autocomplete_provider_unittest.cc
+++ b/components/omnibox/browser/autocomplete_provider_unittest.cc
@@ -384,7 +384,8 @@
       const SuggestionGroupsTestData& test_data);
 
   void RunSearchboxStatsTest(const SearchboxStatsTestData* sbs_test_data,
-                             size_t size);
+                             size_t size,
+                             bool input_is_zero_suggest);
 
   void RunQuery(const std::string& query, bool allow_exact_keyword_match);
 
@@ -641,8 +642,17 @@
 
 void AutocompleteProviderTest::RunSearchboxStatsTest(
     const SearchboxStatsTestData* sbs_test_data,
-    size_t size) {
-  // Prepare input.
+    size_t size,
+    bool input_is_zero_suggest) {
+  if (input_is_zero_suggest) {
+    // Prepare the input.
+    AutocompleteInput input(u"", metrics::OmniboxEventProto::OTHER,
+                            TestingSchemeClassifier());
+    input.set_focus_type(metrics::OmniboxFocusType::INTERACTION_CLOBBER);
+    controller_->input_ = input;
+  }
+
+  // Prepare the results.
   const size_t kMaxRelevance = 1000;
   ACMatches matches;
   for (size_t i = 0; i < size; ++i) {
@@ -1061,7 +1071,7 @@
          omnibox::TYPE_NATIVE_CHROME}};
     SCOPED_TRACE("No matches");
     // Note: We pass 0 here to ignore the dummy data above.
-    RunSearchboxStatsTest(test_data, 0);
+    RunSearchboxStatsTest(test_data, 0, /*input_is_zero_suggest=*/false);
   }
 
   // Note: See suggest.proto for the types and subtypes referenced below.
@@ -1082,7 +1092,8 @@
          searchbox_stats,
          omnibox::TYPE_NATIVE_CHROME}};
     SCOPED_TRACE("One match");
-    RunSearchboxStatsTest(test_data, std::size(test_data));
+    RunSearchboxStatsTest(test_data, std::size(test_data),
+                          /*input_is_zero_suggest=*/false);
   }
 
   {
@@ -1104,7 +1115,8 @@
          omnibox::TYPE_ENTITY,
          {omnibox::SUBTYPE_PERSONAL}}};
     SCOPED_TRACE("One match with provider populated subtypes");
-    RunSearchboxStatsTest(test_data, std::size(test_data));
+    RunSearchboxStatsTest(test_data, std::size(test_data),
+                          /*input_is_zero_suggest=*/false);
   }
 
   {
@@ -1143,7 +1155,8 @@
          {omnibox::SUBTYPE_ZERO_PREFIX_LOCAL_FREQUENT_QUERIES}},
     };
     SCOPED_TRACE("Multiple matches in horizontal render group");
-    RunSearchboxStatsTest(test_data, std::size(test_data));
+    RunSearchboxStatsTest(test_data, std::size(test_data),
+                          /*input_is_zero_suggest=*/true);
   }
 
   {
@@ -1216,7 +1229,8 @@
          {omnibox::SUBTYPE_ZERO_PREFIX}},
     };
     SCOPED_TRACE("Multiple matches with horizontal render group");
-    RunSearchboxStatsTest(test_data, std::size(test_data));
+    RunSearchboxStatsTest(test_data, std::size(test_data),
+                          /*input_is_zero_suggest=*/true);
   }
 
   {
@@ -1316,7 +1330,8 @@
          {omnibox::SUBTYPE_PERSONAL, omnibox::SUBTYPE_TRENDS}},
     };
     SCOPED_TRACE("Complex set of matches with repetitive subtypes");
-    RunSearchboxStatsTest(test_data, std::size(test_data));
+    RunSearchboxStatsTest(test_data, std::size(test_data),
+                          /*input_is_zero_suggest=*/true);
   }
 
   // This test confirms that selection of trivial suggestions does not get
@@ -1440,7 +1455,8 @@
          omnibox::TYPE_NATIVE_CHROME},
     };
     SCOPED_TRACE("Trivial and zero-prefix matches");
-    RunSearchboxStatsTest(test_data, std::size(test_data));
+    RunSearchboxStatsTest(test_data, std::size(test_data),
+                          /*input_is_zero_suggest=*/true);
   }
 }
 
diff --git a/components/omnibox/browser/autocomplete_result.cc b/components/omnibox/browser/autocomplete_result.cc
index a20ccab..4bafdc3d 100644
--- a/components/omnibox/browser/autocomplete_result.cc
+++ b/components/omnibox/browser/autocomplete_result.cc
@@ -1034,6 +1034,7 @@
 
 void AutocompleteResult::Reset() {
   ClearMatches();
+  zero_prefix_enabled_in_session_ = false;
   num_zero_prefix_suggestions_shown_in_session_ = 0u;
 }
 
@@ -1049,6 +1050,8 @@
 void AutocompleteResult::Swap(AutocompleteResult* other) {
   matches_.swap(other->matches_);
   suggestion_groups_map_.swap(other->suggestion_groups_map_);
+  std::swap(zero_prefix_enabled_in_session_,
+            other->zero_prefix_enabled_in_session_);
   std::swap(num_zero_prefix_suggestions_shown_in_session_,
             other->num_zero_prefix_suggestions_shown_in_session_);
 #if BUILDFLAG(IS_ANDROID)
@@ -1063,6 +1066,7 @@
 
   matches_ = other.matches_;
   suggestion_groups_map_ = other.suggestion_groups_map_;
+  zero_prefix_enabled_in_session_ = other.zero_prefix_enabled_in_session_;
   num_zero_prefix_suggestions_shown_in_session_ =
       other.num_zero_prefix_suggestions_shown_in_session_;
 
diff --git a/components/omnibox/browser/autocomplete_result.h b/components/omnibox/browser/autocomplete_result.h
index fadeb152..a604f21 100644
--- a/components/omnibox/browser/autocomplete_result.h
+++ b/components/omnibox/browser/autocomplete_result.h
@@ -231,6 +231,14 @@
     return suggestion_groups_map_;
   }
 
+  bool zero_prefix_enabled_in_session() const {
+    return zero_prefix_enabled_in_session_;
+  }
+
+  void set_zero_prefix_enabled_in_session(bool enabled) {
+    zero_prefix_enabled_in_session_ = enabled;
+  }
+
   size_t num_zero_prefix_suggestions_shown_in_session() const {
     return num_zero_prefix_suggestions_shown_in_session_;
   }
@@ -424,6 +432,11 @@
   // `matches_`. Cleared on `ClearMatches()` or `Reset()`.
   omnibox::GroupConfigMap suggestion_groups_map_;
 
+  // Whether zero-prefix suggestions were enabled in the session (i.e., the
+  // user could have seen zero-prefix suggestions), regardless of current
+  // `matches_` - Cleared on `Reset()`.
+  bool zero_prefix_enabled_in_session_ = false;
+
   // The number of zero-prefix suggestions in the session, regardless of current
   // `matches_`. Cleared on `Reset()`.
   size_t num_zero_prefix_suggestions_shown_in_session_ = 0u;
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal
index f4aea18..1f827a8 160000
--- a/components/optimization_guide/internal
+++ b/components/optimization_guide/internal
@@ -1 +1 @@
-Subproject commit f4aea18550bf707b1d06d65028e7e9edcae7d2c1
+Subproject commit 1f827a8c547d3157e40bbed7e375469c1585d5f7
diff --git a/components/os_crypt/sync/os_crypt_unittest.cc b/components/os_crypt/sync/os_crypt_unittest.cc
index 7c4fe89..c7ef1e0 100644
--- a/components/os_crypt/sync/os_crypt_unittest.cc
+++ b/components/os_crypt/sync/os_crypt_unittest.cc
@@ -8,8 +8,8 @@
 #include <vector>
 
 #include "base/compiler_specific.h"
+#include "base/containers/span.h"
 #include "base/functional/bind.h"
-#include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread.h"
@@ -219,11 +219,10 @@
 // If this test ever breaks do not ignore it as it might result in data loss for
 // users.
 TEST_F(OSCryptTestWin, DPAPIHeader) {
-  std::string plaintext;
-  std::string ciphertext;
-
   OSCryptMocker::SetLegacyEncryption(true);
-  crypto::RandBytes(base::WriteInto(&plaintext, 11), 10);
+  std::string plaintext(10, '\0');
+  crypto::RandBytes(base::as_writable_byte_span(plaintext));
+  std::string ciphertext;
   ASSERT_TRUE(OSCrypt::EncryptString(plaintext, &ciphertext));
 
   using std::string_literals::operator""s;
diff --git a/components/os_crypt/sync/os_crypt_win.cc b/components/os_crypt/sync/os_crypt_win.cc
index e955e6b..6a2ee3f 100644
--- a/components/os_crypt/sync/os_crypt_win.cc
+++ b/components/os_crypt/sync/os_crypt_win.cc
@@ -5,6 +5,7 @@
 #include <windows.h>
 
 #include "base/base64.h"
+#include "base/containers/span.h"
 #include "base/feature_list.h"
 #include "base/logging.h"
 #include "base/memory/singleton.h"
@@ -198,8 +199,8 @@
   DCHECK_EQ(kKeyLength, aead.KeyLength());
   DCHECK_EQ(kNonceLength, aead.NonceLength());
 
-  std::string nonce;
-  crypto::RandBytes(base::WriteInto(&nonce, kNonceLength + 1), kNonceLength);
+  std::string nonce(kNonceLength, '\0');
+  crypto::RandBytes(base::as_writable_byte_span(nonce));
 
   if (!aead.Seal(plaintext, nonce, std::string(), ciphertext))
     return false;
@@ -251,8 +252,8 @@
 
   // If there is no key in the local state, or if DPAPI decryption fails,
   // generate a new key.
-  std::string key;
-  crypto::RandBytes(base::WriteInto(&key, kKeyLength + 1), kKeyLength);
+  std::string key(kKeyLength, '\0');
+  crypto::RandBytes(base::as_writable_byte_span(key));
 
   if (!EncryptAndStoreKey(key, local_state)) {
     return false;
diff --git a/components/permissions/permission_uma_util.cc b/components/permissions/permission_uma_util.cc
index 5563ab8..c2328df 100644
--- a/components/permissions/permission_uma_util.cc
+++ b/components/permissions/permission_uma_util.cc
@@ -1075,7 +1075,7 @@
     content::WebContents* web_contents,
     const GURL& requesting_origin) {
   PermissionsClient::Get()->GetUkmSourceId(
-      browser_context, web_contents, requesting_origin,
+      permission_type, browser_context, web_contents, requesting_origin,
       base::BindOnce(&RecordPermissionUsageUkm, permission_type));
 }
 
@@ -1142,7 +1142,7 @@
   }
 
   PermissionsClient::Get()->GetUkmSourceId(
-      browser_context, web_contents, requesting_origin,
+      permission, browser_context, web_contents, requesting_origin,
       base::BindOnce(
           &RecordPermissionActionUkm, action, gesture_type, permission,
           dismiss_count, ignore_count, source_ui, time_to_decision,
diff --git a/components/permissions/permission_uma_util_unittest.cc b/components/permissions/permission_uma_util_unittest.cc
index eadd646..022e64f6 100644
--- a/components/permissions/permission_uma_util_unittest.cc
+++ b/components/permissions/permission_uma_util_unittest.cc
@@ -853,7 +853,8 @@
       simulated_has_source_id_ = source_id;
     }
 
-    void GetUkmSourceId(content::BrowserContext* browser_context,
+    void GetUkmSourceId(ContentSettingsType permission_type,
+                        content::BrowserContext* browser_context,
                         content::WebContents* web_contents,
                         const GURL& requesting_origin,
                         GetUkmSourceIdCallback callback) override {
@@ -862,7 +863,7 @@
         std::move(callback).Run(std::nullopt);
       } else {
         ukm::SourceId fake_source_id =
-            ukm::ConvertToSourceId(1, ukm::SourceIdType::NAVIGATION_ID);
+            ukm::ConvertToSourceId(1, ukm::SourceIdType::NOTIFICATION_ID);
         std::move(callback).Run(fake_source_id);
       }
     }
diff --git a/components/permissions/permissions_client.cc b/components/permissions/permissions_client.cc
index 1b86a7e..1f553c9 100644
--- a/components/permissions/permissions_client.cc
+++ b/components/permissions/permissions_client.cc
@@ -54,7 +54,8 @@
   return false;
 }
 
-void PermissionsClient::GetUkmSourceId(content::BrowserContext* browser_context,
+void PermissionsClient::GetUkmSourceId(ContentSettingsType permission_type,
+                                       content::BrowserContext* browser_context,
                                        content::WebContents* web_contents,
                                        const GURL& requesting_origin,
                                        GetUkmSourceIdCallback callback) {
diff --git a/components/permissions/permissions_client.h b/components/permissions/permissions_client.h
index d79c94c..8b7c804 100644
--- a/components/permissions/permissions_client.h
+++ b/components/permissions/permissions_client.h
@@ -133,13 +133,14 @@
       content::BrowserContext* browser_context,
       const GURL& origin);
 
-  // Retrieves the ukm::SourceId (if any) associated with this |browser_context|
-  // and |web_contents|. |web_contents| may be null. |callback| will be called
-  // with the result, and may be run synchronously if the result is available
-  // immediately.
+  // Retrieves the ukm::SourceId (if any) associated with this
+  // |permission_type|, |browser_context|, and |web_contents|. |web_contents|
+  // may be null. |callback| will be called with the result, and may be run
+  // synchronously if the result is available immediately.
   using GetUkmSourceIdCallback =
-      base::OnceCallback<void(std::optional<ukm::SourceId>)>;
-  virtual void GetUkmSourceId(content::BrowserContext* browser_context,
+      base::OnceCallback<void(absl::optional<ukm::SourceId>)>;
+  virtual void GetUkmSourceId(ContentSettingsType permission_type,
+                              content::BrowserContext* browser_context,
                               content::WebContents* web_contents,
                               const GURL& requesting_origin,
                               GetUkmSourceIdCallback callback);
diff --git a/components/permissions/test/test_permissions_client.cc b/components/permissions/test/test_permissions_client.cc
index 2ec62dd4..e4318fe 100644
--- a/components/permissions/test/test_permissions_client.cc
+++ b/components/permissions/test/test_permissions_client.cc
@@ -81,6 +81,7 @@
 }
 
 void TestPermissionsClient::GetUkmSourceId(
+    ContentSettingsType permission_type,
     content::BrowserContext* browser_context,
     content::WebContents* web_contents,
     const GURL& requesting_origin,
diff --git a/components/permissions/test/test_permissions_client.h b/components/permissions/test/test_permissions_client.h
index 0599c9a..7091f0e 100644
--- a/components/permissions/test/test_permissions_client.h
+++ b/components/permissions/test/test_permissions_client.h
@@ -38,7 +38,8 @@
   ObjectPermissionContextBase* GetChooserContext(
       content::BrowserContext* browser_context,
       ContentSettingsType type) override;
-  void GetUkmSourceId(content::BrowserContext* browser_context,
+  void GetUkmSourceId(ContentSettingsType permission_type,
+                      content::BrowserContext* browser_context,
                       content::WebContents* web_contents,
                       const GURL& requesting_origin,
                       GetUkmSourceIdCallback callback) override;
diff --git a/components/permissions_strings.grdp b/components/permissions_strings.grdp
index f46371c..3178394 100644
--- a/components/permissions_strings.grdp
+++ b/components/permissions_strings.grdp
@@ -95,10 +95,10 @@
     Control and reprogram your MIDI devices
   </message>
   <message name="IDS_MEDIA_CAPTURE_AUDIO_ONLY_PERMISSION_FRAGMENT" desc="Permission fragment shown in the permissions bubble when a web page requests access to the computer's microphone.">
-    Use your microphone
+    Use your microphones
   </message>
   <message name="IDS_MEDIA_CAPTURE_VIDEO_ONLY_PERMISSION_FRAGMENT" desc="Permission fragment shown in the permissions bubble when a web page requests access to the computer's camera.">
-    Use your camera
+    Use your cameras
   </message>
   <message name="IDS_MEDIA_CAPTURE_CAMERA_PAN_TILT_ZOOM_PERMISSION_FRAGMENT" desc="Permission fragment shown in the permissions prompt when a web page requests access to the computer's camera, including zoom and move.">
     Use &amp; move your camera
diff --git a/components/permissions_strings_grdp/IDS_MEDIA_CAPTURE_AUDIO_ONLY_PERMISSION_FRAGMENT.png.sha1 b/components/permissions_strings_grdp/IDS_MEDIA_CAPTURE_AUDIO_ONLY_PERMISSION_FRAGMENT.png.sha1
new file mode 100644
index 0000000..b85cd24
--- /dev/null
+++ b/components/permissions_strings_grdp/IDS_MEDIA_CAPTURE_AUDIO_ONLY_PERMISSION_FRAGMENT.png.sha1
@@ -0,0 +1 @@
+972029c32944e61ebd37b297fb363aae871199c4
\ No newline at end of file
diff --git a/components/permissions_strings_grdp/IDS_MEDIA_CAPTURE_VIDEO_ONLY_PERMISSION_FRAGMENT.png.sha1 b/components/permissions_strings_grdp/IDS_MEDIA_CAPTURE_VIDEO_ONLY_PERMISSION_FRAGMENT.png.sha1
new file mode 100644
index 0000000..5bb9bdf
--- /dev/null
+++ b/components/permissions_strings_grdp/IDS_MEDIA_CAPTURE_VIDEO_ONLY_PERMISSION_FRAGMENT.png.sha1
@@ -0,0 +1 @@
+971a228dc10517f8efd32a80d44757b89a8a4215
\ No newline at end of file
diff --git a/components/ukm/ukm_recorder_impl.cc b/components/ukm/ukm_recorder_impl.cc
index 5ac9250..738745d 100644
--- a/components/ukm/ukm_recorder_impl.cc
+++ b/components/ukm/ukm_recorder_impl.cc
@@ -68,6 +68,7 @@
     case ukm::SourceIdObj::Type::DEFAULT:
     case ukm::SourceIdObj::Type::DEPRECATED_DESKTOP_WEB_APP_ID:
     case ukm::SourceIdObj::Type::WORKER_ID:
+    case ukm::SourceIdObj::Type::NOTIFICATION_ID:
       return false;
   }
 }
@@ -753,6 +754,7 @@
     case SourceIdType::WEB_IDENTITY_ID:
     case SourceIdType::CHROMEOS_WEBSITE_ID:
     case SourceIdType::EXTENSION_ID:
+    case SourceIdType::NOTIFICATION_ID:
       return UkmConsentType::MSBB;
   }
   return UkmConsentType::MSBB;
@@ -804,7 +806,8 @@
     case ukm::SourceIdObj::Type::NO_URL_ID:
     case ukm::SourceIdObj::Type::WEB_IDENTITY_ID:
     case ukm::SourceIdObj::Type::CHROMEOS_WEBSITE_ID:
-    case ukm::SourceIdObj::Type::EXTENSION_ID: {
+    case ukm::SourceIdObj::Type::EXTENSION_ID:
+    case ukm::SourceIdObj::Type::NOTIFICATION_ID: {
       // Don't keep sources of these types after current report because their
       // entries are logged only at source creation time.
       MarkSourceForDeletion(source_id);
diff --git a/content/browser/devtools/devtools_instrumentation.cc b/content/browser/devtools/devtools_instrumentation.cc
index 3cc4fbe..1192b2cd 100644
--- a/content/browser/devtools/devtools_instrumentation.cc
+++ b/content/browser/devtools/devtools_instrumentation.cc
@@ -6,6 +6,7 @@
 
 #include "base/containers/adapters.h"
 #include "base/feature_list.h"
+#include "base/notreached.h"
 #include "base/strings/stringprintf.h"
 #include "base/trace_event/traced_value.h"
 #include "components/download/public/common/download_create_info.h"
@@ -196,6 +197,10 @@
       return AttributionReportingIssueTypeEnum::WebAndOsHeaders;
     case blink::mojom::AttributionReportingIssueType::kNoWebOrOsSupport:
       return AttributionReportingIssueTypeEnum::NoWebOrOsSupport;
+    case blink::mojom::AttributionReportingIssueType::
+        kNavigationRegistrationWithoutTransientUserActivation:
+      // This issue is not reported from the browser.
+      NOTREACHED_NORETURN();
   }
 }
 
diff --git a/content/browser/launch_as_mojo_client_browsertest.cc b/content/browser/launch_as_mojo_client_browsertest.cc
index 8fd807e..542959ae 100644
--- a/content/browser/launch_as_mojo_client_browsertest.cc
+++ b/content/browser/launch_as_mojo_client_browsertest.cc
@@ -155,6 +155,10 @@
   mojo::Remote<mojom::ShellController> shell_controller_;
 };
 
+// TODO(http://crbug.com/323984075): This test invokes content_shell in a way
+// that is not supported on Lacros (without crosapi data). Figure out what to
+// do about that.
+#if !BUILDFLAG(IS_CHROMEOS_LACROS)
 IN_PROC_BROWSER_TEST_F(LaunchAsMojoClientBrowserTest, LaunchAndBindInterface) {
   // Verifies that we can launch an instance of Content Shell with a Mojo
   // invitation on the command line and reach the new browser process's exposed
@@ -180,6 +184,7 @@
 
   shell_controller->ShutDown();
 }
+#endif  // !BUILDFLAG(IS_CHROMEOS_LACROS)
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
 // TODO(crbug.com/1259557): This test implementation fundamentally conflicts
diff --git a/content/browser/preloading/preloading_config.cc b/content/browser/preloading/preloading_config.cc
index bd9ba4e..4acde79 100644
--- a/content/browser/preloading/preloading_config.cc
+++ b/content/browser/preloading/preloading_config.cc
@@ -31,7 +31,7 @@
 [{
   "preloading_type": "NoStatePrefetch",
   "preloading_predictor": "LinkRel",
-  "sampling_likelihood": "0.007925"
+  "sampling_likelihood": "0.001587"
 }, {
   "preloading_type": "NoStatePrefetch",
   "preloading_predictor": "OmniboxDirectURLInput",
@@ -39,27 +39,27 @@
 }, {
   "preloading_type": "Preconnect",
   "preloading_predictor": "PointerDownOnAnchor",
-  "sampling_likelihood": "0.000182"
+  "sampling_likelihood": "0.000037"
 }, {
   "preloading_type": "Prefetch",
   "preloading_predictor": "DefaultSearchEngine",
-  "sampling_likelihood": "0.022167"
+  "sampling_likelihood": "0.002909"
 }, {
   "preloading_type": "Prefetch",
   "preloading_predictor": "OmniboxMousePredictor",
-  "sampling_likelihood": "1.000000"
+  "sampling_likelihood": "0.125768"
 }, {
   "preloading_type": "Prefetch",
   "preloading_predictor": "OmniboxSearchPredictor",
-  "sampling_likelihood": "1.000000"
+  "sampling_likelihood": "0.342295"
 }, {
   "preloading_type": "Prefetch",
   "preloading_predictor": "OmniboxTouchDownPredirector",
-  "sampling_likelihood": "0.294382"
+  "sampling_likelihood": "0.063827"
 }, {
   "preloading_type": "Prefetch",
   "preloading_predictor": "SpeculationRules",
-  "sampling_likelihood": "0.002463"
+  "sampling_likelihood": "0.000794"
 }, {
   "preloading_type": "Prefetch",
   "preloading_predictor": "SpeculationRulesFromIsolatedWorld",
@@ -67,43 +67,43 @@
 }, {
   "preloading_type": "Prerender",
   "preloading_predictor": "BackButtonHover",
-  "sampling_likelihood": "0.011186"
+  "sampling_likelihood": "0.001920"
 }, {
   "preloading_type": "Prerender",
   "preloading_predictor": "BackGestureNavigation",
-  "sampling_likelihood": "0.364847"
+  "sampling_likelihood": "0.028777"
 }, {
   "preloading_type": "Prerender",
   "preloading_predictor": "DefaultSearchEngine",
-  "sampling_likelihood": "0.024857"
+  "sampling_likelihood": "0.003114"
 }, {
   "preloading_type": "Prerender",
   "preloading_predictor": "MouseBackButton",
-  "sampling_likelihood": "0.117264"
+  "sampling_likelihood": "0.041547"
 }, {
   "preloading_type": "Prerender",
   "preloading_predictor": "MouseHoverOnBookmarkBar",
-  "sampling_likelihood": "0.018748"
+  "sampling_likelihood": "0.003222"
 }, {
   "preloading_type": "Prerender",
   "preloading_predictor": "MouseHoverOnNewTabPage",
-  "sampling_likelihood": "0.823005"
+  "sampling_likelihood": "0.087301"
 }, {
   "preloading_type": "Prerender",
   "preloading_predictor": "OmniboxDirectURLInput",
-  "sampling_likelihood": "0.009997"
+  "sampling_likelihood": "0.001662"
 }, {
   "preloading_type": "Prerender",
   "preloading_predictor": "PointerDownOnBookmarkBar",
-  "sampling_likelihood": "0.058779"
+  "sampling_likelihood": "0.014170"
 }, {
   "preloading_type": "Prerender",
   "preloading_predictor": "PointerDownOnNewTabPage",
-  "sampling_likelihood": "1.000000"
+  "sampling_likelihood": "0.155139"
 }, {
   "preloading_type": "Prerender",
   "preloading_predictor": "SpeculationRules",
-  "sampling_likelihood": "1.000000"
+  "sampling_likelihood": "0.494826"
 }, {
   "preloading_type": "Prerender",
   "preloading_predictor": "SpeculationRulesFromIsolatedWorld",
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_browsertest.cc b/content/browser/renderer_host/render_widget_host_view_aura_browsertest.cc
index 9569432..ef58033 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_browsertest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_browsertest.cc
@@ -33,6 +33,9 @@
 #include "ui/display/screen.h"
 #include "ui/events/event_utils.h"
 #include "ui/events/test/event_generator.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/geometry/vector2d.h"
 
 #if BUILDFLAG(IS_WIN)
 #include "content/browser/renderer_host/legacy_render_widget_host_win.h"
@@ -304,6 +307,127 @@
   EXPECT_TRUE(IsRenderWidgetHostFocused(GetRenderViewHost()->GetWidget()));
 }
 
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraBrowserTest,
+                       UpdatesCaretBoundsAfterFrameScroll) {
+  GURL page(
+      "data:text/html;charset=utf-8,"
+      "<!DOCTYPE html>"
+      "<html>"
+      "<body>"
+      "<style>"
+      "  %23scrollableDiv {"
+      "  height: 10000px;"
+      "  }"
+      "  %23textfield {"
+      "  margin-top: 100px;"
+      "  }"
+      "</style>"
+      "<div id=\"scrollableDiv\">"
+      "  <input id=\"textfield\" type=\"text\" value=\"Some editable text\">"
+      "</div>"
+      "<script type=\"text/javascript\">"
+      "  function focusTextfield() {"
+      "    document.getElementById('textfield').focus({'preventScroll': true});"
+      "  }"
+      "</script>"
+      "</body>"
+      "</html>");
+  EXPECT_TRUE(NavigateToURL(shell(), page));
+  GetRenderWidgetHostView()->SetSize(gfx::Size(600, 500));
+
+  // Focus the textfield and wait for initial caret bounds.
+  auto* web_contents = shell()->web_contents();
+  {
+    // The caret bounds can have briefly have an invalid zero size value when
+    // the textfield initially focuses, so wait for non-zero caret size rather
+    // than waiting for the first caret bounds update.
+    NonZeroCaretSizeWaiter initial_caret_bounds_waiter(web_contents);
+    ASSERT_TRUE(ExecJs(web_contents, "focusTextfield();"));
+    initial_caret_bounds_waiter.Wait();
+  }
+
+  const gfx::Rect initial_caret_bounds =
+      GetRenderWidgetHostView()->GetCaretBounds();
+  EXPECT_NE(initial_caret_bounds, gfx::Rect());
+
+  // Scroll and wait for caret bounds to update.
+  {
+    CaretBoundsUpdateWaiter caret_bounds_update_waiter(web_contents);
+    ASSERT_TRUE(ExecJs(web_contents, "window.scrollBy(0, 50);"));
+    caret_bounds_update_waiter.Wait();
+  }
+
+  EXPECT_EQ(GetRenderWidgetHostView()->GetCaretBounds().x(),
+            initial_caret_bounds.x());
+  EXPECT_LT(GetRenderWidgetHostView()->GetCaretBounds().y(),
+            initial_caret_bounds.y());
+}
+
+IN_PROC_BROWSER_TEST_F(RenderWidgetHostViewAuraBrowserTest,
+                       UpdatesCaretBoundsAfterOverflowScroll) {
+  GURL page(
+      "data:text/html;charset=utf-8,"
+      "<!DOCTYPE html>"
+      "<html>"
+      "<body>"
+      "<style>"
+      "  %23container {"
+      "  height: 200px;"
+      "  overflow: scroll;"
+      "  }"
+      "  %23scrollableDiv {"
+      "  height: 1000px;"
+      "  }"
+      "  %23textfield {"
+      "  margin-top: 100px;"
+      "  }"
+      "</style>"
+      "<div id=\"container\">"
+      "  <div id=\"scrollableDiv\">"
+      "    <input id=\"textfield\" type=\"text\" value=\"Some editable text\">"
+      "  </div>"
+      "</div>"
+      "<script type=\"text/javascript\">"
+      "  function focusTextfield() {"
+      "    document.getElementById('textfield').focus({'preventScroll': true});"
+      "  }"
+      "  function scrollContainerTopBy(dy) {"
+      "    document.getElementById('container').scrollTop += dy;"
+      "  }"
+      "</script>"
+      "</body>"
+      "</html>");
+  EXPECT_TRUE(NavigateToURL(shell(), page));
+  GetRenderWidgetHostView()->SetSize(gfx::Size(600, 500));
+
+  // Focus the textfield and wait for initial caret bounds.
+  auto* web_contents = shell()->web_contents();
+  {
+    // The caret bounds can have briefly have an invalid zero size value when
+    // the textfield initially focuses, so wait for non-zero caret size rather
+    // than waiting for the first caret bounds update.
+    NonZeroCaretSizeWaiter initial_caret_bounds_waiter(web_contents);
+    ASSERT_TRUE(ExecJs(web_contents, "focusTextfield();"));
+    initial_caret_bounds_waiter.Wait();
+  }
+
+  const gfx::Rect initial_caret_bounds =
+      GetRenderWidgetHostView()->GetCaretBounds();
+  EXPECT_NE(initial_caret_bounds, gfx::Rect());
+
+  // Scroll and wait for caret bounds to update.
+  {
+    CaretBoundsUpdateWaiter caret_bounds_update_waiter(web_contents);
+    ASSERT_TRUE(ExecJs(web_contents, "scrollContainerTopBy(50);"));
+    caret_bounds_update_waiter.Wait();
+  }
+
+  EXPECT_EQ(GetRenderWidgetHostView()->GetCaretBounds().x(),
+            initial_caret_bounds.x());
+  EXPECT_LT(GetRenderWidgetHostView()->GetCaretBounds().y(),
+            initial_caret_bounds.y());
+}
+
 class RenderWidgetHostViewAuraDevtoolsBrowserTest
     : public content::DevToolsProtocolTest {
  public:
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index a4bba0b..4d8d36e 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -496,12 +496,13 @@
 // isolated web apps via the isolated-app:// scheme, and other advanced isolated
 // app functionality. See https://github.com/reillyeon/isolated-web-apps for a
 // general overview.
-// This also enables support for IWA Controlled Frame, providing the Controlled
-// Frame tag to IWA apps. See
-// https://github.com/chasephillips/controlled-frame/blob/main/EXPLAINER.md for
-// more info. Please don't use this feature flag directly to guard the IWA code.
-// Use IsolatedWebAppsPolicy::AreIsolatedWebAppsEnabled() in the browser process
-// or check kEnableIsolatedWebAppsInRenderer command line flag in the renderer
+// This also enables support for Controlled Frame, providing the Controlled
+// Frame tag to IWA apps assuming that Controlled Frame isn't otherwise
+// disabled via the kControlledFrame feature. See
+// https://github.com/WICG/controlled-frame/blob/main/README.md for more info.
+// Please don't use this feature flag directly to guard the IWA code.  Use
+// IsolatedWebAppsPolicy::AreIsolatedWebAppsEnabled() in the browser process or
+// check kEnableIsolatedWebAppsInRenderer command line flag in the renderer
 // process.
 BASE_FEATURE(kIsolatedWebApps,
              "IsolatedWebApps",
diff --git a/content/public/test/DEPS b/content/public/test/DEPS
index 0d15af6..abfba8f 100644
--- a/content/public/test/DEPS
+++ b/content/public/test/DEPS
@@ -5,7 +5,7 @@
   # everywhere.
   "+chromeos/crosapi/cpp/crosapi_constants.h",
   "+chromeos/lacros/lacros_service.h",
-  "+chromeos/lacros/lacros_test_helper.h",
+  "+chromeos/startup/browser_params_proxy.h",
   "+chromeos/startup/startup_switches.h",
 
   # Tests can use all content/public headers.
diff --git a/content/public/test/browser_test_base.cc b/content/public/test/browser_test_base.cc
index 501ec5a..b81f943 100644
--- a/content/public/test/browser_test_base.cc
+++ b/content/public/test/browser_test_base.cc
@@ -129,7 +129,7 @@
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "base/files/scoped_file.h"
 #include "chromeos/crosapi/cpp/crosapi_constants.h"  // nogncheck
-#include "chromeos/lacros/lacros_test_helper.h"
+#include "chromeos/startup/browser_params_proxy.h"
 #include "chromeos/startup/startup_switches.h"  // nogncheck
 #include "mojo/public/cpp/platform/socket_utils_posix.h"
 #endif
@@ -455,16 +455,14 @@
   // For more details, please see:
   // //chrome/browser/ash/crosapi/test_mojo_connection_manager.h.
   {
-    // TODO(crbug.com/1127581): Switch to use |kLacrosMojoSocketForTesting| in
-    // //ash/constants/ash_switches.h.
-    // Please refer to the CL comments for why it can't be done now:
-    // http://crrev.com/c/2402580/2/content/public/test/browser_test_base.cc
-    std::string socket_path =
-        command_line->GetSwitchValueASCII("lacros-mojo-socket-for-testing");
-    if (socket_path.empty()) {
-      disable_crosapi_ =
-          std::make_unique<chromeos::ScopedDisableCrosapiForTesting>();
-    } else {
+    if (!chromeos::BrowserParamsProxy::Get()->IsCrosapiDisabledForTesting()) {
+      // TODO(crbug.com/1127581): Switch to use |kLacrosMojoSocketForTesting| in
+      // //ash/constants/ash_switches.h.
+      // Please refer to the CL comments for why it can't be done now:
+      // http://crrev.com/c/2402580/2/content/public/test/browser_test_base.cc
+      CHECK(command_line->HasSwitch("lacros-mojo-socket-for-testing"));
+      std::string socket_path =
+          command_line->GetSwitchValueASCII("lacros-mojo-socket-for-testing");
       auto channel = mojo::NamedPlatformChannel::ConnectToServer(socket_path);
       base::ScopedFD socket_fd = channel.TakePlatformHandle().TakeFD();
 
diff --git a/content/public/test/browser_test_base.h b/content/public/test/browser_test_base.h
index 4f66dbd..c28b0381 100644
--- a/content/public/test/browser_test_base.h
+++ b/content/public/test/browser_test_base.h
@@ -45,10 +45,6 @@
 class TimeDelta;
 }  // namespace base
 
-namespace chromeos {
-class ScopedDisableCrosapiForTesting;
-}
-
 namespace ui {
 class ScopedAnimationDurationScaleMode;
 }
@@ -294,10 +290,6 @@
   // not run and report a false positive result.
   bool set_up_called_ = false;
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  std::unique_ptr<chromeos::ScopedDisableCrosapiForTesting> disable_crosapi_;
-#endif
-
   std::unique_ptr<storage::QuotaSettings> quota_settings_;
 
   std::unique_ptr<NoRendererCrashesAssertion> no_renderer_crashes_assertion_;
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index 3f98a5a..c2b4276 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -22,6 +22,7 @@
 #include "base/process/kill.h"
 #include "base/ranges/algorithm.h"
 #include "base/run_loop.h"
+#include "base/scoped_observation.h"
 #include "base/strings/pattern.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
@@ -135,6 +136,7 @@
 #include "ui/events/keycodes/dom/dom_code.h"
 #include "ui/events/keycodes/dom/keycode_converter.h"
 #include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/rect.h"
 #include "ui/latency/latency_info.h"
 
 #if !BUILDFLAG(IS_ANDROID)
@@ -1247,52 +1249,86 @@
   rwhva->OnGestureEvent(&long_tap);
 }
 
-class BoundingBoxUpdateWaiterImpl : public TextInputManager::Observer {
+// Observer which waits for the selection bounds in a RenderWidgetHostViewAura
+// to meet some desired conditions.
+class SelectionBoundsWaiter : public TextInputManager::Observer {
  public:
-  explicit BoundingBoxUpdateWaiterImpl(RenderWidgetHostViewAura* rwhva)
-      : text_input_manager_(rwhva->GetTextInputManager()),
-        original_bounding_box_(rwhva->GetSelectionBoundingBox()),
-        rwhva_(rwhva) {
-    text_input_manager_->AddObserver(this);
+  using Predicate = base::RepeatingCallback<bool()>;
+
+  SelectionBoundsWaiter(RenderWidgetHostViewAura* rwhva, Predicate predicate)
+      : predicate_(std::move(predicate)) {
+    text_input_manager_observation_.Observe(rwhva->GetTextInputManager());
   }
-  BoundingBoxUpdateWaiterImpl(const BoundingBoxUpdateWaiterImpl&) = delete;
-  BoundingBoxUpdateWaiterImpl& operator=(const BoundingBoxUpdateWaiterImpl&) =
-      delete;
-  virtual ~BoundingBoxUpdateWaiterImpl() {
-    text_input_manager_->RemoveObserver(this);
+  SelectionBoundsWaiter(const SelectionBoundsWaiter&) = delete;
+  SelectionBoundsWaiter& operator=(const SelectionBoundsWaiter&) = delete;
+  virtual ~SelectionBoundsWaiter() = default;
+
+  // TextInputManager::Observer:
+  void OnSelectionBoundsChanged(
+      TextInputManager* text_input_manager,
+      RenderWidgetHostViewBase* updated_view) override {
+    if (predicate_.Run()) {
+      run_loop_.Quit();
+    }
   }
 
   void Wait() { run_loop_.Run(); }
 
  private:
-  // TextInputManager::Observer:
-  void OnSelectionBoundsChanged(
-      TextInputManager* text_input_manager,
-      RenderWidgetHostViewBase* updated_view) override {
-    if (rwhva_->GetSelectionBoundingBox() == original_bounding_box_) {
-      return;
-    }
-
-    run_loop_.Quit();
-  }
-
-  const raw_ptr<TextInputManager> text_input_manager_;
-  const gfx::Rect original_bounding_box_;
-  const raw_ptr<RenderWidgetHostViewAura> rwhva_;
-
   base::RunLoop run_loop_;
+  Predicate predicate_;
+  base::ScopedObservation<TextInputManager, TextInputManager::Observer>
+      text_input_manager_observation_{this};
 };
 
-BoundingBoxUpdateWaiter::BoundingBoxUpdateWaiter(WebContents* web_contents) {
-  impl_ = std::make_unique<BoundingBoxUpdateWaiterImpl>(
+NonZeroCaretSizeWaiter::NonZeroCaretSizeWaiter(WebContents* web_contents) {
+  RenderWidgetHostViewAura* rwhva =
       static_cast<content::RenderWidgetHostViewAura*>(
-          web_contents->GetRenderWidgetHostView()));
+          web_contents->GetRenderWidgetHostView());
+  selection_bounds_waiter_ = std::make_unique<SelectionBoundsWaiter>(
+      rwhva, base::BindLambdaForTesting([rwhva]() {
+        return !rwhva->GetCaretBounds().size().IsZero();
+      }));
+}
+
+NonZeroCaretSizeWaiter::~NonZeroCaretSizeWaiter() = default;
+
+void NonZeroCaretSizeWaiter::Wait() {
+  selection_bounds_waiter_->Wait();
+}
+
+CaretBoundsUpdateWaiter::CaretBoundsUpdateWaiter(WebContents* web_contents) {
+  RenderWidgetHostViewAura* rwhva =
+      static_cast<content::RenderWidgetHostViewAura*>(
+          web_contents->GetRenderWidgetHostView());
+  const gfx::Rect current_caret_bounds = rwhva->GetCaretBounds();
+  selection_bounds_waiter_ = std::make_unique<SelectionBoundsWaiter>(
+      rwhva, base::BindLambdaForTesting([rwhva, current_caret_bounds]() {
+        return rwhva->GetCaretBounds() != current_caret_bounds;
+      }));
+}
+
+CaretBoundsUpdateWaiter::~CaretBoundsUpdateWaiter() = default;
+
+void CaretBoundsUpdateWaiter::Wait() {
+  selection_bounds_waiter_->Wait();
+}
+
+BoundingBoxUpdateWaiter::BoundingBoxUpdateWaiter(WebContents* web_contents) {
+  RenderWidgetHostViewAura* rwhva =
+      static_cast<content::RenderWidgetHostViewAura*>(
+          web_contents->GetRenderWidgetHostView());
+  const gfx::Rect current_bounding_box = rwhva->GetSelectionBoundingBox();
+  selection_bounds_waiter_ = std::make_unique<SelectionBoundsWaiter>(
+      rwhva, base::BindLambdaForTesting([rwhva, current_bounding_box]() {
+        return rwhva->GetSelectionBoundingBox() != current_bounding_box;
+      }));
 }
 
 BoundingBoxUpdateWaiter::~BoundingBoxUpdateWaiter() = default;
 
 void BoundingBoxUpdateWaiter::Wait() {
-  impl_->Wait();
+  selection_bounds_waiter_->Wait();
 }
 #endif
 
diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h
index 76b211d..e6b6c09 100644
--- a/content/public/test/browser_test_utils.h
+++ b/content/public/test/browser_test_utils.h
@@ -130,7 +130,10 @@
 
 namespace content {
 
-class BoundingBoxUpdateWaiterImpl;
+#if defined(USE_AURA)
+class SelectionBoundsWaiter;
+#endif  // defined(USE_AURA)
+
 class BrowserContext;
 class FileSystemAccessPermissionContext;
 class FrameTreeNode;
@@ -429,6 +432,36 @@
 
 void SimulateLongTapAt(WebContents* web_contents, const gfx::Point& point);
 
+// Can be used to wait for the caret bounds associated with `web_contents` to
+// have non-zero size.
+class NonZeroCaretSizeWaiter {
+ public:
+  explicit NonZeroCaretSizeWaiter(WebContents* web_contents);
+  NonZeroCaretSizeWaiter(const NonZeroCaretSizeWaiter&) = delete;
+  NonZeroCaretSizeWaiter& operator=(const NonZeroCaretSizeWaiter&) = delete;
+  ~NonZeroCaretSizeWaiter();
+
+  void Wait();
+
+ private:
+  std::unique_ptr<SelectionBoundsWaiter> selection_bounds_waiter_;
+};
+
+// Can be used to wait for an update to the caret bounds associated with
+// `web_contents`.
+class CaretBoundsUpdateWaiter {
+ public:
+  explicit CaretBoundsUpdateWaiter(WebContents* web_contents);
+  CaretBoundsUpdateWaiter(const CaretBoundsUpdateWaiter&) = delete;
+  CaretBoundsUpdateWaiter& operator=(const CaretBoundsUpdateWaiter&) = delete;
+  ~CaretBoundsUpdateWaiter();
+
+  void Wait();
+
+ private:
+  std::unique_ptr<SelectionBoundsWaiter> selection_bounds_waiter_;
+};
+
 // Can be used to wait for updates to the bounding box (i.e. the rectangle
 // enclosing the selection region) associated with `web_contents`.
 class BoundingBoxUpdateWaiter {
@@ -441,7 +474,7 @@
   void Wait();
 
  private:
-  std::unique_ptr<BoundingBoxUpdateWaiterImpl> impl_;
+  std::unique_ptr<SelectionBoundsWaiter> selection_bounds_waiter_;
 };
 #endif
 
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 83826abc..131f0dea 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -787,6 +787,7 @@
       "//chromeos/crosapi/cpp:crosapi_constants",
       "//chromeos/lacros",
       "//chromeos/lacros:test_support",
+      "//chromeos/startup",
       "//chromeos/startup:constants",
     ]
   }
diff --git a/content/test/data/forms/form_controls_browsertest_textarea_android.png b/content/test/data/forms/form_controls_browsertest_textarea_android.png
index 3c6895fc..5696cb2 100644
--- a/content/test/data/forms/form_controls_browsertest_textarea_android.png
+++ b/content/test/data/forms/form_controls_browsertest_textarea_android.png
Binary files differ
diff --git a/content/test/data/forms/form_controls_browsertest_textarea_chromeos.png b/content/test/data/forms/form_controls_browsertest_textarea_chromeos.png
index ed6ed033..1defed5 100644
--- a/content/test/data/forms/form_controls_browsertest_textarea_chromeos.png
+++ b/content/test/data/forms/form_controls_browsertest_textarea_chromeos.png
Binary files differ
diff --git a/content/test/data/forms/form_controls_browsertest_textarea_win.png b/content/test/data/forms/form_controls_browsertest_textarea_win.png
index 1940e9d..3fd798d 100644
--- a/content/test/data/forms/form_controls_browsertest_textarea_win.png
+++ b/content/test/data/forms/form_controls_browsertest_textarea_win.png
Binary files differ
diff --git a/content/test/gpu/gpu_tests/test_expectations/cast_streaming_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/cast_streaming_expectations.txt
index 02fb72ba..228c75f2 100644
--- a/content/test/gpu/gpu_tests/test_expectations/cast_streaming_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/cast_streaming_expectations.txt
@@ -84,4 +84,5 @@
 ###################
 # Failures/Flakes #
 ###################
+
 crbug.com/1420075 [ fuchsia-board-astro ] CastStreaming_VP8_1Frame [ RetryOnFailure ]
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index 661c8a8..cb944d4 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -342,17 +342,10 @@
 crbug.com/1420067 [ fuchsia fuchsia-board-nelson ] Pixel_Video* [ Failure ]
 crbug.com/1420067 [ fuchsia fuchsia-board-sherlock ] Pixel_Video* [ Failure ]
 
-# Failures on Sherlock
-# fuchsia-fyi-sherlock-qemu is using sherlock x64 image.
-crbug.com/1268144 [ fuchsia fuchsia-board-qemu-x64 renderer-skia-vulkan ] Pixel_BackgroundImage [ Failure ]
-crbug.com/1268144 [ fuchsia fuchsia-board-qemu-x64 renderer-skia-vulkan ] Pixel_SolidColorBackground [ Failure ]
 # These two tests causes a crash on Swarming. Skipping so that we have a signal for other tests.
 crbug.com/1268144 [ fuchsia fuchsia-board-sherlock renderer-skia-vulkan ] Pixel_BackgroundImage [ Skip ]
 crbug.com/1268144 [ fuchsia fuchsia-board-sherlock renderer-skia-vulkan ] Pixel_SolidColorBackground [ Skip ]
 
-# These test renders blank page on the emulator, likely the gpu on the emulator
-# missing some features.
-crbug.com/1490427 [ fuchsia fuchsia-board-qemu-x64 renderer-skia-vulkan ] Pixel_WebGLFloat [ Failure ]
 
 
 # Pixel 6 failures.
@@ -380,7 +373,6 @@
 crbug.com/1517110 [ clang-coverage nvidia-0x2184 nvidia_lt_31.0.15.4601 release-x64 renderer-skia-gl target-cpu-64 win10 ] Pixel_Video_* [ RetryOnFailure ]
 crbug.com/1517110 [ debug-x64 no-clang-coverage nvidia-0x2184 nvidia_lt_31.0.15.4601 renderer-skia-gl target-cpu-64 win10 ] Pixel_Video_* [ RetryOnFailure ]
 crbug.com/1517110 [ no-clang-coverage nvidia-0x2184 nvidia_ge_31.0.15.4601 release-x64 renderer-skia-gl target-cpu-64 win10 ] Pixel_Video_* [ RetryOnFailure ]
-crbug.com/1517110 [ no-clang-coverage nvidia-0x2184 nvidia_lt_31.0.15.4601 release renderer-skia-gl target-cpu-32 win10 ] Pixel_Video_* [ RetryOnFailure ]
 
 
 # Lacros consistent failures on CrOS devices, skip to save some time
@@ -426,8 +418,6 @@
 crbug.com/1495573 [ android-pixel-6 renderer-skia-vulkan ] Pixel_Video_MP4_Rounded_Corner [ RetryOnFailure ]
 
 # ChromeOS Volteer failures
-crbug.com/1516974 [ chromeos cros-chrome chromeos-board-volteer ] Pixel_ScissorTestWithPreserveDrawingBuffer [ Failure ]
-crbug.com/1516974 [ chromeos cros-chrome chromeos-board-volteer ] Pixel_WebGLGreenTriangle_NoAA_NoAlpha [ Failure ]
 crbug.com/1516974 [ chromeos cros-chrome chromeos-board-volteer ] Pixel_WebGLTransparentGreenTriangle_NoAlpha_ImplicitClear [ Failure ]
 crbug.com/1516977 [ chromeos cros-chrome chromeos-board-volteer ] Pixel_WebGLContextRestored [ Failure ]
 
diff --git a/extensions/browser/event_router.cc b/extensions/browser/event_router.cc
index 21f2a07..4149955d 100644
--- a/extensions/browser/event_router.cc
+++ b/extensions/browser/event_router.cc
@@ -287,10 +287,12 @@
         WorkerId{GenerateExtensionIdFromHostId(host_id), rph->GetID(),
                  service_worker_version_id, worker_thread_id},
         event_id);
-  } else if (BackgroundInfo::HasLazyBackgroundPage(extension)) {
+  } else if (BackgroundInfo::HasBackgroundPage(extension)) {
     // TODO(crbug.com/1441221): When creating dispatch time metrics for the
     // DispatchEventToSender event flow, ensure this also handles persistent
     // background pages.
+    // Although it's unnecessary to decrement in-flight events for non-lazy
+    // background pages, we use the logic for event tracking/metrics purposes.
     callback = base::BindOnce(
         &EventRouter::DecrementInFlightEventsForRenderFrameHost,
         weak_factory_.GetWeakPtr(), rph->GetID(), host_id.id, event_id);
@@ -1195,6 +1197,8 @@
                                 service_worker_version_id, worker_thread_id},
                        event_id);
   } else if (BackgroundInfo::HasBackgroundPage(extension)) {
+    // Although it's unnecessary to decrement in-flight events for non-lazy
+    // background pages, we use the logic for event tracking/metrics purposes.
     callback = base::BindOnce(
         &EventRouter::DecrementInFlightEventsForRenderFrameHost,
         weak_factory_.GetWeakPtr(), process->GetID(), extension_id, event_id);
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index 18bf642d1..48c255e 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -356,13 +356,13 @@
       "chrome://welcome/*",
       "chrome://vc-background/*",
       "chrome://app-settings/*",
-      "chrome://compose/*",
       "chrome://search-engine-choice/*"
     ]
   }, {
     "channel": "stable",
     "contexts": ["webui_untrusted"],
     "matches": [
+      "chrome-untrusted://compose/*",
       "chrome-untrusted://help-app/*",
       "chrome-untrusted://media-app/*",
       "chrome-untrusted://mako/*",
diff --git a/gpu/command_buffer/service/dawn_service_serializer.cc b/gpu/command_buffer/service/dawn_service_serializer.cc
index 481653fab..53559c9 100644
--- a/gpu/command_buffer/service/dawn_service_serializer.cc
+++ b/gpu/command_buffer/service/dawn_service_serializer.cc
@@ -44,6 +44,7 @@
 }
 
 void* DawnServiceSerializer::GetCmdSpace(size_t size) {
+  base::AutoLock guard(lock_);
   // Note: Dawn will never call this function with |size| >
   // GetMaximumAllocationSize().
   DCHECK_LE(put_offset_, kMaxWireBufferSize);
@@ -56,7 +57,7 @@
                 "");
   uint32_t next_offset = put_offset_ + static_cast<uint32_t>(size);
   if (next_offset > buffer_.size()) {
-    Flush();
+    FlushInternal();
     // TODO(enga): Keep track of how much command space the application is using
     // and adjust the buffer size accordingly.
 
@@ -74,9 +75,15 @@
 }
 
 bool DawnServiceSerializer::Flush() {
+  base::AutoLock guard(lock_);
+  FlushInternal();
+  return true;
+}
+
+void DawnServiceSerializer::FlushInternal() {
   if (NeedsFlush()) {
     TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("gpu.dawn"),
-                 "DawnServiceSerializer::Flush", "bytes", put_offset_);
+                 "DawnServiceSerializer::Flush", "bytes", put_offset_.load());
 
     bool is_tracing = false;
     TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("gpu.dawn"),
@@ -91,10 +98,9 @@
       header->return_data_header.trace_id = trace_id;
     }
 
-    client_->HandleReturnData(base::span(buffer_).first(put_offset_));
+    client_->HandleReturnData(base::span(buffer_).first(put_offset_.load()));
     put_offset_ = kDawnReturnCmdsOffset;
   }
-  return true;
 }
 
 }  //  namespace gpu::webgpu
diff --git a/gpu/command_buffer/service/dawn_service_serializer.h b/gpu/command_buffer/service/dawn_service_serializer.h
index dbc4bb8..b55b077 100644
--- a/gpu/command_buffer/service/dawn_service_serializer.h
+++ b/gpu/command_buffer/service/dawn_service_serializer.h
@@ -7,9 +7,11 @@
 
 #include <dawn/wire/WireClient.h>
 
+#include <atomic>
 #include <memory>
 
 #include "base/memory/raw_ptr.h"
+#include "base/synchronization/lock.h"
 
 namespace gpu {
 
@@ -27,9 +29,12 @@
   bool NeedsFlush() const;
 
  private:
+  void FlushInternal();
+
+  base::Lock lock_;
   raw_ptr<DecoderClient, DanglingUntriaged> client_;
   std::vector<uint8_t> buffer_;
-  size_t put_offset_;
+  std::atomic<size_t> put_offset_;
 };
 
 }  // namespace webgpu
diff --git a/infra/config/generated/testing/variants.pyl b/infra/config/generated/testing/variants.pyl
index 8cd9e3d..fa6783b7b 100644
--- a/infra/config/generated/testing/variants.pyl
+++ b/infra/config/generated/testing/variants.pyl
@@ -307,16 +307,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 123.0.6297.0',
+    'description': 'Run with ash-chrome version 123.0.6298.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6297.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6298.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v123.0.6297.0',
-          'revision': 'version:123.0.6297.0',
+          'location': 'lacros_version_skew_tests_v123.0.6298.0',
+          'revision': 'version:123.0.6298.0',
         },
       ],
     },
diff --git a/infra/config/targets/lacros-version-skew-variants.json b/infra/config/targets/lacros-version-skew-variants.json
index 3d883f75..e8519d6e 100644
--- a/infra/config/targets/lacros-version-skew-variants.json
+++ b/infra/config/targets/lacros-version-skew-variants.json
@@ -1,16 +1,16 @@
 {
   "LACROS_VERSION_SKEW_CANARY": {
     "args": [
-      "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6297.0/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6298.0/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 123.0.6297.0",
+    "description": "Run with ash-chrome version 123.0.6298.0",
     "identifier": "Lacros version skew testing ash canary",
     "swarming": {
       "cipd_packages": [
         {
           "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-          "location": "lacros_version_skew_tests_v123.0.6297.0",
-          "revision": "version:123.0.6297.0"
+          "location": "lacros_version_skew_tests_v123.0.6298.0",
+          "revision": "version:123.0.6298.0"
         }
       ]
     }
diff --git a/internal b/internal
index 25029d9..523a084 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 25029d94751c6df826cee247c0d57890c2cedbaa
+Subproject commit 523a084bc51377f455f0e042222acbc522a95165
diff --git a/media/audio/apple/audio_auhal.cc b/media/audio/apple/audio_auhal.cc
index f517754a..4c28d6a 100644
--- a/media/audio/apple/audio_auhal.cc
+++ b/media/audio/apple/audio_auhal.cc
@@ -74,6 +74,78 @@
                               sizeof(*format)) == noErr;
 }
 
+// Try map Rls & Rrs channel to Ls & Rs if necessary.
+//
+// Most of the configurable audio channel layout in Audio MIDI uses Side Left
+// (Ls) and Side Right (Rs) as the default surround channel, while some of the
+// WAV/FLAC/AAC audios uses Back Left (Rls) and Back Right (Rrs) as default
+// surround channel. If we unconditionally treat Rls and Rrs as it is, and if
+// Audio MIDI device has no Rls and Rrs channels (only `7.1 Surround` and
+// `7.1.4` configuration has Rls & Rrs channel for now), then these two
+// channels will be silent. QuickTime is doing something correct, so we
+// can do something similar here, the overall logic will be:
+//
+// 1. If Audio has no Rls & Rrs channels -> Do nothing.
+// 2. If Audio has Rls & Rrs channels, and has Ls & Rs channels -> Do nothing.
+// 3. If Audio has Rls & Rrs channels, and has no Ls & Rs channels, device has
+//    Rls & Rrs channels -> Do nothing.
+// 4. If Audio has Rls & Rrs channels, and has no Ls & Rs channels, device has
+//    no Rls & Rrs channels -> Map Rls to Ls, Rrs to Rs.
+void MaybeMapRearSurroundChannelToSurroundChannel(
+    const AudioUnit& audio_unit,
+    AudioChannelLayout* audio_layout) {
+  bool maybe_need_mapping = false;
+  for (UInt32 i = 0; i < audio_layout->mNumberChannelDescriptions; i++) {
+    AudioChannelLabel label =
+        audio_layout->mChannelDescriptions[i].mChannelLabel;
+    // If audio already has Ls or Rs channel, skip.
+    if (label == kAudioChannelLabel_LeftSurround ||
+        label == kAudioChannelLabel_RightSurround) {
+      return;
+    }
+    if (label == kAudioChannelLabel_RearSurroundLeft ||
+        label == kAudioChannelLabel_RearSurroundRight) {
+      maybe_need_mapping = true;
+    }
+  }
+
+  // If audio has no Rls or Rrs channel, skip.
+  if (!maybe_need_mapping) {
+    return;
+  }
+
+  auto scoped_device_layout =
+      AudioManagerApple::GetOutputDeviceChannelLayout(audio_unit);
+  if (!scoped_device_layout) {
+    return;
+  }
+  AudioChannelLayout* device_layout = scoped_device_layout->layout();
+
+  // If device has Rls or Rrs channel, skip. Since we only do mapping when
+  // Rls or Rrs channel do not exist.
+  for (UInt32 i = 0; i < device_layout->mNumberChannelDescriptions; ++i) {
+    AudioChannelLabel label =
+        device_layout->mChannelDescriptions[i].mChannelLabel;
+    if (label == kAudioChannelLabel_RearSurroundLeft ||
+        label == kAudioChannelLabel_RearSurroundRight) {
+      return;
+    }
+  }
+
+  // Map Rls to Ls, Rrs to Rs.
+  for (UInt32 i = 0; i < audio_layout->mNumberChannelDescriptions; i++) {
+    AudioChannelLabel label =
+        audio_layout->mChannelDescriptions[i].mChannelLabel;
+    if (label == kAudioChannelLabel_RearSurroundLeft) {
+      audio_layout->mChannelDescriptions[i].mChannelLabel =
+          kAudioChannelLabel_LeftSurround;
+    } else if (label == kAudioChannelLabel_RearSurroundRight) {
+      audio_layout->mChannelDescriptions[i].mChannelLabel =
+          kAudioChannelLabel_RightSurround;
+    }
+  }
+}
+
 // Converts |channel_layout| into CoreAudio format and sets up the AUHAL with
 // our layout information so it knows how to remap the channels.
 void SetAudioChannelLayout(int channels,
@@ -83,21 +155,11 @@
   DCHECK_GT(channels, 0);
   DCHECK_GT(channel_layout, CHANNEL_LAYOUT_UNSUPPORTED);
 
-  // On macOS, Audio MIDI only support setup 4 channel layout as
-  // Quadraphonic(kAudioChannelLayoutTag_Quadraphonic) which equals to
-  // "CHANNEL_LAYOUT_2_2" in FFMPEG/Chrome. FFMPEG and Chrome will guess
-  // 4 channel layout as "CHANNEL_LAYOUT_QUAD" which will always result
-  // in silent channel 3/4 output on Macs.
-  //
-  // On Windows, the system also support setup 4 channel layout as
-  // Quadraphonic(KSAUDIO_SPEAKER_QUAD) which equals to "CHANNEL_LAYOUT_QUAD"
-  // in FFMPEG/Chrome, so there is not such issue on Windows.
-  auto input_layout = channels == 4 && channel_layout == CHANNEL_LAYOUT_QUAD
-                          ? CHANNEL_LAYOUT_2_2
-                          : channel_layout;
-
   auto coreaudio_layout =
-      ChannelLayoutToAudioChannelLayout(input_layout, channels);
+      ChannelLayoutToAudioChannelLayout(channel_layout, channels);
+
+  MaybeMapRearSurroundChannelToSurroundChannel(audio_unit,
+                                               coreaudio_layout->layout());
 
   OSStatus result = AudioUnitSetProperty(
       audio_unit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Input,
diff --git a/media/audio/apple/audio_manager_apple.cc b/media/audio/apple/audio_manager_apple.cc
index 1d568e5..8bf580c0 100644
--- a/media/audio/apple/audio_manager_apple.cc
+++ b/media/audio/apple/audio_manager_apple.cc
@@ -4,6 +4,11 @@
 
 #include "media/audio/apple/audio_manager_apple.h"
 
+#include <memory>
+#include <utility>
+
+#include "base/apple/osstatus_logging.h"
+
 namespace media {
 
 AudioManagerApple::AudioManagerApple(std::unique_ptr<AudioThread> audio_thread,
@@ -12,4 +17,69 @@
 
 AudioManagerApple::~AudioManagerApple() = default;
 
+// static
+std::unique_ptr<ScopedAudioChannelLayout>
+AudioManagerApple::GetOutputDeviceChannelLayout(AudioUnit audio_unit) {
+  UInt32 size = 0;
+  // Note: We don't use kAudioDevicePropertyPreferredChannelLayout on the device
+  // because it is not available on all devices.
+  OSStatus result = AudioUnitGetPropertyInfo(
+      audio_unit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output,
+      0, &size, nullptr);
+  if (result != noErr) {
+    OSSTATUS_DLOG(ERROR, result)
+        << "Failed to get property info for AudioUnit channel layout.";
+    return nullptr;
+  }
+
+  auto output_layout = std::make_unique<ScopedAudioChannelLayout>(size);
+  result = AudioUnitGetProperty(
+      audio_unit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output,
+      0, output_layout->layout(), &size);
+  if (result != noErr) {
+    OSSTATUS_LOG(ERROR, result) << "Failed to get AudioUnit channel layout.";
+    return nullptr;
+  }
+
+  // We don't want to have to know about all channel layout tags, so force
+  // system to give us the channel descriptions from the bitmap or tag if
+  // necessary.
+  const AudioChannelLayoutTag tag = output_layout->layout()->mChannelLayoutTag;
+  if (tag == kAudioChannelLayoutTag_UseChannelDescriptions) {
+    return output_layout;
+  }
+
+  if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
+    result = AudioFormatGetPropertyInfo(
+        kAudioFormatProperty_ChannelLayoutForBitmap, sizeof(UInt32),
+        &output_layout->layout()->mChannelBitmap, &size);
+  } else {
+    result =
+        AudioFormatGetPropertyInfo(kAudioFormatProperty_ChannelLayoutForTag,
+                                   sizeof(AudioChannelLayoutTag), &tag, &size);
+  }
+  if (result != noErr || !size) {
+    OSSTATUS_DLOG(ERROR, result)
+        << "Failed to get AudioFormat property info, size=" << size;
+    return nullptr;
+  }
+
+  auto new_layout = std::make_unique<ScopedAudioChannelLayout>(size);
+  if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
+    result = AudioFormatGetProperty(
+        kAudioFormatProperty_ChannelLayoutForBitmap, sizeof(UInt32),
+        &output_layout->layout()->mChannelBitmap, &size, new_layout->layout());
+  } else {
+    result = AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag,
+                                    sizeof(AudioChannelLayoutTag), &tag, &size,
+                                    new_layout->layout());
+  }
+  if (result != noErr) {
+    OSSTATUS_DLOG(ERROR, result) << "Failed to get AudioFormat property.";
+    return nullptr;
+  }
+
+  return new_layout;
+}
+
 }  // namespace media
diff --git a/media/audio/apple/audio_manager_apple.h b/media/audio/apple/audio_manager_apple.h
index 4cae91a..441e0a2 100644
--- a/media/audio/apple/audio_manager_apple.h
+++ b/media/audio/apple/audio_manager_apple.h
@@ -8,6 +8,7 @@
 #include <AudioUnit/AudioUnit.h>
 
 #include "media/audio/audio_manager_base.h"
+#include "media/base/mac/channel_layout_util_mac.h"
 
 #if BUILDFLAG(IS_MAC)
 #include <CoreAudio/CoreAudio.h>
@@ -24,6 +25,11 @@
 
   ~AudioManagerApple() override;
 
+  // Retrieve the output channel layout from a given `audio_unit`, Return
+  // nullptr if failed.
+  static std::unique_ptr<ScopedAudioChannelLayout> GetOutputDeviceChannelLayout(
+      AudioUnit audio_unit);
+
   // Apple platform specific implementations overridden by mac and ios.
   // Manage device capabilities for ambient noise reduction. These functionality
   // currently implemented on the Mac platform.
diff --git a/media/audio/mac/audio_manager_mac.cc b/media/audio/mac/audio_manager_mac.cc
index 675f49b..0576209 100644
--- a/media/audio/mac/audio_manager_mac.cc
+++ b/media/audio/mac/audio_manager_mac.cc
@@ -55,9 +55,7 @@
 // Default sample-rate on most Apple hardware.
 static const int kFallbackSampleRate = 44100;
 
-static bool GetDeviceChannels(AudioUnit audio_unit,
-                              AUElement element,
-                              int* channels);
+static bool GetOutputDeviceChannels(AudioUnit audio_unit, int* channels);
 
 // Helper method to construct AudioObjectPropertyAddress structure given
 // property selector and scope. The property element is always set to
@@ -369,74 +367,18 @@
     return false;
   }
 
-  return GetDeviceChannels(au.audio_unit(), element, channels);
+  return GetOutputDeviceChannels(au.audio_unit(), channels);
 }
 
-static bool GetDeviceChannels(AudioUnit audio_unit,
-                              AUElement element,
-                              int* channels) {
+static bool GetOutputDeviceChannels(AudioUnit audio_unit, int* channels) {
   // Attempt to retrieve the channel layout from the AudioUnit.
-  //
-  // Note: We don't use kAudioDevicePropertyPreferredChannelLayout on the device
-  // because it is not available on all devices.
-  UInt32 size;
-  Boolean writable;
-  OSStatus result = AudioUnitGetPropertyInfo(
-      audio_unit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output,
-      element, &size, &writable);
-  if (result != noErr) {
-    OSSTATUS_DLOG(ERROR, result)
-        << "Failed to get property info for AudioUnit channel layout.";
-  }
-
-  std::unique_ptr<uint8_t[]> layout_storage(new uint8_t[size]);
-  AudioChannelLayout* layout =
-      reinterpret_cast<AudioChannelLayout*>(layout_storage.get());
-
-  result =
-      AudioUnitGetProperty(audio_unit, kAudioUnitProperty_AudioChannelLayout,
-                           kAudioUnitScope_Output, element, layout, &size);
-  if (result != noErr) {
-    OSSTATUS_LOG(ERROR, result) << "Failed to get AudioUnit channel layout.";
+  std::unique_ptr<ScopedAudioChannelLayout> audio_unit_layout =
+      AudioManagerApple::GetOutputDeviceChannelLayout(audio_unit);
+  if (!audio_unit_layout) {
+    DLOG(ERROR) << "Failed to retrieve output device channel layout.";
     return false;
   }
-
-  // We don't want to have to know about all channel layout tags, so force OSX
-  // to give us the channel descriptions from the bitmap or tag if necessary.
-  const AudioChannelLayoutTag tag = layout->mChannelLayoutTag;
-  if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
-    const bool is_bitmap = tag == kAudioChannelLayoutTag_UseChannelBitmap;
-    const AudioFormatPropertyID fa =
-        is_bitmap ? kAudioFormatProperty_ChannelLayoutForBitmap
-                  : kAudioFormatProperty_ChannelLayoutForTag;
-
-    if (is_bitmap) {
-      result = AudioFormatGetPropertyInfo(fa, sizeof(UInt32),
-                                          &layout->mChannelBitmap, &size);
-    } else {
-      result = AudioFormatGetPropertyInfo(fa, sizeof(AudioChannelLayoutTag),
-                                          &tag, &size);
-    }
-    if (result != noErr || !size) {
-      OSSTATUS_DLOG(ERROR, result)
-          << "Failed to get AudioFormat property info, size=" << size;
-      return false;
-    }
-
-    layout_storage.reset(new uint8_t[size]);
-    layout = reinterpret_cast<AudioChannelLayout*>(layout_storage.get());
-    if (is_bitmap) {
-      result = AudioFormatGetProperty(fa, sizeof(UInt32),
-                                      &layout->mChannelBitmap, &size, layout);
-    } else {
-      result = AudioFormatGetProperty(fa, sizeof(AudioChannelLayoutTag), &tag,
-                                      &size, layout);
-    }
-    if (result != noErr) {
-      OSSTATUS_DLOG(ERROR, result) << "Failed to get AudioFormat property.";
-      return false;
-    }
-  }
+  AudioChannelLayout* layout = audio_unit_layout->layout();
 
   // There is no channel info for stereo, assume so for mono as well.
   if (layout->mNumberChannelDescriptions <= 2) {
diff --git a/media/audio/win/core_audio_util_win.cc b/media/audio/win/core_audio_util_win.cc
index d4c94d3c0..b79fd5fe 100644
--- a/media/audio/win/core_audio_util_win.cc
+++ b/media/audio/win/core_audio_util_win.cc
@@ -56,20 +56,35 @@
     case CHANNEL_LAYOUT_STEREO:
       DVLOG(2) << "CHANNEL_LAYOUT_STEREO=>KSAUDIO_SPEAKER_STEREO";
       return KSAUDIO_SPEAKER_STEREO;
+    case CHANNEL_LAYOUT_2POINT1:
+      DVLOG(2) << "CHANNEL_LAYOUT_2POINT1=>KSAUDIO_SPEAKER_2POINT1";
+      return KSAUDIO_SPEAKER_2POINT1;
+    case CHANNEL_LAYOUT_SURROUND:
+      DVLOG(2) << "CHANNEL_LAYOUT_SURROUND=>KSAUDIO_SPEAKER_3POINT0";
+      return KSAUDIO_SPEAKER_3POINT0;
+    case CHANNEL_LAYOUT_3_1:
+      DVLOG(2) << "CHANNEL_LAYOUT_3_1=>KSAUDIO_SPEAKER_3POINT1";
+      return KSAUDIO_SPEAKER_3POINT1;
     case CHANNEL_LAYOUT_QUAD:
       DVLOG(2) << "CHANNEL_LAYOUT_QUAD=>KSAUDIO_SPEAKER_QUAD";
       return KSAUDIO_SPEAKER_QUAD;
     case CHANNEL_LAYOUT_4_0:
       DVLOG(2) << "CHANNEL_LAYOUT_4_0=>KSAUDIO_SPEAKER_SURROUND";
       return KSAUDIO_SPEAKER_SURROUND;
+    case CHANNEL_LAYOUT_5_0:
+      DVLOG(2) << "CHANNEL_LAYOUT_5_0=>KSAUDIO_SPEAKER_5POINT0";
+      return KSAUDIO_SPEAKER_5POINT0;
     case CHANNEL_LAYOUT_5_1_BACK:
       DVLOG(2) << "CHANNEL_LAYOUT_5_1_BACK=>KSAUDIO_SPEAKER_5POINT1";
       return KSAUDIO_SPEAKER_5POINT1;
     case CHANNEL_LAYOUT_5_1:
       DVLOG(2) << "CHANNEL_LAYOUT_5_1=>KSAUDIO_SPEAKER_5POINT1_SURROUND";
       return KSAUDIO_SPEAKER_5POINT1_SURROUND;
-    case CHANNEL_LAYOUT_7_1_WIDE:
-      DVLOG(2) << "CHANNEL_LAYOUT_7_1_WIDE=>KSAUDIO_SPEAKER_7POINT1";
+    case CHANNEL_LAYOUT_7_0:
+      DVLOG(2) << "CHANNEL_LAYOUT_7_0=>KSAUDIO_SPEAKER_7POINT0";
+      return KSAUDIO_SPEAKER_7POINT0;
+    case CHANNEL_LAYOUT_7_1_WIDE_BACK:
+      DVLOG(2) << "CHANNEL_LAYOUT_7_1_WIDE_BACK=>KSAUDIO_SPEAKER_7POINT1";
       return KSAUDIO_SPEAKER_7POINT1;
     case CHANNEL_LAYOUT_7_1:
       DVLOG(2) << "CHANNEL_LAYOUT_7_1=>KSAUDIO_SPEAKER_7POINT1_SURROUND";
diff --git a/media/base/channel_layout.cc b/media/base/channel_layout.cc
index f4836f8..704252c 100644
--- a/media/base/channel_layout.cc
+++ b/media/base/channel_layout.cc
@@ -100,13 +100,13 @@
     {  0  , 1  , 2  , 3   , 4  , 5  , -1    , -1    , -1 , -1 , -1 },
 
     // CHANNEL_LAYOUT_7_0
-    {  0  , 1  , 2  , -1  , 5  , 6  , -1    , -1    , -1 , 3  ,  4 },
+    {  0  , 1  , 2  , -1  , 3  , 4  , -1    , -1    , -1 , 5  ,  6 },
 
     // CHANNEL_LAYOUT_7_1
-    {  0  , 1  , 2  , 3   , 6  , 7  , -1    , -1    , -1 , 4  ,  5 },
+    {  0  , 1  , 2  , 3   , 4  , 5  , -1    , -1    , -1 , 6  ,  7 },
 
     // CHANNEL_LAYOUT_7_1_WIDE
-    {  0  , 1  , 2  , 3   , -1 , -1 , 6     , 7     , -1 , 4  ,  5 },
+    {  0  , 1  , 2  , 3   , -1 , -1 , 4     , 5     , -1 , 6  ,  7 },
 
     // CHANNEL_LAYOUT_STEREO_DOWNMIX
     {  0  , 1  , -1 , -1  , -1 , -1 , -1    , -1    , -1 , -1 , -1 },
@@ -118,13 +118,13 @@
     {  0  , 1  ,  2 ,  3  , -1 , -1 , -1    , -1    , -1 , -1 , -1 },
 
     // CHANNEL_LAYOUT_4_1
-    {  0  , 1  ,  2 ,  4  , -1 , -1 , -1    , -1    ,  3 , -1 , -1 },
+    {  0  , 1  ,  2 ,  3  , -1 , -1 , -1    , -1    ,  4 , -1 , -1 },
 
     // CHANNEL_LAYOUT_6_0
-    {  0  , 1  , 2  , -1  , -1 , -1 , -1    , -1    ,  5 , 3  ,  4 },
+    {  0  , 1  , 2  , -1  , -1 , -1 , -1    , -1    ,  3 , 4  ,  5 },
 
     // CHANNEL_LAYOUT_6_0_FRONT
-    {  0  , 1  , -1 , -1  , -1 , -1 ,  4    ,  5    , -1 , 2  ,  3 },
+    {  0  , 1  , -1 , -1  , -1 , -1 ,  2    ,  3    , -1 , 4  ,  5 },
 
     // FL | FR | FC | LFE | BL | BR | FLofC | FRofC | BC | SL | SR
 
@@ -132,22 +132,22 @@
     {  0  , 1  , 2  , -1  , 3  , 4  , -1    , -1    ,  5 , -1 , -1 },
 
     // CHANNEL_LAYOUT_6_1
-    {  0  , 1  , 2  , 3   , -1 , -1 , -1    , -1    ,  6 , 4  ,  5 },
+    {  0  , 1  , 2  , 3   , -1 , -1 , -1    , -1    ,  4 , 5  ,  6 },
 
     // CHANNEL_LAYOUT_6_1_BACK
     {  0  , 1  , 2  , 3   , 4  , 5  , -1    , -1    ,  6 , -1 , -1 },
 
     // CHANNEL_LAYOUT_6_1_FRONT
-    {  0  , 1  , -1 , 6   , -1 , -1 , 4     , 5     , -1 , 2  ,  3 },
+    {  0  , 1  , -1 , 2   , -1 , -1 , 3     , 4     , -1 , 5  ,  6 },
 
     // CHANNEL_LAYOUT_7_0_FRONT
-    {  0  , 1  , 2  , -1  , -1 , -1 , 5     , 6     , -1 , 3  ,  4 },
+    {  0  , 1  , 2  , -1  , -1 , -1 , 3     , 4     , -1 , 5  ,  6 },
 
     // CHANNEL_LAYOUT_7_1_WIDE_BACK
     {  0  , 1  , 2  , 3   , 4  , 5  , 6     , 7     , -1 , -1 , -1 },
 
     // CHANNEL_LAYOUT_OCTAGONAL
-    {  0  , 1  , 2  , -1  , 5  , 6  , -1    , -1    ,  7 , 3  ,  4 },
+    {  0  , 1  , 2  , -1  , 3  , 4  , -1    , -1    ,  5 , 6  ,  7 },
 
     // CHANNEL_LAYOUT_DISCRETE
     {  -1 , -1 , -1 , -1  , -1 , -1 , -1    , -1    , -1 , -1 , -1 },
@@ -156,7 +156,7 @@
     {  0  , 1  , 2  , -1  , -1 , -1 , -1    , -1    , -1 , -1 , -1 },
 
     // CHANNEL_LAYOUT_4_1_QUAD_SIDE
-    {  0  , 1  , -1 ,  4  , -1 , -1 , -1    , -1    , -1 , 2  ,  3 },
+    {  0  , 1  , -1 ,  2  , -1 , -1 , -1    , -1    , -1 , 3  ,  4 },
 
     // CHANNEL_LAYOUT_BITSTREAM
     {  -1 , -1 , -1 , -1  , -1 , -1 , -1    , -1    , -1 , -1 , -1 },
diff --git a/media/base/channel_layout.h b/media/base/channel_layout.h
index 4a26d0c0..ee5bf64a 100644
--- a/media/base/channel_layout.h
+++ b/media/base/channel_layout.h
@@ -48,13 +48,13 @@
   // Front L, Front R, Front C, LFE, Back L, Back R
   CHANNEL_LAYOUT_5_1_BACK = 12,
 
-  // Front L, Front R, Front C, Side L, Side R, Back L, Back R
+  // Front L, Front R, Front C, Back L, Back R, Side L, Side R
   CHANNEL_LAYOUT_7_0 = 13,
 
-  // Front L, Front R, Front C, LFE, Side L, Side R, Back L, Back R
+  // Front L, Front R, Front C, LFE, Back L, Back R, Side L, Side R
   CHANNEL_LAYOUT_7_1 = 14,
 
-  // Front L, Front R, Front C, LFE, Side L, Side R, Front LofC, Front RofC
+  // Front L, Front R, Front C, LFE, Front LofC, Front RofC, Side L, Side R
   CHANNEL_LAYOUT_7_1_WIDE = 15,
 
   // Front L, Front R
@@ -66,34 +66,34 @@
   // Front L, Front R, Front C, LFE
   CHANNEL_LAYOUT_3_1 = 18,
 
-  // Front L, Front R, Front C, Rear C, LFE
+  // Front L, Front R, Front C, LFE, Back C
   CHANNEL_LAYOUT_4_1 = 19,
 
-  // Front L, Front R, Front C, Side L, Side R, Back C
+  // Front L, Front R, Front C, Back C, Side L, Side R
   CHANNEL_LAYOUT_6_0 = 20,
 
-  // Front L, Front R, Side L, Side R, Front LofC, Front RofC
+  // Front L, Front R, Front LofC, Front RofC, Side L, Side R
   CHANNEL_LAYOUT_6_0_FRONT = 21,
 
-  // Front L, Front R, Front C, Rear L, Rear R, Rear C
+  // Front L, Front R, Front C, Back L, Back R, Back C
   CHANNEL_LAYOUT_HEXAGONAL = 22,
 
-  // Front L, Front R, Front C, LFE, Side L, Side R, Rear Center
+  // Front L, Front R, Front C, LFE, Back C, Side L, Side R
   CHANNEL_LAYOUT_6_1 = 23,
 
-  // Front L, Front R, Front C, LFE, Back L, Back R, Rear Center
+  // Front L, Front R, Front C, LFE, Back L, Back R, Back C
   CHANNEL_LAYOUT_6_1_BACK = 24,
 
-  // Front L, Front R, Side L, Side R, Front LofC, Front RofC, LFE
+  // Front L, Front R, LFE, Front LofC, Front RofC, Side L, Side R
   CHANNEL_LAYOUT_6_1_FRONT = 25,
 
-  // Front L, Front R, Front C, Side L, Side R, Front LofC, Front RofC
+  // Front L, Front R, Front C, Front LofC, Front RofC, Side L, Side R
   CHANNEL_LAYOUT_7_0_FRONT = 26,
 
   // Front L, Front R, Front C, LFE, Back L, Back R, Front LofC, Front RofC
   CHANNEL_LAYOUT_7_1_WIDE_BACK = 27,
 
-  // Front L, Front R, Front C, Side L, Side R, Rear L, Back R, Back C.
+  // Front L, Front R, Front C, Back L, Back R, Back C, Side L, Side R
   CHANNEL_LAYOUT_OCTAGONAL = 28,
 
   // Channels are not explicitly mapped to speakers.
@@ -106,7 +106,7 @@
   // of that.
   CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC = 30,
 
-  // Front L, Front R, Side L, Side R, LFE
+  // Front L, Front R, LFE, Side L, Side R
   CHANNEL_LAYOUT_4_1_QUAD_SIDE = 31,
 
   // Actual channel layout is specified in the bitstream and the actual channel
diff --git a/media/base/mac/channel_layout_util_mac_unittests.cc b/media/base/mac/channel_layout_util_mac_unittests.cc
index b159a0e..7422c55 100644
--- a/media/base/mac/channel_layout_util_mac_unittests.cc
+++ b/media/base/mac/channel_layout_util_mac_unittests.cc
@@ -153,19 +153,19 @@
   EXPECT_EQ(output_layout->layout()->mChannelDescriptions[3].mChannelFlags,
             kAudioChannelFlags_AllOff);
   EXPECT_EQ(output_layout->layout()->mChannelDescriptions[4].mChannelLabel,
-            kAudioChannelLabel_LeftSurround);
+            kAudioChannelLabel_RearSurroundLeft);
   EXPECT_EQ(output_layout->layout()->mChannelDescriptions[4].mChannelFlags,
             kAudioChannelFlags_AllOff);
   EXPECT_EQ(output_layout->layout()->mChannelDescriptions[5].mChannelLabel,
-            kAudioChannelLabel_RightSurround);
+            kAudioChannelLabel_RearSurroundRight);
   EXPECT_EQ(output_layout->layout()->mChannelDescriptions[5].mChannelFlags,
             kAudioChannelFlags_AllOff);
   EXPECT_EQ(output_layout->layout()->mChannelDescriptions[6].mChannelLabel,
-            kAudioChannelLabel_RearSurroundLeft);
+            kAudioChannelLabel_LeftSurround);
   EXPECT_EQ(output_layout->layout()->mChannelDescriptions[6].mChannelFlags,
             kAudioChannelFlags_AllOff);
   EXPECT_EQ(output_layout->layout()->mChannelDescriptions[7].mChannelLabel,
-            kAudioChannelLabel_RearSurroundRight);
+            kAudioChannelLabel_RightSurround);
   EXPECT_EQ(output_layout->layout()->mChannelDescriptions[7].mChannelFlags,
             kAudioChannelFlags_AllOff);
 }
diff --git a/media/base/win/mf_helpers.cc b/media/base/win/mf_helpers.cc
index a110cac5..1c784d1 100644
--- a/media/base/win/mf_helpers.cc
+++ b/media/base/win/mf_helpers.cc
@@ -19,6 +19,7 @@
 #include "base/win/windows_version.h"
 #include "media/base/audio_codecs.h"
 #include "media/base/audio_decoder_config.h"
+#include "media/base/channel_layout.h"
 #include "media/base/win/mf_helpers.h"
 #if BUILDFLAG(ENABLE_PLATFORM_AC4_AUDIO)
 #include "media/formats/mp4/ac4.h"
@@ -302,16 +303,26 @@
       return CHANNEL_LAYOUT_MONO;
     case KSAUDIO_SPEAKER_STEREO:
       return CHANNEL_LAYOUT_STEREO;
+    case KSAUDIO_SPEAKER_2POINT1:
+      return CHANNEL_LAYOUT_2POINT1;
+    case KSAUDIO_SPEAKER_3POINT0:
+      return CHANNEL_LAYOUT_SURROUND;
+    case KSAUDIO_SPEAKER_3POINT1:
+      return CHANNEL_LAYOUT_3_1;
     case KSAUDIO_SPEAKER_QUAD:
       return CHANNEL_LAYOUT_QUAD;
     case KSAUDIO_SPEAKER_SURROUND:
       return CHANNEL_LAYOUT_4_0;
+    case KSAUDIO_SPEAKER_5POINT0:
+      return CHANNEL_LAYOUT_5_0;
     case KSAUDIO_SPEAKER_5POINT1:
       return CHANNEL_LAYOUT_5_1_BACK;
     case KSAUDIO_SPEAKER_5POINT1_SURROUND:
       return CHANNEL_LAYOUT_5_1;
+    case KSAUDIO_SPEAKER_7POINT0:
+      return CHANNEL_LAYOUT_7_0;
     case KSAUDIO_SPEAKER_7POINT1:
-      return CHANNEL_LAYOUT_7_1_WIDE;
+      return CHANNEL_LAYOUT_7_1_WIDE_BACK;
     case KSAUDIO_SPEAKER_7POINT1_SURROUND:
       return CHANNEL_LAYOUT_7_1;
     case KSAUDIO_SPEAKER_DIRECTOUT:
diff --git a/media/base/win/mf_helpers.h b/media/base/win/mf_helpers.h
index 3a517ca..7df1504 100644
--- a/media/base/win/mf_helpers.h
+++ b/media/base/win/mf_helpers.h
@@ -110,11 +110,7 @@
 // Converts Microsoft's channel configuration to ChannelLayout.
 // This mapping is not perfect but the best we can do given the current
 // ChannelLayout enumerator and the Windows-specific speaker configurations
-// defined in ksmedia.h. Don't assume that the channel ordering in
-// ChannelLayout is exactly the same as the Windows specific configuration.
-// As an example: KSAUDIO_SPEAKER_7POINT1_SURROUND is mapped to
-// CHANNEL_LAYOUT_7_1 but the positions of Back L, Back R and Side L, Side R
-// speakers are different in these two definitions.
+// defined in ksmedia.h.
 MEDIA_EXPORT ChannelLayout ChannelConfigToChannelLayout(ChannelConfig config);
 
 // Converts a GUID (little endian) to a bytes array (big endian).
diff --git a/media/ffmpeg/ffmpeg_common.cc b/media/ffmpeg/ffmpeg_common.cc
index f290619..b97950f 100644
--- a/media/ffmpeg/ffmpeg_common.cc
+++ b/media/ffmpeg/ffmpeg_common.cc
@@ -843,12 +843,18 @@
       return CHANNEL_LAYOUT_MONO;
     case AV_CH_LAYOUT_STEREO:
       return CHANNEL_LAYOUT_STEREO;
+    case AV_CH_LAYOUT_2POINT1:
+      return CHANNEL_LAYOUT_2POINT1;
     case AV_CH_LAYOUT_2_1:
       return CHANNEL_LAYOUT_2_1;
     case AV_CH_LAYOUT_SURROUND:
       return CHANNEL_LAYOUT_SURROUND;
+    case AV_CH_LAYOUT_3POINT1:
+      return CHANNEL_LAYOUT_3_1;
     case AV_CH_LAYOUT_4POINT0:
       return CHANNEL_LAYOUT_4_0;
+    case AV_CH_LAYOUT_4POINT1:
+      return CHANNEL_LAYOUT_4_1;
     case AV_CH_LAYOUT_2_2:
       return CHANNEL_LAYOUT_2_2;
     case AV_CH_LAYOUT_QUAD:
@@ -861,20 +867,6 @@
       return CHANNEL_LAYOUT_5_0_BACK;
     case AV_CH_LAYOUT_5POINT1_BACK:
       return CHANNEL_LAYOUT_5_1_BACK;
-    case AV_CH_LAYOUT_7POINT0:
-      return CHANNEL_LAYOUT_7_0;
-    case AV_CH_LAYOUT_7POINT1:
-      return CHANNEL_LAYOUT_7_1;
-    case AV_CH_LAYOUT_7POINT1_WIDE:
-      return CHANNEL_LAYOUT_7_1_WIDE;
-    case AV_CH_LAYOUT_STEREO_DOWNMIX:
-      return CHANNEL_LAYOUT_STEREO_DOWNMIX;
-    case AV_CH_LAYOUT_2POINT1:
-      return CHANNEL_LAYOUT_2POINT1;
-    case AV_CH_LAYOUT_3POINT1:
-      return CHANNEL_LAYOUT_3_1;
-    case AV_CH_LAYOUT_4POINT1:
-      return CHANNEL_LAYOUT_4_1;
     case AV_CH_LAYOUT_6POINT0:
       return CHANNEL_LAYOUT_6_0;
     case AV_CH_LAYOUT_6POINT0_FRONT:
@@ -887,14 +879,22 @@
       return CHANNEL_LAYOUT_6_1_BACK;
     case AV_CH_LAYOUT_6POINT1_FRONT:
       return CHANNEL_LAYOUT_6_1_FRONT;
+    case AV_CH_LAYOUT_7POINT0:
+      return CHANNEL_LAYOUT_7_0;
     case AV_CH_LAYOUT_7POINT0_FRONT:
       return CHANNEL_LAYOUT_7_0_FRONT;
+    case AV_CH_LAYOUT_7POINT1:
+      return CHANNEL_LAYOUT_7_1;
+    case AV_CH_LAYOUT_7POINT1_WIDE:
+      return CHANNEL_LAYOUT_7_1_WIDE;
 #ifdef AV_CH_LAYOUT_7POINT1_WIDE_BACK
     case AV_CH_LAYOUT_7POINT1_WIDE_BACK:
       return CHANNEL_LAYOUT_7_1_WIDE_BACK;
 #endif
     case AV_CH_LAYOUT_OCTAGONAL:
       return CHANNEL_LAYOUT_OCTAGONAL;
+    case AV_CH_LAYOUT_STEREO_DOWNMIX:
+      return CHANNEL_LAYOUT_STEREO_DOWNMIX;
     default:
       // FFmpeg channel_layout is 0 for .wav and .mp3.  Attempt to guess layout
       // based on the channel count.
diff --git a/media/filters/stream_parser_factory.cc b/media/filters/stream_parser_factory.cc
index 1204736b..36f59c23 100644
--- a/media/filters/stream_parser_factory.cc
+++ b/media/filters/stream_parser_factory.cc
@@ -388,7 +388,7 @@
 
 static StreamParser* BuildMP4Parser(base::span<const std::string> codecs,
                                     MediaLog* media_log) {
-  std::set<int> audio_object_types;
+  base::flat_set<int> audio_object_types;
   bool has_sbr = false;
   bool has_dv = false;
 
diff --git a/media/formats/mp2t/mp2t_stream_parser.cc b/media/formats/mp2t/mp2t_stream_parser.cc
index aedfe501..d78ba685 100644
--- a/media/formats/mp2t/mp2t_stream_parser.cc
+++ b/media/formats/mp2t/mp2t_stream_parser.cc
@@ -37,14 +37,6 @@
 
 namespace {
 
-constexpr int64_t kSampleAESPrivateDataIndicatorAVC = 0x7a617663;
-constexpr int64_t kSampleAESPrivateDataIndicatorAAC = 0x61616364;
-// TODO(dougsteed). Consider adding support for the following:
-// const int64_t kSampleAESPrivateDataIndicatorAC3 = 0x61633364;
-// const int64_t kSampleAESPrivateDataIndicatorEAC3 = 0x65633364;
-
-}  // namespace
-
 enum StreamType {
   // ISO-13818.1 / ITU H.222 Table 2.34 "Stream type assignments"
   kStreamTypeMpeg1Audio = 0x3,
@@ -59,6 +51,57 @@
   //  kStreamTypeEAC3WithSampleAES = 0xc2,
 };
 
+constexpr int64_t kSampleAESPrivateDataIndicatorAVC = 0x7a617663;
+constexpr int64_t kSampleAESPrivateDataIndicatorAAC = 0x61616364;
+// TODO(dougsteed). Consider adding support for the following:
+// const int64_t kSampleAESPrivateDataIndicatorAC3 = 0x61633364;
+// const int64_t kSampleAESPrivateDataIndicatorEAC3 = 0x65633364;
+
+std::optional<base::flat_set<int>> MapAllowedStreamTypes(
+    std::optional<base::span<const std::string>> allowed_codecs) {
+  if (!allowed_codecs.has_value()) {
+    return std::nullopt;
+  }
+  base::flat_set<int> allowed_stream_types;
+  for (const std::string& codec_name : *allowed_codecs) {
+    switch (StringToVideoCodec(codec_name)) {
+      case VideoCodec::kH264:
+        allowed_stream_types.insert(kStreamTypeAVC);
+        allowed_stream_types.insert(kStreamTypeAVCWithSampleAES);
+        continue;
+      case VideoCodec::kUnknown:
+        // Probably audio.
+        break;
+      default:
+        DLOG(WARNING) << "Unsupported video codec " << codec_name;
+        continue;
+    }
+
+    switch (StringToAudioCodec(codec_name)) {
+      case AudioCodec::kAAC:
+        allowed_stream_types.insert(kStreamTypeAAC);
+        allowed_stream_types.insert(kStreamTypeAACWithSampleAES);
+        continue;
+      case AudioCodec::kMP3:
+        allowed_stream_types.insert(kStreamTypeMpeg1Audio);
+        allowed_stream_types.insert(kStreamTypeMpeg2Audio);
+        continue;
+      case AudioCodec::kUnknown:
+        // Neither audio, nor video.
+        break;
+      default:
+        DLOG(WARNING) << "Unsupported audio codec " << codec_name;
+        continue;
+    }
+
+    // Failed to parse as an audio or a video codec.
+    DLOG(WARNING) << "Unknown codec " << codec_name;
+  }
+  return allowed_stream_types;
+}
+
+}  // namespace
+
 class PidState {
  public:
   enum PidType {
@@ -186,48 +229,15 @@
 Mp2tStreamParser::BufferQueueWithConfig::~BufferQueueWithConfig() {
 }
 
-Mp2tStreamParser::Mp2tStreamParser(base::span<const std::string> allowed_codecs,
-                                   bool sbr_in_mimetype)
-    : sbr_in_mimetype_(sbr_in_mimetype),
+Mp2tStreamParser::Mp2tStreamParser(
+    std::optional<base::span<const std::string>> allowed_codecs,
+    bool sbr_in_mimetype)
+    : allowed_stream_types_(MapAllowedStreamTypes(allowed_codecs)),
+      sbr_in_mimetype_(sbr_in_mimetype),
       selected_audio_pid_(-1),
       selected_video_pid_(-1),
       is_initialized_(false),
-      segment_started_(false) {
-  for (const std::string& codec_name : allowed_codecs) {
-    switch (StringToVideoCodec(codec_name)) {
-      case VideoCodec::kH264:
-        allowed_stream_types_.insert(kStreamTypeAVC);
-        allowed_stream_types_.insert(kStreamTypeAVCWithSampleAES);
-        continue;
-      case VideoCodec::kUnknown:
-        // Probably audio.
-        break;
-      default:
-        DLOG(WARNING) << "Unsupported video codec " << codec_name;
-        continue;
-    }
-
-    switch (StringToAudioCodec(codec_name)) {
-      case AudioCodec::kAAC:
-        allowed_stream_types_.insert(kStreamTypeAAC);
-        allowed_stream_types_.insert(kStreamTypeAACWithSampleAES);
-        continue;
-      case AudioCodec::kMP3:
-        allowed_stream_types_.insert(kStreamTypeMpeg1Audio);
-        allowed_stream_types_.insert(kStreamTypeMpeg2Audio);
-        continue;
-      case AudioCodec::kUnknown:
-        // Neither audio, nor video.
-        break;
-      default:
-        DLOG(WARNING) << "Unsupported audio codec " << codec_name;
-        continue;
-    }
-
-    // Failed to parse as an audio or a video codec.
-    DLOG(WARNING) << "Unknown codec " << codec_name;
-  }
-}
+      segment_started_(false) {}
 
 Mp2tStreamParser::~Mp2tStreamParser() = default;
 
@@ -568,7 +578,8 @@
   // See https://crbug.com/1169393.
   // TODO(https://crbug.com/535738): Remove this hack when MSE stream/mime type
   // checks have been relaxed.
-  if (allowed_stream_types_.find(stream_type) == allowed_stream_types_.end()) {
+  if (allowed_stream_types_.has_value() &&
+      !allowed_stream_types_->contains(stream_type)) {
     DVLOG(1) << "Stream type not allowed for this parser: " << stream_type;
     return;
   }
diff --git a/media/formats/mp2t/mp2t_stream_parser.h b/media/formats/mp2t/mp2t_stream_parser.h
index dbff1f5c..fc10d41 100644
--- a/media/formats/mp2t/mp2t_stream_parser.h
+++ b/media/formats/mp2t/mp2t_stream_parser.h
@@ -12,6 +12,7 @@
 #include <memory>
 #include <set>
 
+#include "base/containers/flat_set.h"
 #include "base/containers/span.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
@@ -36,8 +37,9 @@
 
 class MEDIA_EXPORT Mp2tStreamParser : public StreamParser {
  public:
-  explicit Mp2tStreamParser(base::span<const std::string> allowed_codecs,
-                            bool sbr_in_mimetype);
+  explicit Mp2tStreamParser(
+      std::optional<base::span<const std::string>> allowed_codecs,
+      bool sbr_in_mimetype);
 
   Mp2tStreamParser(const Mp2tStreamParser&) = delete;
   Mp2tStreamParser& operator=(const Mp2tStreamParser&) = delete;
@@ -150,8 +152,10 @@
   EndMediaSegmentCB end_of_segment_cb_;
   raw_ptr<MediaLog> media_log_;
 
-  // List of allowed stream types for this parser.
-  std::set<int> allowed_stream_types_;
+  // List of allowed stream types for this parser. If this set is `nullopt`,
+  // allowed stream type checking is disabled. An empty set implies no codecs
+  // are allowed.
+  std::optional<base::flat_set<int>> allowed_stream_types_;
 
   // True when AAC SBR extension is signalled in the mimetype
   // (mp4a.40.5 in the codecs parameter).
diff --git a/media/formats/mp2t/mp2t_stream_parser_unittest.cc b/media/formats/mp2t/mp2t_stream_parser_unittest.cc
index 802c803..042a70c 100644
--- a/media/formats/mp2t/mp2t_stream_parser_unittest.cc
+++ b/media/formats/mp2t/mp2t_stream_parser_unittest.cc
@@ -172,9 +172,7 @@
         current_audio_config_(),
         current_video_config_(),
         capture_buffers(false) {
-    bool has_sbr = false;
-    const std::string codecs[] = {"avc1.64001e", "mp3", "aac"};
-    parser_ = std::make_unique<Mp2tStreamParser>(codecs, has_sbr);
+    CreateStrictParser();
   }
 
  protected:
@@ -199,6 +197,17 @@
   std::vector<scoped_refptr<StreamParserBuffer>> video_buffer_capture_;
   bool capture_buffers;
 
+  void CreateNonStrictParser() {
+    bool has_sbr = false;
+    parser_ = std::make_unique<Mp2tStreamParser>(std::nullopt, has_sbr);
+  }
+
+  void CreateStrictParser() {
+    bool has_sbr = false;
+    const std::string codecs[] = {"avc1.64001e", "mp3", "aac"};
+    parser_ = std::make_unique<Mp2tStreamParser>(codecs, has_sbr);
+  }
+
   void ResetStats() {
     segment_count_ = 0;
     config_count_ = 0;
@@ -404,6 +413,20 @@
   }
 };
 
+TEST_F(Mp2tStreamParserTest, NonStrictCodecChecking) {
+  CreateNonStrictParser();
+  InitializeParser();
+  ParseMpeg2TsFile("bear-1280x720.ts", 17);
+  parser_->Flush();
+  EXPECT_EQ(audio_frame_count_, 119);
+  EXPECT_EQ(video_frame_count_, 82);
+
+  // This stream has no mid-stream configuration change.
+  EXPECT_EQ(config_count_, 1);
+  EXPECT_EQ(segment_count_, 1);
+  CreateStrictParser();
+}
+
 TEST_F(Mp2tStreamParserTest, UnalignedAppend17) {
   // Test small, non-segment-aligned appends.
   InitializeParser();
diff --git a/media/formats/mp4/mp4_stream_parser.cc b/media/formats/mp4/mp4_stream_parser.cc
index fac3102..f66632d 100644
--- a/media/formats/mp4/mp4_stream_parser.cc
+++ b/media/formats/mp4/mp4_stream_parser.cc
@@ -73,18 +73,19 @@
 
 }  // namespace
 
-MP4StreamParser::MP4StreamParser(const std::set<int>& audio_object_types,
-                                 bool has_sbr,
-                                 bool has_flac,
-                                 bool has_iamf,
-                                 bool has_dv)
+MP4StreamParser::MP4StreamParser(
+    std::optional<base::flat_set<int>> strict_audio_object_types,
+    bool has_sbr,
+    bool has_flac,
+    bool has_iamf,
+    bool has_dv)
     : state_(kWaitingForInit),
       moof_head_(0),
       mdat_tail_(0),
       highest_end_offset_(0),
       has_audio_(false),
       has_video_(false),
-      audio_object_types_(audio_object_types),
+      strict_audio_object_types_(strict_audio_object_types),
       has_sbr_(has_sbr),
       has_flac_(has_flac),
       has_iamf_(has_iamf),
@@ -541,12 +542,14 @@
         }
 #endif  // BUILDFLAG(ENABLE_PLATFORM_DTS_AUDIO)
         DVLOG(1) << "audio_type 0x" << std::hex << static_cast<int>(audio_type);
-        if (audio_object_types_.find(audio_type) == audio_object_types_.end()) {
-          MEDIA_LOG(ERROR, media_log_)
-              << "audio object type 0x" << std::hex
-              << static_cast<int>(audio_type)
-              << " does not match what is specified in the mimetype.";
-          return false;
+        if (strict_audio_object_types_.has_value()) {
+          if (!strict_audio_object_types_->contains(audio_type)) {
+            MEDIA_LOG(ERROR, media_log_)
+                << "audio object type 0x" << std::hex
+                << static_cast<int>(audio_type)
+                << " does not match what is specified in the mimetype.";
+            return false;
+          }
         }
 
         // Check if it is MPEG4 AAC defined in ISO 14496 Part 3 or
diff --git a/media/formats/mp4/mp4_stream_parser.h b/media/formats/mp4/mp4_stream_parser.h
index ca0d2d5b..d375586e 100644
--- a/media/formats/mp4/mp4_stream_parser.h
+++ b/media/formats/mp4/mp4_stream_parser.h
@@ -12,6 +12,7 @@
 #include <vector>
 
 #include "base/compiler_specific.h"
+#include "base/containers/flat_set.h"
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "media/base/media_export.h"
@@ -33,7 +34,7 @@
 
 class MEDIA_EXPORT MP4StreamParser : public StreamParser {
  public:
-  MP4StreamParser(const std::set<int>& audio_object_types,
+  MP4StreamParser(std::optional<base::flat_set<int>> strict_audio_object_types,
                   bool has_sbr,
                   bool has_flac,
                   bool has_iamf,
@@ -165,9 +166,11 @@
   bool has_video_;
   std::set<uint32_t> audio_track_ids_;
   std::set<uint32_t> video_track_ids_;
+
   // The object types allowed for audio tracks. For FLAC indication, use
-  // |has_flac_|;
-  const std::set<int> audio_object_types_;
+  // |has_flac_|. If this is a nullopt, then strict object type assertion will
+  // not happen.
+  const std::optional<base::flat_set<int>> strict_audio_object_types_;
   const bool has_sbr_;
   const bool has_flac_;
   const bool has_iamf_;
diff --git a/media/formats/mp4/mp4_stream_parser_unittest.cc b/media/formats/mp4/mp4_stream_parser_unittest.cc
index 6f1ac07..4918c4ff 100644
--- a/media/formats/mp4/mp4_stream_parser_unittest.cc
+++ b/media/formats/mp4/mp4_stream_parser_unittest.cc
@@ -81,7 +81,7 @@
       : configs_received_(false),
         lower_bound_(kMaxDecodeTimestamp),
         verifying_keyframeness_sequence_(false) {
-    std::set<int> audio_object_types;
+    base::flat_set<int> audio_object_types;
     audio_object_types.insert(kISO_14496_3);
     parser_.reset(
         new MP4StreamParser(audio_object_types, false, false, false, false));
@@ -401,7 +401,7 @@
 
 TEST_F(MP4StreamParserTest, MPEG2_AAC_LC) {
   InSequence s;
-  std::set<int> audio_object_types;
+  base::flat_set<int> audio_object_types;
   audio_object_types.insert(kISO_13818_7_AAC_LC);
   parser_.reset(
       new MP4StreamParser(audio_object_types, false, false, false, false));
@@ -412,9 +412,19 @@
   EXPECT_EQ(audio_decoder_config_.profile(), AudioCodecProfile::kUnknown);
 }
 
+TEST_F(MP4StreamParserTest, ParsingAACLCNoAudioTypeStrictness) {
+  InSequence s;
+  parser_.reset(new MP4StreamParser(std::nullopt, false, false, false, false));
+  auto params = GetDefaultInitParametersExpectations();
+  params.detected_video_track_count = 0;
+  InitializeParserWithInitParametersExpectations(params);
+  ParseMP4File("bear-mpeg2-aac-only_frag.mp4", 512);
+  EXPECT_EQ(audio_decoder_config_.profile(), AudioCodecProfile::kUnknown);
+}
+
 TEST_F(MP4StreamParserTest, MPEG4_XHE_AAC) {
   InSequence s;  // The keyframeness sequence matters for this test.
-  std::set<int> audio_object_types;
+  base::flat_set<int> audio_object_types;
   audio_object_types.insert(kISO_14496_3);
   parser_.reset(
       new MP4StreamParser(audio_object_types, false, false, false, false));
@@ -624,7 +634,7 @@
 }
 
 TEST_F(MP4StreamParserTest, DemuxingDVProfile5WithDVMimeTypeSourceBuffer) {
-  std::set<int> audio_object_types;
+  base::flat_set<int> audio_object_types;
   parser_.reset(
       new MP4StreamParser(audio_object_types, false, false, false, true));
 
@@ -652,7 +662,7 @@
 }
 
 TEST_F(MP4StreamParserTest, DemuxingDVProfile5WithHEVCMimeTypeSourceBuffer) {
-  std::set<int> audio_object_types;
+  base::flat_set<int> audio_object_types;
   parser_.reset(
       new MP4StreamParser(audio_object_types, false, false, false, false));
 
@@ -680,7 +690,7 @@
 }
 
 TEST_F(MP4StreamParserTest, DemuxingDVProfile8WithDVMimeTypeSourceBuffer) {
-  std::set<int> audio_object_types;
+  base::flat_set<int> audio_object_types;
   parser_.reset(
       new MP4StreamParser(audio_object_types, false, false, false, true));
 
@@ -708,7 +718,7 @@
 }
 
 TEST_F(MP4StreamParserTest, DemuxingDVProfile8WithHEVCMimeTypeSourceBuffer) {
-  std::set<int> audio_object_types;
+  base::flat_set<int> audio_object_types;
   parser_.reset(
       new MP4StreamParser(audio_object_types, false, false, false, false));
 
@@ -740,7 +750,7 @@
 }
 
 TEST_F(MP4StreamParserTest, DemuxingAC3) {
-  std::set<int> audio_object_types;
+  base::flat_set<int> audio_object_types;
   audio_object_types.insert(kAC3);
   parser_.reset(
       new MP4StreamParser(audio_object_types, false, false, false, false));
@@ -765,7 +775,7 @@
 }
 
 TEST_F(MP4StreamParserTest, DemuxingEAC3) {
-  std::set<int> audio_object_types;
+  base::flat_set<int> audio_object_types;
   audio_object_types.insert(kEAC3);
   parser_.reset(
       new MP4StreamParser(audio_object_types, false, false, false, false));
@@ -790,7 +800,7 @@
 }
 
 TEST_F(MP4StreamParserTest, DemuxingAc4Ims) {
-  std::set<int> audio_object_types;
+  base::flat_set<int> audio_object_types;
   audio_object_types.insert(kAC4);
   parser_.reset(
       new MP4StreamParser(audio_object_types, false, false, false, false));
@@ -815,7 +825,7 @@
 }
 
 TEST_F(MP4StreamParserTest, DemuxingAc4AJoc) {
-  std::set<int> audio_object_types;
+  base::flat_set<int> audio_object_types;
   audio_object_types.insert(kAC4);
   parser_.reset(
       new MP4StreamParser(audio_object_types, false, false, false, false));
@@ -840,7 +850,7 @@
 }
 
 TEST_F(MP4StreamParserTest, DemuxingAc4ChannelBasedCoding) {
-  std::set<int> audio_object_types;
+  base::flat_set<int> audio_object_types;
   audio_object_types.insert(kAC4);
   parser_.reset(
       new MP4StreamParser(audio_object_types, false, false, false, false));
@@ -865,7 +875,7 @@
 }
 
 TEST_F(MP4StreamParserTest, DemuxingDTS) {
-  std::set<int> audio_object_types;
+  base::flat_set<int> audio_object_types;
   audio_object_types.insert(kDTS);
   parser_.reset(
       new MP4StreamParser(audio_object_types, false, false, false, false));
@@ -889,7 +899,7 @@
 }
 
 TEST_F(MP4StreamParserTest, DemuxingDTSE) {
-  std::set<int> audio_object_types;
+  base::flat_set<int> audio_object_types;
   audio_object_types.insert(kDTSE);
   parser_.reset(
       new MP4StreamParser(audio_object_types, false, false, false, false));
@@ -913,7 +923,7 @@
 }
 
 TEST_F(MP4StreamParserTest, DemuxingDTSX) {
-  std::set<int> audio_object_types;
+  base::flat_set<int> audio_object_types;
   audio_object_types.insert(kDTSX);
   parser_.reset(
       new MP4StreamParser(audio_object_types, false, false, false, false));
@@ -938,7 +948,7 @@
 
 TEST_F(MP4StreamParserTest, Flac) {
   parser_.reset(
-      new MP4StreamParser(std::set<int>(), false, true, false, false));
+      new MP4StreamParser(base::flat_set<int>(), false, true, false, false));
 
   auto params = GetDefaultInitParametersExpectations();
   params.detected_video_track_count = 0;
@@ -951,7 +961,7 @@
 
 TEST_F(MP4StreamParserTest, Flac192kHz) {
   parser_.reset(
-      new MP4StreamParser(std::set<int>(), false, true, false, false));
+      new MP4StreamParser(base::flat_set<int>(), false, true, false, false));
 
   auto params = GetDefaultInitParametersExpectations();
   params.detected_video_track_count = 0;
@@ -1100,7 +1110,7 @@
     : public ::testing::TestWithParam<MatrixRotationTestCaseParam> {
  public:
   MP4StreamParserRotationMatrixEvaluatorTest() {
-    std::set<int> audio_object_types;
+    base::flat_set<int> audio_object_types;
     audio_object_types.insert(kISO_14496_3);
     parser_.reset(
         new MP4StreamParser(audio_object_types, false, false, false, false));
diff --git a/media/gpu/BUILD.gn b/media/gpu/BUILD.gn
index ffb87ef..e6138b9 100644
--- a/media/gpu/BUILD.gn
+++ b/media/gpu/BUILD.gn
@@ -680,6 +680,16 @@
   }
 }
 
+fuzzer_test("media_h264_decoder_fuzzer") {
+  sources = [ "h264_decoder_fuzzer.cc" ]
+  deps = [
+    ":gpu",
+    "//base",
+    "//media",
+  ]
+  seed_corpuses = [ "//media/test/data" ]
+}
+
 if (use_av1_hw_decoder) {
   fuzzer_test("media_av1_decoder_fuzzer") {
     sources = [ "av1_decoder_fuzzertest.cc" ]
diff --git a/media/gpu/h264_decoder_fuzzer.cc b/media/gpu/h264_decoder_fuzzer.cc
new file mode 100644
index 0000000..eacb5df
--- /dev/null
+++ b/media/gpu/h264_decoder_fuzzer.cc
@@ -0,0 +1,91 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+
+#include "base/numerics/safe_conversions.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/video_codecs.h"
+#include "media/base/video_types.h"
+#include "media/gpu/h264_decoder.h"
+
+namespace {
+
+class FakeH264Accelerator : public media::H264Decoder::H264Accelerator {
+ public:
+  FakeH264Accelerator() = default;
+
+  FakeH264Accelerator(const FakeH264Accelerator&) = delete;
+  FakeH264Accelerator& operator=(const FakeH264Accelerator&) = delete;
+
+  ~FakeH264Accelerator() override = default;
+
+  // media::H264Decoder::H264Accelerator
+  scoped_refptr<media::H264Picture> CreateH264Picture() override {
+    return new media::H264Picture();
+  }
+
+  Status SubmitFrameMetadata(const media::H264SPS* sps,
+                             const media::H264PPS* pps,
+                             const media::H264DPB& dpb,
+                             const media::H264Picture::Vector& ref_pic_listp0,
+                             const media::H264Picture::Vector& ref_pic_listb0,
+                             const media::H264Picture::Vector& ref_pic_listb1,
+                             scoped_refptr<media::H264Picture> pic) override {
+    return Status::kOk;
+  }
+  Status SubmitSlice(
+      const media::H264PPS* pps,
+      const media::H264SliceHeader* slice_hdr,
+      const media::H264Picture::Vector& ref_pic_list0,
+      const media::H264Picture::Vector& ref_pic_list1,
+      scoped_refptr<media::H264Picture> pic,
+      const uint8_t* data,
+      size_t size,
+      const std::vector<media::SubsampleEntry>& subsamples) override {
+    return Status::kOk;
+  }
+  Status SubmitDecode(scoped_refptr<media::H264Picture> pic) override {
+    return Status::kOk;
+  }
+  bool OutputPicture(scoped_refptr<media::H264Picture> pic) override {
+    return true;
+  }
+  void Reset() override {}
+  Status SetStream(base::span<const uint8_t> stream,
+                   const media::DecryptConfig* decrypt_config) override {
+    return Status::kOk;
+  }
+};
+
+}  // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if (!size) {
+    return 0;
+  }
+
+  media::H264Decoder decoder(std::make_unique<FakeH264Accelerator>(),
+                             media::H264PROFILE_MAIN);
+  auto external_memory = std::make_unique<media::DecoderBuffer::ExternalMemory>(
+      base::make_span(data, size));
+  scoped_refptr<media::DecoderBuffer> decoder_buffer =
+      media::DecoderBuffer::FromExternalMemory(std::move(external_memory));
+  decoder.SetStream(1, *decoder_buffer);
+
+  size_t retry_count = 0;
+  while (true) {
+    switch (decoder.Decode()) {
+      case media::AcceleratedVideoDecoder::DecodeResult::kConfigChange:
+        break;
+      case media::AcceleratedVideoDecoder::DecodeResult::kTryAgain:
+        if (++retry_count > 3) {
+          return 0;
+        }
+        break;
+      default:
+        return 0;
+    }
+  }
+}
diff --git a/media/muxers/mp4_muxer_delegate_unittest.cc b/media/muxers/mp4_muxer_delegate_unittest.cc
index eff907b1..1ca8dbb 100644
--- a/media/muxers/mp4_muxer_delegate_unittest.cc
+++ b/media/muxers/mp4_muxer_delegate_unittest.cc
@@ -203,7 +203,7 @@
 
   {
     // Validate MP4 format.
-    std::set<int> audio_object_types;
+    base::flat_set<int> audio_object_types;
     audio_object_types.insert(mp4::kISO_14496_3);
     mp4::MP4StreamParser mp4_stream_parser(audio_object_types, false, false,
                                            false, false);
@@ -501,7 +501,7 @@
 
   {
     // Validate MP4 format.
-    std::set<int> audio_object_types;
+    base::flat_set<int> audio_object_types;
     audio_object_types.insert(mp4::kISO_14496_3);
     mp4::MP4StreamParser mp4_stream_parser(audio_object_types, false, false,
                                            false, false);
diff --git a/media/video/h265_parser_fuzzertest.cc b/media/video/h265_parser_fuzzertest.cc
index 53bc346..79cd420c 100644
--- a/media/video/h265_parser_fuzzertest.cc
+++ b/media/video/h265_parser_fuzzertest.cc
@@ -17,6 +17,8 @@
 
   // Parse until the end of stream/unsupported stream/error in stream is
   // found.
+  media::H265SliceHeader shdr;
+  media::H265SliceHeader prior_shdr;
   while (true) {
     media::H265NALU nalu;
     media::H265SEI sei;
@@ -24,8 +26,6 @@
     if (res != media::H265Parser::kOk)
       break;
 
-    media::H265SliceHeader shdr;
-    media::H265SliceHeader prior_shdr;
     switch (nalu.nal_unit_type) {
       case media::H265NALU::VPS_NUT:
         int vps_id;
diff --git a/media/video/vpx_video_encoder.cc b/media/video/vpx_video_encoder.cc
index 899deb5eaa..9a7efa30 100644
--- a/media/video/vpx_video_encoder.cc
+++ b/media/video/vpx_video_encoder.cc
@@ -5,6 +5,7 @@
 #include "media/video/vpx_video_encoder.h"
 
 #include <algorithm>
+#include <memory>
 #include <optional>
 
 #include "base/logging.h"
@@ -76,6 +77,7 @@
                          "Frame is too large.");
 
   config->g_pass = VPX_RC_ONE_PASS;
+  // libvpx encoding is performed synchronously.
   config->g_lag_in_frames = 0;
   config->rc_max_quantizer = 58;
   // Increase min QP to 12 for vp8 screen sharing; It reduces the encoding
@@ -679,7 +681,8 @@
   vpx_codec_flags_t flags = key_frame ? VPX_EFLAG_FORCE_KF : 0;
 
   int temporal_id = 0;
-  if (codec_config_.ts_number_layers > 1) {
+  const bool is_layer_encoding = codec_config_.ts_number_layers > 1;
+  if (is_layer_encoding) {
     if (key_frame)
       temporal_svc_frame_index_ = 0;
     unsigned int index_in_temp_cycle =
@@ -716,7 +719,20 @@
     return;
   }
 
-  DrainOutputs(temporal_id, frame->timestamp(), frame->ColorSpace());
+  auto output =
+      GetEncoderOutput(temporal_id, frame->timestamp(), frame->ColorSpace());
+  if (is_layer_encoding) {
+    // If we got an unexpected key frame, |temporal_svc_frame_index_| needs to
+    // be adjusted, because the next frame should have index 1.
+    if (output.key_frame) {
+      temporal_svc_frame_index_ = 0;
+    }
+    if (output.size != 0) {
+      temporal_svc_frame_index_++;
+    }
+  }
+
+  output_cb_.Run(std::move(output), {});
   std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 }
 
@@ -819,66 +835,36 @@
     return;
   }
 
-  auto vpx_error = vpx_codec_encode(codec_.get(), nullptr, -1, 0, 0, 0);
-  if (vpx_error != VPX_CODEC_OK) {
-    auto msg =
-        LogVpxErrorMessage(codec_.get(), "VPX flushing error", vpx_error);
-    auto status = EncoderStatus(EncoderStatus::Codes::kEncoderFailedEncode, msg)
-                      .WithData("vpx_error", vpx_error);
-    std::move(done_cb).Run(std::move(status));
-    return;
-  }
-  DrainOutputs(0, std::nullopt, gfx::ColorSpace());
+  // The libvpx encoder is operating synchronously and thus doesn't have to
+  // flush if and only if |g_lag_in_frames| is set to 0.
+  CHECK_EQ(codec_config_.g_lag_in_frames, 0u);
   std::move(done_cb).Run(EncoderStatus::Codes::kOk);
 }
 
-void VpxVideoEncoder::DrainOutputs(int temporal_id,
-                                   std::optional<base::TimeDelta> ts,
-                                   gfx::ColorSpace color_space) {
-  const bool flush = !ts.has_value();
-  bool dropped_frame = true;
+VideoEncoderOutput VpxVideoEncoder::GetEncoderOutput(
+    int temporal_id,
+    base::TimeDelta timestamp,
+    gfx::ColorSpace color_space) const {
   vpx_codec_iter_t iter = nullptr;
   const vpx_codec_cx_pkt_t* pkt = nullptr;
+  VideoEncoderOutput output;
+  // We don't given timestamps to vpx_codec_encode() that's why
+  // pkt->data.frame.pts can't be used here.
+  output.timestamp = timestamp;
   while ((pkt = vpx_codec_get_cx_data(codec_.get(), &iter))) {
     if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) {
-      dropped_frame = false;
-      VideoEncoderOutput result;
-      result.key_frame = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0;
-
-      if (result.key_frame) {
-        // If we got an unexpected key frame, |temporal_svc_frame_index_| needs
-        // to be adjusted, because the next frame should have index 1.
-        temporal_svc_frame_index_ = 0;
-        result.temporal_id = 0;
-      } else {
-        result.temporal_id = temporal_id;
-      }
-
-      // We don't given timestamps to vpx_codec_encode() that's why
-      // pkt->data.frame.pts can't be used here.
-      result.timestamp = *ts;
-      result.color_space = color_space;
-      result.size = pkt->data.frame.sz;
-      result.data = std::make_unique<uint8_t[]>(result.size);
-      memcpy(result.data.get(), pkt->data.frame.buf, result.size);
-      output_cb_.Run(std::move(result), {});
+      // The encoder is operating synchronously. There should be exactly one
+      // encoded packet, or the frame is dropped.
+      CHECK_EQ(output.size, 0u);
+      output.size = pkt->data.frame.sz;
+      output.data = std::make_unique<uint8_t[]>(output.size);
+      memcpy(output.data.get(), pkt->data.frame.buf, output.size);
+      output.key_frame = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0;
+      output.temporal_id = output.key_frame ? 0 : temporal_id;
+      output.color_space = color_space;
     }
   }
-
-  if (flush) {
-    return;
-  }
-
-  if (dropped_frame) {
-    VideoEncoderOutput result;
-    result.timestamp = *ts;
-    output_cb_.Run(std::move(result), {});
-    return;
-  }
-
-  if (codec_config_.ts_number_layers > 1) {
-    temporal_svc_frame_index_++;
-  }
+  return output;
 }
 
 void VpxVideoEncoder::RecreateVpxImageIfNeeded(vpx_img_fmt fmt,
diff --git a/media/video/vpx_video_encoder.h b/media/video/vpx_video_encoder.h
index b5e437d..099a3a2 100644
--- a/media/video/vpx_video_encoder.h
+++ b/media/video/vpx_video_encoder.h
@@ -41,9 +41,9 @@
 
  private:
   base::TimeDelta GetFrameDuration(const VideoFrame& frame);
-  void DrainOutputs(int temporal_id,
-                    std::optional<base::TimeDelta> ts,
-                    gfx::ColorSpace color_space);
+  VideoEncoderOutput GetEncoderOutput(int temporal_id,
+                                      base::TimeDelta timestamp,
+                                      gfx::ColorSpace color_space) const;
   void RecreateVpxImageIfNeeded(vpx_img_fmt fmt, bool needs_memory);
   void UpdateEncoderColorSpace();
 
diff --git a/net/quic/quic_session_pool.cc b/net/quic/quic_session_pool.cc
index 098ad85..f0e461e 100644
--- a/net/quic/quic_session_pool.cc
+++ b/net/quic/quic_session_pool.cc
@@ -168,7 +168,7 @@
     url::SchemeHostPort destination,
     quic::ParsedQuicVersion quic_version,
     const ProxyChain& proxy_chain,
-    const absl::optional<NetworkTrafficAnnotationTag> proxy_annotation_tag,
+    const std::optional<NetworkTrafficAnnotationTag> proxy_annotation_tag,
     SessionUsage session_usage,
     PrivacyMode privacy_mode,
     RequestPriority priority,
@@ -504,7 +504,7 @@
     const QuicSessionKey& session_key,
     url::SchemeHostPort destination,
     quic::ParsedQuicVersion quic_version,
-    const absl::optional<NetworkTrafficAnnotationTag> proxy_annotation_tag,
+    const std::optional<NetworkTrafficAnnotationTag> proxy_annotation_tag,
     RequestPriority priority,
     bool use_dns_aliases,
     int cert_verify_flags,
diff --git a/net/quic/quic_session_pool.h b/net/quic/quic_session_pool.h
index ccfd09cd..be2ee30 100644
--- a/net/quic/quic_session_pool.h
+++ b/net/quic/quic_session_pool.h
@@ -151,7 +151,7 @@
       url::SchemeHostPort destination,
       quic::ParsedQuicVersion quic_version,
       const ProxyChain& proxy_chain,
-      const absl::optional<NetworkTrafficAnnotationTag> proxy_annotation_tag,
+      const std::optional<NetworkTrafficAnnotationTag> proxy_annotation_tag,
       SessionUsage session_usage,
       PrivacyMode privacy_mode,
       RequestPriority priority,
@@ -343,7 +343,7 @@
       const QuicSessionKey& session_key,
       url::SchemeHostPort destination,
       quic::ParsedQuicVersion quic_version,
-      const absl::optional<NetworkTrafficAnnotationTag> proxy_annotation_tag,
+      const std::optional<NetworkTrafficAnnotationTag> proxy_annotation_tag,
       RequestPriority priority,
       bool use_dns_aliases,
       int cert_verify_flags,
diff --git a/net/quic/quic_session_pool_direct_job.cc b/net/quic/quic_session_pool_direct_job.cc
index c76d20887..26cd9e2 100644
--- a/net/quic/quic_session_pool_direct_job.cc
+++ b/net/quic/quic_session_pool_direct_job.cc
@@ -264,6 +264,7 @@
 
   return rv;
 }
+
 int QuicSessionPool::DirectJob::DoCreateSessionComplete(int rv) {
   session_creation_finished_ = true;
   if (rv != OK) {
@@ -423,23 +424,15 @@
 }
 
 void QuicSessionPool::DirectJob::OnCreateSessionComplete(int rv) {
-  if (rv != OK) {
-    DCHECK(!session_);
-    if (rv == ERR_QUIC_PROTOCOL_ERROR) {
-      HistogramProtocolErrorLocation(
-          JobProtocolErrorLocation::kCreateSessionFailedAsync);
-    }
-    for (QuicSessionRequest* request : requests()) {
-      request->OnQuicSessionCreationComplete(rv);
-    }
-    if (!callback_.is_null()) {
-      std::move(callback_).Run(rv);
-    }
-    return;
+  if (rv == ERR_QUIC_PROTOCOL_ERROR) {
+    HistogramProtocolErrorLocation(
+        JobProtocolErrorLocation::kCreateSessionFailedAsync);
   }
-  DCHECK(session_);
-  DVLOG(1) << "Created session on network: " << network_;
-  io_state_ = STATE_CREATE_SESSION_COMPLETE;
+  if (rv == OK) {
+    DCHECK(session_);
+    DVLOG(1) << "Created session on network: " << network_;
+  }
+
   rv = DoLoop(rv);
 
   for (QuicSessionRequest* request : requests()) {
@@ -523,4 +516,5 @@
 
   return quic::ParsedQuicVersion::Unsupported();
 }
+
 }  // namespace net
diff --git a/net/quic/quic_session_pool_test.cc b/net/quic/quic_session_pool_test.cc
index 4561da88..83b0a9b 100644
--- a/net/quic/quic_session_pool_test.cc
+++ b/net/quic/quic_session_pool_test.cc
@@ -375,7 +375,7 @@
     url::SchemeHostPort destination = kDefaultDestination;
     quic::ParsedQuicVersion quic_version;
     ProxyChain proxy_chain = ProxyChain::Direct();
-    absl::optional<NetworkTrafficAnnotationTag> proxy_annotation_tag =
+    std::optional<NetworkTrafficAnnotationTag> proxy_annotation_tag =
         TRAFFIC_ANNOTATION_FOR_TESTS;
     SessionUsage session_usage = SessionUsage::kDestination;
     PrivacyMode privacy_mode = PRIVACY_MODE_DISABLED;
diff --git a/net/third_party/quiche/src b/net/third_party/quiche/src
index bccc7cc..bb4f684 160000
--- a/net/third_party/quiche/src
+++ b/net/third_party/quiche/src
@@ -1 +1 @@
-Subproject commit bccc7cc648236d93d8cd4f07c82d7a15a6218bf6
+Subproject commit bb4f68479fd8fa4738339cdcc5782b04b985b72b
diff --git a/sandbox/linux/OWNERS b/sandbox/linux/OWNERS
index 29cb7a1..2ac3792 100644
--- a/sandbox/linux/OWNERS
+++ b/sandbox/linux/OWNERS
@@ -1,2 +1,3 @@
 jorgelo@chromium.org
 mpdenton@chromium.org
+rsesek@chromium.org
diff --git a/services/metrics/public/cpp/ukm_recorder.cc b/services/metrics/public/cpp/ukm_recorder.cc
index 41d2d4cd..8448958 100644
--- a/services/metrics/public/cpp/ukm_recorder.cc
+++ b/services/metrics/public/cpp/ukm_recorder.cc
@@ -89,6 +89,22 @@
                                                SourceIdType::EXTENSION_ID);
 }
 
+// static
+ukm::SourceId UkmRecorder::GetSourceIdForNotificationPermission(
+    base::PassKey<ChromePermissionsClient>,
+    const GURL& origin) {
+  return UkmRecorder::GetSourceIdFromScopeImpl(origin,
+                                               SourceIdType::NOTIFICATION_ID);
+}
+
+// static
+ukm::SourceId UkmRecorder::GetSourceIdForNotificationEvent(
+    base::PassKey<PlatformNotificationServiceImpl>,
+    const GURL& origin) {
+  return UkmRecorder::GetSourceIdFromScopeImpl(origin,
+                                               SourceIdType::NOTIFICATION_ID);
+}
+
 void UkmRecorder::RecordOtherURL(ukm::SourceIdObj source_id, const GURL& url) {
   UpdateSourceURL(source_id.ToInt64(), url);
 }
diff --git a/services/metrics/public/cpp/ukm_recorder.h b/services/metrics/public/cpp/ukm_recorder.h
index 1f835fb..c78fa3b 100644
--- a/services/metrics/public/cpp/ukm_recorder.h
+++ b/services/metrics/public/cpp/ukm_recorder.h
@@ -17,9 +17,11 @@
 #include "services/metrics/public/mojom/ukm_interface.mojom-forward.h"
 #include "url/gurl.h"
 
+class ChromePermissionsClient;
 class DIPSNavigationHandle;
 class DIPSService;
 class PermissionUmaUtil;
+class PlatformNotificationServiceImpl;
 
 namespace apps {
 class WebsiteMetrics;
@@ -141,6 +143,19 @@
       base::PassKey<apps::WebsiteMetrics>,
       const GURL& chromeos_website_url);
 
+  // Gets a new SourceId of NOTIFICATION_ID type. This should only be
+  // used for recording Permission UKM events related to persistent and
+  // nonpersistent notifications. `origin` is the domain that uses the Push API.
+  static SourceId GetSourceIdForNotificationPermission(
+      base::PassKey<ChromePermissionsClient>,
+      const GURL& origin);
+
+  // Gets a new SourceId of NOTIFICATION_ID type. This should only be used
+  // for recording persistent and nonpersistent notification UKM events.
+  static SourceId GetSourceIdForNotificationEvent(
+      base::PassKey<PlatformNotificationServiceImpl>,
+      const GURL& origin);
+
   // This method should be called when the system is about to shutdown, but
   // `UkmRecorder` is still available to record metrics.
   // Calls `OnStartingShutdown` on each observer from `observers_`.
diff --git a/services/metrics/public/cpp/ukm_source.cc b/services/metrics/public/cpp/ukm_source.cc
index 035d2355..77aa22d 100644
--- a/services/metrics/public/cpp/ukm_source.cc
+++ b/services/metrics/public/cpp/ukm_source.cc
@@ -66,6 +66,8 @@
       return SourceType::CHROMEOS_WEBSITE_ID;
     case SourceIdType::EXTENSION_ID:
       return SourceType::EXTENSION_ID;
+    case SourceIdType::NOTIFICATION_ID:
+      return SourceType::NOTIFICATION_ID;
   }
 }
 
diff --git a/services/metrics/public/cpp/ukm_source_id.cc b/services/metrics/public/cpp/ukm_source_id.cc
index ed7c07a..b693d582 100644
--- a/services/metrics/public/cpp/ukm_source_id.cc
+++ b/services/metrics/public/cpp/ukm_source_id.cc
@@ -113,6 +113,8 @@
       return "CHROMEOS_WEBSITE_ID";
     case SourceIdObj::Type::EXTENSION_ID:
       return "EXTENSION_ID";
+    case SourceIdObj::Type::NOTIFICATION_ID:
+      return "NOTIFICATION_ID";
   }
 }
 
diff --git a/services/metrics/public/cpp/ukm_source_id.h b/services/metrics/public/cpp/ukm_source_id.h
index 4af9acc..91f18ea 100644
--- a/services/metrics/public/cpp/ukm_source_id.h
+++ b/services/metrics/public/cpp/ukm_source_id.h
@@ -90,8 +90,16 @@
     // Some criteria (e.g. checking if it's a synced extension) will be applied
     // when recording metrics with this type.
     EXTENSION_ID = 12,
+    // Source ID type for service-worker triggered persisted notification
+    // events.
+    // Notification events may occur in the background and an associated URL is
+    // not necessarily present in the browsing history. A new source of this
+    // type and associated events are expected to be recorded within the same
+    // report
+    // interval; it will not be kept in memory between different reports.
+    NOTIFICATION_ID = 13,
 
-    kMaxValue = EXTENSION_ID,
+    kMaxValue = NOTIFICATION_ID,
   };
 
   // Default constructor has the invalid value.
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 6ce7499f..19cf30ad 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5311,9 +5311,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6297.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6298.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6297.0",
+        "description": "Run with ash-chrome version 123.0.6298.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5323,8 +5323,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6297.0",
-              "revision": "version:123.0.6297.0"
+              "location": "lacros_version_skew_tests_v123.0.6298.0",
+              "revision": "version:123.0.6298.0"
             }
           ],
           "dimensions": {
@@ -5467,9 +5467,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6297.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6298.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6297.0",
+        "description": "Run with ash-chrome version 123.0.6298.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5479,8 +5479,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6297.0",
-              "revision": "version:123.0.6297.0"
+              "location": "lacros_version_skew_tests_v123.0.6298.0",
+              "revision": "version:123.0.6298.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index b8711799..f498a05 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -20464,9 +20464,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6297.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6298.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6297.0",
+        "description": "Run with ash-chrome version 123.0.6298.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20476,8 +20476,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6297.0",
-              "revision": "version:123.0.6297.0"
+              "location": "lacros_version_skew_tests_v123.0.6298.0",
+              "revision": "version:123.0.6298.0"
             }
           ],
           "dimensions": {
@@ -20614,9 +20614,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6297.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6298.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6297.0",
+        "description": "Run with ash-chrome version 123.0.6298.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20626,8 +20626,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6297.0",
-              "revision": "version:123.0.6297.0"
+              "location": "lacros_version_skew_tests_v123.0.6298.0",
+              "revision": "version:123.0.6298.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index f1a8481..914161b 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -41453,9 +41453,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6297.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6298.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6297.0",
+        "description": "Run with ash-chrome version 123.0.6298.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41464,8 +41464,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6297.0",
-              "revision": "version:123.0.6297.0"
+              "location": "lacros_version_skew_tests_v123.0.6298.0",
+              "revision": "version:123.0.6298.0"
             }
           ],
           "dimensions": {
@@ -41603,9 +41603,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6297.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6298.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6297.0",
+        "description": "Run with ash-chrome version 123.0.6298.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41614,8 +41614,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6297.0",
-              "revision": "version:123.0.6297.0"
+              "location": "lacros_version_skew_tests_v123.0.6298.0",
+              "revision": "version:123.0.6298.0"
             }
           ],
           "dimensions": {
@@ -42935,9 +42935,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6297.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6298.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6297.0",
+        "description": "Run with ash-chrome version 123.0.6298.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -42946,8 +42946,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6297.0",
-              "revision": "version:123.0.6297.0"
+              "location": "lacros_version_skew_tests_v123.0.6298.0",
+              "revision": "version:123.0.6298.0"
             }
           ],
           "dimensions": {
@@ -43085,9 +43085,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6297.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6298.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6297.0",
+        "description": "Run with ash-chrome version 123.0.6298.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -43096,8 +43096,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6297.0",
-              "revision": "version:123.0.6297.0"
+              "location": "lacros_version_skew_tests_v123.0.6298.0",
+              "revision": "version:123.0.6298.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index 23e4ce5..7490f1c 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -16507,12 +16507,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6297.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6298.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 123.0.6297.0",
+        "description": "Run with ash-chrome version 123.0.6298.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16522,8 +16522,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6297.0",
-              "revision": "version:123.0.6297.0"
+              "location": "lacros_version_skew_tests_v123.0.6298.0",
+              "revision": "version:123.0.6298.0"
             }
           ],
           "dimensions": {
@@ -16683,12 +16683,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6297.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6298.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 123.0.6297.0",
+        "description": "Run with ash-chrome version 123.0.6298.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16698,8 +16698,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6297.0",
-              "revision": "version:123.0.6297.0"
+              "location": "lacros_version_skew_tests_v123.0.6298.0",
+              "revision": "version:123.0.6298.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter b/testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter
index 62db514..fc44ec8 100644
--- a/testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter
+++ b/testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter
@@ -57,3 +57,9 @@
 -TabDragging/DetachToBrowserTabDragControllerTest.SelectTabDuringDrag/2
 -TabDragging/DetachToBrowserTabDragControllerTest.TabDragContextOwnsDraggedTabs/0
 -TabDragging/DetachToBrowserTabDragControllerTest.TabDragContextOwnsDraggedTabs/2
+
+# TODO(b/324465984) These tests are failing because of an issue with the skew
+# roller. They're using a new testing API which requires support from a version
+# of Ash > 121.0.6167.16. Remove this once the skew roller is fixed and a more
+# recent version of Ash lands in the skew tests.
+-TabDragging/DetachToBrowserInSeparateDisplayTabDragControllerTest.DragTabToWindowInSeparateDisplay/*
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 8cd9e3d..fa6783b7b 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -307,16 +307,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 123.0.6297.0',
+    'description': 'Run with ash-chrome version 123.0.6298.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6297.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6298.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v123.0.6297.0',
-          'revision': 'version:123.0.6297.0',
+          'location': 'lacros_version_skew_tests_v123.0.6298.0',
+          'revision': 'version:123.0.6298.0',
         },
       ],
     },
diff --git a/testing/libfuzzer/OWNERS b/testing/libfuzzer/OWNERS
index a173923..f084f05 100644
--- a/testing/libfuzzer/OWNERS
+++ b/testing/libfuzzer/OWNERS
@@ -1,10 +1,9 @@
 titouan@chromium.org
-pgrace@chromium.org
 ahijazi@chromium.org
 paulsemel@chromium.org
 pmeuleman@chromium.org
+tiszka@chromium.org
 
 adetaylor@chromium.org    #{LAST_RESORT_SUGGESTION}
 bookholt@chromium.org     #{LAST_RESORT_SUGGESTION}
-tiszka@chromium.org       #{LAST_RESORT_SUGGESTION}
 metzman@chromium.org      #{LAST_RESORT_SUGGESTION}
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index c59cc328..fd333d1 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -17840,6 +17840,27 @@
             ]
         }
     ],
+    "SharedStorageAPIEnableWALForDatabase": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "fuchsia",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "EnableWAL",
+                    "enable_features": [
+                        "SharedStorageAPIEnableWALForDatabase"
+                    ]
+                }
+            ]
+        }
+    ],
     "SharedStorageWorkletThreadImplementation": [
         {
             "platforms": [
diff --git a/third_party/angle b/third_party/angle
index e784b1e..cb7d3cc 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit e784b1ec822888844ec60abcf50d169f0347f6cf
+Subproject commit cb7d3cc206d6a4165c0012d78387b714b55a0556
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index c3a9c45a..a01baca 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -1228,11 +1228,6 @@
         &kLCPCriticalPathPredictor, "lcpp_image_load_priority",
         LcppResourceLoadPriority::kVeryHigh, &lcpp_resource_load_priorities};
 
-const base::FeatureParam<LcppResourceLoadPriority>
-    kLCPCriticalPathPredictorInfluencerScriptLoadPriority{
-        &kLCPCriticalPathPredictor, "lcpp_script_load_priority",
-        LcppResourceLoadPriority::kVeryHigh, &lcpp_resource_load_priorities};
-
 const base::FeatureParam<bool>
     kLCPCriticalPathPredictorEnableElementLocatorPerformanceImprovements{
         &kLCPCriticalPathPredictor, "lcpp_enable_perf_improvements", true};
@@ -1246,11 +1241,25 @@
              "LCPScriptObserver",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+const base::FeatureParam<LcppResourceLoadPriority>
+    kLCPScriptObserverScriptLoadPriority{
+        &kLCPScriptObserver, "lcpscriptobserver_script_load_priority",
+        LcppResourceLoadPriority::kVeryHigh, &lcpp_resource_load_priorities};
+
+const base::FeatureParam<LcppResourceLoadPriority>
+    kLCPScriptObserverImageLoadPriority{
+        &kLCPScriptObserver, "lcpscriptobserver_image_load_priority",
+        LcppResourceLoadPriority::kVeryHigh, &lcpp_resource_load_priorities};
+
 const base::FeatureParam<int> kLCPScriptObserverMaxUrlLength{
-    &kLCPScriptObserver, "lcpp_max_url_length", 1024};
+    &kLCPScriptObserver, "lcpscriptobserver_script_max_url_length", 1024};
 
 const base::FeatureParam<int> kLCPScriptObserverMaxUrlCountPerOrigin{
-    &kLCPScriptObserver, "lcpp_max_url_count_per_origin", 5};
+    &kLCPScriptObserver, "lcpscriptobserver_script_max_url_count_per_origin",
+    5};
+
+const base::FeatureParam<bool> kLCPScriptObserverAdjustImageLoadPriority{
+    &kLCPScriptObserver, "lcpscriptobserver_adjust_image_load_priority", false};
 
 BASE_FEATURE(kLCPPAutoPreconnectLcpOrigin,
              "LCPPAutoPreconnectLcpOrigin",
diff --git a/third_party/blink/common/loader/lcp_critical_path_predictor_util.cc b/third_party/blink/common/loader/lcp_critical_path_predictor_util.cc
index d0bca58..14ed75c 100644
--- a/third_party/blink/common/loader/lcp_critical_path_predictor_util.cc
+++ b/third_party/blink/common/loader/lcp_critical_path_predictor_util.cc
@@ -24,4 +24,12 @@
              blink::features::kLCPPAutoPreconnectLcpOrigin);
 }
 
+bool LcppScriptObserverEnabled() {
+  return base::FeatureList::IsEnabled(blink::features::kLCPScriptObserver) ||
+         (base::FeatureList::IsEnabled(
+              features::kLowPriorityAsyncScriptExecution) &&
+          features::kLowPriorityAsyncScriptExecutionExcludeLcpInfluencersParam
+              .Get());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index fd25b8a7..dd4fc02 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -690,10 +690,6 @@
 BLINK_COMMON_EXPORT extern const base::FeatureParam<LcppResourceLoadPriority>
     kLCPCriticalPathPredictorImageLoadPriority;
 
-// The ResourceLoadPriority for scripts that are expected to be LCP influencers.
-BLINK_COMMON_EXPORT extern const base::FeatureParam<LcppResourceLoadPriority>
-    kLCPCriticalPathPredictorInfluencerScriptLoadPriority;
-
 // Enables LCPP ElementLocator performance improvements
 BLINK_COMMON_EXPORT extern const base::FeatureParam<bool>
     kLCPCriticalPathPredictorEnableElementLocatorPerformanceImprovements;
@@ -706,6 +702,14 @@
 // the LCP element.
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kLCPScriptObserver);
 
+// The ResourceLoadPriority for scripts that are expected to be LCP influencers.
+BLINK_COMMON_EXPORT extern const base::FeatureParam<LcppResourceLoadPriority>
+    kLCPScriptObserverScriptLoadPriority;
+
+// The ResourceLoadPriority for images that are expected to LCP elements.
+BLINK_COMMON_EXPORT extern const base::FeatureParam<LcppResourceLoadPriority>
+    kLCPScriptObserverImageLoadPriority;
+
 // The maximum URL count for LCPP.
 BLINK_COMMON_EXPORT extern const base::FeatureParam<int>
     kLCPScriptObserverMaxUrlCountPerOrigin;
@@ -714,6 +718,10 @@
 BLINK_COMMON_EXPORT extern const base::FeatureParam<int>
     kLCPScriptObserverMaxUrlLength;
 
+// Enable ResourceLoadPriority changes for all HTMLImageElement loaded images.
+BLINK_COMMON_EXPORT extern const base::FeatureParam<bool>
+    kLCPScriptObserverAdjustImageLoadPriority;
+
 // If enabled, LCP image origin is predicted and preconnected automatically.
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kLCPPAutoPreconnectLcpOrigin);
 
diff --git a/third_party/blink/public/common/loader/lcp_critical_path_predictor_util.h b/third_party/blink/public/common/loader/lcp_critical_path_predictor_util.h
index c8f563b..7056cbc6 100644
--- a/third_party/blink/public/common/loader/lcp_critical_path_predictor_util.h
+++ b/third_party/blink/public/common/loader/lcp_critical_path_predictor_util.h
@@ -10,6 +10,7 @@
 namespace blink {
 
 BLINK_COMMON_EXPORT bool LcppEnabled();
+BLINK_COMMON_EXPORT bool LcppScriptObserverEnabled();
 
 }  // namespace blink
 
diff --git a/third_party/blink/public/mojom/devtools/inspector_issue.mojom b/third_party/blink/public/mojom/devtools/inspector_issue.mojom
index d4c618a..86618ffb 100644
--- a/third_party/blink/public/mojom/devtools/inspector_issue.mojom
+++ b/third_party/blink/public/mojom/devtools/inspector_issue.mojom
@@ -91,6 +91,7 @@
   kInvalidRegisterOsTriggerHeader,
   kWebAndOsHeaders,
   kNoWebOrOsSupport,
+  kNavigationRegistrationWithoutTransientUserActivation,
 };
 
 struct AttributionReportingIssueDetails {
diff --git a/third_party/blink/renderer/core/editing/dom_selection.cc b/third_party/blink/renderer/core/editing/dom_selection.cc
index 798fe21..b9eed89 100644
--- a/third_party/blink/renderer/core/editing/dom_selection.cc
+++ b/third_party/blink/renderer/core/editing/dom_selection.cc
@@ -100,20 +100,6 @@
   return Selection().GetSelectionInDOMTree().IsBaseFirst();
 }
 
-// TODO(tkent): Following four functions based on VisibleSelection should be
-// removed.
-static Position AnchorPosition(const VisibleSelection& selection) {
-  Position anchor =
-      selection.IsBaseFirst() ? selection.Start() : selection.End();
-  return anchor.ParentAnchoredEquivalent();
-}
-
-static Position FocusPosition(const VisibleSelection& selection) {
-  Position focus =
-      selection.IsBaseFirst() ? selection.End() : selection.Start();
-  return focus.ParentAnchoredEquivalent();
-}
-
 Node* DOMSelection::anchorNode() const {
   if (Range* range = PrimaryRangeOrNull()) {
     if (!DomWindow() || IsBaseFirstInSelection())
@@ -548,7 +534,7 @@
 
 EphemeralRange DOMSelection::CreateRangeFromSelectionEditor() const {
   const VisibleSelection& selection = GetVisibleSelection();
-  const Position& anchor = blink::AnchorPosition(selection);
+  const Position& anchor = selection.Base().ParentAnchoredEquivalent();
   if (IsSelectionOfDocument() && !anchor.AnchorNode()->IsInShadowTree())
     return FirstEphemeralRangeOf(selection);
 
@@ -556,7 +542,7 @@
   if (!anchor_node)  // crbug.com/595100
     return EphemeralRange();
 
-  const Position& focus = FocusPosition(selection);
+  const Position& focus = selection.Extent().ParentAnchoredEquivalent();
   const Position shadow_adjusted_focus =
       Position(ShadowAdjustedNode(focus), ShadowAdjustedOffset(focus));
   const Position shadow_adjusted_anchor =
diff --git a/third_party/blink/renderer/core/editing/frame_selection_test.cc b/third_party/blink/renderer/core/editing/frame_selection_test.cc
index d7310bc..c842391c 100644
--- a/third_party/blink/renderer/core/editing/frame_selection_test.cc
+++ b/third_party/blink/renderer/core/editing/frame_selection_test.cc
@@ -35,6 +35,8 @@
 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
 #include "third_party/blink/renderer/platform/testing/fake_display_item_client.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/vector2d.h"
 
 namespace blink {
 
@@ -1396,6 +1398,33 @@
             frame_view->FrameToDocument(Selection().AbsoluteUnclippedBounds()));
 }
 
+TEST_F(FrameSelectionTest, AbosluteSelectionBoundsAfterScroll) {
+  SetBodyContent(
+      "<style>"
+      "  html, body { height: 2000px; }"
+      "</style>"
+      "<div style='height:1000px;'>"
+      "  <p style='margin-top:100px; font-size:30px'>text</p>"
+      "</div>");
+  Selection().SelectAll();
+
+  gfx::Rect initial_anchor, initial_focus;
+  Selection().ComputeAbsoluteBounds(initial_anchor, initial_focus);
+
+  // Scroll 50px down.
+  const int scroll_offset = 50;
+  GetDocument().View()->LayoutViewport()->SetScrollOffset(
+      ScrollOffset(0, scroll_offset), mojom::blink::ScrollType::kProgrammatic);
+
+  // Check absolute selection bounds are updated.
+  gfx::Rect anchor_after_scroll, focus_after_scroll;
+  Selection().ComputeAbsoluteBounds(anchor_after_scroll, focus_after_scroll);
+  EXPECT_EQ(anchor_after_scroll,
+            initial_anchor - gfx::Vector2d(0, scroll_offset));
+  EXPECT_EQ(focus_after_scroll,
+            initial_focus - gfx::Vector2d(0, scroll_offset));
+}
+
 TEST_F(FrameSelectionTest, SelectionContainsBidiBoundary) {
   InsertStyleElement("div{font:10px/10px Ahem}");
   // Rendered as abcFED
diff --git a/third_party/blink/renderer/core/frame/attribution_src_loader.cc b/third_party/blink/renderer/core/frame/attribution_src_loader.cc
index 5d49715..f4ce11dd 100644
--- a/third_party/blink/renderer/core/frame/attribution_src_loader.cc
+++ b/third_party/blink/renderer/core/frame/attribution_src_loader.cc
@@ -39,6 +39,7 @@
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/conversions/attribution_data_host.mojom-blink.h"
 #include "third_party/blink/public/mojom/conversions/conversions.mojom-blink.h"
+#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom-blink.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom-blink.h"
 #include "third_party/blink/public/platform/platform.h"
@@ -84,6 +85,8 @@
 using ::attribution_reporting::mojom::SourceType;
 using ::network::mojom::AttributionReportingEligibility;
 
+using mojom::blink::AttributionReportingIssueType;
+
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
 enum class AttributionSrcRequestStatus {
diff --git a/third_party/blink/renderer/core/html/html_image_element.cc b/third_party/blink/renderer/core/html/html_image_element.cc
index ae63ba1..4221de5 100644
--- a/third_party/blink/renderer/core/html/html_image_element.cc
+++ b/third_party/blink/renderer/core/html/html_image_element.cc
@@ -23,6 +23,7 @@
 
 #include "third_party/blink/renderer/core/html/html_image_element.h"
 
+#include "third_party/blink/public/common/loader/lcp_critical_path_predictor_util.h"
 #include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_image_bitmap_options.h"
 #include "third_party/blink/renderer/core/css/css_property_names.h"
@@ -120,7 +121,7 @@
       is_changed_shortly_after_mouseover_(false),
       is_auto_sized_(false),
       is_predicted_lcp_element_(false) {
-  if (base::FeatureList::IsEnabled(features::kLCPScriptObserver)) {
+  if (blink::LcppScriptObserverEnabled()) {
     if (LocalFrame* frame = document.GetFrame()) {
       if (LCPCriticalPathPredictor* lcpp = frame->GetLCPP()) {
         if (LCPScriptObserver* script_observer = lcpp->lcp_script_observer()) {
diff --git a/third_party/blink/renderer/core/inspector/inspector_audits_issue.cc b/third_party/blink/renderer/core/inspector/inspector_audits_issue.cc
index d1b8ceb..f28b1f5 100644
--- a/third_party/blink/renderer/core/inspector/inspector_audits_issue.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_audits_issue.cc
@@ -187,6 +187,8 @@
 
 namespace {
 
+using mojom::blink::AttributionReportingIssueType;
+
 protocol::Audits::AttributionReportingIssueType
 BuildAttributionReportingIssueType(AttributionReportingIssueType type) {
   switch (type) {
@@ -239,11 +241,12 @@
 
 }  // namespace
 
-void AuditsIssue::ReportAttributionIssue(ExecutionContext* execution_context,
-                                         AttributionReportingIssueType type,
-                                         Element* element,
-                                         const String& request_id,
-                                         const String& invalid_parameter) {
+void AuditsIssue::ReportAttributionIssue(
+    ExecutionContext* execution_context,
+    AttributionReportingIssueType type,
+    Element* element,
+    const String& request_id,
+    const String& invalid_parameter) {
   auto details = protocol::Audits::AttributionReportingIssueDetails::create()
                      .setViolationType(BuildAttributionReportingIssueType(type))
                      .build();
diff --git a/third_party/blink/renderer/core/inspector/inspector_audits_issue.h b/third_party/blink/renderer/core/inspector/inspector_audits_issue.h
index cecce24..6c23774 100644
--- a/third_party/blink/renderer/core/inspector/inspector_audits_issue.h
+++ b/third_party/blink/renderer/core/inspector/inspector_audits_issue.h
@@ -47,24 +47,6 @@
   kNoCorsRedirectModeNotFollow,
 };
 
-enum class AttributionReportingIssueType {
-  kPermissionPolicyDisabled,
-  kUntrustworthyReportingOrigin,
-  kInsecureContext,
-  kInvalidRegisterSourceHeader,
-  kInvalidRegisterTriggerHeader,
-  kSourceAndTriggerHeaders,
-  kSourceIgnored,
-  kTriggerIgnored,
-  kOsSourceIgnored,
-  kOsTriggerIgnored,
-  kInvalidRegisterOsSourceHeader,
-  kInvalidRegisterOsTriggerHeader,
-  kWebAndOsHeaders,
-  kNoWebOrOsSupport,
-  kNavigationRegistrationWithoutTransientUserActivation,
-};
-
 enum class SharedArrayBufferIssueType {
   kTransferIssue,
   kCreationIssue,
@@ -124,11 +106,12 @@
                               WTF::String failedParameter,
                               std::optional<base::UnguessableToken> issue_id);
 
-  static void ReportAttributionIssue(ExecutionContext* execution_context,
-                                     AttributionReportingIssueType type,
-                                     Element* element,
-                                     const String& request_id,
-                                     const String& invalid_parameter);
+  static void ReportAttributionIssue(
+      ExecutionContext* execution_context,
+      mojom::blink::AttributionReportingIssueType type,
+      Element* element,
+      const String& request_id,
+      const String& invalid_parameter);
 
   static void ReportSharedArrayBufferIssue(
       ExecutionContext* execution_context,
diff --git a/third_party/blink/renderer/core/layout/inline/inline_items_builder.cc b/third_party/blink/renderer/core/layout/inline/inline_items_builder.cc
index aefefccd..000970b0 100644
--- a/third_party/blink/renderer/core/layout/inline/inline_items_builder.cc
+++ b/third_party/blink/renderer/core/layout/inline/inline_items_builder.cc
@@ -529,9 +529,10 @@
   TextOffsetMap offset_map;
   String transformed =
       layout_text->TransformAndSecureText(original, offset_map);
-  DCHECK_EQ(layout_text->TransformedText(), transformed);
+  CHECK_EQ(layout_text->TransformedText().length(), transformed.length());
   const Vector<unsigned> length_map = TransformedString::CreateLengthMap(
       original.length(), transformed.length(), offset_map);
+  CHECK(transformed.length() == length_map.size() || length_map.size() == 0);
   AppendText(TransformedString(layout_text->TransformedText(),
                                {length_map.data(), length_map.size()}),
              *layout_text);
diff --git a/third_party/blink/renderer/core/layout/inline/transformed_string.cc b/third_party/blink/renderer/core/layout/inline/transformed_string.cc
index 50f40aaa..7127097 100644
--- a/third_party/blink/renderer/core/layout/inline/transformed_string.cc
+++ b/third_party/blink/renderer/core/layout/inline/transformed_string.cc
@@ -62,6 +62,9 @@
   if (length_map_.empty()) {
     return TransformedString(sub_view);
   }
+  CHECK_EQ(view_.length(), length_map_.size());
+  CHECK_LE(start, view_.length());
+  CHECK_LE(start + length, view_.length());
   return TransformedString(sub_view, length_map_.subspan(start, length));
 }
 
diff --git a/third_party/blink/renderer/core/lcp_critical_path_predictor/lcp_critical_path_predictor.cc b/third_party/blink/renderer/core/lcp_critical_path_predictor/lcp_critical_path_predictor.cc
index 4e258e10..f6d546a 100644
--- a/third_party/blink/renderer/core/lcp_critical_path_predictor/lcp_critical_path_predictor.cc
+++ b/third_party/blink/renderer/core/lcp_critical_path_predictor/lcp_critical_path_predictor.cc
@@ -30,7 +30,7 @@
       host_(frame.DomWindow()),
       task_runner_(frame.GetTaskRunner(TaskType::kInternalLoading)) {
   CHECK(LcppEnabled());
-  if (base::FeatureList::IsEnabled(features::kLCPScriptObserver)) {
+  if (blink::LcppScriptObserverEnabled()) {
     lcp_script_observer_ = MakeGarbageCollected<LCPScriptObserver>(frame_);
   }
 }
@@ -210,7 +210,7 @@
     }
   }
 
-  if (base::FeatureList::IsEnabled(features::kLCPScriptObserver)) {
+  if (blink::LcppScriptObserverEnabled()) {
     if (const HTMLImageElement* image_element =
             DynamicTo<HTMLImageElement>(lcp_element)) {
       auto& creators = image_element->creator_scripts();
diff --git a/third_party/blink/renderer/core/lcp_critical_path_predictor/lcp_script_observer.cc b/third_party/blink/renderer/core/lcp_critical_path_predictor/lcp_script_observer.cc
index 43dde3f..320c158 100644
--- a/third_party/blink/renderer/core/lcp_critical_path_predictor/lcp_script_observer.cc
+++ b/third_party/blink/renderer/core/lcp_critical_path_predictor/lcp_script_observer.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/core/lcp_critical_path_predictor/lcp_script_observer.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/loader/lcp_critical_path_predictor_util.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 
 namespace blink {
@@ -67,7 +68,7 @@
 
 LCPScriptObserver::LCPScriptObserver(LocalFrame* local_root)
     : local_root_(local_root) {
-  CHECK(base::FeatureList::IsEnabled(features::kLCPScriptObserver));
+  CHECK(blink::LcppScriptObserverEnabled());
   local_root_->GetProbeSink()->AddLCPScriptObserver(this);
 }
 
diff --git a/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc b/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
index ac8d45b..7a00436 100644
--- a/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
@@ -843,37 +843,19 @@
   )HTML");
   ForceFullCompositingUpdate();
 
-  // The scrolling contents layer is fully marked as pan-y.
+  // The outer layer (not scrollable) will be fully marked as pan-y (100x100)
+  // and the scrollable layer will only have the contents marked as pan-y
+  // (50x150).
   const auto* scrolling_contents_layer =
       ScrollingContentsLayerByDOMElementId("scrollable");
   cc::Region region =
       scrolling_contents_layer->touch_action_region().GetRegionForTouchAction(
           TouchAction::kPanY | TouchAction::kInternalNotWritable);
-  if (RuntimeEnabledFeatures::HitTestOpaquenessEnabled()) {
-    EXPECT_EQ(scrolling_contents_layer->bounds(), gfx::Size(100, 150));
-    EXPECT_EQ(region.bounds(), gfx::Rect(0, 0, 100, 150));
-  } else {
-    // When HitTestOpaqueness is not enabled, the scrolling contents layer
-    // contains only the drawable contents due to the lack of the hit test
-    // data for the whole scrolling contents.
-    EXPECT_EQ(scrolling_contents_layer->bounds(), gfx::Size(50, 150));
-    EXPECT_EQ(region.bounds(), gfx::Rect(0, 0, 50, 150));
-  }
+  EXPECT_EQ(region.bounds(), gfx::Rect(0, 0, 50, 150));
 
-  const auto* container_layer = LayerByDOMElementId("scrollable");
+  const auto* container_layer = MainFrameScrollingContentsLayer();
   region = container_layer->touch_action_region().GetRegionForTouchAction(
       TouchAction::kPanY | TouchAction::kInternalNotWritable);
-  EXPECT_EQ(region.bounds(), gfx::Rect());
-  // TODO(crbug.com/324285520): Do we need touch action data in a ScrollHitTest
-  // layer?
-  EXPECT_EQ(container_layer->bounds(), gfx::Size(100, 100));
-
-  // The area of the scroller (8,8 100x100) in the main frame scrolling
-  // contents layer is also marked as pan-y.
-  const auto* main_frame_scrolling_layer = MainFrameScrollingContentsLayer();
-  region =
-      main_frame_scrolling_layer->touch_action_region().GetRegionForTouchAction(
-          TouchAction::kPanY | TouchAction::kInternalNotWritable);
   EXPECT_EQ(region.bounds(), gfx::Rect(8, 8, 100, 100));
 }
 
diff --git a/third_party/blink/renderer/core/paint/block_painter_test.cc b/third_party/blink/renderer/core/paint/block_painter_test.cc
index 6446c06d..c38e47ab 100644
--- a/third_party/blink/renderer/core/paint/block_painter_test.cc
+++ b/third_party/blink/renderer/core/paint/block_painter_test.cc
@@ -659,10 +659,7 @@
       scroller->FirstFragment().PaintProperties()->ScrollTranslation();
   scroll_hit_test_data.scroll_hit_test_rect = gfx::Rect(0, 0, 100, 100);
   HitTestData scrolled_hit_test_data;
-  scrolled_hit_test_data.touch_action_rects = {
-      {RuntimeEnabledFeatures::HitTestOpaquenessEnabled()
-           ? gfx::Rect(0, 0, 200, 100)
-           : gfx::Rect(0, 0, 200, 50)}};
+  scrolled_hit_test_data.touch_action_rects = {{gfx::Rect(0, 0, 200, 50)}};
 
   const auto& paint_chunks = ContentPaintChunks();
   EXPECT_THAT(
@@ -677,17 +674,11 @@
               1, 1, PaintChunk::Id(scroller->Id(), DisplayItem::kScrollHitTest),
               scroller->FirstFragment().LocalBorderBoxProperties(),
               &scroll_hit_test_data, gfx::Rect(0, 0, 100, 100)),
-          IsPaintChunk(
-              1, 1,
-              PaintChunk::Id(scroller->Id(),
-                             RuntimeEnabledFeatures::HitTestOpaquenessEnabled()
-                                 ? kScrollingBackgroundChunkType
-                                 : kClippedContentsBackgroundChunkType),
-              scroller->FirstFragment().ContentsProperties(),
-              &scrolled_hit_test_data,
-              RuntimeEnabledFeatures::HitTestOpaquenessEnabled()
-                  ? gfx::Rect(0, 0, 200, 100)
-                  : gfx::Rect(0, 0, 200, 50))));
+          IsPaintChunk(1, 1,
+                       PaintChunk::Id(scroller->Id(),
+                                      kClippedContentsBackgroundChunkType),
+                       scroller->FirstFragment().ContentsProperties(),
+                       &scrolled_hit_test_data, gfx::Rect(0, 0, 200, 50))));
 
   const auto& scroller_paint_chunk = paint_chunks[1];
   // The hit test rect for the scroller itself should not be scrolled.
diff --git a/third_party/blink/renderer/core/paint/box_fragment_painter.cc b/third_party/blink/renderer/core/paint/box_fragment_painter.cc
index 9ac3710..3b05112 100644
--- a/third_party/blink/renderer/core/paint/box_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/box_fragment_painter.cc
@@ -476,21 +476,14 @@
     // We need to call PaintObject twice: one for painting background in the
     // border box space, and the other for painting background in the scrolling
     // contents space.
-    const LayoutBox& box = To<LayoutBox>(*box_fragment_.GetLayoutObject());
-    auto paint_location = box.GetBackgroundPaintLocation();
+    auto paint_location = To<LayoutBox>(*box_fragment_.GetLayoutObject())
+                              .GetBackgroundPaintLocation();
     if (!(paint_location & kBackgroundPaintInBorderBoxSpace))
       info.SetSkipsBackground(true);
     PaintObject(info, paint_offset);
     info.SetSkipsBackground(false);
 
-    if ((RuntimeEnabledFeatures::HitTestOpaquenessEnabled() &&
-         // We need to record hit test data for the scrolling contents.
-         box.ScrollsOverflow()) ||
-        (paint_location & kBackgroundPaintInContentsSpace)) {
-      if (!(paint_location & kBackgroundPaintInContentsSpace)) {
-        DCHECK(RuntimeEnabledFeatures::HitTestOpaquenessEnabled());
-        info.SetSkipsBackground(true);
-      }
+    if (paint_location & kBackgroundPaintInContentsSpace) {
       // If possible, paint overflow controls before scrolling background to
       // make it easier to merge scrolling background and scrolling contents
       // into the same layer. The function checks if it's appropriate to paint
@@ -500,9 +493,7 @@
       info.SetIsPaintingBackgroundInContentsSpace(true);
       PaintObject(info, paint_offset);
       info.SetIsPaintingBackgroundInContentsSpace(false);
-      info.SetSkipsBackground(false);
     }
-
     if (ShouldPaintDescendantBlockBackgrounds(original_phase))
       info.phase = PaintPhase::kDescendantBlockBackgroundsOnly;
   }
@@ -1072,12 +1063,7 @@
   }
 
   Element* element = DynamicTo<Element>(layout_object.GetNode());
-  if (element && element->GetRegionCaptureCropId() &&
-      // TODO(wangxianzhu): This is to avoid the side-effect of
-      // HitTestOpaqueness on region capture data. Verify if the side-effect
-      // really matters.
-      !(paint_info.IsPaintingBackgroundInContentsSpace() &&
-        paint_info.ShouldSkipBackground())) {
+  if (element && element->GetRegionCaptureCropId()) {
     paint_info.context.GetPaintController().RecordRegionCaptureData(
         *background_client, *(element->GetRegionCaptureCropId()),
         ToPixelSnappedRect(paint_rect));
diff --git a/third_party/blink/renderer/core/paint/box_paint_invalidator.cc b/third_party/blink/renderer/core/paint/box_paint_invalidator.cc
index af8f8c6..7180477 100644
--- a/third_party/blink/renderer/core/paint/box_paint_invalidator.cc
+++ b/third_party/blink/renderer/core/paint/box_paint_invalidator.cc
@@ -389,13 +389,6 @@
         .SetShouldDoFullPaintInvalidationWithoutLayoutChange(
             PaintInvalidationReason::kBackground);
   }
-
-  if (background_invalidation_type == BackgroundInvalidationType::kNone &&
-      box_.ScrollsOverflow() &&
-      box_.PreviousScrollableOverflowRect() != box_.ScrollableOverflowRect()) {
-    // We need to re-record the hit test data for scrolling contents.
-    context_.painting_layer->SetNeedsRepaint();
-  }
 }
 
 void BoxPaintInvalidator::InvalidatePaint() {
diff --git a/third_party/blink/renderer/core/paint/box_painter_test.cc b/third_party/blink/renderer/core/paint/box_painter_test.cc
index a18170f..981599b 100644
--- a/third_party/blink/renderer/core/paint/box_painter_test.cc
+++ b/third_party/blink/renderer/core/paint/box_painter_test.cc
@@ -214,13 +214,10 @@
               2, 2, PaintChunk::Id(container.Id(), DisplayItem::kScrollHitTest),
               container.FirstFragment().LocalBorderBoxProperties(),
               &scroll_hit_test_data, gfx::Rect(0, 0, 200, 200)),
-          IsPaintChunk(
-              2, 3,
-              PaintChunk::Id(container.Id(),
-                             RuntimeEnabledFeatures::HitTestOpaquenessEnabled()
-                                 ? kScrollingBackgroundChunkType
-                                 : kClippedContentsBackgroundChunkType),
-              scrolling_contents_properties)));
+          IsPaintChunk(2, 3,
+                       PaintChunk::Id(container.Id(),
+                                      kClippedContentsBackgroundChunkType),
+                       scrolling_contents_properties)));
 
   // We always create scroll node for the root layer.
   const auto& root_transform =
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 b952085..948f22a 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
@@ -834,15 +834,13 @@
     return layers.empty() ? nullptr : layers[0];
   }
 
-  const cc::Layer* CcLayerByOwnerNode(Node* node) {
-    return CcLayerByOwnerNodeId(RootCcLayer(), node->GetDomNodeId());
-  }
-
-  const cc::Layer* CcLayerForIFrameContent(Document* iframe_doc) {
-    if (RuntimeEnabledFeatures::HitTestOpaquenessEnabled()) {
-      return CcLayerByOwnerNode(iframe_doc);
+  const cc::Layer* CcLayerByOwnerNodeId(Node* node) {
+    DOMNodeId id = node->GetDomNodeId();
+    for (auto& layer : RootCcLayer()->children()) {
+      if (layer->debug_info() && layer->debug_info()->owner_node_id == id)
+        return layer.get();
     }
-    return CcLayerByOwnerNode(iframe_doc->documentElement());
+    return nullptr;
   }
 
   Element* GetElementById(const char* id) {
@@ -1968,7 +1966,8 @@
   Compositor().BeginFrame();
   Document* iframe_doc =
       To<HTMLFrameOwnerElement>(GetElementById("iframe"))->contentDocument();
-  auto* layer = CcLayerForIFrameContent(iframe_doc);
+  Node* owner_node = iframe_doc->documentElement();
+  auto* layer = CcLayerByOwnerNodeId(owner_node);
   EXPECT_TRUE(layer);
   EXPECT_EQ(layer->bounds(), gfx::Size(300, 150));
 }
@@ -1990,7 +1989,8 @@
 
   Document* iframe_doc =
       To<HTMLFrameOwnerElement>(GetElementById("iframe"))->contentDocument();
-  EXPECT_TRUE(CcLayerForIFrameContent(iframe_doc));
+  Node* owner_node = iframe_doc->documentElement();
+  EXPECT_TRUE(CcLayerByOwnerNodeId(owner_node));
 }
 
 // An iframe that is cross-origin to the parent should be composited. This test
@@ -2016,12 +2016,13 @@
   Document* iframe_doc =
       To<HTMLFrameOwnerElement>(GetElementById("main_iframe"))
           ->contentDocument();
-  EXPECT_TRUE(CcLayerByOwnerNode(iframe_doc));
+  EXPECT_TRUE(CcLayerByOwnerNodeId(iframe_doc));
 
   iframe_doc = To<HTMLFrameOwnerElement>(
                    iframe_doc->getElementById(AtomicString("child_iframe")))
                    ->contentDocument();
-  EXPECT_TRUE(CcLayerForIFrameContent(iframe_doc));
+  Node* owner_node = iframe_doc->documentElement();
+  EXPECT_TRUE(CcLayerByOwnerNodeId(owner_node));
 }
 
 // Initially the iframe is cross-origin and should be composited. After changing
@@ -2043,7 +2044,8 @@
 
   Document* iframe_doc =
       To<HTMLFrameOwnerElement>(GetElementById("iframe"))->contentDocument();
-  EXPECT_TRUE(CcLayerForIFrameContent(iframe_doc));
+  Node* owner_node = iframe_doc->documentElement();
+  EXPECT_TRUE(CcLayerByOwnerNodeId(owner_node));
 
   NonThrowableExceptionState exception_state;
   GetDocument().setDomain(String("origin-a.com"), exception_state);
@@ -2055,7 +2057,8 @@
 
   iframe_doc =
       To<HTMLFrameOwnerElement>(GetElementById("iframe"))->contentDocument();
-  EXPECT_FALSE(CcLayerForIFrameContent(iframe_doc));
+  owner_node = iframe_doc->documentElement();
+  EXPECT_FALSE(CcLayerByOwnerNodeId(owner_node));
 }
 
 // This test sets up nested frames with domains A -> B -> A. Initially, the
@@ -2082,12 +2085,13 @@
   Document* iframe_doc =
       To<HTMLFrameOwnerElement>(GetElementById("main_iframe"))
           ->contentDocument();
-  EXPECT_TRUE(CcLayerByOwnerNode(iframe_doc));
+  EXPECT_TRUE(CcLayerByOwnerNodeId(iframe_doc));
 
   iframe_doc = To<HTMLFrameOwnerElement>(
                    iframe_doc->getElementById(AtomicString("child_iframe")))
                    ->contentDocument();
-  EXPECT_TRUE(CcLayerForIFrameContent(iframe_doc));
+  Node* owner_node = iframe_doc->documentElement();
+  EXPECT_TRUE(CcLayerByOwnerNodeId(owner_node));
 
   auto* main_iframe_element = To<HTMLIFrameElement>(
       GetDocument().getElementById(AtomicString("main_iframe")));
@@ -2107,12 +2111,13 @@
   UpdateAllLifecyclePhases();
   iframe_doc = To<HTMLFrameOwnerElement>(GetElementById("main_iframe"))
                    ->contentDocument();
-  EXPECT_FALSE(CcLayerByOwnerNode(iframe_doc));
+  EXPECT_FALSE(CcLayerByOwnerNodeId(iframe_doc));
 
   iframe_doc = To<HTMLFrameOwnerElement>(
                    iframe_doc->getElementById(AtomicString("child_iframe")))
                    ->contentDocument();
-  EXPECT_FALSE(CcLayerForIFrameContent(iframe_doc));
+  owner_node = iframe_doc->documentElement();
+  EXPECT_FALSE(CcLayerByOwnerNodeId(owner_node));
 }
 
 // Regression test for https://crbug.com/1095167. Render surfaces require that
@@ -2341,7 +2346,8 @@
   // containing document.
   Document* iframe_doc =
       To<HTMLFrameOwnerElement>(GetElementById("iframe"))->contentDocument();
-  auto* iframe_layer = CcLayerForIFrameContent(iframe_doc);
+  Node* owner_node = iframe_doc->documentElement();
+  auto* iframe_layer = CcLayerByOwnerNodeId(owner_node);
   ASSERT_TRUE(iframe_layer);
   auto* iframe_transform_node = GetTransformNode(iframe_layer);
   EXPECT_TRUE(iframe_transform_node);
@@ -2365,7 +2371,7 @@
   Compositor().BeginFrame();
 
   // Ensure that the toplevel is marked as a visible root.
-  auto* toplevel_layer = CcLayerByOwnerNode(&GetDocument());
+  auto* toplevel_layer = CcLayerByOwnerNodeId(&GetDocument());
   ASSERT_TRUE(toplevel_layer);
   auto* toplevel_transform_node = GetTransformNode(toplevel_layer);
   ASSERT_TRUE(toplevel_transform_node);
@@ -2375,7 +2381,8 @@
   // Ensure that the iframe is marked as a visible root.
   Document* iframe_doc =
       To<HTMLFrameOwnerElement>(GetElementById("iframe"))->contentDocument();
-  auto* iframe_layer = CcLayerForIFrameContent(iframe_doc);
+  Node* owner_node = iframe_doc->documentElement();
+  auto* iframe_layer = CcLayerByOwnerNodeId(owner_node);
   ASSERT_TRUE(iframe_layer);
   auto* iframe_transform_node = GetTransformNode(iframe_layer);
   ASSERT_TRUE(iframe_transform_node);
@@ -2389,7 +2396,7 @@
 
   UpdateAllLifecyclePhases();
 
-  iframe_layer = CcLayerForIFrameContent(iframe_doc);
+  iframe_layer = CcLayerByOwnerNodeId(owner_node);
   ASSERT_TRUE(iframe_layer);
   iframe_transform_node = GetTransformNode(iframe_layer);
   ASSERT_TRUE(iframe_transform_node);
diff --git a/third_party/blink/renderer/core/paint/paint_controller_paint_test.cc b/third_party/blink/renderer/core/paint/paint_controller_paint_test.cc
index 337aca22..a6e1c7ce 100644
--- a/third_party/blink/renderer/core/paint/paint_controller_paint_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_controller_paint_test.cc
@@ -230,13 +230,10 @@
               1, 1, PaintChunk::Id(container.Id(), DisplayItem::kScrollHitTest),
               container.FirstFragment().LocalBorderBoxProperties(),
               &container_scroll_hit_test, gfx::Rect(0, 0, 200, 200)),
-          IsPaintChunk(
-              1, 3,
-              PaintChunk::Id(container.Id(),
-                             RuntimeEnabledFeatures::HitTestOpaquenessEnabled()
-                                 ? kScrollingBackgroundChunkType
-                                 : kClippedContentsBackgroundChunkType),
-              container.FirstFragment().ContentsProperties())));
+          IsPaintChunk(1, 3,
+                       PaintChunk::Id(container.Id(),
+                                      kClippedContentsBackgroundChunkType),
+                       container.FirstFragment().ContentsProperties())));
 
   container.GetScrollableArea()->SetScrollOffset(
       ScrollOffset(4000, 4000), mojom::blink::ScrollType::kProgrammatic);
@@ -261,13 +258,10 @@
               1, 1, PaintChunk::Id(container.Id(), DisplayItem::kScrollHitTest),
               container.FirstFragment().LocalBorderBoxProperties(),
               &container_scroll_hit_test, gfx::Rect(0, 0, 200, 200)),
-          IsPaintChunk(
-              1, 3,
-              PaintChunk::Id(container.Id(),
-                             RuntimeEnabledFeatures::HitTestOpaquenessEnabled()
-                                 ? kScrollingBackgroundChunkType
-                                 : kClippedContentsBackgroundChunkType),
-              container.FirstFragment().ContentsProperties())));
+          IsPaintChunk(1, 3,
+                       PaintChunk::Id(container.Id(),
+                                      kClippedContentsBackgroundChunkType),
+                       container.FirstFragment().ContentsProperties())));
 }
 
 TEST_P(PaintControllerPaintTest, ScrollHitTestOrder) {
@@ -548,13 +542,10 @@
               2, 2, PaintChunk::Id(container.Id(), DisplayItem::kScrollHitTest),
               container.FirstFragment().LocalBorderBoxProperties(),
               &container_scroll_hit_test, gfx::Rect(0, 0, 200, 200)),
-          IsPaintChunk(
-              2, 3,
-              PaintChunk::Id(container.Id(),
-                             RuntimeEnabledFeatures::HitTestOpaquenessEnabled()
-                                 ? kScrollingBackgroundChunkType
-                                 : kClippedContentsBackgroundChunkType),
-              container.FirstFragment().ContentsProperties()),
+          IsPaintChunk(2, 3,
+                       PaintChunk::Id(container.Id(),
+                                      kClippedContentsBackgroundChunkType),
+                       container.FirstFragment().ContentsProperties()),
           IsPaintChunk(
               3, 4,
               PaintChunk::Id(pos_z_child.Layer()->Id(),
diff --git a/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc b/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc
index 88f2d97..f338c557 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_painter_test.cc
@@ -170,17 +170,10 @@
   auto* filler_layer = To<LayoutBoxModelObject>(filler)->Layer();
 
   auto chunks = ContentPaintChunks();
-  if (RuntimeEnabledFeatures::HitTestOpaquenessEnabled()) {
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*container_layer, chunks.begin() + 1, 6);
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*content_layer, chunks.begin() + 4, 2);
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*inner_content_layer, chunks.begin() + 5, 1);
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*filler_layer, chunks.begin() + 6, 1);
-  } else {
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*container_layer, chunks.begin() + 1, 5);
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*content_layer, chunks.begin() + 3, 2);
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*inner_content_layer, chunks.begin() + 4, 1);
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*filler_layer, chunks.begin() + 5, 1);
-  }
+  EXPECT_SUBSEQUENCE_FROM_CHUNK(*container_layer, chunks.begin() + 1, 5);
+  EXPECT_SUBSEQUENCE_FROM_CHUNK(*content_layer, chunks.begin() + 3, 2);
+  EXPECT_SUBSEQUENCE_FROM_CHUNK(*inner_content_layer, chunks.begin() + 4, 1);
+  EXPECT_SUBSEQUENCE_FROM_CHUNK(*filler_layer, chunks.begin() + 5, 1);
 
   auto container_properties =
       container->FirstFragment().LocalBorderBoxProperties();
@@ -190,65 +183,31 @@
       container->FirstFragment().PaintProperties()->ScrollTranslation();
   scroll_hit_test.scroll_hit_test_rect = gfx::Rect(0, 0, 150, 150);
 
-  if (RuntimeEnabledFeatures::HitTestOpaquenessEnabled()) {
-    EXPECT_THAT(
-        chunks,
-        ElementsAre(
-            VIEW_SCROLLING_BACKGROUND_CHUNK_COMMON,
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(container_layer->Id(), DisplayItem::kLayerChunk),
-                container_properties, nullptr, gfx::Rect(0, 0, 150, 150)),
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(container->Id(), DisplayItem::kScrollHitTest),
-                container_properties, &scroll_hit_test,
-                gfx::Rect(0, 0, 150, 150)),
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(container->Id(), kScrollingBackgroundChunkType),
-                content_properties, nullptr, gfx::Rect(0, 0, 300, 400)),
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(content_layer->Id(), DisplayItem::kLayerChunk),
-                content_properties, nullptr, gfx::Rect(0, 0, 200, 100)),
-            IsPaintChunk(1, 1,
-                         PaintChunk::Id(inner_content_layer->Id(),
-                                        DisplayItem::kLayerChunk),
-                         content_properties, nullptr,
-                         gfx::Rect(0, 0, 100, 100)),
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(filler_layer->Id(), DisplayItem::kLayerChunk),
-                content_properties, nullptr, gfx::Rect(0, 100, 300, 300))));
-  } else {
-    EXPECT_THAT(
-        chunks,
-        ElementsAre(
-            VIEW_SCROLLING_BACKGROUND_CHUNK_COMMON,
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(container_layer->Id(), DisplayItem::kLayerChunk),
-                container_properties, nullptr, gfx::Rect(0, 0, 150, 150)),
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(container->Id(), DisplayItem::kScrollHitTest),
-                container_properties, &scroll_hit_test,
-                gfx::Rect(0, 0, 150, 150)),
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(content_layer->Id(), DisplayItem::kLayerChunk),
-                content_properties, nullptr, gfx::Rect(0, 0, 200, 100)),
-            IsPaintChunk(1, 1,
-                         PaintChunk::Id(inner_content_layer->Id(),
-                                        DisplayItem::kLayerChunk),
-                         content_properties, nullptr,
-                         gfx::Rect(0, 0, 100, 100)),
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(filler_layer->Id(), DisplayItem::kLayerChunk),
-                content_properties, nullptr, gfx::Rect(0, 100, 300, 300))));
-  }
+  EXPECT_THAT(
+      chunks,
+      ElementsAre(
+          VIEW_SCROLLING_BACKGROUND_CHUNK_COMMON,
+          IsPaintChunk(
+              1, 1,
+              PaintChunk::Id(container_layer->Id(), DisplayItem::kLayerChunk),
+              container_properties, nullptr, gfx::Rect(0, 0, 150, 150)),
+          IsPaintChunk(
+              1, 1,
+              PaintChunk::Id(container->Id(), DisplayItem::kScrollHitTest),
+              container_properties, &scroll_hit_test,
+              gfx::Rect(0, 0, 150, 150)),
+          IsPaintChunk(
+              1, 1,
+              PaintChunk::Id(content_layer->Id(), DisplayItem::kLayerChunk),
+              content_properties, nullptr, gfx::Rect(0, 0, 200, 100)),
+          IsPaintChunk(1, 1,
+                       PaintChunk::Id(inner_content_layer->Id(),
+                                      DisplayItem::kLayerChunk),
+                       content_properties, nullptr, gfx::Rect(0, 0, 100, 100)),
+          IsPaintChunk(
+              1, 1,
+              PaintChunk::Id(filler_layer->Id(), DisplayItem::kLayerChunk),
+              content_properties, nullptr, gfx::Rect(0, 100, 300, 300))));
 
   To<HTMLElement>(inner_content->GetNode())
       ->setAttribute(
@@ -269,75 +228,37 @@
                    kBackgroundType)));
 
   chunks = ContentPaintChunks();
-  if (RuntimeEnabledFeatures::HitTestOpaquenessEnabled()) {
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*container_layer, chunks.begin() + 1, 6);
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*content_layer, chunks.begin() + 4, 2);
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*inner_content_layer, chunks.begin() + 5, 1);
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*filler_layer, chunks.begin() + 6, 1);
+  EXPECT_SUBSEQUENCE_FROM_CHUNK(*container_layer, chunks.begin() + 1, 5);
+  EXPECT_SUBSEQUENCE_FROM_CHUNK(*content_layer, chunks.begin() + 3, 2);
+  EXPECT_SUBSEQUENCE_FROM_CHUNK(*inner_content_layer, chunks.begin() + 4, 1);
+  EXPECT_SUBSEQUENCE_FROM_CHUNK(*filler_layer, chunks.begin() + 5, 1);
 
-    EXPECT_THAT(
-        ContentPaintChunks(),
-        ElementsAre(
-            VIEW_SCROLLING_BACKGROUND_CHUNK_COMMON,
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(container_layer->Id(), DisplayItem::kLayerChunk),
-                container_properties, nullptr, gfx::Rect(0, 0, 150, 150)),
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(container->Id(), DisplayItem::kScrollHitTest),
-                container_properties, &scroll_hit_test,
-                gfx::Rect(0, 0, 150, 150)),
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(container->Id(), kScrollingBackgroundChunkType),
-                content_properties, nullptr, gfx::Rect(0, 0, 300, 400)),
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(content_layer->Id(), DisplayItem::kLayerChunk),
-                content_properties, nullptr, gfx::Rect(0, 0, 200, 100)),
-            IsPaintChunk(1, 2,
-                         PaintChunk::Id(inner_content_layer->Id(),
-                                        DisplayItem::kLayerChunk),
-                         content_properties, nullptr,
-                         gfx::Rect(0, 100, 100, 100)),
-            IsPaintChunk(
-                2, 2,
-                PaintChunk::Id(filler_layer->Id(), DisplayItem::kLayerChunk),
-                content_properties, nullptr, gfx::Rect(0, 100, 300, 300))));
-  } else {
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*container_layer, chunks.begin() + 1, 5);
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*content_layer, chunks.begin() + 3, 2);
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*inner_content_layer, chunks.begin() + 4, 1);
-    EXPECT_SUBSEQUENCE_FROM_CHUNK(*filler_layer, chunks.begin() + 5, 1);
-
-    EXPECT_THAT(
-        ContentPaintChunks(),
-        ElementsAre(
-            VIEW_SCROLLING_BACKGROUND_CHUNK_COMMON,
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(container_layer->Id(), DisplayItem::kLayerChunk),
-                container_properties, nullptr, gfx::Rect(0, 0, 150, 150)),
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(container->Id(), DisplayItem::kScrollHitTest),
-                container_properties, &scroll_hit_test,
-                gfx::Rect(0, 0, 150, 150)),
-            IsPaintChunk(
-                1, 1,
-                PaintChunk::Id(content_layer->Id(), DisplayItem::kLayerChunk),
-                content_properties, nullptr, gfx::Rect(0, 0, 200, 100)),
-            IsPaintChunk(1, 2,
-                         PaintChunk::Id(inner_content_layer->Id(),
-                                        DisplayItem::kLayerChunk),
-                         content_properties, nullptr,
-                         gfx::Rect(0, 100, 100, 100)),
-            IsPaintChunk(
-                2, 2,
-                PaintChunk::Id(filler_layer->Id(), DisplayItem::kLayerChunk),
-                content_properties, nullptr, gfx::Rect(0, 100, 300, 300))));
-  }
+  EXPECT_THAT(
+      ContentPaintChunks(),
+      ElementsAre(
+          VIEW_SCROLLING_BACKGROUND_CHUNK_COMMON,
+          IsPaintChunk(
+              1, 1,
+              PaintChunk::Id(container_layer->Id(), DisplayItem::kLayerChunk),
+              container_properties, nullptr, gfx::Rect(0, 0, 150, 150)),
+          IsPaintChunk(
+              1, 1,
+              PaintChunk::Id(container->Id(), DisplayItem::kScrollHitTest),
+              container_properties, &scroll_hit_test,
+              gfx::Rect(0, 0, 150, 150)),
+          IsPaintChunk(
+              1, 1,
+              PaintChunk::Id(content_layer->Id(), DisplayItem::kLayerChunk),
+              content_properties, nullptr, gfx::Rect(0, 0, 200, 100)),
+          IsPaintChunk(1, 2,
+                       PaintChunk::Id(inner_content_layer->Id(),
+                                      DisplayItem::kLayerChunk),
+                       content_properties, nullptr,
+                       gfx::Rect(0, 100, 100, 100)),
+          IsPaintChunk(
+              2, 2,
+              PaintChunk::Id(filler_layer->Id(), DisplayItem::kLayerChunk),
+              content_properties, nullptr, gfx::Rect(0, 100, 300, 300))));
 }
 
 TEST_P(PaintLayerPainterTest, CachedSubsequenceOnCullRectChange) {
diff --git a/third_party/blink/renderer/core/paint/view_painter.cc b/third_party/blink/renderer/core/paint/view_painter.cc
index c0a6d218..3736dac 100644
--- a/third_party/blink/renderer/core/paint/view_painter.cc
+++ b/third_party/blink/renderer/core/paint/view_painter.cc
@@ -74,21 +74,13 @@
   if (layout_view_.StyleRef().Visibility() != EVisibility::kVisible)
     return;
 
+  bool has_hit_test_data =
+      ObjectPainter(layout_view_).ShouldRecordSpecialHitTestData(paint_info);
   bool painting_background_in_contents_space =
       paint_info.IsPaintingBackgroundInContentsSpace();
-  bool paints_hit_test_data =
-      (RuntimeEnabledFeatures::HitTestOpaquenessEnabled() &&
-       painting_background_in_contents_space) ||
-      ObjectPainter(layout_view_).ShouldRecordSpecialHitTestData(paint_info);
 
   Element* element = DynamicTo<Element>(layout_view_.GetNode());
-  bool paints_region_capture_data =
-      element && element->GetRegionCaptureCropId() &&
-      // TODO(wangxianzhu): This is to avoid the side-effect of
-      // HitTestOpaqueness on region capture data. Verify if the side-effect
-      // really matters.
-      !(painting_background_in_contents_space &&
-        paint_info.ShouldSkipBackground());
+  bool has_region_capture_data = element && element->GetRegionCaptureCropId();
   bool paints_scroll_hit_test =
       !painting_background_in_contents_space &&
       layout_view_.FirstFragment().PaintProperties()->Scroll();
@@ -99,8 +91,8 @@
     }
     return false;
   }();
-  if (!layout_view_.HasBoxDecorationBackground() && !paints_hit_test_data &&
-      !paints_scroll_hit_test && !paints_region_capture_data &&
+  if (!layout_view_.HasBoxDecorationBackground() && !has_hit_test_data &&
+      !paints_scroll_hit_test && !has_region_capture_data &&
       !is_represented_via_pseudo_elements) {
     return;
   }
@@ -202,13 +194,13 @@
                           *background_client, painted_separate_backdrop,
                           painted_separate_effect);
   }
-  if (paints_hit_test_data) {
+  if (has_hit_test_data) {
     ObjectPainter(layout_view_)
         .RecordHitTestData(paint_info, pixel_snapped_background_rect,
                            *background_client);
   }
 
-  if (paints_region_capture_data) {
+  if (has_region_capture_data) {
     BoxPainter(layout_view_)
         .RecordRegionCaptureData(paint_info,
                                  PhysicalRect(pixel_snapped_background_rect),
diff --git a/third_party/blink/renderer/core/paint/view_painter_test.cc b/third_party/blink/renderer/core/paint/view_painter_test.cc
index 8d0360ad..23557ed 100644
--- a/third_party/blink/renderer/core/paint/view_painter_test.cc
+++ b/third_party/blink/renderer/core/paint/view_painter_test.cc
@@ -231,13 +231,7 @@
   EXPECT_THAT(
       ContentPaintChunks(),
       ElementsAre(IsPaintChunk(
-          1, 1,
-          RuntimeEnabledFeatures::HitTestOpaquenessEnabled()
-              ? PaintChunk::Id(view->GetScrollableArea()
-                                   ->GetScrollingBackgroundDisplayItemClient()
-                                   .Id(),
-                               DisplayItem::kDocumentBackground)
-              : PaintChunk::Id(html->Layer()->Id(), DisplayItem::kLayerChunk),
+          1, 1, PaintChunk::Id(html->Layer()->Id(), DisplayItem::kLayerChunk),
           scrolling_properties, &scrolling_hit_test_data,
           gfx::Rect(0, 0, 800, 3000))));
 }
diff --git a/third_party/blink/renderer/core/scheduler_integration_tests/frame_throttling_test.cc b/third_party/blink/renderer/core/scheduler_integration_tests/frame_throttling_test.cc
index c4a96d6..8e77dba 100644
--- a/third_party/blink/renderer/core/scheduler_integration_tests/frame_throttling_test.cc
+++ b/third_party/blink/renderer/core/scheduler_integration_tests/frame_throttling_test.cc
@@ -554,16 +554,11 @@
 
   auto* frame_element = To<HTMLIFrameElement>(
       GetDocument().getElementById(AtomicString("frame")));
-  auto* frame_doc = frame_element->contentDocument();
-  auto* frame_view = frame_doc->View();
+  auto* frame_view = frame_element->contentDocument()->View();
   EXPECT_FALSE(frame_view->CanThrottleRendering());
   auto* root_layer = WebView().MainFrameImpl()->GetFrameView()->RootCcLayer();
   EXPECT_EQ(0u, CcLayersByDOMElementId(root_layer, "container").size());
-  if (RuntimeEnabledFeatures::HitTestOpaquenessEnabled()) {
-    EXPECT_TRUE(CcLayerByOwnerNodeId(root_layer, frame_doc->GetDomNodeId()));
-  } else {
-    EXPECT_EQ(1u, CcLayersByDOMElementId(root_layer, "inner_frame").size());
-  }
+  EXPECT_EQ(1u, CcLayersByDOMElementId(root_layer, "inner_frame").size());
 
   // First make the child hidden to enable throttling, and composite
   // the container.
@@ -575,11 +570,7 @@
   CompositeFrame();
   EXPECT_TRUE(frame_view->CanThrottleRendering());
   EXPECT_EQ(1u, CcLayersByDOMElementId(root_layer, "container").size());
-  if (RuntimeEnabledFeatures::HitTestOpaquenessEnabled()) {
-    EXPECT_TRUE(CcLayerByOwnerNodeId(root_layer, frame_doc->GetDomNodeId()));
-  } else {
-    EXPECT_EQ(1u, CcLayersByDOMElementId(root_layer, "inner_frame").size());
-  }
+  EXPECT_EQ(1u, CcLayersByDOMElementId(root_layer, "inner_frame").size());
 
   // Then bring it back on-screen, and decomposite container.
   container_element->setAttribute(kStyleAttr, g_empty_atom);
@@ -588,11 +579,7 @@
   CompositeFrame();
   EXPECT_FALSE(frame_view->CanThrottleRendering());
   EXPECT_EQ(0u, CcLayersByDOMElementId(root_layer, "container").size());
-  if (RuntimeEnabledFeatures::HitTestOpaquenessEnabled()) {
-    EXPECT_TRUE(CcLayerByOwnerNodeId(root_layer, frame_doc->GetDomNodeId()));
-  } else {
-    EXPECT_EQ(1u, CcLayersByDOMElementId(root_layer, "inner_frame").size());
-  }
+  EXPECT_EQ(1u, CcLayersByDOMElementId(root_layer, "inner_frame").size());
 }
 
 TEST_P(FrameThrottlingTest, MutatingThrottledFrameDoesNotCauseAnimation) {
diff --git a/third_party/blink/renderer/core/script/classic_pending_script.cc b/third_party/blink/renderer/core/script/classic_pending_script.cc
index 2c9bb5dc..033dd0e8 100644
--- a/third_party/blink/renderer/core/script/classic_pending_script.cc
+++ b/third_party/blink/renderer/core/script/classic_pending_script.cc
@@ -6,6 +6,7 @@
 
 #include "base/feature_list.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/loader/lcp_critical_path_predictor_util.h"
 #include "third_party/blink/public/mojom/script/script_type.mojom-blink-forward.h"
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/renderer/bindings/core/v8/referrer_script_info.h"
@@ -275,8 +276,7 @@
   static const bool exclude_lcp_influencers =
       features::kLowPriorityAsyncScriptExecutionExcludeLcpInfluencersParam
           .Get();
-  if (exclude_lcp_influencers &&
-      base::FeatureList::IsEnabled(features::kLCPScriptObserver)) {
+  if (exclude_lcp_influencers && LcppScriptObserverEnabled()) {
     if (LCPCriticalPathPredictor* lcpp = top_document.GetFrame()->GetLCPP()) {
       if (lcpp->IsLcpInfluencerScript(GetResource()->Url())) {
         return false;
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.cc b/third_party/blink/renderer/core/scroll/scrollable_area.cc
index e4ce59e..a6de1b6906 100644
--- a/third_party/blink/renderer/core/scroll/scrollable_area.cc
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.cc
@@ -42,6 +42,7 @@
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/core/animation/scroll_timeline.h"
 #include "third_party/blink/renderer/core/css/properties/longhands.h"
+#include "third_party/blink/renderer/core/editing/frame_selection.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/frame/visual_viewport.h"
@@ -711,6 +712,9 @@
   if (offset_changed && GetLayoutBox() && GetLayoutBox()->GetFrameView()) {
     GetLayoutBox()->GetFrameView()->GetLayoutShiftTracker().NotifyScroll(
         scroll_type, delta);
+    // FrameSelection caches visual selection information which needs to be
+    // invalidated after scrolling.
+    GetLayoutBox()->GetFrameView()->GetFrame().Selection().MarkCacheDirty();
   }
 
   GetScrollAnimator().SetCurrentOffset(offset);
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
index 652f980..a9cc7db 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
@@ -46,6 +46,7 @@
 #include "services/network/public/cpp/request_mode.h"
 #include "services/network/public/mojom/url_loader_factory.mojom-blink.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/loader/lcp_critical_path_predictor_util.h"
 #include "third_party/blink/public/common/mime_util/mime_util.h"
 #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
 #include "third_party/blink/public/mojom/devtools/console_message.mojom-blink.h"
@@ -517,26 +518,49 @@
   }
 
   // LCP Critical Path Predictor identified resources get a priority boost.
-  if ((is_potentially_lcp_element || is_potentially_lcp_influencer) &&
-      !features::kLCPCriticalPathPredictorDryRun.Get()) {
+  if (LcppEnabled()) {
+    bool should_modify_request_priority = false;
     features::LcppResourceLoadPriority preferred_priority =
-        is_potentially_lcp_element
-            ? features::kLCPCriticalPathPredictorImageLoadPriority.Get()
-            : features::kLCPCriticalPathPredictorInfluencerScriptLoadPriority
-                  .Get();
+        features::LcppResourceLoadPriority::kMedium;
 
-    ++potentially_lcp_resource_priority_boosts_;
+    if (is_potentially_lcp_element) {
+      // Adjust priority of LCP image request.
+      if (base::FeatureList::IsEnabled(features::kLCPCriticalPathPredictor) &&
+          !features::kLCPCriticalPathPredictorDryRun.Get()) {
+        should_modify_request_priority = true;
+        preferred_priority =
+            features::kLCPCriticalPathPredictorImageLoadPriority.Get();
+      }
 
-    switch (preferred_priority) {
-      case features::LcppResourceLoadPriority::kMedium:
-        priority = std::max(priority, ResourceLoadPriority::kMedium);
-        break;
-      case features::LcppResourceLoadPriority::kHigh:
-        priority = std::max(priority, ResourceLoadPriority::kHigh);
-        break;
-      case features::LcppResourceLoadPriority::kVeryHigh:
-        priority = std::max(priority, ResourceLoadPriority::kVeryHigh);
-        break;
+      if (base::FeatureList::IsEnabled(features::kLCPScriptObserver) &&
+          features::kLCPScriptObserverAdjustImageLoadPriority.Get()) {
+        should_modify_request_priority = true;
+        preferred_priority =
+            features::kLCPScriptObserverImageLoadPriority.Get();
+      }
+    }
+
+    if (is_potentially_lcp_influencer &&
+        base::FeatureList::IsEnabled(features::kLCPScriptObserver)) {
+      // Adjust priority of LCP influencing script request.
+      should_modify_request_priority = true;
+      preferred_priority = features::kLCPScriptObserverScriptLoadPriority.Get();
+    }
+
+    if (should_modify_request_priority) {
+      ++potentially_lcp_resource_priority_boosts_;
+
+      switch (preferred_priority) {
+        case features::LcppResourceLoadPriority::kMedium:
+          priority = std::max(priority, ResourceLoadPriority::kMedium);
+          break;
+        case features::LcppResourceLoadPriority::kHigh:
+          priority = std::max(priority, ResourceLoadPriority::kHigh);
+          break;
+        case features::LcppResourceLoadPriority::kVeryHigh:
+          priority = std::max(priority, ResourceLoadPriority::kVeryHigh);
+          break;
+      }
     }
   }
 
diff --git a/third_party/blink/renderer/platform/testing/find_cc_layer.cc b/third_party/blink/renderer/platform/testing/find_cc_layer.cc
index 93bf473d..e7bcd5e5 100644
--- a/third_party/blink/renderer/platform/testing/find_cc_layer.cc
+++ b/third_party/blink/renderer/platform/testing/find_cc_layer.cc
@@ -63,19 +63,6 @@
   return CcLayerByCcElementId(const_cast<cc::Layer*>(root), element_id);
 }
 
-cc::Layer* CcLayerByOwnerNodeId(cc::Layer* root, DOMNodeId id) {
-  for (auto& layer : root->children()) {
-    if (layer->debug_info() && layer->debug_info()->owner_node_id == id) {
-      return layer.get();
-    }
-  }
-  return nullptr;
-}
-
-const cc::Layer* CcLayerByOwnerNodeId(const cc::Layer* root, DOMNodeId id) {
-  return CcLayerByOwnerNodeId(const_cast<cc::Layer*>(root), id);
-}
-
 cc::Layer* ScrollingContentsCcLayerByScrollElementId(
     cc::Layer* root,
     const CompositorElementId& scroll_element_id) {
diff --git a/third_party/blink/renderer/platform/testing/find_cc_layer.h b/third_party/blink/renderer/platform/testing/find_cc_layer.h
index fbcad9e..c64dff3 100644
--- a/third_party/blink/renderer/platform/testing/find_cc_layer.h
+++ b/third_party/blink/renderer/platform/testing/find_cc_layer.h
@@ -36,10 +36,6 @@
 const cc::Layer* CcLayerByCcElementId(const cc::Layer* root,
                                       const CompositorElementId&);
 
-cc::Layer* CcLayerByOwnerNodeId(cc::Layer* root, DOMNodeId);
-
-const cc::Layer* CcLayerByOwnerNodeId(const cc::Layer* root, DOMNodeId);
-
 cc::Layer* ScrollingContentsCcLayerByScrollElementId(
     cc::Layer* root,
     const CompositorElementId& scroll_element_id);
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-skia-graphite b/third_party/blink/web_tests/FlagExpectations/enable-skia-graphite
index 2f68756..6ad2491 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-skia-graphite
+++ b/third_party/blink/web_tests/FlagExpectations/enable-skia-graphite
@@ -36,6 +36,7 @@
 crbug.com/1518086 external/wpt/html/semantics/embedded-content/media-elements/ready-states/autoplay-hidden.optional.html [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 virtual/view-transition/external/wpt/css/css-view-transitions/pseudo-with-classes-new-with-class-old-without.html [ Failure ]
 crbug.com/626703 virtual/view-transition-wide-gamut/external/wpt/css/css-view-transitions/pseudo-with-classes-view-transition-group.html [ Failure ]
 crbug.com/626703 virtual/view-transition/external/wpt/css/css-view-transitions/pseudo-with-classes-view-transition-group.html [ Failure ]
 crbug.com/626703 virtual/view-transition-mpa-serialization/external/wpt/css/css-view-transitions/pseudo-with-classes-match-multiple.html [ Failure ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index a6b38ec..e1013a6 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2573,6 +2573,11 @@
 crbug.com/626703 external/wpt/css/printing/table-overflow-quirks-frameset-crash-print.html [ Crash Timeout ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 [ Win11-arm64 ] external/wpt/html/cross-origin-embedder-policy/credentialless/fetch.https.window.html [ Timeout ]
+crbug.com/626703 external/wpt/webrtc/RTCRtpReceiver-getStats.https.html [ Skip Timeout ]
+crbug.com/626703 external/wpt/webrtc/RTCRtpSender-getStats.https.html [ Skip Timeout ]
+crbug.com/626703 [ Win11-arm64 ] external/wpt/webtransport/echo-large-bidirectional-streams.https.any.worker.html [ Timeout ]
+crbug.com/626703 [ Win11-arm64 ] virtual/shared-storage-fenced-frame-mparch/external/wpt/shared-storage/shared-storage-writable-service-worker-fetch.tentative.https.sub.html [ Failure Timeout ]
 crbug.com/626703 [ Win11-arm64 ] virtual/keepalive-in-browser-migration/external/wpt/xhr/send-redirect-bogus-sync.htm [ Timeout ]
 crbug.com/626703 external/wpt/css/css-backgrounds/background-position-negative-percentage-comparison-002.html [ Failure ]
 crbug.com/626703 external/wpt/svg/text/reftests/first-letter.svg [ Failure ]
diff --git a/third_party/blink/web_tests/compositing/fixed-body-background-positioned-expected.png b/third_party/blink/web_tests/compositing/fixed-body-background-positioned-expected.png
index 6e3e1aa..dd307e379 100644
--- a/third_party/blink/web_tests/compositing/fixed-body-background-positioned-expected.png
+++ b/third_party/blink/web_tests/compositing/fixed-body-background-positioned-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/compositing/fixed-body-background-positioned-expected.txt b/third_party/blink/web_tests/compositing/fixed-body-background-positioned-expected.txt
index 3769f1e..69cd9119 100644
--- a/third_party/blink/web_tests/compositing/fixed-body-background-positioned-expected.txt
+++ b/third_party/blink/web_tests/compositing/fixed-body-background-positioned-expected.txt
@@ -7,7 +7,7 @@
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [785, 3700],
       "transform": 1
     },
diff --git a/third_party/blink/web_tests/compositing/layer-creation/fixed-position-in-fixed-overflow-expected.txt b/third_party/blink/web_tests/compositing/layer-creation/fixed-position-in-fixed-overflow-expected.txt
index f9bc3ed..9c55152 100644
--- a/third_party/blink/web_tests/compositing/layer-creation/fixed-position-in-fixed-overflow-expected.txt
+++ b/third_party/blink/web_tests/compositing/layer-creation/fixed-position-in-fixed-overflow-expected.txt
@@ -27,7 +27,9 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV class='overflow fixed'",
-      "bounds": [785, 1000],
+      "bounds": [100, 1000],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
       "transform": 1
     },
     {
diff --git a/third_party/blink/web_tests/compositing/overflow/accelerated-overflow-scroll-should-not-affect-perspective-expected.txt b/third_party/blink/web_tests/compositing/overflow/accelerated-overflow-scroll-should-not-affect-perspective-expected.txt
index ad7e5442..7d9613b5 100644
--- a/third_party/blink/web_tests/compositing/overflow/accelerated-overflow-scroll-should-not-affect-perspective-expected.txt
+++ b/third_party/blink/web_tests/compositing/overflow/accelerated-overflow-scroll-should-not-affect-perspective-expected.txt
@@ -32,12 +32,6 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow (positioned, children-inline) DIV class='container'",
-      "bounds": [185, 290],
-      "drawsContent": false,
-      "transform": 2
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV class='child first'",
       "bounds": [60, 200],
       "contentsOpaque": true,
diff --git a/third_party/blink/web_tests/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.txt b/third_party/blink/web_tests/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.txt
index 3ba755d2..4a640d15 100644
--- a/third_party/blink/web_tests/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.txt
+++ b/third_party/blink/web_tests/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.txt
@@ -23,14 +23,6 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow DIV id='scroller'",
-      "position": [1, 1],
-      "bounds": [100, 180],
-      "drawsContent": false,
-      "backfaceVisibility": "hidden",
-      "transform": 1
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV id='fixed'",
       "position": [60, 60],
       "bounds": [80, 80],
diff --git a/third_party/blink/web_tests/compositing/overflow/scrolling-content-clip-to-viewport-expected.txt b/third_party/blink/web_tests/compositing/overflow/scrolling-content-clip-to-viewport-expected.txt
index bd047c9..3c7daf3 100644
--- a/third_party/blink/web_tests/compositing/overflow/scrolling-content-clip-to-viewport-expected.txt
+++ b/third_party/blink/web_tests/compositing/overflow/scrolling-content-clip-to-viewport-expected.txt
@@ -23,8 +23,10 @@
       "contentsOpaque": true
     },
     {
-      "name": "LayoutNGBlockFlow DIV class='scroller'",
-      "bounds": [305, 1224],
+      "name": "LayoutNGBlockFlow (relative positioned) DIV class='column'",
+      "position": [10, 10],
+      "bounds": [284, 1204],
+      "contentsOpaque": true,
       "backgroundColor": "#C0C0C0"
     }
   ]
diff --git a/third_party/blink/web_tests/compositing/overflow/universal-accelerated-overflow-scroll-expected.txt b/third_party/blink/web_tests/compositing/overflow/universal-accelerated-overflow-scroll-expected.txt
index ebba1fd..020b860 100644
--- a/third_party/blink/web_tests/compositing/overflow/universal-accelerated-overflow-scroll-expected.txt
+++ b/third_party/blink/web_tests/compositing/overflow/universal-accelerated-overflow-scroll-expected.txt
@@ -14,6 +14,12 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV id='container-absolute-grandchildren-not-contained' class='overflow'",
+      "position": [2, 6],
+      "bounds": [85, 136],
+      "transform": 2
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [10, 97],
       "bounds": [104, 257]
@@ -26,12 +32,6 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow DIV id='container-absolute-grandchildren-not-contained' class='overflow'",
-      "position": [2, 2],
-      "bounds": [85, 144],
-      "transform": 2
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV id='positioned-absolute-grandchildren-not-contained' class='positionAbsolute positioned'",
       "position": [35, 10],
       "bounds": [199, 105]
@@ -70,8 +70,8 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='container-absolute-grandchildren' class='positionAbsolute overflow'",
-      "position": [2, 2],
-      "bounds": [105, 144],
+      "position": [2, 6],
+      "bounds": [105, 136],
       "transform": 4
     },
     {
@@ -95,12 +95,6 @@
       "transform": 5
     },
     {
-      "name": "LayoutNGBlockFlow DIV id='container-absolute-not-contained' class='overflow'",
-      "position": [2, 2],
-      "bounds": [85, 144],
-      "transform": 6
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV id='positioned-absolute-not-contained' class='positionAbsolute positioned'",
       "position": [275, 10],
       "bounds": [199, 105]
@@ -119,6 +113,12 @@
       "transform": 7
     },
     {
+      "name": "LayoutNGBlockFlow DIV id='container-absolute-sibling-grandchildren-not-contained' class='overflow'",
+      "position": [2, 6],
+      "bounds": [85, 136],
+      "transform": 8
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [2, 87],
       "bounds": [100, 15],
@@ -132,12 +132,6 @@
       "transform": 7
     },
     {
-      "name": "LayoutNGBlockFlow DIV id='container-absolute-sibling-grandchildren-not-contained' class='overflow'",
-      "position": [2, 2],
-      "bounds": [85, 144],
-      "transform": 8
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV id='positioned-absolute-sibling-grandchildren-not-contained' class='positionAbsolute positioned'",
       "position": [395, 75],
       "bounds": [80, 40],
@@ -180,8 +174,8 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='container-absolute-sibling-grandchildren' class='positionAbsolute overflow'",
-      "position": [2, 2],
-      "bounds": [105, 144],
+      "position": [2, 6],
+      "bounds": [105, 136],
       "transform": 10
     },
     {
@@ -204,22 +198,14 @@
       "transform": 11
     },
     {
-      "name": "LayoutNGBlockFlow DIV id='container-absolute-sibling-not-contained' class='overflow'",
-      "position": [2, 2],
-      "bounds": [85, 144],
-      "transform": 12
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV id='positioned-absolute-sibling-not-contained' class='positionAbsolute positioned'",
       "position": [155, 130],
       "bounds": [199, 105]
     },
     {
       "name": "LayoutNGBlockFlow (relative positioned) DIV id='scrolled-absolute-sibling-not-contained' class='scrolled'",
-      "position": [6, 90],
-      "bounds": [75, 24],
-      "contentsOpaque": true,
-      "backgroundColor": "#0000FF",
+      "position": [6, 34],
+      "bounds": [75, 80],
       "transform": 12
     },
     {
@@ -249,9 +235,9 @@
       "bounds": [137, 104]
     },
     {
-      "name": "LayoutNGBlockFlow (positioned) DIV id='container-absolute-sibling' class='positionAbsolute overflow'",
-      "position": [2, 2],
-      "bounds": [105, 144],
+      "name": "LayoutNGBlockFlow (positioned) DIV id='positioned-absolute-sibling' class='positionAbsolute positioned'",
+      "position": [6, 34],
+      "bounds": [101, 80],
       "transform": 14
     },
     {
@@ -281,9 +267,9 @@
       "bounds": [104, 137]
     },
     {
-      "name": "LayoutNGBlockFlow (positioned) DIV id='container-absolute' class='positionAbsolute overflow'",
-      "position": [2, 2],
-      "bounds": [105, 144],
+      "name": "LayoutNGBlockFlow (positioned) DIV id='positioned-absolute' class='positionAbsolute positioned'",
+      "position": [6, 34],
+      "bounds": [101, 80],
       "transform": 16
     },
     {
@@ -308,8 +294,8 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='container-fixed-sibling-grandchildren' class='positionAbsolute overflow'",
-      "position": [2, 2],
-      "bounds": [85, 144],
+      "position": [2, 6],
+      "bounds": [85, 136],
       "transform": 18
     },
     {
@@ -348,25 +334,17 @@
       "transform": 20
     },
     {
-      "name": "LayoutNGBlockFlow (positioned) DIV id='container-fixed-sibling' class='positionAbsolute overflow'",
-      "position": [2, 2],
-      "bounds": [85, 144],
-      "transform": 21
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV id='positioned-fixed-sibling' class='positionFixed positioned'",
       "bounds": [80, 40],
       "contentsOpaque": true,
       "backgroundColor": "#800080",
-      "transform": 22
+      "transform": 21
     },
     {
       "name": "LayoutNGBlockFlow (relative positioned) DIV id='scrolled-fixed-sibling' class='scrolled'",
-      "position": [6, 90],
-      "bounds": [75, 24],
-      "contentsOpaque": true,
-      "backgroundColor": "#0000FF",
-      "transform": 21
+      "position": [6, 34],
+      "bounds": [75, 80],
+      "transform": 22
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='container-fixed-grandchildren' class='positionAbsolute overflow'",
@@ -390,8 +368,8 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='container-fixed-grandchildren' class='positionAbsolute overflow'",
-      "position": [2, 2],
-      "bounds": [85, 144],
+      "position": [2, 6],
+      "bounds": [85, 136],
       "transform": 24
     },
     {
@@ -430,25 +408,17 @@
       "transform": 26
     },
     {
-      "name": "LayoutNGBlockFlow (positioned) DIV id='container-fixed' class='positionAbsolute overflow'",
-      "position": [2, 2],
-      "bounds": [85, 144],
-      "transform": 27
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV id='positioned-fixed' class='positionFixed positioned'",
       "bounds": [80, 40],
       "contentsOpaque": true,
       "backgroundColor": "#800080",
-      "transform": 28
+      "transform": 27
     },
     {
       "name": "LayoutNGBlockFlow (relative positioned) DIV id='scrolled-fixed' class='scrolled'",
-      "position": [6, 90],
-      "bounds": [75, 24],
-      "contentsOpaque": true,
-      "backgroundColor": "#0000FF",
-      "transform": 27
+      "position": [6, 34],
+      "bounds": [75, 80],
+      "transform": 28
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='sibling-absolute-sibling-grandchildren-not-contained' class='positionFixed sibling'",
@@ -532,7 +502,7 @@
       "name": "LayoutNGBlockFlow (relative positioned) DIV id='scrolled-fixed-sibling' class='scrolled onTop'",
       "position": [6, 6],
       "bounds": [75, 136],
-      "transform": 21
+      "transform": 22
     },
     {
       "name": "LayoutNGBlockFlow (relative positioned) DIV id='scrolled-fixed-grandchildren' class='scrolled onTop'",
@@ -544,7 +514,7 @@
       "name": "LayoutNGBlockFlow (relative positioned) DIV id='scrolled-fixed' class='scrolled onTop'",
       "position": [6, 6],
       "bounds": [75, 136],
-      "transform": 27
+      "transform": 28
     },
     {
       "name": "VerticalScrollbar",
@@ -745,6 +715,15 @@
     },
     {
       "id": 21,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [155, 315, 0, 1]
+      ]
+    },
+    {
+      "id": 22,
       "parent": 20,
       "transform": [
         [1, 0, 0, 0],
@@ -754,15 +733,6 @@
       ]
     },
     {
-      "id": 22,
-      "transform": [
-        [1, 0, 0, 0],
-        [0, 1, 0, 0],
-        [0, 0, 1, 0],
-        [155, 315, 0, 1]
-      ]
-    },
-    {
       "id": 23,
       "transform": [
         [1, 0, 0, 0],
@@ -801,6 +771,15 @@
     },
     {
       "id": 27,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [395, 315, 0, 1]
+      ]
+    },
+    {
+      "id": 28,
       "parent": 26,
       "transform": [
         [1, 0, 0, 0],
@@ -810,15 +789,6 @@
       ]
     },
     {
-      "id": 28,
-      "transform": [
-        [1, 0, 0, 0],
-        [0, 1, 0, 0],
-        [0, 0, 1, 0],
-        [395, 315, 0, 1]
-      ]
-    },
-    {
       "id": 29,
       "transform": [
         [1, 0, 0, 0],
diff --git a/third_party/blink/web_tests/compositing/scrollbars/composited-overlay-scrollbars-with-scrollbar-color-expected.txt b/third_party/blink/web_tests/compositing/scrollbars/composited-overlay-scrollbars-with-scrollbar-color-expected.txt
index 3a82d86..1b0fb89 100644
--- a/third_party/blink/web_tests/compositing/scrollbars/composited-overlay-scrollbars-with-scrollbar-color-expected.txt
+++ b/third_party/blink/web_tests/compositing/scrollbars/composited-overlay-scrollbars-with-scrollbar-color-expected.txt
@@ -15,11 +15,17 @@
     {
       "name": "LayoutNGBlockFlow DIV id='outer'",
       "position": [1, 1],
-      "bounds": [400, 602],
+      "bounds": [400, 500],
+      "drawsContent": false,
       "transform": 1
     },
     {
       "name": "LayoutNGBlockFlow DIV id='inner'",
+      "bounds": [102, 102],
+      "transform": 2
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV id='inner'",
       "position": [1, 1],
       "bounds": [100, 100],
       "drawsContent": false,
diff --git a/third_party/blink/web_tests/compositing/scrollbars/nested-overlay-scrollbars-expected.png b/third_party/blink/web_tests/compositing/scrollbars/nested-overlay-scrollbars-expected.png
index fd384bd2..7500557 100644
--- a/third_party/blink/web_tests/compositing/scrollbars/nested-overlay-scrollbars-expected.png
+++ b/third_party/blink/web_tests/compositing/scrollbars/nested-overlay-scrollbars-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/compositing/scrollbars/nested-overlay-scrollbars-expected.txt b/third_party/blink/web_tests/compositing/scrollbars/nested-overlay-scrollbars-expected.txt
index 1ee3546..a94e897 100644
--- a/third_party/blink/web_tests/compositing/scrollbars/nested-overlay-scrollbars-expected.txt
+++ b/third_party/blink/web_tests/compositing/scrollbars/nested-overlay-scrollbars-expected.txt
@@ -14,10 +14,9 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow (positioned, children-inline) DIV id='outer'",
-      "position": [2, 2],
-      "bounds": [400, 704],
-      "transform": 2
+      "name": "LayoutNGBlockFlow (positioned) DIV id='inner'",
+      "bounds": [204, 204],
+      "transform": 3
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='inner'",
@@ -29,7 +28,14 @@
     {
       "name": "LayoutNGBlockFlow (positioned) DIV id='inner'",
       "position": [2, 2],
-      "bounds": [5000, 9000],
+      "bounds": [2000, 9000],
+      "transform": 3
+    },
+    {
+      "name": "LayoutNGBlockFlow (positioned) DIV id='spacer'",
+      "position": [2, 2002],
+      "bounds": [5000, 1000],
+      "drawsContent": false,
       "transform": 3
     },
     {
diff --git a/third_party/blink/web_tests/compositing/squashing/composited-bounds-for-negative-z-expected.txt b/third_party/blink/web_tests/compositing/squashing/composited-bounds-for-negative-z-expected.txt
index 07cab82..506d1dc0 100644
--- a/third_party/blink/web_tests/compositing/squashing/composited-bounds-for-negative-z-expected.txt
+++ b/third_party/blink/web_tests/compositing/squashing/composited-bounds-for-negative-z-expected.txt
@@ -33,7 +33,7 @@
       "transform": 2
     },
     {
-      "name": "LayoutNGBlockFlow DIV",
+      "name": "LayoutNGBlockFlow (relative positioned) DIV",
       "bounds": [285, 1000],
       "contentsOpaque": true,
       "backgroundColor": "#ADD8E6",
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index f10ea1d..b2d728f 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -161992,6 +161992,19 @@
        {}
       ]
      ],
+     "multicol-fill-balance-029.html": [
+      "837141bd0593716c5b5fb46ef3f68efb4aaf077e",
+      [
+       null,
+       [
+        [
+         "/css/reference/ref-filled-green-100px-square.xht",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "multicol-fill-balance-nested-000.html": [
       "5e466df8077545b4d6474389d296bc26c5b28b86",
       [
@@ -237917,6 +237930,19 @@
        }
       ]
      ],
+     "class-specificity.html": [
+      "55de2ec3884c174a4978be193fde2220fb0c5164",
+      [
+       null,
+       [
+        [
+         "/css/css-view-transitions/class-specificity-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "content-smaller-than-box-size.html": [
       "d2b8f63ca068ffc744b7b1a7b0ca8d1c25a32854",
       [
@@ -291907,7 +291933,7 @@
      }
     },
     "DIR_METADATA": [
-     "90ae077137c9b3993cb978aa35719ab986a16f3d",
+     "6cbcf6fcc7a6934be10ad0c19859f0bcdd5169ca",
      []
     ],
     "FileReader": {
@@ -292043,7 +292069,7 @@
    },
    "IndexedDB": {
     "DIR_METADATA": [
-     "fd463f526deb09c01c207280281f0916e362cb3a",
+     "ba37f8208671e49f54067071896d46ad25ad8f85",
      []
     ],
     "META.yml": [
@@ -292123,7 +292149,7 @@
    ],
    "WebCryptoAPI": {
     "DIR_METADATA": [
-     "d048f385de5c03d1e61db0bb0a67c3fc3e78e33a",
+     "9524f8ead3d8f9d2645e2a2cb12b0f2f89a0283a",
      []
     ],
     "META.yml": [
@@ -292591,7 +292617,7 @@
      []
     ],
     "DIR_METADATA": [
-     "caea56d67a654fb8b15e43a1b0eab7e413a88177",
+     "22cfa46b2f5ffb1fa3db78d07e69585ff3c48e8d",
      []
     ],
     "META.yml": [
@@ -292751,7 +292777,7 @@
      []
     ],
     "DIR_METADATA": [
-     "caea56d67a654fb8b15e43a1b0eab7e413a88177",
+     "22cfa46b2f5ffb1fa3db78d07e69585ff3c48e8d",
      []
     ],
     "META.yml": [
@@ -293585,7 +293611,7 @@
    },
    "background-fetch": {
     "DIR_METADATA": [
-     "7950c4dd3b7cfec535e3f6b50ad175bcf4306653",
+     "00b3667937a21dbbd3b54f06d23157ce72fa2e0a",
      []
     ],
     "META.yml": [
@@ -293679,7 +293705,7 @@
    },
    "battery-status": {
     "DIR_METADATA": [
-     "54f06e6c8d511931b441399b1e47092601b80cd9",
+     "fa5fe8a7b8a32af4f0a7fd263bff08955c6bd57d",
      []
     ],
     "META.yml": [
@@ -294069,7 +294095,7 @@
    },
    "clear-site-data": {
     "DIR_METADATA": [
-     "f070ceb51ea0821263c460d1372a090076cba928",
+     "da9cab8ad438c0b30ab8537a9da51f7345f7def6",
      []
     ],
     "META.yml": [
@@ -294121,7 +294147,7 @@
    },
    "client-hints": {
     "DIR_METADATA": [
-     "8fb918d73800b63618e1334894180edd5fe67fb1",
+     "b61d0e7dc0285e1ddb71faeb844f3fddf9807a0a",
      []
     ],
     "META.yml": [
@@ -295441,7 +295467,7 @@
    },
    "compute-pressure": {
     "DIR_METADATA": [
-     "866fe18e3429ef9da9ba99d79d4b542efeb7d147",
+     "84eada454f76c911fecb599c57547c9254615461",
      []
     ],
     "OWNERS": [
@@ -295517,7 +295543,7 @@
    },
    "contacts": {
     "DIR_METADATA": [
-     "00a602c2a0354a48c5cd803982b4f5a315828a90",
+     "4f90941a61a60c8950b228e6498713e9b6393f83",
      []
     ],
     "META.yml": [
@@ -295597,7 +295623,7 @@
    },
    "content-security-policy": {
     "DIR_METADATA": [
-     "3978889a17ef79b1fc3cd4439f8289b49cc6c9d2",
+     "afb7c1def9e72ff0434de9e7df2570c6fc961f0a",
      []
     ],
     "META.yml": [
@@ -297709,7 +297735,7 @@
    },
    "cookie-store": {
     "DIR_METADATA": [
-     "2d1e4e3e2f3bdb3cbc142d17a8ed3f5e08a34238",
+     "e3f78621264b6d32d7c8baeba34ba0e245d83458",
      []
     ],
     "META.yml": [
@@ -297757,7 +297783,7 @@
    },
    "cookies": {
     "DIR_METADATA": [
-     "5bfb93a27c7eff04e12c781490299d92b5d251bd",
+     "6816daf9256ea5a0ca0871a16d623ed31cae3391",
      []
     ],
     "META.yml": [
@@ -298071,13 +298097,13 @@
    },
    "core-aam": {
     "DIR_METADATA": [
-     "803582c48bb6a7ce275ce6dffaef5f172d12cfc6",
+     "c536c667a8d5dbeea8a1de059c919a3ed9ba4336",
      []
     ]
    },
    "cors": {
     "DIR_METADATA": [
-     "6f7db9f1903cf820990d8c18a0bc673b6bbdad40",
+     "b8c8e48dccf37bca36a1fb7bee2ac662768f35a6",
      []
     ],
     "META.yml": [
@@ -298448,7 +298474,7 @@
       []
      ],
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -305489,7 +305515,7 @@
      }
     },
     "DIR_METADATA": [
-     "c41ee089b8e083540febdab0fb23c1c8bafbbfc5",
+     "38c4cd0efc94eb8941cf45405784304d216b6aab",
      []
     ],
     "README.md": [
@@ -305506,7 +305532,7 @@
       []
      ],
      "DIR_METADATA": [
-      "12a5b0640db68c21567548a4542814ae563e3230",
+      "9e975836343f290b261804bc4b15cbf616b018d7",
       []
      ],
      "META.yml": [
@@ -305848,7 +305874,7 @@
     },
     "css-align": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -306052,7 +306078,7 @@
     },
     "css-anchor-position": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "WEB_FEATURES.yml": [
@@ -306071,10 +306097,6 @@
       "6ac93ee7a49bb2d31902ed9afdc0f4f4bd432f09",
       []
      ],
-     "anchor-position-auto-003-expected.txt": [
-      "3c133b4e49254d3d91c06be88bf675b6827f7f4f",
-      []
-     ],
      "anchor-position-multicol-003-expected.txt": [
       "7f03f52a900220153e28834fdbb2cca9f2d99da5",
       []
@@ -306146,7 +306168,7 @@
       []
      ],
      "DIR_METADATA": [
-      "849d9c0f45574344e9c71d57c57cdb030cd7205d",
+      "8ea8b499efa75b870181f1f6eadf3aa765603855",
       []
      ],
      "Document-getAnimations.tentative-expected.txt": [
@@ -306290,7 +306312,7 @@
     },
     "css-backgrounds": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -308410,7 +308432,7 @@
     },
     "css-box": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -308638,7 +308660,7 @@
     },
     "css-break": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -310054,7 +310076,7 @@
     },
     "css-contain": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -310311,7 +310333,7 @@
      ],
      "container-queries": {
       "DIR_METADATA": [
-       "2fa113f5f777ecbc33fcb295316c42fd7dfa8f71",
+       "b7dc9730f956fd4eda032aeeef33e920110545ee",
        []
       ],
       "WEB_FEATURES.yml": [
@@ -312388,7 +312410,7 @@
     },
     "css-display": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -312596,7 +312618,7 @@
     },
     "css-easing": {
      "DIR_METADATA": [
-      "849d9c0f45574344e9c71d57c57cdb030cd7205d",
+      "8ea8b499efa75b870181f1f6eadf3aa765603855",
       []
      ],
      "META.yml": [
@@ -312620,7 +312642,7 @@
     },
     "css-fill-stroke": {
      "DIR_METADATA": [
-      "aeb9395befd7af9295ff79f2a97d85da49320d75",
+      "23b65fc8f89f5ba15ff74a1e87f5bb1faffe1352",
       []
      ],
      "META.yml": [
@@ -312636,7 +312658,7 @@
     },
     "css-flexbox": {
      "DIR_METADATA": [
-      "e51f58e0dd2d33f85b8907874dab7a2afbd5224c",
+      "660b8c13ffbf14e5460f4f0c1eff016a2e07a881",
       []
      ],
      "META.yml": [
@@ -314840,7 +314862,7 @@
     },
     "css-font-loading": {
      "DIR_METADATA": [
-      "79a21decd5a038b674382ce2c897a53fb471f13f",
+      "1abfaf02dc5ff5f1ae04d981c9595326078f54e5",
       []
      ],
      "META.yml": [
@@ -314902,7 +314924,7 @@
     },
     "css-fonts": {
      "DIR_METADATA": [
-      "79a21decd5a038b674382ce2c897a53fb471f13f",
+      "1abfaf02dc5ff5f1ae04d981c9595326078f54e5",
       []
      ],
      "META.yml": [
@@ -322494,7 +322516,7 @@
     },
     "css-grid": {
      "DIR_METADATA": [
-      "0a52f7cafb7518ceb6f9a00a65377c7910e7cba2",
+      "8ff2dfc2bd4e1a8e4c2ce2d83347bab21e27b239",
       []
      ],
      "META.yml": [
@@ -325338,7 +325360,7 @@
     },
     "css-inline": {
      "DIR_METADATA": [
-      "d7542b2d18dbe34bd58c0ae8721b27f4bc134f76",
+      "51348ef089d5a8b4a54a07ddc701500237fd94eb",
       []
      ],
      "META.yml": [
@@ -325612,7 +325634,7 @@
     },
     "css-layout-api": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -326192,7 +326214,7 @@
     },
     "css-logical": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -326282,7 +326304,7 @@
     },
     "css-masking": {
      "DIR_METADATA": [
-      "3bd51be2ecdace0a79f51401df6803cb4d0d8beb",
+      "0dfef80521e06207d84f383c466f84b725226a40",
       []
      ],
      "META.yml": [
@@ -327172,7 +327194,7 @@
     },
     "css-multicol": {
      "DIR_METADATA": [
-      "d7aaf3d206d9205a622667caad552dbbbe3db583",
+      "19a8f8ddd5a15bb5661eac37d91f4f96dc66747d",
       []
      ],
      "META.yml": [
@@ -328306,7 +328328,7 @@
     },
     "css-overflow": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -328840,7 +328862,7 @@
     },
     "css-overscroll-behavior": {
      "DIR_METADATA": [
-      "913fe32f2a162e65713812b39ce842da97ab2189",
+      "5139078a147d01a3b4abfbca662e439539352f22",
       []
      ],
      "META.yml": [
@@ -328850,7 +328872,7 @@
     },
     "css-page": {
      "DIR_METADATA": [
-      "444bfd257b407e5443e23403f0209147a2c570d1",
+      "558bab8adc0265a4370657df61bad4751502e5a9",
       []
      ],
      "META.yml": [
@@ -329598,7 +329620,7 @@
     },
     "css-position": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -330954,7 +330976,7 @@
     },
     "css-rhythm": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "parsing": {
@@ -330978,7 +331000,7 @@
     },
     "css-ruby": {
      "DIR_METADATA": [
-      "abc2fd0b20698d7c05bc9be576fd9fb853a14501",
+      "d883c1fdde11f4047da00a462512b0f2051340b6",
       []
      ],
      "META.yml": [
@@ -331368,7 +331390,7 @@
     },
     "css-scroll-anchoring": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -331416,7 +331438,7 @@
     },
     "css-scroll-snap": {
      "DIR_METADATA": [
-      "913fe32f2a162e65713812b39ce842da97ab2189",
+      "5139078a147d01a3b4abfbca662e439539352f22",
       []
      ],
      "META.yml": [
@@ -331508,7 +331530,7 @@
     },
     "css-scroll-snap-2": {
      "DIR_METADATA": [
-      "913fe32f2a162e65713812b39ce842da97ab2189",
+      "5139078a147d01a3b4abfbca662e439539352f22",
       []
      ],
      "resources": {
@@ -331560,7 +331582,7 @@
     },
     "css-scrollbars": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -331808,7 +331830,7 @@
     },
     "css-shapes": {
      "DIR_METADATA": [
-      "2a197612af0b04fa4f2a3b0aeee84598813b9ac1",
+      "1cd8252c31b31677c9faae54d3918c1aa41c9638",
       []
      ],
      "META.yml": [
@@ -332652,7 +332674,7 @@
     },
     "css-sizing": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -333512,7 +333534,7 @@
     },
     "css-tables": {
      "DIR_METADATA": [
-      "b0e995291a114006c858e0e729f9004547f8fd37",
+      "96713364d9283e0d61e3618028156e5ac8069c9a",
       []
      ],
      "META.yml": [
@@ -333810,7 +333832,7 @@
     },
     "css-text": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -337820,7 +337842,7 @@
     },
     "css-text-decor": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -338574,7 +338596,7 @@
       []
      ],
      "DIR_METADATA": [
-      "0c19f2542ada4b1e9ab5661d9555fd3cc1c828f2",
+      "1ccb381510827bf921b5cd82f64832d9a9f145fb",
       []
      ],
      "META.yml": [
@@ -340026,7 +340048,7 @@
     },
     "css-transitions": {
      "DIR_METADATA": [
-      "849d9c0f45574344e9c71d57c57cdb030cd7205d",
+      "8ea8b499efa75b870181f1f6eadf3aa765603855",
       []
      ],
      "KeyframeEffect-getKeyframes.tentative-expected.txt": [
@@ -340770,7 +340792,7 @@
     },
     "css-ui": {
      "DIR_METADATA": [
-      "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+      "5e354710d3b40f776065d9eadb2feb229fb8a225",
       []
      ],
      "META.yml": [
@@ -342862,7 +342884,7 @@
       []
      ],
      "DIR_METADATA": [
-      "92f4c431c19b1194009c9b591111f3ee70a34522",
+      "6b3fe4d05f3e015b933a583232fedb6f9e65d8a6",
       []
      ],
      "WEB_FEATURES.yml": [
@@ -342909,6 +342931,10 @@
       "c47b31e4d5a3a2121bf8191c1e612b5a5f2f1663",
       []
      ],
+     "class-specificity-ref.html": [
+      "5c882ada040ec700eda008e016d0a1827e31d368",
+      []
+     ],
      "content-object-fit-fill-ref.html": [
       "b86e0a95a172bcc191f17d954b2cf6bd710a1fd1",
       []
@@ -343504,7 +343530,7 @@
     },
     "css-writing-modes": {
      "DIR_METADATA": [
-      "6d01f16a1c7c74fc298cf2730ecf4026e206ca04",
+      "509b85e788247f8eb8d15f98be34a2079bd7e294",
       []
      ],
      "META.yml": [
@@ -346594,7 +346620,7 @@
     },
     "filter-effects": {
      "DIR_METADATA": [
-      "3bd51be2ecdace0a79f51401df6803cb4d0d8beb",
+      "0dfef80521e06207d84f383c466f84b725226a40",
       []
      ],
      "META.yml": [
@@ -347396,7 +347422,7 @@
     },
     "geometry": {
      "DIR_METADATA": [
-      "7b5b42f0644d3bf645c8a419c626c7992e15eae7",
+      "bf8bf7ef91a3c5c8db4c2bb317a7f28be4db6cae",
       []
      ],
      "META.yml": [
@@ -347746,7 +347772,7 @@
     },
     "printing": {
      "DIR_METADATA": [
-      "444bfd257b407e5443e23403f0209147a2c570d1",
+      "558bab8adc0265a4370657df61bad4751502e5a9",
       []
      ],
      "OWNERS": [
@@ -348924,12 +348950,12 @@
      "mozilla": {
       "mozilla-central-reftests": {
        "DIR_METADATA": [
-        "97e2659bf8cff713dc731eec03ac942794cc5fd2",
+        "5e354710d3b40f776065d9eadb2feb229fb8a225",
         []
        ],
        "flexbox": {
         "DIR_METADATA": [
-         "e51f58e0dd2d33f85b8907874dab7a2afbd5224c",
+         "660b8c13ffbf14e5460f4f0c1eff016a2e07a881",
          []
         ],
         "OWNERS": [
@@ -348939,7 +348965,7 @@
        },
        "shapes1": {
         "DIR_METADATA": [
-         "2a197612af0b04fa4f2a3b0aeee84598813b9ac1",
+         "1cd8252c31b31677c9faae54d3918c1aa41c9638",
          []
         ]
        },
@@ -348971,7 +348997,7 @@
    },
    "custom-elements": {
     "DIR_METADATA": [
-     "70523b2671d129a214c5b1660a887430fdf24c38",
+     "da4ec085247a73295cfd55a20b7d5589f4f09246",
      []
     ],
     "Document-createElement-customized-builtins-expected.txt": [
@@ -349173,7 +349199,7 @@
    },
    "device-memory": {
     "DIR_METADATA": [
-     "49afa4246ce1f9872f63629d058a6fb0a9bb8898",
+     "51daa9d1f9980472f510632b968dfdbc827ad069",
      []
     ],
     "META.yml": [
@@ -349187,7 +349213,7 @@
    },
    "direct-sockets": {
     "DIR_METADATA": [
-     "b9d644b2b52ab0c94b02de0ccb94c2a5708155be",
+     "e168aa3489d82b455687f96e7e256ebef77b94c0",
      []
     ],
     "META.yml": [
@@ -349799,7 +349825,7 @@
    },
    "dom": {
     "DIR_METADATA": [
-     "c899c23a6f6d5f830eaf301f1b099c7400697746",
+     "f9a56cc928857bcba3fde2d0b7c11cc679b5d417",
      []
     ],
     "META.yml": [
@@ -349905,7 +349931,7 @@
      },
      "scrolling": {
       "DIR_METADATA": [
-       "71b1a71b1fc10f62a9c9b29c86269a4b75f64892",
+       "5f3ed792088ab2ebde6d2cff982ed0ff3978c748",
        []
       ],
       "OWNERS": [
@@ -350459,7 +350485,7 @@
    },
    "domparsing": {
     "DIR_METADATA": [
-     "5a884ce65add9a794aa76a284a9f7c6328837d3c",
+     "49ea5cdbba15cd5a86e7c3d560ca92798e581465",
      []
     ],
     "DOMParser-parseFromString-xml-expected.txt": [
@@ -350519,7 +350545,7 @@
    },
    "domxpath": {
     "DIR_METADATA": [
-     "5a884ce65add9a794aa76a284a9f7c6328837d3c",
+     "49ea5cdbba15cd5a86e7c3d560ca92798e581465",
      []
     ],
     "META.yml": [
@@ -350567,7 +350593,7 @@
    },
    "dpub-aam": {
     "DIR_METADATA": [
-     "803582c48bb6a7ce275ce6dffaef5f172d12cfc6",
+     "c536c667a8d5dbeea8a1de059c919a3ed9ba4336",
      []
     ],
     "META.yml": [
@@ -350577,7 +350603,7 @@
    },
    "dpub-aria": {
     "DIR_METADATA": [
-     "803582c48bb6a7ce275ce6dffaef5f172d12cfc6",
+     "c536c667a8d5dbeea8a1de059c919a3ed9ba4336",
      []
     ],
     "META.yml": [
@@ -350601,7 +350627,7 @@
    },
    "editing": {
     "DIR_METADATA": [
-     "df514395075eba326bc9cc0f767e30f7fd298d59",
+     "b02baf2b975aae8fbe87db8500b9905575c44a9f",
      []
     ],
     "META.yml": [
@@ -351627,7 +351653,7 @@
    },
    "element-timing": {
     "DIR_METADATA": [
-     "17e24c0942c8113028f9267cb32724f45d651c53",
+     "e1a0e70f54c6a2b32e056562aba8680ed9465d9b",
      []
     ],
     "META.yml": [
@@ -351703,7 +351729,7 @@
    },
    "encoding": {
     "DIR_METADATA": [
-     "1856ad29fa833ed89056471d00f06fc5b83cca1e",
+     "c4133b0ddece7ac66c2aa5f306b2eb17c9d47c4d",
      []
     ],
     "META.yml": [
@@ -352773,7 +352799,7 @@
    },
    "encrypted-media": {
     "DIR_METADATA": [
-     "b2d54848137f71e991f587b6a9c2c10c7c9ed490",
+     "2fa48e26bc85cc184fbf2d2594022f22376d97dd",
      []
     ],
     "META.yml": [
@@ -353117,7 +353143,7 @@
    },
    "entries-api": {
     "DIR_METADATA": [
-     "3fe14ca40e52343cd7f7a3478f3f8b770e2e2ce0",
+     "16b976ab451500a09882e7fda75b8bf2f4af2acc",
      []
     ],
     "META.yml": [
@@ -353189,7 +353215,7 @@
    },
    "event-timing": {
     "DIR_METADATA": [
-     "17e24c0942c8113028f9267cb32724f45d651c53",
+     "e1a0e70f54c6a2b32e056562aba8680ed9465d9b",
      []
     ],
     "META.yml": [
@@ -353229,7 +353255,7 @@
    },
    "eventsource": {
     "DIR_METADATA": [
-     "7db289519cc9a2f553de565b2d6efaada4cfbaee",
+     "8e8b91ddda00a87040e7cf7c38025c98ffc2968e",
      []
     ],
     "META.yml": [
@@ -353403,7 +353429,7 @@
    },
    "feature-policy": {
     "DIR_METADATA": [
-     "6764282f2a3b50937eddd107a9f626b99766d1d6",
+     "9a5e0775d4e3bac1fb4223a9d8889bdde19319c6",
      []
     ],
     "META.yml": [
@@ -353681,7 +353707,7 @@
    },
    "fenced-frame": {
     "DIR_METADATA": [
-     "8c8f7d738ac69e05d9d64833808488b8074eb238",
+     "80d1ce82d8b1b50a92823c3cc8a1df61127fd05b",
      []
     ],
     "README.md": [
@@ -354541,7 +354567,7 @@
    },
    "fetch": {
     "DIR_METADATA": [
-     "7576a20a63e1e4a132ffa7bcd4727416fb0460e8",
+     "31ad696e5b3b222730440a3fd7277ff0aa88aac9",
      []
     ],
     "META.yml": [
@@ -355406,7 +355432,7 @@
     },
     "corb": {
      "DIR_METADATA": [
-      "086a544b71cbb52cd5add472e1bc35916c1a71a1",
+      "60d71a6305f9c7a4d32c6d6e23c52c3ff86044a1",
       []
      ],
      "OWNERS": [
@@ -355598,7 +355624,7 @@
     },
     "cross-origin-resource-policy": {
      "DIR_METADATA": [
-      "086a544b71cbb52cd5add472e1bc35916c1a71a1",
+      "60d71a6305f9c7a4d32c6d6e23c52c3ff86044a1",
       []
      ],
      "OWNERS": [
@@ -356629,7 +356655,7 @@
    },
    "file-system-access": {
     "DIR_METADATA": [
-     "3fe14ca40e52343cd7f7a3478f3f8b770e2e2ce0",
+     "16b976ab451500a09882e7fda75b8bf2f4af2acc",
      []
     ],
     "META.yml": [
@@ -357107,7 +357133,7 @@
    },
    "font-access": {
     "DIR_METADATA": [
-     "c72979d5cee8d80d814524f24cb99b8c5b164692",
+     "453e8964f0fb3116ab83c8bf43aae7af0e95729b",
      []
     ],
     "OWNERS": [
@@ -358103,7 +358129,7 @@
    },
    "fs": {
     "DIR_METADATA": [
-     "12cf0e36d35303a5936a5a1f0fba87083b219262",
+     "16b976ab451500a09882e7fda75b8bf2f4af2acc",
      []
     ],
     "META.yml": [
@@ -358293,7 +358319,7 @@
    },
    "fullscreen": {
     "DIR_METADATA": [
-     "1652c4fa811abc3de5100db23c1e35ff86f5d20d",
+     "9231eb5bf517e390a361b2a66a1aa807b165670f",
      []
     ],
     "META.yml": [
@@ -358377,7 +358403,7 @@
    },
    "gamepad": {
     "DIR_METADATA": [
-     "4f543fde4f6384f3e2dd959cc5cb697981c3e9d2",
+     "8cac0a9f585ea681683966eb2e7041447aa6faf6",
      []
     ],
     "META.yml": [
@@ -358411,7 +358437,7 @@
    },
    "generic-sensor": {
     "DIR_METADATA": [
-     "caea56d67a654fb8b15e43a1b0eab7e413a88177",
+     "22cfa46b2f5ffb1fa3db78d07e69585ff3c48e8d",
      []
     ],
     "META.yml": [
@@ -358451,7 +358477,7 @@
    },
    "geolocation-API": {
     "DIR_METADATA": [
-     "1fb69db5e8b213d5076cf202a51b3e92d192c471",
+     "e20089c8e5f1c6f06bfaa2177ac3905cbe89f5a1",
      []
     ],
     "META.yml": [
@@ -358491,7 +358517,7 @@
    },
    "geolocation-sensor": {
     "DIR_METADATA": [
-     "caea56d67a654fb8b15e43a1b0eab7e413a88177",
+     "22cfa46b2f5ffb1fa3db78d07e69585ff3c48e8d",
      []
     ],
     "GeolocationSensor-disabled-by-feature-policy.https-expected.txt": [
@@ -358555,7 +358581,7 @@
    },
    "graphics-aam": {
     "DIR_METADATA": [
-     "44396ee85cd0abfe885866f20a0f64d5b61eb8c5",
+     "c536c667a8d5dbeea8a1de059c919a3ed9ba4336",
      []
     ],
     "META.yml": [
@@ -358565,7 +358591,7 @@
    },
    "graphics-aria": {
     "DIR_METADATA": [
-     "44396ee85cd0abfe885866f20a0f64d5b61eb8c5",
+     "c536c667a8d5dbeea8a1de059c919a3ed9ba4336",
      []
     ],
     "META.yml": [
@@ -358575,7 +358601,7 @@
    },
    "gyroscope": {
     "DIR_METADATA": [
-     "caea56d67a654fb8b15e43a1b0eab7e413a88177",
+     "22cfa46b2f5ffb1fa3db78d07e69585ff3c48e8d",
      []
     ],
     "Gyroscope-disabled-by-feature-policy.https.html.headers": [
@@ -358607,7 +358633,7 @@
    },
    "hr-time": {
     "DIR_METADATA": [
-     "17e24c0942c8113028f9267cb32724f45d651c53",
+     "e1a0e70f54c6a2b32e056562aba8680ed9465d9b",
      []
     ],
     "META.yml": [
@@ -358667,7 +358693,7 @@
    },
    "html": {
     "DIR_METADATA": [
-     "83562e71cd20d551852247d15c9573d68078bb17",
+     "80e019d94891585c88cab66335aa3a4a7953b722",
      []
     ],
     "META.yml": [
@@ -358680,7 +358706,7 @@
     ],
     "anonymous-iframe": {
      "DIR_METADATA": [
-      "a1a39b2ec27a6984e710b48b3478e2da2979bd15",
+      "aefe36ceb35d80b7321e8fbae910e74a8b0e6b2f",
       []
      ],
      "fenced-frame-bypass.tentative.https.window-expected.txt": [
@@ -358717,12 +358743,12 @@
     "browsers": {
      "browsing-the-web": {
       "DIR_METADATA": [
-       "7f2331a3c7a9ee714fa7dc7ef99d85d4c9875f78",
+       "a95e8282333741e60e6c60e17dedc5d37d6fe74e",
        []
       ],
       "back-forward-cache": {
        "DIR_METADATA": [
-        "27e202f1dd9f77ec5704168b248a9a8fdd62f7d7",
+        "25a99cf75a9c7c561ca6fc54c2e908e524a0f0ae",
         []
        ],
        "README.md": [
@@ -359651,7 +359677,7 @@
      },
      "history": {
       "DIR_METADATA": [
-       "d1616e608283fd58960d220a0c41adaffaab43b8",
+       "6a5a4e506745134ef1923f18d8d60dff0db9339a",
        []
       ],
       "joint-session-history": {
@@ -360019,7 +360045,7 @@
      },
      "offline": {
       "DIR_METADATA": [
-       "572fffe4d37f43eeb09e2238c4f1b19540adeb98",
+       "64b6100d95b31245ff9a771a792a65db2a922ea3",
        []
       ],
       "changestonetworkingmodel": {
@@ -360081,7 +360107,7 @@
      },
      "origin": {
       "DIR_METADATA": [
-       "a19d4eeecc75654a46966efa7badc560e5ebc3e1",
+       "4b3272cebd1b67dca68efcb1078a74ab4eadde12",
        []
       ],
       "cross-origin-objects": {
@@ -360419,7 +360445,7 @@
      },
      "sandboxing": {
       "DIR_METADATA": [
-       "1545c0cd3269bb1cf0aea96953deb81729f96d47",
+       "e8afad2413e607029d81b9e95fafd3c363902dd3",
        []
       ],
       "inner-iframe.html": [
@@ -361026,7 +361052,7 @@
     },
     "canvas": {
      "DIR_METADATA": [
-      "ab76b6227db42798d91c09a8f9f772f64ae67d9c",
+      "2ce9a46f05ee2614aff69c66d621b20bcb93b396",
       []
      ],
      "META.yml": [
@@ -363502,7 +363528,7 @@
     },
     "capability-delegation": {
      "DIR_METADATA": [
-      "4315492acc29e0a504bc5293b4f7de6560bde28f",
+      "1e6300806fa1d60c270698a2adc69c1047a8cdbe",
       []
      ],
      "resources": {
@@ -363518,7 +363544,7 @@
     },
     "cross-origin-embedder-policy": {
      "DIR_METADATA": [
-      "02e1e27f9856a51cbb836be0da5c94a024c9f96b",
+      "3b21e94ee860dce2a04f515fb0432dbedfdbc89d",
       []
      ],
      "META.yml": [
@@ -363896,7 +363922,7 @@
     },
     "cross-origin-opener-policy": {
      "DIR_METADATA": [
-      "0fbc9925cf1cba4a314fee38b2e76026cf021657",
+      "71b31a9ac09e02f15e408a5b862a9a9e4c43268d",
       []
      ],
      "META.yml": [
@@ -365032,7 +365058,7 @@
         []
        ],
        "utils.js": [
-        "9a890ab685c675ee5baca618ce5d85b367761760",
+        "8a9a537e96df89a2944d62dcc2915b8a51796fd5",
         []
        ]
       }
@@ -365050,7 +365076,7 @@
     },
     "editing": {
      "DIR_METADATA": [
-      "df514395075eba326bc9cc0f767e30f7fd298d59",
+      "b02baf2b975aae8fbe87db8500b9905575c44a9f",
       []
      ],
      "activation": {
@@ -368583,7 +368609,7 @@
     "interaction": {
      "focus": {
       "DIR_METADATA": [
-       "71b1a71b1fc10f62a9c9b29c86269a4b75f64892",
+       "5f3ed792088ab2ebde6d2cff982ed0ff3978c748",
        []
       ],
       "OWNERS": [
@@ -368804,7 +368830,7 @@
      "non-replaced-elements": {
       "flow-content-0": {
        "DIR_METADATA": [
-        "aab9ded40a44aa888745a4a544ff6b6874015624",
+        "a7142360eb190ab0466fca43bfeb7a068c043b1d",
         []
        ],
        "div-align-ref.html": [
@@ -368870,7 +368896,7 @@
       ],
       "lists": {
        "DIR_METADATA": [
-        "aab9ded40a44aa888745a4a544ff6b6874015624",
+        "a7142360eb190ab0466fca43bfeb7a068c043b1d",
         []
        ],
        "TODO-lists.html": [
@@ -368935,14 +368961,14 @@
          []
         ],
         "DIR_METADATA": [
-         "aab9ded40a44aa888745a4a544ff6b6874015624",
+         "a7142360eb190ab0466fca43bfeb7a068c043b1d",
          []
         ]
        }
       },
       "tables": {
        "DIR_METADATA": [
-        "4f37329faad59e57da0239577be1f4216e54cd0a",
+        "c8909f2b9c86237b881f6f2465f13e4fb1ce162f",
         []
        ],
        "colgroup_valign-ref.xhtml": [
@@ -369074,7 +369100,7 @@
       },
       "the-fieldset-and-legend-elements": {
        "DIR_METADATA": [
-        "aab9ded40a44aa888745a4a544ff6b6874015624",
+        "a7142360eb190ab0466fca43bfeb7a068c043b1d",
         []
        ],
        "META.yml": [
@@ -369258,7 +369284,7 @@
       },
       "the-frameset-and-frame-elements": {
        "DIR_METADATA": [
-        "aab9ded40a44aa888745a4a544ff6b6874015624",
+        "a7142360eb190ab0466fca43bfeb7a068c043b1d",
         []
        ],
        "reference": {
@@ -369288,7 +369314,7 @@
       },
       "the-hr-element-0": {
        "DIR_METADATA": [
-        "aab9ded40a44aa888745a4a544ff6b6874015624",
+        "a7142360eb190ab0466fca43bfeb7a068c043b1d",
         []
        ],
        "align-ref.html": [
@@ -369368,7 +369394,7 @@
      "replaced-elements": {
       "attributes-for-embedded-content-and-images": {
        "DIR_METADATA": [
-        "aab9ded40a44aa888745a4a544ff6b6874015624",
+        "a7142360eb190ab0466fca43bfeb7a068c043b1d",
         []
        ],
        "img-aspect-ratio-expected.txt": [
@@ -369452,7 +369478,7 @@
       },
       "embedded-content-rendering-rules": {
        "DIR_METADATA": [
-        "aab9ded40a44aa888745a4a544ff6b6874015624",
+        "a7142360eb190ab0466fca43bfeb7a068c043b1d",
         []
        ],
        "canvas-fallback-ref.html": [
@@ -369474,7 +369500,7 @@
       },
       "images": {
        "DIR_METADATA": [
-        "aab9ded40a44aa888745a4a544ff6b6874015624",
+        "a7142360eb190ab0466fca43bfeb7a068c043b1d",
         []
        ],
        "blocked-by-csp-ref.html": [
@@ -369595,7 +369621,7 @@
      },
      "the-details-element": {
       "DIR_METADATA": [
-       "df439d2a81c457f7988c5a7f458343bc277fd049",
+       "72387ccff958b7a6fea9972a4f0cedad09e83c03",
        []
       ],
       "details-display-property-is-ignored-ref.html": [
@@ -369808,7 +369834,7 @@
     },
     "select": {
      "DIR_METADATA": [
-      "9a1c39ffa1cc8fd0e858ced30bdee0405f14be67",
+      "c8ba5e73831c7af891acb9ba7fd72d83926a78c9",
       []
      ]
     },
@@ -369862,7 +369888,7 @@
       },
       "the-base-element": {
        "DIR_METADATA": [
-        "b302b2d63e0743df407f4e38626936cb7fbdc7c1",
+        "fcaa81db5f93d5eaee17ffb04ea5ce4b13ea421e",
         []
        ],
        "base-data-expected.txt": [
@@ -369884,7 +369910,7 @@
       },
       "the-link-element": {
        "DIR_METADATA": [
-        "bd63d0f587f37e7af822a01cff5751f2799d4ec0",
+        "bfdbe5913d3b90f505ae594d16d62bcf8dc91f24",
         []
        ],
        "all": [
@@ -370118,7 +370144,7 @@
       },
       "media-elements": {
        "DIR_METADATA": [
-        "672ea355b505d0280084c26506ff98a640bfb36e",
+        "6b6e7c9b9bfe0b1e97d09f49850840f0ab755115",
         []
        ],
        "autoplay-allowed-by-feature-policy.https.sub.html.headers": [
@@ -370642,7 +370668,7 @@
       },
       "the-audio-element": {
        "DIR_METADATA": [
-        "4a4d0c998e0d0b772051919750136bd0ff91f2fc",
+        "0a1bdecc9eb0aa602f7f560964939d957e8cc52b",
         []
        ],
        "audio_content-ref.htm": [
@@ -370652,7 +370678,7 @@
       },
       "the-canvas-element": {
        "DIR_METADATA": [
-        "ab76b6227db42798d91c09a8f9f772f64ae67d9c",
+        "2ce9a46f05ee2614aff69c66d621b20bcb93b396",
         []
        ],
        "imagedata-expected.txt": [
@@ -370726,7 +370752,7 @@
       },
       "the-embed-element": {
        "DIR_METADATA": [
-        "41cb89f8902d75fd47ba0e9bab7a774bdb7335b1",
+        "23ebfecc93a139823e907a1e82dcc5d75d841690",
         []
        ],
        "embed-hidden-attribute-ref.html": [
@@ -370752,13 +370778,13 @@
       },
       "the-frame-element": {
        "DIR_METADATA": [
-        "1fdab2993377d969915d6c53cf299494eb6ea27c",
+        "16b6d7c9f7dd414631d9a1f3f7781d456e48b7a4",
         []
        ]
       },
       "the-iframe-element": {
        "DIR_METADATA": [
-        "54bf5e2d38634747e0098bf3318a1b8c95011f33",
+        "ec28560a3120e8de48e517b75c1f1d4c1df8429a",
         []
        ],
        "change_child.html": [
@@ -371362,7 +371388,7 @@
       },
       "the-video-element": {
        "DIR_METADATA": [
-        "4069fda1d1abc9cfa3172454d234e485eac0d33a",
+        "399cf2aa213438f79a37707a56cb751b07da869e",
         []
        ],
        "video-poster-shown-preload-auto-ref.html": [
@@ -371385,7 +371411,7 @@
      },
      "forms": {
       "DIR_METADATA": [
-       "47ef0b4add5234a8dd1947589b2023ae19937187",
+       "a1a17b487752c8c1f6a78294533c03fbf088d2a6",
        []
       ],
       "META.yml": [
@@ -371414,7 +371440,7 @@
       },
       "form-submission-0": {
        "DIR_METADATA": [
-        "216811a6b87c1aadebaeed1a292aba36d3d9cb25",
+        "8744800ee36273cc28c4804ad9554d7eb9de1c77",
         []
        ],
        "enctypes-helper.js": [
@@ -371466,7 +371492,7 @@
       },
       "form-submission-target": {
        "DIR_METADATA": [
-        "216811a6b87c1aadebaeed1a292aba36d3d9cb25",
+        "8744800ee36273cc28c4804ad9554d7eb9de1c77",
         []
        ],
        "form-target-iframe-helper.py": [
@@ -371516,7 +371542,7 @@
       },
       "the-button-element": {
        "DIR_METADATA": [
-        "7f4667d5d230f86997119c19665fec5234a0aec6",
+        "4881f7e9eece3beb376c2b42cd4ad7e9c6a4413c",
         []
        ],
        "button-activate-frame.html": [
@@ -371534,13 +371560,13 @@
       },
       "the-datalist-element": {
        "DIR_METADATA": [
-        "2ec1cb8c2fbd413a2b158d041c2595ebdf7547c9",
+        "d4bdb2a6271c098b3f5cd52d4b47c90e220a5825",
         []
        ]
       },
       "the-fieldset-element": {
        "DIR_METADATA": [
-        "880fbeebd89d0a4e670b691f2900d41a2f5ef30e",
+        "66c747a5b5224140e9bcae06106116307db2157c",
         []
        ],
        "accessibility": {
@@ -371678,7 +371704,7 @@
       },
       "the-label-element": {
        "DIR_METADATA": [
-        "90f14b62ceaa4f9e5cf1d55828c16dd11d61966d",
+        "f16207d340f6ae6875949a274bdc4742194e1625",
         []
        ],
        "iframe-label-attributes.html": [
@@ -371692,13 +371718,13 @@
       },
       "the-legend-element": {
        "DIR_METADATA": [
-        "880fbeebd89d0a4e670b691f2900d41a2f5ef30e",
+        "66c747a5b5224140e9bcae06106116307db2157c",
         []
        ]
       },
       "the-meter-element": {
        "DIR_METADATA": [
-        "4e6647688ef56831da0d51393ad03aafca8f7db3",
+        "09bf11ef4c5459e8edd2f436b36d675375bd00bc",
         []
        ],
        "meter-min-rendering-ref.html": [
@@ -371708,13 +371734,13 @@
       },
       "the-optgroup-element": {
        "DIR_METADATA": [
-        "9a1c39ffa1cc8fd0e858ced30bdee0405f14be67",
+        "c8ba5e73831c7af891acb9ba7fd72d83926a78c9",
         []
        ]
       },
       "the-option-element": {
        "DIR_METADATA": [
-        "9a1c39ffa1cc8fd0e858ced30bdee0405f14be67",
+        "c8ba5e73831c7af891acb9ba7fd72d83926a78c9",
         []
        ],
        "dynamic-content-change-rendering-ref.html": [
@@ -371728,19 +371754,19 @@
       },
       "the-output-element": {
        "DIR_METADATA": [
-        "92fcea609bc899be614fbf094527324d0fa03d15",
+        "5f96515e41ec80c00c0fd4075b1a4f15c274879d",
         []
        ]
       },
       "the-progress-element": {
        "DIR_METADATA": [
-        "db9a278eac4943e267462d0e6ba785d5fc32a0c7",
+        "bffb9a7a62945645e3e2afdbdca700c5018f2e96",
         []
        ]
       },
       "the-select-element": {
        "DIR_METADATA": [
-        "9a1c39ffa1cc8fd0e858ced30bdee0405f14be67",
+        "c8ba5e73831c7af891acb9ba7fd72d83926a78c9",
         []
        ],
        "reset-algorithm-rendering-ref.html": [
@@ -371753,7 +371779,7 @@
          []
         ],
         "stylable-select-styles.css": [
-         "726380f940d2e32b390269afca9617c88e3bb251",
+         "a7e9a87cdfa96a56cd82da56e78717efb4b0c966",
          []
         ]
        },
@@ -371768,7 +371794,7 @@
       },
       "the-selectlist-element": {
        "DIR_METADATA": [
-        "973a72f0c76ce10096fcd255f83c0cec54cf69fa",
+        "53ce4c9e0b1b1bbeae0ccf5375622d4cc5e1f9f3",
         []
        ],
        "button-type-selectlist-appearance-ref.html": [
@@ -371876,7 +371902,7 @@
       },
       "the-textarea-element": {
        "DIR_METADATA": [
-        "ccd3628d451d3fcf7ab70d815c2bb6716f66dea5",
+        "67b102f814349e5a672d01449796307ee7a37316",
         []
        ],
        "multiline-placeholder-ref.html": [
@@ -372030,7 +372056,7 @@
       },
       "the-details-element": {
        "DIR_METADATA": [
-        "df439d2a81c457f7988c5a7f458343bc277fd049",
+        "72387ccff958b7a6fea9972a4f0cedad09e83c03",
         []
        ],
        "details-add-summary-ref.html": [
@@ -372046,7 +372072,7 @@
       },
       "the-dialog-element": {
        "DIR_METADATA": [
-        "12964ec475f8051987f0e0b3b21da40c24deb907",
+        "dbd93e12e34bc8a6fded50ec431c10193276b701",
         []
        ],
        "WEB_FEATURES.yml": [
@@ -372206,7 +372232,7 @@
       },
       "the-summary-element": {
        "DIR_METADATA": [
-        "df439d2a81c457f7988c5a7f458343bc277fd049",
+        "72387ccff958b7a6fea9972a4f0cedad09e83c03",
         []
        ]
       }
@@ -372335,7 +372361,7 @@
      },
      "popovers": {
       "DIR_METADATA": [
-       "84360244f4fef37b9d80991df49dc030e0744d5f",
+       "643fadf00993764164f913173d081f0fb781ebbb",
        []
       ],
       "WEB_FEATURES.yml": [
@@ -374281,7 +374307,7 @@
      },
      "tabular-data": {
       "DIR_METADATA": [
-       "0888feef801b9eae64055d36616c459b6ae263e5",
+       "a50bbad8a217186ed1f52615cdce6d92bc77dce2",
        []
       ],
       "META.yml": [
@@ -374296,7 +374322,7 @@
      "text-level-semantics": {
       "the-a-element": {
        "DIR_METADATA": [
-        "c830afebf42164fda5c66cdb1ca4f34d31dd44b3",
+        "ce0c8aa742a2b0ff6b8d07802f95d181c04c9d02",
         []
        ],
        "a-download-404.py": [
@@ -374434,7 +374460,7 @@
     },
     "syntax": {
      "DIR_METADATA": [
-      "ef1ead61c0a42f90fbb5f2c2e4a51380ac651ccc",
+      "9defd5b2f11fab493420e8e6a90f37a4a5e5bcb6",
       []
      ],
      "charset": {
@@ -375736,7 +375762,7 @@
     },
     "the-xhtml-syntax": {
      "DIR_METADATA": [
-      "a7b9f49c7d02fd94ca00a167f1213052e36bc5fb",
+      "e4d35e1ea7db3074420192430a8fd24384ddb8e8",
       []
      ],
      "parsing-xhtml-documents": {
@@ -375792,7 +375818,7 @@
     },
     "user-activation": {
      "DIR_METADATA": [
-      "71b1a71b1fc10f62a9c9b29c86269a4b75f64892",
+      "5f3ed792088ab2ebde6d2cff982ed0ff3978c748",
       []
      ],
      "META.yml": [
@@ -375845,7 +375871,7 @@
     "webappapis": {
      "animation-frames": {
       "DIR_METADATA": [
-       "b3548e292a5c44cb66e004f343a63ff0823c7525",
+       "3ebd4ca7994eabac158da14f99c3454780e943a7",
        []
       ]
      },
@@ -375992,7 +376018,7 @@
       },
       "html-unsafe-methods": {
        "DIR_METADATA": [
-        "359e0a1c4901bb9deaf88f94b4e04cccf9a2235e",
+        "3954de55ef246a5e85592fadb0055a85ba005371",
         []
        ],
        "resources": {
@@ -376457,7 +376483,7 @@
    },
    "html-aam": {
     "DIR_METADATA": [
-     "44396ee85cd0abfe885866f20a0f64d5b61eb8c5",
+     "c536c667a8d5dbeea8a1de059c919a3ed9ba4336",
      []
     ],
     "META.yml": [
@@ -376471,7 +376497,7 @@
    },
    "html-media-capture": {
     "DIR_METADATA": [
-     "9d6cfb7ba022d236a5be2a0fedf3b44367b1b071",
+     "d4bb4ac4b9187864a168c35f55bf51465f2e1a06",
      []
     ],
     "META.yml": [
@@ -376551,7 +376577,7 @@
    },
    "imagebitmap-renderingcontext": {
     "DIR_METADATA": [
-     "43096d5e3f23002dc54a8181361f88d4bcce822f",
+     "549d477488a8cd8f5425eee3c7ecb8d35464c326",
      []
     ],
     "META.yml": [
@@ -376565,7 +376591,7 @@
      []
     ],
     "DIR_METADATA": [
-     "9b0121386d1d5435a8420caea934ef0d59a826aa",
+     "03f4b4ad9b9145a88413664de8c7b2341b0b85fa",
      []
     ],
     "META.yml": [
@@ -377257,7 +377283,7 @@
     "testdriver": {
      "actions": {
       "DIR_METADATA": [
-       "71b1a71b1fc10f62a9c9b29c86269a4b75f64892",
+       "5f3ed792088ab2ebde6d2cff982ed0ff3978c748",
        []
       ],
       "OWNERS": [
@@ -377359,7 +377385,7 @@
    },
    "input-events": {
     "DIR_METADATA": [
-     "71b1a71b1fc10f62a9c9b29c86269a4b75f64892",
+     "5f3ed792088ab2ebde6d2cff982ed0ff3978c748",
      []
     ],
     "META.yml": [
@@ -378647,7 +378673,7 @@
    },
    "intersection-observer": {
     "DIR_METADATA": [
-     "aab9ded40a44aa888745a4a544ff6b6874015624",
+     "a7142360eb190ab0466fca43bfeb7a068c043b1d",
      []
     ],
     "META.yml": [
@@ -378899,7 +378925,7 @@
    },
    "keyboard-map": {
     "DIR_METADATA": [
-     "21c3f0496c6abe11737266979e496e58726edf83",
+     "f5a8ef9fcbc1324cd1c7fe685ab5ca1c6f63c3f6",
      []
     ],
     "META.yml": [
@@ -378927,7 +378953,7 @@
    },
    "largest-contentful-paint": {
     "DIR_METADATA": [
-     "17e24c0942c8113028f9267cb32724f45d651c53",
+     "e1a0e70f54c6a2b32e056562aba8680ed9465d9b",
      []
     ],
     "META.yml": [
@@ -378975,7 +379001,7 @@
    },
    "layout-instability": {
     "DIR_METADATA": [
-     "17e24c0942c8113028f9267cb32724f45d651c53",
+     "e1a0e70f54c6a2b32e056562aba8680ed9465d9b",
      []
     ],
     "META.yml": [
@@ -379337,7 +379363,7 @@
    },
    "longtask-timing": {
     "DIR_METADATA": [
-     "17e24c0942c8113028f9267cb32724f45d651c53",
+     "e1a0e70f54c6a2b32e056562aba8680ed9465d9b",
      []
     ],
     "META.yml": [
@@ -379385,7 +379411,7 @@
    },
    "magnetometer": {
     "DIR_METADATA": [
-     "caea56d67a654fb8b15e43a1b0eab7e413a88177",
+     "22cfa46b2f5ffb1fa3db78d07e69585ff3c48e8d",
      []
     ],
     "META.yml": [
@@ -381275,7 +381301,7 @@
    },
    "mediacapture-insertable-streams": {
     "DIR_METADATA": [
-     "563fa6720ba3a89d6ad8add86cd903465fe456f2",
+     "433a390634c6a8e750aaae8e8888c16adb296b1d",
      []
     ],
     "META.yml": [
@@ -381381,7 +381407,7 @@
    },
    "mediacapture-record": {
     "DIR_METADATA": [
-     "6d96e9ae712a1fc39291ba9f92ea94fbb3c2ea00",
+     "95af59abd1a79a3f96f30743c5ea787afc25ae08",
      []
     ],
     "META.yml": [
@@ -381433,7 +381459,7 @@
    },
    "mediacapture-streams": {
     "DIR_METADATA": [
-     "914b263a9ad0c34d7a2d75c935a1addc64e60045",
+     "433a390634c6a8e750aaae8e8888c16adb296b1d",
      []
     ],
     "GUM-deny.https-expected.txt": [
@@ -381653,7 +381679,7 @@
    },
    "mixed-content": {
     "DIR_METADATA": [
-     "370f0a1242d9a04c2e96bedc07e3fff2e0da50d7",
+     "1b162928708ea1f75425a7063d6ebfd6616105e4",
      []
     ],
     "META.yml": [
@@ -382229,7 +382255,7 @@
    },
    "navigation-timing": {
     "DIR_METADATA": [
-     "c907af0b21f16fa5683875467904fd498879089c",
+     "25862ba2b4cd6effa5c1ac2c151e196077ba3f86",
      []
     ],
     "META.yml": [
@@ -382451,7 +382477,7 @@
    },
    "orientation-event": {
     "DIR_METADATA": [
-     "dc60f5a939a7938b5df7b0a1671c6b2d408139fe",
+     "2e439f2f1c0f5bcc773804a3bd9711efdb93436b",
      []
     ],
     "META.yml": [
@@ -382487,7 +382513,7 @@
      []
     ],
     "DIR_METADATA": [
-     "caea56d67a654fb8b15e43a1b0eab7e413a88177",
+     "22cfa46b2f5ffb1fa3db78d07e69585ff3c48e8d",
      []
     ],
     "META.yml": [
@@ -382529,7 +382555,7 @@
    },
    "page-visibility": {
     "DIR_METADATA": [
-     "ad88c7283bfa1315fe5d8d47084a600d4fa4c588",
+     "945d3913671d593f7c70b121223d76837b819fa8",
      []
     ],
     "META.yml": [
@@ -382577,7 +382603,7 @@
    },
    "paint-timing": {
     "DIR_METADATA": [
-     "17e24c0942c8113028f9267cb32724f45d651c53",
+     "e1a0e70f54c6a2b32e056562aba8680ed9465d9b",
      []
     ],
     "META.yml": [
@@ -382631,7 +382657,7 @@
    },
    "payment-handler": {
     "DIR_METADATA": [
-     "d1e9bdd8e19ae45f3f7f2893100012a80e9d7b44",
+     "360c6927a5d51b1e6fa589afc8507dd8d462a437",
      []
     ],
     "META.yml": [
@@ -382723,7 +382749,7 @@
    },
    "payment-method-basic-card": {
     "DIR_METADATA": [
-     "d1e9bdd8e19ae45f3f7f2893100012a80e9d7b44",
+     "360c6927a5d51b1e6fa589afc8507dd8d462a437",
      []
     ],
     "META.yml": [
@@ -382741,7 +382767,7 @@
    },
    "payment-method-id": {
     "DIR_METADATA": [
-     "d1e9bdd8e19ae45f3f7f2893100012a80e9d7b44",
+     "360c6927a5d51b1e6fa589afc8507dd8d462a437",
      []
     ],
     "META.yml": [
@@ -382759,7 +382785,7 @@
    },
    "payment-request": {
     "DIR_METADATA": [
-     "d1e9bdd8e19ae45f3f7f2893100012a80e9d7b44",
+     "360c6927a5d51b1e6fa589afc8507dd8d462a437",
      []
     ],
     "META.yml": [
@@ -382869,7 +382895,7 @@
    },
    "performance-timeline": {
     "DIR_METADATA": [
-     "17e24c0942c8113028f9267cb32724f45d651c53",
+     "e1a0e70f54c6a2b32e056562aba8680ed9465d9b",
      []
     ],
     "META.yml": [
@@ -383023,7 +383049,7 @@
    },
    "permissions-policy": {
     "DIR_METADATA": [
-     "6764282f2a3b50937eddd107a9f626b99766d1d6",
+     "9a5e0775d4e3bac1fb4223a9d8889bdde19319c6",
      []
     ],
     "META.yml": [
@@ -383533,7 +383559,7 @@
    },
    "picture-in-picture": {
     "DIR_METADATA": [
-     "d18b4d5206bdc6de4c562d7002cfd19bb52b9e68",
+     "2848df5083857a860a27b87457766bfee1bb228e",
      []
     ],
     "META.yml": [
@@ -383799,7 +383825,7 @@
    },
    "pointerevents": {
     "DIR_METADATA": [
-     "71b1a71b1fc10f62a9c9b29c86269a4b75f64892",
+     "5f3ed792088ab2ebde6d2cff982ed0ff3978c748",
      []
     ],
     "META.yml": [
@@ -383893,7 +383919,7 @@
    },
    "pointerlock": {
     "DIR_METADATA": [
-     "71b1a71b1fc10f62a9c9b29c86269a4b75f64892",
+     "5f3ed792088ab2ebde6d2cff982ed0ff3978c748",
      []
     ],
     "META.yml": [
@@ -383907,7 +383933,7 @@
    },
    "preload": {
     "DIR_METADATA": [
-     "0eda2ab480bb6f4fb6aa21b32505add40dfc9f54",
+     "9fd2956443224ad3a11731bca782dd855e216142",
      []
     ],
     "META.yml": [
@@ -384175,7 +384201,7 @@
    },
    "presentation-api": {
     "DIR_METADATA": [
-     "886ef51d7b5fa1a88bcaf59acfc56265b4b732d3",
+     "3eec6d938f788c607f9030fcae431e55dae6b47a",
      []
     ],
     "META.yml": [
@@ -384335,7 +384361,7 @@
    },
    "quirks": {
     "DIR_METADATA": [
-     "c41ee089b8e083540febdab0fb23c1c8bafbbfc5",
+     "38c4cd0efc94eb8941cf45405784304d216b6aab",
      []
     ],
     "META.yml": [
@@ -385053,7 +385079,7 @@
      ]
     },
     "DIR_METADATA": [
-     "97c04a7da7988985f213888de90ceb99d53a2d2f",
+     "f1d29c3bf1b433e390f52e831e9fca46b04d62d6",
      []
     ],
     "META.yml": [
@@ -385867,7 +385893,7 @@
    },
    "remote-playback": {
     "DIR_METADATA": [
-     "4bbb82d3a9c1eb024eaa6577bd467a99d038123a",
+     "03d258a59320854ca3feb8b90d0b81727e96b2dd",
      []
     ],
     "META.yml": [
@@ -386029,7 +386055,7 @@
    },
    "requestidlecallback": {
     "DIR_METADATA": [
-     "188cb769c8116c13d402e4e304d763b0c6087dbd",
+     "e85f220758bdd61bae3c88298ee1620ebafea394",
      []
     ],
     "META.yml": [
@@ -386101,7 +386127,7 @@
      []
     ],
     "DIR_METADATA": [
-     "931e51bc45c9d2b60007e45aad0bb2bb63b82762",
+     "98756eed92aff5d953cacb3b9963ac213c842d84",
      []
     ],
     "META.yml": [
@@ -386853,7 +386879,7 @@
    },
    "sanitizer-api": {
     "DIR_METADATA": [
-     "c1c57ff93ec12d1699f3f75ef46bc941df6e93f0",
+     "d54fad0ddb22c364ef82d5b06d64fbbc345212ce",
      []
     ],
     "META.yml": [
@@ -386907,7 +386933,7 @@
    },
    "scheduler": {
     "DIR_METADATA": [
-     "c451247ee94cd356c5b9b16f3f11bf44abac0919",
+     "b1e1ab51974c9cbe46d3b4d876e1cb7b7fb4764c",
      []
     ],
     "META.yml": [
@@ -386925,7 +386951,7 @@
    },
    "screen-capture": {
     "DIR_METADATA": [
-     "e5ad3abb40f534b400469e2c7e1bf22a9bdc5e1a",
+     "e93c1ac7c5b2c9ca0c8122b8eadf37b3bf43bed1",
      []
     ],
     "META.yml": [
@@ -386951,7 +386977,7 @@
    },
    "screen-orientation": {
     "DIR_METADATA": [
-     "726cc0038d469c7904537d0d8c92f66229fa34e4",
+     "747d0ddf80a3321ffad6856560137476a551ce35",
      []
     ],
     "META.yml": [
@@ -387175,7 +387201,7 @@
    },
    "scroll-to-text-fragment": {
     "DIR_METADATA": [
-     "71b1a71b1fc10f62a9c9b29c86269a4b75f64892",
+     "5f3ed792088ab2ebde6d2cff982ed0ff3978c748",
      []
     ],
     "META.yml": [
@@ -387279,7 +387305,7 @@
    },
    "secure-contexts": {
     "DIR_METADATA": [
-     "bfec72293a64b00de9850a9f25222e91c86ef1aa",
+     "1f59bfc499e36be1e4b3e3ae27708acfba12b825",
      []
     ],
     "META.yml": [
@@ -387427,7 +387453,7 @@
    },
    "selection": {
     "DIR_METADATA": [
-     "c8c1b8eba51e7469644730155ce541a27776cbfe",
+     "25eea5bb6a6a1c107ad0a01001a5f0b6cee4cb1e",
      []
     ],
     "META.yml": [
@@ -387577,7 +387603,7 @@
    },
    "server-timing": {
     "DIR_METADATA": [
-     "0a9aa94aac4ec920912f69b55cde725f3f8fc78a",
+     "2d7f3dfdc5052c716b0a56edce2f0684b6180f85",
      []
     ],
     "META.yml": [
@@ -388331,7 +388357,7 @@
    },
    "service-workers": {
     "DIR_METADATA": [
-     "870c4d33d576efa9415f5f1f77f593bc1fac35ca",
+     "d52b3f7d9baf1daa46a3ef122d5d50e3f165cb4b",
      []
     ],
     "META.yml": [
@@ -388344,7 +388370,7 @@
     ],
     "cache-storage": {
      "DIR_METADATA": [
-      "049926d70493335c0502f9f567536d90c31afb61",
+      "c727bca735fdc4298e2517f162a8156a588e49e0",
       []
      ],
      "META.yml": [
@@ -390463,7 +390489,7 @@
    },
    "shadow-dom": {
     "DIR_METADATA": [
-     "ab2d7cb4c4ae84d18301995b2cb9584dd040cd25",
+     "940ee2c1ebe2bf0563aa83cc94b5582f8795b479",
      []
     ],
     "META.yml": [
@@ -390831,6 +390857,10 @@
       "a475ee4f96ebdba21095eae2ecf98e524d344d72",
       []
      ],
+     "credentials-test-helper.py": [
+      "81a988e3581f0567a29a58b7303f124c5cc5e38e",
+      []
+     ],
      "delete-key.https.html": [
       "41595f3ee3868a1e7fa1f866bcc8936de16f5cbd",
       []
@@ -391843,7 +391873,7 @@
    },
    "storage": {
     "DIR_METADATA": [
-     "e2269a82f197486d5e37db2fe294a4a40a056e91",
+     "9f57b8eb7d13433c726dcd3775a6b5a8febeb366",
      []
     ],
     "META.yml": [
@@ -391915,7 +391945,7 @@
    },
    "storage-access-api": {
     "DIR_METADATA": [
-     "1da7c78a0f02e4a4a10aaf1e7da005be04c4fa75",
+     "d9cdeb078674a7a09266de8b41a6f3a5a4a558dd",
      []
     ],
     "META.yml": [
@@ -391995,7 +392025,7 @@
    },
    "streams": {
     "DIR_METADATA": [
-     "3376bf31c0f931846fe7a0144837d5f8c8b7854b",
+     "8fe190dc70e8a1ef7d989388293972dc0277808f",
      []
     ],
     "META.yml": [
@@ -392599,7 +392629,7 @@
    },
    "subresource-integrity": {
     "DIR_METADATA": [
-     "a19d4eeecc75654a46966efa7badc560e5ebc3e1",
+     "4b3272cebd1b67dca68efcb1078a74ab4eadde12",
      []
     ],
     "META.yml": [
@@ -392663,7 +392693,7 @@
    },
    "svg": {
     "DIR_METADATA": [
-     "e66538f202184551183384a925fc9c76d0b25062",
+     "8c4bb5a5af3e4235eeb6fae14af464fe16a6164e",
      []
     ],
     "META.yml": [
@@ -393905,7 +393935,7 @@
    },
    "svg-aam": {
     "DIR_METADATA": [
-     "803582c48bb6a7ce275ce6dffaef5f172d12cfc6",
+     "c536c667a8d5dbeea8a1de059c919a3ed9ba4336",
      []
     ],
     "META.yml": [
@@ -393955,7 +393985,7 @@
    },
    "touch-events": {
     "DIR_METADATA": [
-     "71b1a71b1fc10f62a9c9b29c86269a4b75f64892",
+     "5f3ed792088ab2ebde6d2cff982ed0ff3978c748",
      []
     ],
     "META.yml": [
@@ -393991,7 +394021,7 @@
    },
    "trusted-types": {
     "DIR_METADATA": [
-     "a19d4eeecc75654a46966efa7badc560e5ebc3e1",
+     "4b3272cebd1b67dca68efcb1078a74ab4eadde12",
      []
     ],
     "META.yml": [
@@ -394141,7 +394171,7 @@
    },
    "uievents": {
     "DIR_METADATA": [
-     "71b1a71b1fc10f62a9c9b29c86269a4b75f64892",
+     "5f3ed792088ab2ebde6d2cff982ed0ff3978c748",
      []
     ],
     "META.yml": [
@@ -394491,7 +394521,7 @@
    },
    "upgrade-insecure-requests": {
     "DIR_METADATA": [
-     "a19d4eeecc75654a46966efa7badc560e5ebc3e1",
+     "4b3272cebd1b67dca68efcb1078a74ab4eadde12",
      []
     ],
     "META.yml": [
@@ -394847,7 +394877,7 @@
    },
    "url": {
     "DIR_METADATA": [
-     "7db289519cc9a2f553de565b2d6efaada4cfbaee",
+     "8e8b91ddda00a87040e7cf7c38025c98ffc2968e",
      []
     ],
     "IdnaTestV2.window-expected.txt": [
@@ -395025,7 +395055,7 @@
    },
    "user-timing": {
     "DIR_METADATA": [
-     "17e24c0942c8113028f9267cb32724f45d651c53",
+     "e1a0e70f54c6a2b32e056562aba8680ed9465d9b",
      []
     ],
     "META.yml": [
@@ -395057,7 +395087,7 @@
    },
    "vibration": {
     "DIR_METADATA": [
-     "dc10621ad5f761c6dd6dd29e4d848115e7d138b9",
+     "e3e50e15209e52bf06a9e77b5f56ec354ecc1784",
      []
     ],
     "META.yml": [
@@ -395077,7 +395107,7 @@
    },
    "virtual-keyboard": {
     "DIR_METADATA": [
-     "71b1a71b1fc10f62a9c9b29c86269a4b75f64892",
+     "5f3ed792088ab2ebde6d2cff982ed0ff3978c748",
      []
     ],
     "META.yml": [
@@ -395095,7 +395125,7 @@
    },
    "visual-viewport": {
     "DIR_METADATA": [
-     "71b1a71b1fc10f62a9c9b29c86269a4b75f64892",
+     "5f3ed792088ab2ebde6d2cff982ed0ff3978c748",
      []
     ],
     "META.yml": [
@@ -395117,7 +395147,7 @@
    },
    "wai-aria": {
     "DIR_METADATA": [
-     "803582c48bb6a7ce275ce6dffaef5f172d12cfc6",
+     "c536c667a8d5dbeea8a1de059c919a3ed9ba4336",
      []
     ],
     "META.yml": [
@@ -395165,7 +395195,7 @@
    },
    "wasm": {
     "DIR_METADATA": [
-     "62bf56d0945188c5d5c3d9896e4d65859355bfae",
+     "4b909b680ad9a6427138483665e3f32dbcb093f7",
      []
     ],
     "META.yml": [
@@ -395867,7 +395897,7 @@
    },
    "web-animations": {
     "DIR_METADATA": [
-     "849d9c0f45574344e9c71d57c57cdb030cd7205d",
+     "8ea8b499efa75b870181f1f6eadf3aa765603855",
      []
     ],
     "META.yml": [
@@ -396539,7 +396569,7 @@
    },
    "web-locks": {
     "DIR_METADATA": [
-     "fdf5b1166c3fa35cc2d40c4a118e9fdc5a6f5bdd",
+     "154e22b5b088f616f6728986a0c31986eb435f43",
      []
     ],
     "META.yml": [
@@ -396601,7 +396631,7 @@
    },
    "web-nfc": {
     "DIR_METADATA": [
-     "a6a4d266d010fd64d202bfdfe2f20423d26ad9a3",
+     "627969dbb18a0953bbec6fc2d0e7bebe367b4b68",
      []
     ],
     "META.yml": [
@@ -396671,7 +396701,7 @@
      []
     ],
     "DIR_METADATA": [
-     "f74af73c5aa293b815a36f5a5d402b721203fdec",
+     "21b16eeff4248021978819aed5ef78de5ac6b694",
      []
     ],
     "META.yml": [
@@ -397025,7 +397055,7 @@
    },
    "webauthn": {
     "DIR_METADATA": [
-     "9b2a65804f6cef4528f335c826530320b9991c57",
+     "4587771a9e7b7840842b3f294adcadbf37ab073c",
      []
     ],
     "META.yml": [
@@ -397272,7 +397302,7 @@
     ],
     "tests": {
      "DIR_METADATA": [
-      "6f03308a1ae0de450984d9420a35e50d9c0a50a3",
+      "d1fdc95bb61bac6035daf0840aa82b91c0639bb0",
       []
      ],
      "__init__.py": [
@@ -398393,7 +398423,7 @@
    },
    "webhid": {
     "DIR_METADATA": [
-     "5ef1de28cfb2d526e5dac080f6d60ab33314b7f0",
+     "b0539f275812c26ac08422b4abeb097286fdd93f",
      []
     ],
     "META.yml": [
@@ -398523,7 +398553,7 @@
    },
    "webmessaging": {
     "DIR_METADATA": [
-     "b431244896dbcdb96e0199478d7289603f79cfd1",
+     "9c4c47b1f615728aded280900be2a89052862775",
      []
     ],
     "META.yml": [
@@ -399239,7 +399269,7 @@
    },
    "webrtc": {
     "DIR_METADATA": [
-     "e5ad3abb40f534b400469e2c7e1bf22a9bdc5e1a",
+     "e93c1ac7c5b2c9ca0c8122b8eadf37b3bf43bed1",
      []
     ],
     "META.yml": [
@@ -399474,7 +399504,7 @@
       []
      ],
      "webrtc-test-helpers.sub.js": [
-      "48882b30ccc263c6fb72e9e910d3dbe9f06d8141",
+      "8a46302668a2d6d443728122942bbef0cfe9a5f8",
       []
      ]
     },
@@ -399895,7 +399925,7 @@
      []
     ],
     "DIR_METADATA": [
-     "64adb78363ab700eb76a5518e4af14c9d459cb00",
+     "8f6200917d040466c08a12f834d305343127bcf1",
      []
     ],
     "META.yml": [
@@ -400425,7 +400455,7 @@
    },
    "webstorage": {
     "DIR_METADATA": [
-     "ce4f4b22854c2392fcf49fa0a523e1161a0dc60b",
+     "72c4c8fd00c5f08cddd3d525b75fb6bd31d6d51c",
      []
     ],
     "META.yml": [
@@ -400703,7 +400733,7 @@
    },
    "webusb": {
     "DIR_METADATA": [
-     "5a176d6b781915288dcb5e978d45e02d071acee7",
+     "8aa2b3ee5e5b2368e09b878c6b5bd7b17ef4d4ef",
      []
     ],
     "META.yml": [
@@ -400787,7 +400817,7 @@
    },
    "webvtt": {
     "DIR_METADATA": [
-     "9d51636449fb3e2f34ba125067982b378d209613",
+     "af661f0095d99a64ae3555fb35ab6c3b80b3faea",
      []
     ],
     "META.yml": [
@@ -402797,7 +402827,7 @@
    },
    "webxr": {
     "DIR_METADATA": [
-     "88aee7ea4e6d785271b82c45fffb161567924a98",
+     "014bf0909d4e057ea53863976a83bcae490865de",
      []
     ],
     "META.yml": [
@@ -402925,7 +402955,7 @@
    },
    "workers": {
     "DIR_METADATA": [
-     "be0381fa0cac6832c90c06a64d8468be654aadf6",
+     "b93f1971cbecb4d09043593e84191f7b8d2052f3",
      []
     ],
     "META.yml": [
@@ -404175,7 +404205,7 @@
    },
    "worklets": {
     "DIR_METADATA": [
-     "be0381fa0cac6832c90c06a64d8468be654aadf6",
+     "b93f1971cbecb4d09043593e84191f7b8d2052f3",
      []
     ],
     "META.yml": [
@@ -404335,7 +404365,7 @@
    },
    "x-frame-options": {
     "DIR_METADATA": [
-     "ba32a64a1254123a1b9e35d9485d28f74b4c1e57",
+     "7ce99aa674ffbf48c34b20d98c947d504884794c",
      []
     ],
     "META.yml": [
@@ -404371,7 +404401,7 @@
    },
    "xhr": {
     "DIR_METADATA": [
-     "8b3df48a61563c79c82abb42dfc16bebe9a8927f",
+     "9c5bfd99ab4ba2e747a038d8284033717b11a88c",
      []
     ],
     "META.yml": [
@@ -427774,7 +427804,7 @@
      ]
     ],
     "clipboard-item.https.html": [
-     "b50a1c97d746168fd4166726ead0964faa03aa39",
+     "7e148703a2679286d563053904d3accc820900a9",
      [
       null,
       {}
@@ -441789,34 +441819,6 @@
        {}
       ]
      ],
-     "anchor-position-auto-001.html": [
-      "b5e19522b00d8327e2eb1da96a2714ff2c62c60b",
-      [
-       null,
-       {}
-      ]
-     ],
-     "anchor-position-auto-002.html": [
-      "1c86fe2e4678f5fc7600d16f6580b24b60117330",
-      [
-       null,
-       {}
-      ]
-     ],
-     "anchor-position-auto-003.html": [
-      "33ef58b2af4692f071596aa005f4e72954d6c04f",
-      [
-       null,
-       {}
-      ]
-     ],
-     "anchor-position-auto-004.html": [
-      "befd5fcff068d7af8d9b9a1a658eddfb3008b55d",
-      [
-       null,
-       {}
-      ]
-     ],
      "anchor-position-borders-001.html": [
       "1e2ecbc909cee0f9f301481c12ab6a8fc9b4ff0c",
       [
@@ -457890,6 +457892,15 @@
      }
     },
     "css-page": {
+     "cssom": {
+      "page-001.html": [
+       "2f6b4dde0500cf7ef40b2187708539523472d33a",
+       [
+        null,
+        {}
+       ]
+      ]
+     },
      "inheritance.html": [
       "565287bce50fa46fdc17cf438ff9eeb2862106a7",
       [
@@ -472864,6 +472875,13 @@
        {}
       ]
      ],
+     "variables-animation-math-functions-tentative.html": [
+      "7c33d6cfa364efcad60ab224a926686ba6279b47",
+      [
+       null,
+       {}
+      ]
+     ],
      "variables-substitute-guaranteed-invalid.html": [
       "4abfe28d1f21b2891599543b3972b26fbd15b77e",
       [
@@ -476505,7 +476523,7 @@
       ]
      ],
      "display-mode.html": [
-      "e6633de856c5f438e47ebe1d05b1593753948fbf",
+      "4ade16799f9db32f502df643f33094e5a2552e70",
       [
        null,
        {}
@@ -476773,6 +476791,13 @@
         {}
        ]
       ],
+      "offset-rotate-interpolation-math-functions-tentative.html": [
+       "34e0abf2b0debc151c85d439c649de60b47ab5d2",
+       [
+        null,
+        {}
+       ]
+      ],
       "offset-rotate-interpolation.html": [
        "55845108ebf5f3c42a8b0532121199136160d695",
        [
@@ -501436,6 +501461,15 @@
       }
      ]
     ],
+    "modal-dialog-interrupt-paint.html": [
+     "011311a03a37df4e33e6b3a67d9c07ea2776843c",
+     [
+      null,
+      {
+       "testdriver": true
+      }
+     ]
+    ],
     "mousedown.html": [
      "35a07faeb1bcaaadb0ca606f8948004b45a115ae",
      [
@@ -563587,7 +563621,7 @@
         ]
        ],
        "the-anchor-attribute-003.tentative.html": [
-        "fe4657aabb33e63f58d7b623d1fd27d55e38b7c6",
+        "ec2d8d5ead9673e307350bc7346a5f3b971a391b",
         [
          null,
          {}
@@ -563936,196 +563970,196 @@
        ]
       ],
       "element-render-blocking-001.tentative.html": [
-       "9624b41a194946794fc17c71f66fb4cc2ec6abe1",
+       "36567f9d54adea41a703b23adac438e160f397cf",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-002.tentative.html": [
-       "ab0fd511156bc9edb89fe9350d87a000616f2438",
+       "3c907597f7ac35b84e29ccd37627499c347f511a",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-003.tentative.html": [
-       "eb3a347a6ebbb7b05a5e3bb7905c9098bf755c51",
+       "2858798a35f58e1152b7047c35359878d1820ecc",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-004.tentative.html": [
-       "2c50f2d362a54c8252f92222a71025301222aa9f",
+       "f45f5587204674691349f298f9367204233aa235",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-005.tentative.html": [
-       "04cdab467deff3f8880855592cd5724d064b3371",
+       "098a3c57676cc88788a23efa0818e00b9a8e6344",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-006.tentative.html": [
-       "1c9da255a622503c2f2bb74fc98876938bce19e1",
+       "223e42109e5c65985e0aec2b0c24ef8f1d810d15",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-007.tentative.html": [
-       "df8f9ae3d1258194088f4b8cbadbf2eee9b85500",
+       "9aa0aeea79834833ff0151227723c1a6c17e06e2",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-008.tentative.html": [
-       "c2458a0bab31244de67c707d32b2669a5aaa746a",
+       "e671dda19c43b44d3f065b8de0e68f7326e88aba",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-009.tentative.html": [
-       "d765ac8a5d3c9b742fa9b24d3401d6c1ff098a08",
+       "8498816ea5c435cce0ea3d597b7f0b381d73d7f6",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-010.tentative.html": [
-       "7ef6a1baf35b460791ac54b8891dc3e019aaee85",
+       "ef6f709012e4bd4bb222b8cc74317abdbc40768f",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-011.tentative.html": [
-       "31df9b068c01b5a149941f24d619898f6b926301",
+       "dee82d8c5950c16360d2ff799393a50bed1f7a95",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-012.tentative.html": [
-       "8f2594d25903a60911f89d7eb6a39968ca2e9d82",
+       "4110e54c5f20420d8da783d21e43d2f9961aed5a",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-013.tentative.html": [
-       "9d65bd96b73189f4fafbbcd59261261e0f154fb9",
+       "ecd97be86a6633daf0d03f86f231de295abee459",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-014.tentative.html": [
-       "d042b96b64370c1ad08cd235729a2c0e6abb597e",
+       "ea8948de42dec015384aa2bd7a13edb283b6d522",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-015.tentative.html": [
-       "f7ac0b1015d79635d83fced00cbcb8c7c5e6400b",
+       "a775ee417403ab3fef3635194ff1d62b4c335266",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-016.tentative.html": [
-       "d32a0468e79b4109607d920ec72279ae0af0e0d6",
+       "8968c5dacd329537b9d7c1d0d0ce38786077f583",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-017.tentative.html": [
-       "d3a6046cbb37f90b3bf5721f1799f8df313d06c7",
+       "2d3b5747216f50902714bff725cddf50ec7556c0",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-018.tentative.html": [
-       "0d7402201accb05ce81636be43057128cfaf976b",
+       "76e6394b5b16586de768ad553c671b8594d0635e",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-019.tentative.html": [
-       "fea9e3a2b21821722eb956beac19acfb44721034",
+       "80a7019edc4beb272ef65d7cd1d28f88fb18a813",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-020.tentative.html": [
-       "7fc0fe19bbae57d15b5a8bbc27cfb6400661114b",
+       "10019c943f402c91c0ca07efefe462d5bc6ece48",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-021.tentative.html": [
-       "29430349f04689578cc7e5f90ba86ef77626bde5",
+       "1ca2114689a6cdbc961c9eecd1fa4e328117d431",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-022.tentative.html": [
-       "6548c9ec4df21169af9c97dfa30acf49acb1c95a",
+       "5dfbcac30a4c065a888f5ea804ba306758d83e6c",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-023.tentative.html": [
-       "c3661bcaa6a9c3b2e6fc64dc937c73688041a43d",
+       "8fe8b6a8c8e30f05250c2ca569f5b4d938bed71c",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-024.tentative.html": [
-       "c98022cfeabef10171061432168274eb81324834",
+       "19e4020fb769bb59053a6a64cc1c499583044c73",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-025.tentative.html": [
-       "29868b92cbc76a7f9b6da3e645e02c0701f5e31b",
+       "689ae69f452d421b66554c8e547ca95f5f269af9",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-026.tentative.html": [
-       "dc23211b8b00d2011592dd529bd554670456acc5",
+       "6abfc43b8b237b3f8f0f6474d36fcce1c3aaac52",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-027.tentative.html": [
-       "5b8a5eb24d5e973a25ca8fbf594f85f006ba7a06",
+       "56f88e0fc2b773f74afb86e306f42a1aa39d9c1b",
        [
         null,
         {}
        ]
       ],
       "element-render-blocking-028.tentative.html": [
-       "57ba3d602602d59f55bdaa34d18eeb06254e826a",
+       "a64d542c4ac7ef97a2e3427dabe485519da97518",
        [
         null,
         {}
@@ -564686,6 +564720,15 @@
          {}
         ]
        ]
+      },
+      "writing-suggestions": {
+       "writingsuggestions.html": [
+        "49e75000d3eb9cb828fb5e702010a6bd555c73b7",
+        [
+         null,
+         {}
+        ]
+       ]
       }
      },
      "focus": {
@@ -625777,7 +625820,7 @@
       ]
      ],
      "modifying-selection-with-non-primary-mouse-button.tentative.html": [
-      "cb2e44295a9405e8a247bcc0aa9760e42a0b8286",
+      "79fc52ac7dbfa75884cef22017e89df1f38a9e4c",
       [
        "selection/contenteditable/modifying-selection-with-non-primary-mouse-button.tentative.html?middle",
        {
@@ -631267,6 +631310,48 @@
       {}
      ]
     ],
+    "same-origin-add-module-credentials-include.tentative.https.sub.html": [
+     "a9082661a8b6cee451e7258b154631e2d1f1831d",
+     [
+      null,
+      {}
+     ]
+    ],
+    "same-origin-add-module-credentials-omit.tentative.https.sub.html": [
+     "a5945725c010950aaacf5e252ffc4ebdb096158f",
+     [
+      null,
+      {}
+     ]
+    ],
+    "same-origin-add-module-credentials-same-origin.tentative.https.sub.html": [
+     "aea76264384afffe37da04e919e843d554a87637",
+     [
+      null,
+      {}
+     ]
+    ],
+    "same-origin-create-worklet-credentials-include.tentative.https.sub.html": [
+     "2e1d9cf723562416844c1ee550073b7bac4ee2d7",
+     [
+      null,
+      {}
+     ]
+    ],
+    "same-origin-create-worklet-credentials-omit.tentative.https.sub.html": [
+     "ef90c02d6434a6b472e618e3f3d95ad7e4d08775",
+     [
+      null,
+      {}
+     ]
+    ],
+    "same-origin-create-worklet-credentials-same-origin.tentative.https.sub.html": [
+     "6d9fd7c1362a06930df2f71d3c2fbd193e2746d4",
+     [
+      null,
+      {}
+     ]
+    ],
     "select-url-keep-alive.tentative.https.sub.html": [
      "f3755538b9d4b23e026fec85c161cb730531d4dc",
      [
@@ -671649,7 +671734,7 @@
      ]
     ],
     "RTCRtpReceiver-getStats.https.html": [
-     "d9344b176e39dcb697d11b70a626706f4b794445",
+     "bfa82b979c08bdbe056f82e2b6eb5449a716b063",
      [
       null,
       {
@@ -671683,7 +671768,7 @@
      ]
     ],
     "RTCRtpSender-getStats.https.html": [
-     "797365e9f4e89b649839fb9b7bc8a19b879df672",
+     "5c27af2134a033dc0bffdca9909afe12ea375183",
      [
       null,
       {
@@ -672730,10 +672815,12 @@
      ]
     ],
     "RTCRtpParameters-codec.html": [
-     "cce2c1661f7d7761da12d2ee4ba7be1446fcfe9b",
+     "5fc1401badf49d6fbc2f46c9936b3711cd3c2466",
      [
       null,
-      {}
+      {
+       "timeout": "long"
+      }
      ]
     ],
     "RTCRtpReceiver-jitterBufferTarget-stats.html": [
diff --git a/third_party/blink/web_tests/external/wpt/selection/contenteditable/modifying-selection-with-non-primary-mouse-button.tentative.html b/third_party/blink/web_tests/external/wpt/selection/contenteditable/modifying-selection-with-non-primary-mouse-button.tentative.html
index cb2e442..79fc52a 100644
--- a/third_party/blink/web_tests/external/wpt/selection/contenteditable/modifying-selection-with-non-primary-mouse-button.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/selection/contenteditable/modifying-selection-with-non-primary-mouse-button.tentative.html
@@ -128,6 +128,11 @@
   resetEditor();
   editor.focus();
   selection.collapse(span1.firstChild, 2);
+  let contextmenuFired = false;
+  function onContextMenu() {
+    contextmenuFired = true;
+  }
+  document.addEventListener("contextmenu", onContextMenu, {capture: true});
   let actions = new test_driver.Actions();
   await actions
     .pointerMove(0, 0)
@@ -137,13 +142,26 @@
     .pointerUp({button: getButtonType(actions)})
     .keyUp("\uE008")
     .send();
+  document.removeEventListener("contextmenu", onContextMenu, {capture: true});
 
-  assert_equals(selection.anchorNode, span1.firstChild,
-    "Selection#anchorNode should keep in the first <span> element");
-  assert_equals(selection.anchorOffset, 2,
-    "Selection#anchorNode should keep at 2 of the first <span> element");
-  assert_equals(selection.focusNode, span2.firstChild,
-    `Selection#focusNode should be in the second <span> element which was clicked by ${button} button`);
+  if (button != "secondary" || contextmenuFired) {
+    assert_equals(selection.anchorNode, span1.firstChild,
+      "Selection#anchorNode should keep in the first <span> element");
+    assert_equals(selection.anchorOffset, 2,
+      "Selection#anchorNode should keep at 2 of the first <span> element");
+    assert_equals(selection.focusNode, span2.firstChild,
+      `Selection#focusNode should be in the second <span> element which was clicked by ${button} button`);
+  } else {
+    // Special case for Firefox.  Firefox users can forcibly open context menu
+    // with pressing shift key.  In this case, users may want the secondary
+    // button click to work as without Shift key press.
+    assert_true(selection.isCollapsed,
+      `Selection should be collapsed after ${button} button click because contextmenu was not opened with Shift key`);
+    assert_equals(selection.focusNode, span2.firstChild,
+      `Selection should be collapsed in the second <span> element which was clicked by ${
+        button
+      } button because contextmenu was not opened with Shift key`);
+  }
 }, `Shift + ${button} click should extend the selection`);
 
 promise_test(async () => {
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpReceiver-getStats.https.html b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpReceiver-getStats.https.html
index d9344b17..bfa82b9 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpReceiver-getStats.https.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpReceiver-getStats.https.html
@@ -23,7 +23,8 @@
 
     exchangeIceCandidates(caller, callee);
     await exchangeOfferAnswer(caller, callee);
-    await listenToConnected(callee);
+    // Wait for RTP
+    await new Promise(r => receiver.track.onunmute = r);
     const statsReport = await receiver.getStats();
     assert_true(!![...statsReport.values()].find(({type}) => type === 'inbound-rtp'));
   }, 'receiver.getStats() via addTransceiver should return stats report containing inbound-rtp stats');
@@ -40,8 +41,9 @@
 
     exchangeIceCandidates(caller, callee);
     await exchangeOfferAnswer(caller, callee);
-    await listenToConnected(callee);
     const receiver = callee.getReceivers()[0];
+    // Wait for RTP
+    await new Promise(r => receiver.track.onunmute = r);
     const statsReport = await receiver.getStats();
     assert_true(!![...statsReport.values()].find(({type}) => type === 'inbound-rtp'));
   }, 'receiver.getStats() via addTrack should return stats report containing inbound-rtp stats');
@@ -58,8 +60,9 @@
 
     exchangeIceCandidates(caller, callee);
     await exchangeOfferAnswer(caller, callee);
-    await listenToConnected(callee);
     const [receiver] = callee.getReceivers();
+    // Wait for RTP
+    await new Promise(r => receiver.track.onunmute = r);
     const [transceiver] = callee.getTransceivers();
     const statsPromiseFirst = receiver.getStats();
     transceiver.stop();
@@ -81,8 +84,9 @@
 
     exchangeIceCandidates(caller, callee);
     await exchangeOfferAnswer(caller, callee);
-    await listenToConnected(callee);
     const [receiver] = callee.getReceivers();
+    // Wait for RTP
+    await new Promise(r => receiver.track.onunmute = r);
     const statsReportFirst = await receiver.getStats();
     callee.close();
     const statsReportSecond = await receiver.getStats();
@@ -102,8 +106,9 @@
 
     exchangeIceCandidates(caller, callee);
     await exchangeOfferAnswer(caller, callee);
-    await listenToConnected(callee);
     const receiver = callee.getReceivers()[0];
+    // Wait for RTP
+    await new Promise(r => receiver.track.onunmute = r);
     const statsReport = await receiver.getStats();
     assert_true(!![...statsReport.values()].find(({type}) => type === 'candidate-pair'));
     assert_true(!![...statsReport.values()].find(({type}) => type === 'local-candidate'));
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpSender-getStats.https.html b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpSender-getStats.https.html
index 797365e9..5c27af2 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpSender-getStats.https.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpSender-getStats.https.html
@@ -21,7 +21,9 @@
 
     exchangeIceCandidates(caller, callee);
     await exchangeOfferAnswer(caller, callee);
-    await listenToConnected(caller);
+    const [ receiver ] = callee.getReceivers();
+    // Wait for RTP
+    await new Promise(r => receiver.track.onunmute = r);
     const statsReport = await sender.getStats();
     assert_true(!![...statsReport.values()].find(({type}) => type === 'outbound-rtp'));
   }, 'sender.getStats() via addTransceiver should return stats report containing outbound-rtp stats');
@@ -38,7 +40,9 @@
 
     exchangeIceCandidates(caller, callee);
     await exchangeOfferAnswer(caller, callee);
-    await listenToConnected(caller);
+    const [ receiver ] = callee.getReceivers();
+    // Wait for RTP
+    await new Promise(r => receiver.track.onunmute = r);
     const statsReport = await sender.getStats();
     assert_true(!![...statsReport.values()].find(({type}) => type === 'outbound-rtp'));
   }, 'sender.getStats() via addTrack should return stats report containing outbound-rtp stats');
@@ -55,7 +59,9 @@
 
     exchangeIceCandidates(caller, callee);
     await exchangeOfferAnswer(caller, callee);
-    await listenToConnected(caller);
+    const [ receiver ] = callee.getReceivers();
+    // Wait for RTP
+    await new Promise(r => receiver.track.onunmute = r);
 
     const [sender] = caller.getSenders();
     const [transceiver] = caller.getTransceivers();
@@ -78,7 +84,9 @@
 
     exchangeIceCandidates(caller, callee);
     await exchangeOfferAnswer(caller, callee);
-    await listenToConnected(callee);
+    const [ receiver ] = callee.getReceivers();
+    // Wait for RTP
+    await new Promise(r => receiver.track.onunmute = r);
     const [sender] = caller.getSenders();
     const statsReportFirst = await sender.getStats();
     caller.close();
@@ -99,7 +107,9 @@
 
     exchangeIceCandidates(caller, callee);
     await exchangeOfferAnswer(caller, callee);
-    await listenToConnected(caller);
+    const [ receiver ] = callee.getReceivers();
+    // Wait for RTP
+    await new Promise(r => receiver.track.onunmute = r);
     const statsReport = await sender.getStats();
     assert_true(!![...statsReport.values()].find(({type}) => type === 'candidate-pair'));
     assert_true(!![...statsReport.values()].find(({type}) => type === 'local-candidate'));
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/resources/webrtc-test-helpers.sub.js b/third_party/blink/web_tests/external/wpt/webrtc/resources/webrtc-test-helpers.sub.js
index 48882b30..8a46302 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/resources/webrtc-test-helpers.sub.js
+++ b/third_party/blink/web_tests/external/wpt/webrtc/resources/webrtc-test-helpers.sub.js
@@ -62,18 +62,18 @@
 // Opens a new WebRTC connection.
 async function openWebRTC(remoteContextHelper) {
   await remoteContextHelper.executeScript(async (sessionDesc, candidate) => {
-    const testRTCPeerConnection = new RTCPeerConnection();
-    await testRTCPeerConnection.setRemoteDescription(sessionDesc);
-    await testRTCPeerConnection.addIceCandidate(candidate);
+    window.testRTCPeerConnection = new RTCPeerConnection();
+    await window.testRTCPeerConnection.setRemoteDescription(sessionDesc);
+    await window.testRTCPeerConnection.addIceCandidate(candidate);
   }, [sessionDesc, candidate]);
 }
 
 // Opens a new WebRTC connection and then close it.
 async function openThenCloseWebRTC(remoteContextHelper) {
   await remoteContextHelper.executeScript(async (sessionDesc, candidate) => {
-    const testRTCPeerConnection = new RTCPeerConnection();
-    await testRTCPeerConnection.setRemoteDescription(sessionDesc);
-    await testRTCPeerConnection.addIceCandidate(candidate);
-    testRTCPeerConnection.close();
+    window.testRTCPeerConnection = new RTCPeerConnection();
+    await window.testRTCPeerConnection.setRemoteDescription(sessionDesc);
+    await window.testRTCPeerConnection.addIceCandidate(candidate);
+    window.testRTCPeerConnection.close();
   }, [sessionDesc, candidate]);
 }
diff --git a/third_party/blink/web_tests/fast/events/touch/compositor-touch-hit-rects-scroll-expected.txt b/third_party/blink/web_tests/fast/events/touch/compositor-touch-hit-rects-scroll-expected.txt
index fe17b4c..02a8f64 100644
--- a/third_party/blink/web_tests/fast/events/touch/compositor-touch-hit-rects-scroll-expected.txt
+++ b/third_party/blink/web_tests/fast/events/touch/compositor-touch-hit-rects-scroll-expected.txt
@@ -10,17 +10,17 @@
 nestedContent: layer(140x10) has hit test rect (0,0 140x10)
 nestedContent: layer(256x72) has hit test rect (0,30 256x12)
 
-overflowwithhandler: layer(256x116) has hit test rect (-1,-1 258x118)
 overflowwithhandler: layer(250x20) has hit test rect (0,0 250x10)
+overflowwithhandler: layer(256x116) has hit test rect (0,0 256x116)
 overflowwithhandler: layer(250x20) has hit test rect (0,10 70x10)
 overflowwithhandler: layer(273x112) has hit test rect (0,10 273x52)
 
-overflowwithborder: layer(263x124) has hit test rect (-6,-6 275x136)
 overflowwithborder: layer(250x20) has hit test rect (0,0 250x10)
+overflowwithborder: layer(255x116) has hit test rect (0,0 255x116)
 overflowwithborder: layer(250x20) has hit test rect (0,10 240x10)
 overflowwithborder: layer(769x881) has hit test rect (5,349 290x70)
 
 withTransform: layer(271x12) has hit test rect (0,0 271x12)
-withTransform: layer(282x77) has hit test rect (0,13 273x14)
+withTransform: layer(273x77) has hit test rect (0,13 273x14)
 
 
diff --git a/third_party/blink/web_tests/flag-specific/enable-skia-graphite/fast/overflow/overflow-update-transform-expected.png b/third_party/blink/web_tests/flag-specific/enable-skia-graphite/fast/overflow/overflow-update-transform-expected.png
new file mode 100644
index 0000000..fab310bb
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-skia-graphite/fast/overflow/overflow-update-transform-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/enable-skia-graphite/virtual/prefer_compositing_to_lcd_text/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.png b/third_party/blink/web_tests/flag-specific/enable-skia-graphite/virtual/prefer_compositing_to_lcd_text/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.png
new file mode 100644
index 0000000..fd5051a1
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-skia-graphite/virtual/prefer_compositing_to_lcd_text/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/compositing/force-compositing-mode/overflow-iframe-layer-expected.txt b/third_party/blink/web_tests/flag-specific/highdpi/compositing/force-compositing-mode/overflow-iframe-layer-expected.txt
index 74e7c32..ecfbdde 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/compositing/force-compositing-mode/overflow-iframe-layer-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/highdpi/compositing/force-compositing-mode/overflow-iframe-layer-expected.txt
@@ -7,7 +7,7 @@
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [762, 774],
       "backgroundColor": "#0000FF",
       "transform": 1
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-composited-expected.txt b/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-composited-expected.txt
deleted file mode 100644
index 822e231..0000000
--- a/third_party/blink/web_tests/flag-specific/highdpi/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-composited-expected.txt
+++ /dev/null
@@ -1,73 +0,0 @@
-{
-  "layers": [
-    {
-      "name": "LayoutView #document",
-      "bounds": [600, 250],
-      "contentsOpaque": true,
-      "backgroundColor": "#FFFFFF",
-      "invalidations": [
-        [0, 0, 600, 250]
-      ]
-    },
-    {
-      "name": "LayoutNGBlockFlow HTML",
-      "bounds": [600, 8],
-      "drawsContent": false
-    }
-  ]
-}
-{
-  "layers": [
-    {
-      "name": "LayoutView #document",
-      "bounds": [400, 250],
-      "contentsOpaque": true,
-      "backgroundColor": "#FFFFFF",
-      "invalidations": [
-        [0, 0, 400, 250]
-      ]
-    },
-    {
-      "name": "LayoutNGBlockFlow HTML",
-      "bounds": [400, 8],
-      "drawsContent": false
-    }
-  ]
-}
-{
-  "layers": [
-    {
-      "name": "LayoutView #document",
-      "bounds": [400, 600],
-      "contentsOpaque": true,
-      "backgroundColor": "#FFFFFF",
-      "invalidations": [
-        [0, 0, 400, 600]
-      ]
-    },
-    {
-      "name": "LayoutNGBlockFlow HTML",
-      "bounds": [400, 8],
-      "drawsContent": false
-    }
-  ]
-}
-{
-  "layers": [
-    {
-      "name": "LayoutView #document",
-      "bounds": [800, 600],
-      "contentsOpaque": true,
-      "backgroundColor": "#FFFFFF",
-      "invalidations": [
-        [0, 0, 800, 600]
-      ]
-    },
-    {
-      "name": "LayoutNGBlockFlow HTML",
-      "bounds": [800, 8],
-      "drawsContent": false
-    }
-  ]
-}
-
diff --git a/third_party/blink/web_tests/http/tests/resources/prevent-bfcache.js b/third_party/blink/web_tests/http/tests/resources/prevent-bfcache.js
index 5b1301e..2a261a2 100644
--- a/third_party/blink/web_tests/http/tests/resources/prevent-bfcache.js
+++ b/third_party/blink/web_tests/http/tests/resources/prevent-bfcache.js
@@ -7,7 +7,11 @@
 // page. The function should be async even if it doesn't seem necessary, so that
 // if we need to change how it blocks in the future to something async, we will
 // not need to update all callers.
-let bc;
 async function preventBFCache() {
-  bc = new BroadcastChannel("blocker");
+  await new Promise(resolve => {
+    // Use a random UUID as the (highly likely) unique lock name.
+    navigator.locks.request(Math.random(), () => {
+      resolve();
+    });
+  });
 }
diff --git a/third_party/blink/web_tests/paint/invalidation/background/no-repaint-for-composited-background-attachment-fixed-expected.txt b/third_party/blink/web_tests/paint/invalidation/background/no-repaint-for-composited-background-attachment-fixed-expected.txt
index 04cbc5de..d2198c89 100644
--- a/third_party/blink/web_tests/paint/invalidation/background/no-repaint-for-composited-background-attachment-fixed-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/background/no-repaint-for-composited-background-attachment-fixed-expected.txt
@@ -7,7 +7,7 @@
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [800, 5016],
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/composited-overlay-scrollbars-scrollbar-color-repaint-expected.txt b/third_party/blink/web_tests/paint/invalidation/compositing/composited-overlay-scrollbars-scrollbar-color-repaint-expected.txt
index 3a82d86..1b0fb89 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/composited-overlay-scrollbars-scrollbar-color-repaint-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/composited-overlay-scrollbars-scrollbar-color-repaint-expected.txt
@@ -15,11 +15,17 @@
     {
       "name": "LayoutNGBlockFlow DIV id='outer'",
       "position": [1, 1],
-      "bounds": [400, 602],
+      "bounds": [400, 500],
+      "drawsContent": false,
       "transform": 1
     },
     {
       "name": "LayoutNGBlockFlow DIV id='inner'",
+      "bounds": [102, 102],
+      "transform": 2
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV id='inner'",
       "position": [1, 1],
       "bounds": [100, 100],
       "drawsContent": false,
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/new-stacking-context-expected.png b/third_party/blink/web_tests/paint/invalidation/compositing/new-stacking-context-expected.png
index f6743d9..dc85ee8 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/new-stacking-context-expected.png
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/new-stacking-context-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/new-stacking-context-expected.txt b/third_party/blink/web_tests/paint/invalidation/compositing/new-stacking-context-expected.txt
index 86c90ee..e5c14c3 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/new-stacking-context-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/new-stacking-context-expected.txt
@@ -13,7 +13,7 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow DIV",
+      "name": "LayoutNGBlockFlow DIV id='target'",
       "bounds": [200, 200],
       "backgroundColor": "#00800080",
       "invalidations": [
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt b/third_party/blink/web_tests/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt
index 5f25487..dfb74a5 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/pointer-events-composited-scrolling-expected.txt
@@ -20,7 +20,10 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV id='target'",
-      "bounds": [100, 210],
+      "position": [0, 80],
+      "bounds": [50, 50],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
       "transform": 2
     }
   ],
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/scrolling-neg-z-index-descendants-expected.txt b/third_party/blink/web_tests/paint/invalidation/compositing/scrolling-neg-z-index-descendants-expected.txt
index 115bd31a..dc0883d2 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/scrolling-neg-z-index-descendants-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/scrolling-neg-z-index-descendants-expected.txt
@@ -33,14 +33,6 @@
       "drawsContent": false,
       "backfaceVisibility": "hidden",
       "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow (relative positioned) DIV id='container'",
-      "position": [1, 1],
-      "bounds": [100, 430],
-      "drawsContent": false,
-      "backfaceVisibility": "hidden",
-      "transform": 2
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/scrolling-without-painting-expected.txt b/third_party/blink/web_tests/paint/invalidation/compositing/scrolling-without-painting-expected.txt
index 2b6fe7bd..10fa8f2 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/scrolling-without-painting-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/scrolling-without-painting-expected.txt
@@ -14,6 +14,13 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV id='scroller'",
+      "position": [1, 26],
+      "bounds": [185, 1000],
+      "drawsContent": false,
+      "transform": 2
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [1, 186],
       "bounds": [200, 15],
@@ -25,13 +32,6 @@
       "bounds": [15, 185],
       "contentsOpaque": true,
       "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow DIV id='scroller'",
-      "position": [1, 1],
-      "bounds": [185, 1025],
-      "drawsContent": false,
-      "transform": 2
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/should-not-clip-composited-overflow-scrolling-layer-expected.png b/third_party/blink/web_tests/paint/invalidation/compositing/should-not-clip-composited-overflow-scrolling-layer-expected.png
index e56a6ab..b29e8db5 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/should-not-clip-composited-overflow-scrolling-layer-expected.png
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/should-not-clip-composited-overflow-scrolling-layer-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/paint/invalidation/compositing/updating-scrolling-content-expected.txt b/third_party/blink/web_tests/paint/invalidation/compositing/updating-scrolling-content-expected.txt
index 7572f4d..8efbb8a 100644
--- a/third_party/blink/web_tests/paint/invalidation/compositing/updating-scrolling-content-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/compositing/updating-scrolling-content-expected.txt
@@ -13,6 +13,14 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV id='scroller'",
+      "bounds": [185, 1200],
+      "invalidations": [
+        [0, 0, 185, 200]
+      ],
+      "transform": 1
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [0, 185],
       "bounds": [200, 15],
@@ -24,14 +32,6 @@
       "bounds": [15, 185],
       "contentsOpaque": true,
       "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow DIV id='scroller'",
-      "bounds": [185, 1200],
-      "invalidations": [
-        [0, 0, 185, 200]
-      ],
-      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/paint/invalidation/repaint-overlay/layers-expected.txt b/third_party/blink/web_tests/paint/invalidation/repaint-overlay/layers-expected.txt
index 2d72e89..208e273 100644
--- a/third_party/blink/web_tests/paint/invalidation/repaint-overlay/layers-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/repaint-overlay/layers-expected.txt
@@ -29,13 +29,6 @@
       "transform": 3
     },
     {
-      "name": "LayoutNGBlockFlow (relative positioned) DIV id='scrollable'",
-      "position": [1, 1],
-      "bounds": [795, 1491],
-      "drawsContent": false,
-      "transform": 4
-    },
-    {
       "name": "LayoutNGBlockFlow (relative positioned) DIV id='transform'",
       "bounds": [200, 200],
       "contentsOpaque": true,
diff --git a/third_party/blink/web_tests/paint/invalidation/repaint-overlay/layers-overlay.html b/third_party/blink/web_tests/paint/invalidation/repaint-overlay/layers-overlay.html
index d326bb4..ade0fff9 100644
--- a/third_party/blink/web_tests/paint/invalidation/repaint-overlay/layers-overlay.html
+++ b/third_party/blink/web_tests/paint/invalidation/repaint-overlay/layers-overlay.html
@@ -84,13 +84,6 @@
       "transform": 3
     },
     {
-      "name": "LayoutNGBlockFlow (relative positioned) DIV id='scrollable'",
-      "position": [1, 1],
-      "bounds": [795, 1491],
-      "drawsContent": false,
-      "transform": 4
-    },
-    {
       "name": "LayoutNGBlockFlow (relative positioned) DIV id='transform'",
       "bounds": [200, 200],
       "contentsOpaque": true,
@@ -196,13 +189,6 @@
       "transform": 3
     },
     {
-      "name": "LayoutNGBlockFlow (relative positioned) DIV id='scrollable'",
-      "position": [1, 1],
-      "bounds": [795, 1491],
-      "drawsContent": false,
-      "transform": 4
-    },
-    {
       "name": "LayoutNGBlockFlow (relative positioned) DIV id='transform'",
       "bounds": [200, 200],
       "contentsOpaque": true,
diff --git a/third_party/blink/web_tests/paint/invalidation/scroll/flipped-blocks-writing-mode-scroll-expected.txt b/third_party/blink/web_tests/paint/invalidation/scroll/flipped-blocks-writing-mode-scroll-expected.txt
index 4e950eac..f53b136f 100644
--- a/third_party/blink/web_tests/paint/invalidation/scroll/flipped-blocks-writing-mode-scroll-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/scroll/flipped-blocks-writing-mode-scroll-expected.txt
@@ -27,11 +27,9 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV id='scroller'",
-      "bounds": [10000, 385],
+      "bounds": [10000, 200],
+      "contentsOpaque": true,
       "backgroundColor": "#008000",
-      "invalidations": [
-        [0, 0, 10000, 200]
-      ],
       "transform": 1
     }
   ],
diff --git a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-composited-expected.txt b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-composited-expected.txt
index 64f9acc..822e231 100644
--- a/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-composited-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-composited-expected.txt
@@ -10,8 +10,8 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
-      "bounds": [600, 250],
+      "name": "LayoutNGBlockFlow HTML",
+      "bounds": [600, 8],
       "drawsContent": false
     }
   ]
@@ -28,8 +28,8 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
-      "bounds": [400, 250],
+      "name": "LayoutNGBlockFlow HTML",
+      "bounds": [400, 8],
       "drawsContent": false
     }
   ]
@@ -46,8 +46,8 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
-      "bounds": [400, 600],
+      "name": "LayoutNGBlockFlow HTML",
+      "bounds": [400, 8],
       "drawsContent": false
     }
   ]
@@ -64,8 +64,8 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
-      "bounds": [800, 600],
+      "name": "LayoutNGBlockFlow HTML",
+      "bounds": [800, 8],
       "drawsContent": false
     }
   ]
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/color-scheme/text/input-disabled-state-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/color-scheme/text/input-disabled-state-expected.png
index 202c2c2..fca14fba 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/color-scheme/text/input-disabled-state-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/color-scheme/text/input-disabled-state-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/textarea/basic-textareas-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/textarea/basic-textareas-expected.png
index 9c50e8bc..e44e08f 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/textarea/basic-textareas-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/textarea/basic-textareas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/textarea/basic-textareas-quirks-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/textarea/basic-textareas-quirks-expected.png
index 6190496..7f4e916 100644
--- a/third_party/blink/web_tests/platform/linux/fast/forms/textarea/basic-textareas-quirks-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/textarea/basic-textareas-quirks-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/bugzilla-6473-expected.txt b/third_party/blink/web_tests/platform/linux/paint/invalidation/bugzilla-6473-expected.txt
deleted file mode 100644
index 72432d79..0000000
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/bugzilla-6473-expected.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "layers": [
-    {
-      "name": "Scrolling background of LayoutView #document",
-      "bounds": [800, 600],
-      "contentsOpaque": true,
-      "backgroundColor": "#FFFFFF",
-      "invalidations": [
-        [8, 152, 784, 20],
-        [8, 136, 784, 20],
-        [8, 16, 661, 100]
-      ]
-    }
-  ]
-}
-
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/compositing/scrolling-without-painting-expected.png b/third_party/blink/web_tests/platform/linux/paint/invalidation/compositing/scrolling-without-painting-expected.png
index ad8c5f90..24dcc6f 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/compositing/scrolling-without-painting-expected.png
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/compositing/scrolling-without-painting-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/compositing/updating-scrolling-content-expected.png b/third_party/blink/web_tests/platform/linux/paint/invalidation/compositing/updating-scrolling-content-expected.png
index cd9bd54..4b512b5 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/compositing/updating-scrolling-content-expected.png
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/compositing/updating-scrolling-content-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/flipped-blocks-writing-mode-scroll-expected.png b/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/flipped-blocks-writing-mode-scroll-expected.png
index 2b604b3..6aaf628 100644
--- a/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/flipped-blocks-writing-mode-scroll-expected.png
+++ b/third_party/blink/web_tests/platform/linux/paint/invalidation/scroll/flipped-blocks-writing-mode-scroll-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/dark-color-scheme/fast/forms/color-scheme/autofill/autofilled-expected.png b/third_party/blink/web_tests/platform/linux/virtual/dark-color-scheme/fast/forms/color-scheme/autofill/autofilled-expected.png
index 4ad4882..673824a 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/dark-color-scheme/fast/forms/color-scheme/autofill/autofilled-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/dark-color-scheme/fast/forms/color-scheme/autofill/autofilled-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/compositing/force-compositing-mode/overflow-iframe-enter-compositing-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/force-compositing-mode/overflow-iframe-enter-compositing-expected.txt
index ecf0b99..c99fa95 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/force-compositing-mode/overflow-iframe-enter-compositing-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/force-compositing-mode/overflow-iframe-enter-compositing-expected.txt
@@ -7,7 +7,7 @@
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [508, 516],
       "backgroundColor": "#0000FF",
       "transform": 1
diff --git a/third_party/blink/web_tests/platform/mac/compositing/force-compositing-mode/overflow-iframe-layer-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/force-compositing-mode/overflow-iframe-layer-expected.txt
index ecf0b99..c99fa95 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/force-compositing-mode/overflow-iframe-layer-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/force-compositing-mode/overflow-iframe-layer-expected.txt
@@ -7,7 +7,7 @@
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [508, 516],
       "backgroundColor": "#0000FF",
       "transform": 1
diff --git a/third_party/blink/web_tests/platform/mac/compositing/iframes/invisible-nested-iframe-show-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/iframes/invisible-nested-iframe-show-expected.txt
index 6169ab5e..12359218 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/iframes/invisible-nested-iframe-show-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/iframes/invisible-nested-iframe-show-expected.txt
@@ -7,7 +7,7 @@
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [285, 192],
       "backgroundColor": "#C0C0C0",
       "transform": 1
diff --git a/third_party/blink/web_tests/platform/mac/compositing/layer-creation/fixed-position-in-fixed-overflow-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/layer-creation/fixed-position-in-fixed-overflow-expected.txt
index 255ae4b..5218f2a2 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/layer-creation/fixed-position-in-fixed-overflow-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/layer-creation/fixed-position-in-fixed-overflow-expected.txt
@@ -27,7 +27,9 @@
     },
     {
       "name": "LayoutNGBlockFlow (positioned) DIV class='overflow fixed'",
-      "bounds": [785, 1000],
+      "bounds": [100, 1000],
+      "contentsOpaque": true,
+      "backgroundColor": "#0000FF",
       "transform": 1
     },
     {
diff --git a/third_party/blink/web_tests/platform/mac/compositing/layer-creation/fixed-position-nonscrollable-body-mismatch-containers-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/layer-creation/fixed-position-nonscrollable-body-mismatch-containers-expected.txt
index 2f917b3..3eb107c 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/layer-creation/fixed-position-nonscrollable-body-mismatch-containers-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/layer-creation/fixed-position-nonscrollable-body-mismatch-containers-expected.txt
@@ -16,6 +16,12 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV class='scrollable bigBox'",
+      "position": [1, 1],
+      "bounds": [285, 800],
+      "transform": 1
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [1, 286],
       "bounds": [300, 15],
@@ -29,12 +35,6 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow DIV class='scrollable bigBox'",
-      "position": [1, 1],
-      "bounds": [285, 800],
-      "transform": 1
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV class='fixed lime box'",
       "bounds": [100, 100],
       "contentsOpaque": true,
diff --git a/third_party/blink/web_tests/platform/mac/compositing/overflow/content-gains-scrollbars-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/overflow/content-gains-scrollbars-expected.txt
index 63754ca..12811299 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/overflow/content-gains-scrollbars-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/overflow/content-gains-scrollbars-expected.txt
@@ -26,12 +26,6 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow (positioned, children-inline) DIV class='container'",
-      "bounds": [85, 200],
-      "drawsContent": false,
-      "transform": 1
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV id='vertical' class='content tall'",
       "bounds": [10, 200],
       "drawsContent": false,
@@ -57,12 +51,6 @@
       "transform": 2
     },
     {
-      "name": "LayoutNGBlockFlow (positioned, children-inline) DIV class='container'",
-      "bounds": [200, 85],
-      "drawsContent": false,
-      "transform": 2
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV id='horizontal' class='content wide'",
       "bounds": [200, 10],
       "drawsContent": false,
@@ -101,12 +89,6 @@
       "transform": 3
     },
     {
-      "name": "LayoutNGBlockFlow (positioned, children-inline) DIV class='container'",
-      "bounds": [200, 200],
-      "drawsContent": false,
-      "transform": 3
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV id='both' class='content wide tall'",
       "bounds": [200, 200],
       "drawsContent": false,
diff --git a/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-auto-with-touch-toggle-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-auto-with-touch-toggle-expected.txt
index da8aa1c..e6d1d06 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-auto-with-touch-toggle-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-auto-with-touch-toggle-expected.txt
@@ -13,6 +13,13 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV class='scroller'",
+      "bounds": [1000, 1000],
+      "contentsOpaque": true,
+      "backgroundColor": "#C0C0C0",
+      "transform": 1
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [0, 285],
       "bounds": [285, 15],
@@ -31,13 +38,6 @@
       "position": [285, 285],
       "bounds": [15, 15],
       "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow DIV class='scroller'",
-      "bounds": [1000, 1000],
-      "contentsOpaque": true,
-      "backgroundColor": "#C0C0C0",
-      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-scrollbar-layers-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-scrollbar-layers-expected.txt
index 297a77a..a5951e0 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-scrollbar-layers-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-scrollbar-layers-expected.txt
@@ -26,12 +26,6 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow (positioned, children-inline) DIV class='container'",
-      "bounds": [85, 200],
-      "drawsContent": false,
-      "transform": 1
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV class='content tall'",
       "bounds": [10, 200],
       "drawsContent": false,
@@ -57,12 +51,6 @@
       "transform": 2
     },
     {
-      "name": "LayoutNGBlockFlow (positioned, children-inline) DIV class='container'",
-      "bounds": [200, 85],
-      "drawsContent": false,
-      "transform": 2
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV class='content wide'",
       "bounds": [200, 10],
       "drawsContent": false,
@@ -101,12 +89,6 @@
       "transform": 3
     },
     {
-      "name": "LayoutNGBlockFlow (positioned, children-inline) DIV class='container'",
-      "bounds": [200, 200],
-      "drawsContent": false,
-      "transform": 3
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV class='content wide tall'",
       "bounds": [200, 200],
       "drawsContent": false,
diff --git a/third_party/blink/web_tests/platform/mac/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.txt
index 44bc053..a49cadd9 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/overflow/scroll-parent-with-non-stacking-context-composited-ancestor-expected.txt
@@ -23,14 +23,6 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow DIV id='scroller'",
-      "position": [1, 1],
-      "bounds": [100, 180],
-      "drawsContent": false,
-      "backfaceVisibility": "hidden",
-      "transform": 1
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV id='fixed'",
       "bounds": [80, 80],
       "contentsOpaque": true,
diff --git a/third_party/blink/web_tests/platform/mac/compositing/squashing/invalidations-with-large-negative-margin-inline-content-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/squashing/invalidations-with-large-negative-margin-inline-content-expected.txt
index 2347f45..406dc10 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/squashing/invalidations-with-large-negative-margin-inline-content-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/squashing/invalidations-with-large-negative-margin-inline-content-expected.txt
@@ -26,10 +26,11 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow DIV",
-      "bounds": [585, 500],
+      "name": "LayoutNGBlockFlow (relative positioned) DIV",
+      "position": [0, 100],
+      "bounds": [585, 400],
       "invalidations": [
-        [400, 100, 20, 20]
+        [400, 0, 20, 20]
       ],
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/platform/mac/fast/borders/overflow-hidden-border-radius-force-backing-store-expected.txt b/third_party/blink/web_tests/platform/mac/fast/borders/overflow-hidden-border-radius-force-backing-store-expected.txt
index d5b25d4..9e46c07 100644
--- a/third_party/blink/web_tests/platform/mac/fast/borders/overflow-hidden-border-radius-force-backing-store-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/fast/borders/overflow-hidden-border-radius-force-backing-store-expected.txt
@@ -20,12 +20,6 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow DIV id='inner'",
-      "bounds": [285, 1000],
-      "drawsContent": false,
-      "transform": 1
-    },
-    {
       "name": "LayoutNGBlockFlow DIV id='content'",
       "bounds": [285, 1000],
       "transform": 1
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color-scheme/text/input-disabled-state-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color-scheme/text/input-disabled-state-expected.png
index e13312d..134893d8 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color-scheme/text/input-disabled-state-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color-scheme/text/input-disabled-state-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/textarea/basic-textareas-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/textarea/basic-textareas-expected.png
index 5ab0ddc6..aed0a5f 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/textarea/basic-textareas-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/textarea/basic-textareas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/textarea/basic-textareas-quirks-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/textarea/basic-textareas-quirks-expected.png
index 0c37a88..8a25ad4 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/textarea/basic-textareas-quirks-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/textarea/basic-textareas-quirks-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/overflow/overflow-update-transform-expected.png b/third_party/blink/web_tests/platform/mac/fast/overflow/overflow-update-transform-expected.png
index d4dc57d..696b202 100644
--- a/third_party/blink/web_tests/platform/mac/fast/overflow/overflow-update-transform-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/overflow/overflow-update-transform-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/bugzilla-6473-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/bugzilla-6473-expected.txt
index 6f1bed4..1594a37 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/bugzilla-6473-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/bugzilla-6473-expected.txt
@@ -7,8 +7,7 @@
       "backgroundColor": "#FFFFFF",
       "invalidations": [
         [8, 146, 784, 18],
-        [8, 130, 784, 18],
-        [8, 16, 667, 95]
+        [8, 130, 784, 18]
       ]
     }
   ]
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/iframe-inside-squashed-layer-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/iframe-inside-squashed-layer-expected.txt
index 67e0509..53f2b8c 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/iframe-inside-squashed-layer-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/iframe-inside-squashed-layer-expected.txt
@@ -29,7 +29,7 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [285, 216],
       "invalidations": [
         [0, 0, 285, 216]
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/invalidations-with-large-negative-margin-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/invalidations-with-large-negative-margin-expected.txt
index d36ceee..82d98a9 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/invalidations-with-large-negative-margin-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/invalidations-with-large-negative-margin-expected.txt
@@ -26,7 +26,7 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow DIV",
+      "name": "LayoutNGBlockFlow (relative positioned) DIV",
       "bounds": [585, 400],
       "invalidations": [
         [400, 0, 50, 50],
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/repaint-overflow-scrolled-squashed-content-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/repaint-overflow-scrolled-squashed-content-expected.txt
index 28aa13e..d9e17eb 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/repaint-overflow-scrolled-squashed-content-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/repaint-overflow-scrolled-squashed-content-expected.txt
@@ -26,12 +26,6 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow (positioned) DIV",
-      "bounds": [185, 1000],
-      "drawsContent": false,
-      "transform": 1
-    },
-    {
       "name": "LayoutNGBlockFlow DIV id='foo2'",
       "bounds": [150, 1000],
       "contentsOpaque": true,
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/should-not-clip-composited-overflow-scrolling-layer-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/should-not-clip-composited-overflow-scrolling-layer-expected.txt
index 1d3600299c..f85324ee 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/should-not-clip-composited-overflow-scrolling-layer-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/should-not-clip-composited-overflow-scrolling-layer-expected.txt
@@ -13,6 +13,16 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV id='container'",
+      "bounds": [1000, 1000],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "invalidations": [
+        [0, 0, 1000, 1000]
+      ],
+      "transform": 2
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [0, 285],
       "bounds": [385, 15],
@@ -31,16 +41,6 @@
       "position": [385, 285],
       "bounds": [15, 15],
       "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow DIV id='container'",
-      "bounds": [1000, 1000],
-      "contentsOpaque": true,
-      "backgroundColor": "#008000",
-      "invalidations": [
-        [0, 0, 1000, 1000]
-      ],
-      "transform": 2
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/should-not-paint-outline-on-foreground-layer-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/should-not-paint-outline-on-foreground-layer-expected.txt
index c5d24929..6a20f9d 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/should-not-paint-outline-on-foreground-layer-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/should-not-paint-outline-on-foreground-layer-expected.txt
@@ -39,12 +39,6 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow DIV id='container'",
-      "bounds": [3000, 3000],
-      "drawsContent": false,
-      "transform": 2
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV",
       "bounds": [100, 100],
       "contentsOpaque": true,
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-color-change-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-color-change-expected.txt
index a7dc253..a920f83 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-color-change-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-color-change-expected.txt
@@ -27,7 +27,7 @@
     },
     {
       "name": "LayoutNGBlockFlow (children-inline) PRE id='scroller'",
-      "bounds": [185, 615],
+      "bounds": [47, 615],
       "invalidations": [
         [0, 0, 47, 615]
       ],
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-match-highlight-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-match-highlight-expected.txt
index 7b416b2..c95bf3d2 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-match-highlight-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-match-highlight-expected.txt
@@ -16,19 +16,6 @@
       "transform": 1
     },
     {
-      "name": "HorizontalScrollbar",
-      "position": [0, 485],
-      "bounds": [800, 15],
-      "transform": 1
-    },
-    {
-      "name": "VerticalScrollbar",
-      "position": [785, 0],
-      "bounds": [15, 485],
-      "contentsOpaque": true,
-      "transform": 1
-    },
-    {
       "name": "LayoutNGBlockFlow DIV",
       "bounds": [785, 1340],
       "invalidations": [
@@ -41,6 +28,19 @@
         [52, 18, 45, 18]
       ],
       "transform": 1
+    },
+    {
+      "name": "HorizontalScrollbar",
+      "position": [0, 485],
+      "bounds": [800, 15],
+      "transform": 1
+    },
+    {
+      "name": "VerticalScrollbar",
+      "position": [785, 0],
+      "bounds": [15, 485],
+      "contentsOpaque": true,
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-and-content-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-and-content-expected.txt
index 7eb9ac3..505d3ac 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-and-content-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-and-content-expected.txt
@@ -27,7 +27,7 @@
     },
     {
       "name": "LayoutNGBlockFlow (children-inline) DIV id='container'",
-      "bounds": [185, 234],
+      "bounds": [75, 234],
       "invalidations": [
         [0, 216, 75, 18],
         [0, 198, 75, 18],
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-expected.txt
index 749f297..023e69f 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-expected.txt
@@ -17,6 +17,12 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV id='container'",
+      "position": [5, 5],
+      "bounds": [400, 400],
+      "transform": 1
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [5, 190],
       "bounds": [185, 15],
@@ -35,12 +41,6 @@
       "position": [190, 190],
       "bounds": [15, 15],
       "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow DIV id='container'",
-      "position": [5, 5],
-      "bounds": [400, 400],
-      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/float-offscreen-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/float-offscreen-expected.txt
index 8af7c96..245c92d 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/float-offscreen-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/float-offscreen-expected.txt
@@ -14,7 +14,7 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV id='target'",
-      "bounds": [784, 1027],
+      "bounds": [100, 1027],
       "transform": 1
     }
   ],
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/line-flow-with-floats-9-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/line-flow-with-floats-9-expected.txt
index 5e6c20d..f03e4e7 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/line-flow-with-floats-9-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/line-flow-with-floats-9-expected.txt
@@ -7,7 +7,7 @@
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [485, 606],
       "invalidations": [
         [0, 0, 485, 606]
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/composited-overflow-local-background-removed-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/composited-overflow-local-background-removed-expected.txt
index 285230c..2edc5e7 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/composited-overflow-local-background-removed-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/composited-overflow-local-background-removed-expected.txt
@@ -36,9 +36,10 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV id='scroller'",
-      "bounds": [185, 350],
+      "position": [0, 16],
+      "bounds": [185, 334],
       "invalidations": [
-        [0, 0, 185, 350]
+        [0, 0, 185, 334]
       ],
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-child-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-child-expected.txt
index bc26bde..f7b98e7 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-child-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-child-expected.txt
@@ -14,6 +14,15 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV class='container'",
+      "position": [1, 1],
+      "bounds": [2100, 100],
+      "invalidations": [
+        [2000, 0, 100, 100]
+      ],
+      "transform": 2
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [1, 186],
       "bounds": [285, 15],
@@ -25,15 +34,6 @@
       "position": [286, 1],
       "bounds": [15, 200],
       "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow DIV class='container'",
-      "position": [1, 1],
-      "bounds": [2100, 185],
-      "invalidations": [
-        [2000, 0, 100, 100]
-      ],
-      "transform": 2
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-parent-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-parent-expected.txt
index fbc2489..8e9fbac 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-parent-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-parent-expected.txt
@@ -14,6 +14,15 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV class='container'",
+      "position": [1, 1],
+      "bounds": [2100, 100],
+      "invalidations": [
+        [0, 0, 100, 100]
+      ],
+      "transform": 1
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [1, 186],
       "bounds": [285, 15],
@@ -25,15 +34,6 @@
       "position": [286, 1],
       "bounds": [15, 200],
       "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow DIV class='container'",
-      "position": [1, 1],
-      "bounds": [2100, 185],
-      "invalidations": [
-        [0, 0, 100, 100]
-      ],
-      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-same-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-same-expected.txt
index b26a78e..9a21d37 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-same-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-same-expected.txt
@@ -14,6 +14,15 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV class='vertical-rl container'",
+      "position": [1, 1],
+      "bounds": [2100, 100],
+      "invalidations": [
+        [0, 0, 100, 100]
+      ],
+      "transform": 1
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [1, 186],
       "bounds": [285, 15],
@@ -25,15 +34,6 @@
       "position": [286, 1],
       "bounds": [15, 200],
       "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow DIV class='vertical-rl container'",
-      "position": [1, 1],
-      "bounds": [2100, 185],
-      "invalidations": [
-        [0, 0, 100, 100]
-      ],
-      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/raster-under-invalidation-checking-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/raster-under-invalidation-checking-expected.txt
index b9eb545..337ab60 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/raster-under-invalidation-checking-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/raster-under-invalidation-checking-expected.txt
@@ -969,7 +969,7 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV id='scroller'",
-      "bounds": [4000, 20060],
+      "bounds": [4000, 20000],
       "invalidations": [
         [40, 10010, 60, 40]
       ],
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/fixed-descendant-of-transformed-scrolled-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/fixed-descendant-of-transformed-scrolled-expected.txt
index aa4e8da8..d9966282 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/fixed-descendant-of-transformed-scrolled-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/fixed-descendant-of-transformed-scrolled-expected.txt
@@ -33,10 +33,11 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow (positioned) DIV id='transformed'",
-      "bounds": [1050, 1050],
+      "name": "LayoutNGBlockFlow (relative positioned) DIV id='relative'",
+      "position": [50, 50],
+      "bounds": [1000, 1000],
       "invalidations": [
-        [100, 150, 100, 100]
+        [50, 100, 100, 100]
       ],
       "transform": 2
     }
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/iframe-scrollbar-hover-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/iframe-scrollbar-hover-expected.txt
index 60ed6b8..d2d91db 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/iframe-scrollbar-hover-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/iframe-scrollbar-hover-expected.txt
@@ -7,7 +7,7 @@
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [185, 316],
       "drawsContent": false,
       "transform": 1
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-auto-in-overflow-auto-scrolled-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-auto-in-overflow-auto-scrolled-expected.txt
index 448414e..c61c8ca 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-auto-in-overflow-auto-scrolled-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-auto-in-overflow-auto-scrolled-expected.txt
@@ -13,13 +13,6 @@
       "transform": 1
     },
     {
-      "name": "VerticalScrollbar",
-      "position": [769, 0],
-      "bounds": [15, 300],
-      "contentsOpaque": true,
-      "transform": 1
-    },
-    {
       "name": "LayoutNGBlockFlow DIV id='outerDiv'",
       "bounds": [769, 700],
       "transform": 2
@@ -31,6 +24,13 @@
       "transform": 3
     },
     {
+      "name": "LayoutNGBlockFlow DIV id='innerDiv'",
+      "bounds": [300, 800],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 4
+    },
+    {
       "name": "VerticalScrollbar",
       "position": [754, 0],
       "bounds": [15, 400],
@@ -38,9 +38,11 @@
       "transform": 3
     },
     {
-      "name": "LayoutNGBlockFlow DIV id='innerDiv'",
-      "bounds": [754, 800],
-      "transform": 4
+      "name": "VerticalScrollbar",
+      "position": [769, 0],
+      "bounds": [15, 300],
+      "contentsOpaque": true,
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-composited-non-stacking-child-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-composited-non-stacking-child-expected.txt
index fca30d6..93ace43 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-composited-non-stacking-child-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-composited-non-stacking-child-expected.txt
@@ -40,18 +40,18 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow DIV class='scroller'",
-      "position": [5, 5],
-      "bounds": [285, 290],
-      "drawsContent": false,
-      "transform": 2
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV class='icon'",
       "bounds": [40, 40],
       "contentsOpaque": true,
       "backgroundColor": "#FFDDBB",
       "transform": 3
+    },
+    {
+      "name": "LayoutNGBlockFlow (relative positioned) DIV class='list'",
+      "position": [25, 25],
+      "bounds": [180, 250],
+      "drawsContent": false,
+      "transform": 2
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-delete-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-delete-expected.txt
index 8544e60..4c4010a 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-delete-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-delete-expected.txt
@@ -21,9 +21,10 @@
     },
     {
       "name": "LayoutNGBlockFlow (children-inline) DIV id='t'",
-      "bounds": [65, 198],
+      "position": [0, 162],
+      "bounds": [44, 36],
       "invalidations": [
-        [0, 162, 44, 36]
+        [0, 0, 44, 36]
       ],
       "transform": 2
     }
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.txt
index 448414e..c61c8ca 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.txt
@@ -13,13 +13,6 @@
       "transform": 1
     },
     {
-      "name": "VerticalScrollbar",
-      "position": [769, 0],
-      "bounds": [15, 300],
-      "contentsOpaque": true,
-      "transform": 1
-    },
-    {
       "name": "LayoutNGBlockFlow DIV id='outerDiv'",
       "bounds": [769, 700],
       "transform": 2
@@ -31,6 +24,13 @@
       "transform": 3
     },
     {
+      "name": "LayoutNGBlockFlow DIV id='innerDiv'",
+      "bounds": [300, 800],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "transform": 4
+    },
+    {
       "name": "VerticalScrollbar",
       "position": [754, 0],
       "bounds": [15, 400],
@@ -38,9 +38,11 @@
       "transform": 3
     },
     {
-      "name": "LayoutNGBlockFlow DIV id='innerDiv'",
-      "bounds": [754, 800],
-      "transform": 4
+      "name": "VerticalScrollbar",
+      "position": [769, 0],
+      "bounds": [15, 300],
+      "contentsOpaque": true,
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/resize-scrollable-iframe-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/resize-scrollable-iframe-expected.txt
index e422b36..a520c3e 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/resize-scrollable-iframe-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/resize-scrollable-iframe-expected.txt
@@ -7,7 +7,7 @@
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [1008, 1016],
       "drawsContent": false,
       "transform": 1
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-ancestor-clip-change-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-ancestor-clip-change-expected.txt
index 5ac04e3..f636d082 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-ancestor-clip-change-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-ancestor-clip-change-expected.txt
@@ -13,6 +13,12 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV id='target'",
+      "bounds": [400, 400],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [0, 185],
       "bounds": [85, 15],
@@ -34,12 +40,6 @@
         [0, 0, 15, 15]
       ],
       "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow DIV id='target'",
-      "bounds": [400, 400],
-      "drawsContent": false,
-      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-damage-and-full-viewport-repaint-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-damage-and-full-viewport-repaint-expected.txt
index 2f4117b7..857f9922 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-damage-and-full-viewport-repaint-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-damage-and-full-viewport-repaint-expected.txt
@@ -7,8 +7,8 @@
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "Scrolling background of LayoutView #document",
-      "bounds": [1000, 600]
+      "name": "LayoutNGBlockFlow HTML",
+      "bounds": [1000, 252]
     },
     {
       "name": "LayoutNGBlockFlow DIV id='container'",
@@ -18,6 +18,13 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV id='container'",
+      "position": [1, 1],
+      "bounds": [2000, 2000],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [1, 186],
       "bounds": [185, 15],
@@ -39,13 +46,6 @@
         [0, 0, 15, 15]
       ],
       "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow DIV id='container'",
-      "position": [1, 1],
-      "bounds": [2000, 2000],
-      "drawsContent": false,
-      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-parts-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-parts-expected.txt
index e510f32..bc7f98a9 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-parts-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-parts-expected.txt
@@ -13,6 +13,12 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV",
+      "bounds": [150, 300],
+      "drawsContent": false,
+      "transform": 1
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [0, 85],
       "bounds": [85, 15],
@@ -31,12 +37,6 @@
       "position": [85, 85],
       "bounds": [15, 15],
       "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow DIV",
-      "bounds": [150, 300],
-      "drawsContent": false,
-      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/sticky/invalidate-after-composited-scroll-with-sticky-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/sticky/invalidate-after-composited-scroll-with-sticky-expected.txt
index 3e80898..05261d6 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/sticky/invalidate-after-composited-scroll-with-sticky-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/sticky/invalidate-after-composited-scroll-with-sticky-expected.txt
@@ -13,6 +13,13 @@
       "transform": 2
     },
     {
+      "name": "LayoutNGBlockFlow MAT id='scroller'",
+      "position": [0, 18],
+      "bounds": [345, 2000],
+      "drawsContent": false,
+      "transform": 3
+    },
+    {
       "name": "VerticalScrollbar",
       "position": [345, 0],
       "bounds": [15, 640],
@@ -20,12 +27,6 @@
       "transform": 2
     },
     {
-      "name": "LayoutNGBlockFlow MAT id='scroller'",
-      "bounds": [345, 2018],
-      "drawsContent": false,
-      "transform": 3
-    },
-    {
       "name": "LayoutNGBlockFlow (sticky positioned, children-inline) DIV id='sticky'",
       "bounds": [345, 18],
       "transform": 4
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-inside-table-cell-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-inside-table-cell-expected.txt
index 9874980..f155145b 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-inside-table-cell-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-inside-table-cell-expected.txt
@@ -27,9 +27,19 @@
       "transform": 1
     },
     {
-      "name": "LayoutTableCell (relative positioned) TD id='cellToScroll' class='relative'",
+      "name": "LayoutNGBlockFlow (relative positioned) DIV class='relative red'",
       "position": [2, 2],
-      "bounds": [950, 450],
+      "bounds": [450, 450],
+      "contentsOpaque": true,
+      "backgroundColor": "#FF0000",
+      "transform": 2
+    },
+    {
+      "name": "LayoutNGBlockFlow (positioned) DIV class='absolute green'",
+      "position": [502, 2],
+      "bounds": [450, 450],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
       "transform": 2
     }
   ],
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-relative-table-inside-table-cell-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-relative-table-inside-table-cell-expected.txt
index e213f6f..6f8c494 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-relative-table-inside-table-cell-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-relative-table-inside-table-cell-expected.txt
@@ -28,9 +28,19 @@
       "transform": 2
     },
     {
-      "name": "LayoutTableCell (relative positioned) TD id='cellToScroll' class='relative'",
+      "name": "LayoutNGBlockFlow (relative positioned) DIV class='relative red'",
       "position": [2, 2],
-      "bounds": [950, 450],
+      "bounds": [450, 450],
+      "contentsOpaque": true,
+      "backgroundColor": "#FF0000",
+      "transform": 3
+    },
+    {
+      "name": "LayoutNGBlockFlow (positioned) DIV class='absolute green'",
+      "position": [502, 2],
+      "bounds": [450, 450],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
       "transform": 3
     },
     {
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-auto-in-overflow-auto-scrolled-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-auto-in-overflow-auto-scrolled-expected.txt
index c511804..1ac9ffc 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-auto-in-overflow-auto-scrolled-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-auto-in-overflow-auto-scrolled-expected.txt
@@ -13,13 +13,6 @@
       "transform": 1
     },
     {
-      "name": "VerticalScrollbar",
-      "position": [769, 0],
-      "bounds": [15, 300],
-      "contentsOpaque": true,
-      "transform": 1
-    },
-    {
       "name": "LayoutNGBlockFlow DIV id='outerDiv'",
       "bounds": [769, 700],
       "transform": 2
@@ -31,6 +24,11 @@
       "transform": 3
     },
     {
+      "name": "LayoutNGBlockFlow DIV id='innerDiv'",
+      "bounds": [306, 810],
+      "transform": 4
+    },
+    {
       "name": "VerticalScrollbar",
       "position": [754, 0],
       "bounds": [15, 400],
@@ -38,9 +36,11 @@
       "transform": 3
     },
     {
-      "name": "LayoutNGBlockFlow DIV id='innerDiv'",
-      "bounds": [754, 810],
-      "transform": 4
+      "name": "VerticalScrollbar",
+      "position": [769, 0],
+      "bounds": [15, 300],
+      "contentsOpaque": true,
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.txt
index c511804..1ac9ffc 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.txt
@@ -13,13 +13,6 @@
       "transform": 1
     },
     {
-      "name": "VerticalScrollbar",
-      "position": [769, 0],
-      "bounds": [15, 300],
-      "contentsOpaque": true,
-      "transform": 1
-    },
-    {
       "name": "LayoutNGBlockFlow DIV id='outerDiv'",
       "bounds": [769, 700],
       "transform": 2
@@ -31,6 +24,11 @@
       "transform": 3
     },
     {
+      "name": "LayoutNGBlockFlow DIV id='innerDiv'",
+      "bounds": [306, 810],
+      "transform": 4
+    },
+    {
       "name": "VerticalScrollbar",
       "position": [754, 0],
       "bounds": [15, 400],
@@ -38,9 +36,11 @@
       "transform": 3
     },
     {
-      "name": "LayoutNGBlockFlow DIV id='innerDiv'",
-      "bounds": [754, 810],
-      "transform": 4
+      "name": "VerticalScrollbar",
+      "position": [769, 0],
+      "bounds": [15, 300],
+      "contentsOpaque": true,
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/text-match-document-change-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/text-match-document-change-expected.txt
index 1a0958ce..aa34951f 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/text-match-document-change-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/text-match-document-change-expected.txt
@@ -7,7 +7,7 @@
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [285, 1052],
       "invalidations": [
         [8, 26, 256, 36]
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-expected.txt
index 64f9acc..822e231 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/window-resize/window-resize-background-image-fixed-centered-expected.txt
@@ -10,8 +10,8 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
-      "bounds": [600, 250],
+      "name": "LayoutNGBlockFlow HTML",
+      "bounds": [600, 8],
       "drawsContent": false
     }
   ]
@@ -28,8 +28,8 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
-      "bounds": [400, 250],
+      "name": "LayoutNGBlockFlow HTML",
+      "bounds": [400, 8],
       "drawsContent": false
     }
   ]
@@ -46,8 +46,8 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
-      "bounds": [400, 600],
+      "name": "LayoutNGBlockFlow HTML",
+      "bounds": [400, 8],
       "drawsContent": false
     }
   ]
@@ -64,8 +64,8 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
-      "bounds": [800, 600],
+      "name": "LayoutNGBlockFlow HTML",
+      "bounds": [800, 8],
       "drawsContent": false
     }
   ]
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/window-resize/window-resize-background-image-fixed-generated-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/window-resize/window-resize-background-image-fixed-generated-expected.txt
index 64f9acc..822e231 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/window-resize/window-resize-background-image-fixed-generated-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/window-resize/window-resize-background-image-fixed-generated-expected.txt
@@ -10,8 +10,8 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
-      "bounds": [600, 250],
+      "name": "LayoutNGBlockFlow HTML",
+      "bounds": [600, 8],
       "drawsContent": false
     }
   ]
@@ -28,8 +28,8 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
-      "bounds": [400, 250],
+      "name": "LayoutNGBlockFlow HTML",
+      "bounds": [400, 8],
       "drawsContent": false
     }
   ]
@@ -46,8 +46,8 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
-      "bounds": [400, 600],
+      "name": "LayoutNGBlockFlow HTML",
+      "bounds": [400, 8],
       "drawsContent": false
     }
   ]
@@ -64,8 +64,8 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
-      "bounds": [800, 600],
+      "name": "LayoutNGBlockFlow HTML",
+      "bounds": [800, 8],
       "drawsContent": false
     }
   ]
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/window-resize/window-resize-background-image-fixed-scrolling-contents-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/window-resize/window-resize-background-image-fixed-scrolling-contents-expected.txt
index 66d92ae..b9da11c 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/window-resize/window-resize-background-image-fixed-scrolling-contents-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/window-resize/window-resize-background-image-fixed-scrolling-contents-expected.txt
@@ -10,7 +10,7 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [585, 3016],
       "drawsContent": false
     },
@@ -34,7 +34,7 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [385, 3016],
       "drawsContent": false
     },
@@ -58,7 +58,7 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [385, 3016],
       "drawsContent": false
     },
@@ -82,7 +82,7 @@
       ]
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [785, 3016],
       "drawsContent": false
     },
diff --git a/third_party/blink/web_tests/platform/mac/scrollbars/overlay-scrollbars-within-overflow-scroll-expected.txt b/third_party/blink/web_tests/platform/mac/scrollbars/overlay-scrollbars-within-overflow-scroll-expected.txt
index d9ce637..135e8c9 100644
--- a/third_party/blink/web_tests/platform/mac/scrollbars/overlay-scrollbars-within-overflow-scroll-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/scrollbars/overlay-scrollbars-within-overflow-scroll-expected.txt
@@ -15,11 +15,17 @@
     {
       "name": "LayoutNGBlockFlow DIV id='outer'",
       "position": [1, 1],
-      "bounds": [400, 602],
+      "bounds": [400, 500],
+      "drawsContent": false,
       "transform": 1
     },
     {
       "name": "LayoutNGBlockFlow DIV id='inner'",
+      "bounds": [102, 102],
+      "transform": 2
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV id='inner'",
       "position": [1, 1],
       "bounds": [100, 100],
       "drawsContent": false,
diff --git a/third_party/blink/web_tests/platform/mac/virtual/dark-color-scheme/fast/forms/color-scheme/autofill/autofilled-expected.png b/third_party/blink/web_tests/platform/mac/virtual/dark-color-scheme/fast/forms/color-scheme/autofill/autofilled-expected.png
index 12f7e23..b224558 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/dark-color-scheme/fast/forms/color-scheme/autofill/autofilled-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/dark-color-scheme/fast/forms/color-scheme/autofill/autofilled-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/force-compositing-mode/overflow-iframe-enter-compositing-expected.txt b/third_party/blink/web_tests/platform/win/compositing/force-compositing-mode/overflow-iframe-enter-compositing-expected.txt
index 147236e..d418fde 100644
--- a/third_party/blink/web_tests/platform/win/compositing/force-compositing-mode/overflow-iframe-enter-compositing-expected.txt
+++ b/third_party/blink/web_tests/platform/win/compositing/force-compositing-mode/overflow-iframe-enter-compositing-expected.txt
@@ -7,7 +7,7 @@
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [508, 516],
       "backgroundColor": "#0000FF",
       "transform": 1
diff --git a/third_party/blink/web_tests/platform/win/compositing/force-compositing-mode/overflow-iframe-layer-expected.txt b/third_party/blink/web_tests/platform/win/compositing/force-compositing-mode/overflow-iframe-layer-expected.txt
index 147236e..d418fde 100644
--- a/third_party/blink/web_tests/platform/win/compositing/force-compositing-mode/overflow-iframe-layer-expected.txt
+++ b/third_party/blink/web_tests/platform/win/compositing/force-compositing-mode/overflow-iframe-layer-expected.txt
@@ -7,7 +7,7 @@
       "backgroundColor": "#FFFFFF"
     },
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "LayoutNGBlockFlow HTML",
       "bounds": [508, 516],
       "backgroundColor": "#0000FF",
       "transform": 1
diff --git a/third_party/blink/web_tests/platform/win/compositing/layer-creation/fixed-position-nonscrollable-body-mismatch-containers-expected.txt b/third_party/blink/web_tests/platform/win/compositing/layer-creation/fixed-position-nonscrollable-body-mismatch-containers-expected.txt
index f71f3a7a..227d6d4 100644
--- a/third_party/blink/web_tests/platform/win/compositing/layer-creation/fixed-position-nonscrollable-body-mismatch-containers-expected.txt
+++ b/third_party/blink/web_tests/platform/win/compositing/layer-creation/fixed-position-nonscrollable-body-mismatch-containers-expected.txt
@@ -16,6 +16,12 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV class='scrollable bigBox'",
+      "position": [1, 1],
+      "bounds": [285, 800],
+      "transform": 1
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [1, 286],
       "bounds": [300, 15],
@@ -29,12 +35,6 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow DIV class='scrollable bigBox'",
-      "position": [1, 1],
-      "bounds": [285, 800],
-      "transform": 1
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV class='fixed lime box'",
       "position": [10, 100],
       "bounds": [100, 100],
diff --git a/third_party/blink/web_tests/platform/win/compositing/overflow/content-gains-scrollbars-expected.txt b/third_party/blink/web_tests/platform/win/compositing/overflow/content-gains-scrollbars-expected.txt
index 3130a44..3f78ad3 100644
--- a/third_party/blink/web_tests/platform/win/compositing/overflow/content-gains-scrollbars-expected.txt
+++ b/third_party/blink/web_tests/platform/win/compositing/overflow/content-gains-scrollbars-expected.txt
@@ -26,12 +26,6 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow (positioned, children-inline) DIV class='container'",
-      "bounds": [85, 200],
-      "drawsContent": false,
-      "transform": 1
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV id='vertical' class='content tall'",
       "bounds": [10, 200],
       "drawsContent": false,
@@ -57,12 +51,6 @@
       "transform": 2
     },
     {
-      "name": "LayoutNGBlockFlow (positioned, children-inline) DIV class='container'",
-      "bounds": [200, 85],
-      "drawsContent": false,
-      "transform": 2
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV id='horizontal' class='content wide'",
       "bounds": [200, 10],
       "drawsContent": false,
@@ -102,12 +90,6 @@
       "transform": 3
     },
     {
-      "name": "LayoutNGBlockFlow (positioned, children-inline) DIV class='container'",
-      "bounds": [200, 200],
-      "drawsContent": false,
-      "transform": 3
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV id='both' class='content wide tall'",
       "bounds": [200, 200],
       "drawsContent": false,
diff --git a/third_party/blink/web_tests/platform/win/compositing/overflow/overflow-auto-with-touch-toggle-expected.txt b/third_party/blink/web_tests/platform/win/compositing/overflow/overflow-auto-with-touch-toggle-expected.txt
index 0a79633..53d0152 100644
--- a/third_party/blink/web_tests/platform/win/compositing/overflow/overflow-auto-with-touch-toggle-expected.txt
+++ b/third_party/blink/web_tests/platform/win/compositing/overflow/overflow-auto-with-touch-toggle-expected.txt
@@ -13,6 +13,13 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV class='scroller'",
+      "bounds": [1000, 1000],
+      "contentsOpaque": true,
+      "backgroundColor": "#C0C0C0",
+      "transform": 1
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [0, 285],
       "bounds": [285, 15],
@@ -32,13 +39,6 @@
       "bounds": [15, 15],
       "contentsOpaque": true,
       "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow DIV class='scroller'",
-      "bounds": [1000, 1000],
-      "contentsOpaque": true,
-      "backgroundColor": "#C0C0C0",
-      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/win/compositing/overflow/overflow-scrollbar-layers-expected.txt b/third_party/blink/web_tests/platform/win/compositing/overflow/overflow-scrollbar-layers-expected.txt
index 02f0c31..aa80a7e 100644
--- a/third_party/blink/web_tests/platform/win/compositing/overflow/overflow-scrollbar-layers-expected.txt
+++ b/third_party/blink/web_tests/platform/win/compositing/overflow/overflow-scrollbar-layers-expected.txt
@@ -26,12 +26,6 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow (positioned, children-inline) DIV class='container'",
-      "bounds": [85, 200],
-      "drawsContent": false,
-      "transform": 1
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV class='content tall'",
       "bounds": [10, 200],
       "drawsContent": false,
@@ -57,12 +51,6 @@
       "transform": 2
     },
     {
-      "name": "LayoutNGBlockFlow (positioned, children-inline) DIV class='container'",
-      "bounds": [200, 85],
-      "drawsContent": false,
-      "transform": 2
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV class='content wide'",
       "bounds": [200, 10],
       "drawsContent": false,
@@ -102,12 +90,6 @@
       "transform": 3
     },
     {
-      "name": "LayoutNGBlockFlow (positioned, children-inline) DIV class='container'",
-      "bounds": [200, 200],
-      "drawsContent": false,
-      "transform": 3
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV class='content wide tall'",
       "bounds": [200, 200],
       "drawsContent": false,
diff --git a/third_party/blink/web_tests/platform/win/compositing/overflow/universal-accelerated-overflow-scroll-expected.png b/third_party/blink/web_tests/platform/win/compositing/overflow/universal-accelerated-overflow-scroll-expected.png
index 94c97f2..7161ab5 100644
--- a/third_party/blink/web_tests/platform/win/compositing/overflow/universal-accelerated-overflow-scroll-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/overflow/universal-accelerated-overflow-scroll-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/color-scheme/text/input-disabled-state-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/color-scheme/text/input-disabled-state-expected.png
index e7943cd1..8e7ee92 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/color-scheme/text/input-disabled-state-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/color-scheme/text/input-disabled-state-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/textarea/basic-textareas-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/textarea/basic-textareas-expected.png
index 1eb382b..ccbe6db 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/textarea/basic-textareas-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/textarea/basic-textareas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/textarea/basic-textareas-quirks-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/textarea/basic-textareas-quirks-expected.png
index 57921ed..8d9c0bb 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/textarea/basic-textareas-quirks-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/forms/textarea/basic-textareas-quirks-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/bugzilla-6473-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/bugzilla-6473-expected.txt
index 55d892ce..537ff88 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/bugzilla-6473-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/bugzilla-6473-expected.txt
@@ -7,8 +7,7 @@
       "backgroundColor": "#FFFFFF",
       "invalidations": [
         [8, 152, 784, 20],
-        [8, 136, 784, 20],
-        [8, 16, 626, 100]
+        [8, 136, 784, 20]
       ]
     }
   ]
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/compositing/should-not-clip-composited-overflow-scrolling-layer-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/compositing/should-not-clip-composited-overflow-scrolling-layer-expected.txt
index 2970a09..28b6727 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/compositing/should-not-clip-composited-overflow-scrolling-layer-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/compositing/should-not-clip-composited-overflow-scrolling-layer-expected.txt
@@ -13,6 +13,16 @@
       "transform": 1
     },
     {
+      "name": "LayoutNGBlockFlow DIV id='container'",
+      "bounds": [1000, 1000],
+      "contentsOpaque": true,
+      "backgroundColor": "#008000",
+      "invalidations": [
+        [0, 0, 1000, 1000]
+      ],
+      "transform": 2
+    },
+    {
       "name": "HorizontalScrollbar",
       "position": [0, 285],
       "bounds": [385, 15],
@@ -32,16 +42,6 @@
       "bounds": [15, 15],
       "contentsOpaque": true,
       "transform": 1
-    },
-    {
-      "name": "LayoutNGBlockFlow DIV id='container'",
-      "bounds": [1000, 1000],
-      "contentsOpaque": true,
-      "backgroundColor": "#008000",
-      "invalidations": [
-        [0, 0, 1000, 1000]
-      ],
-      "transform": 2
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/compositing/should-not-paint-outline-on-foreground-layer-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/compositing/should-not-paint-outline-on-foreground-layer-expected.txt
index cea750e..1773cdb45 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/compositing/should-not-paint-outline-on-foreground-layer-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/compositing/should-not-paint-outline-on-foreground-layer-expected.txt
@@ -40,12 +40,6 @@
       "transform": 1
     },
     {
-      "name": "LayoutNGBlockFlow DIV id='container'",
-      "bounds": [3000, 3000],
-      "drawsContent": false,
-      "transform": 2
-    },
-    {
       "name": "LayoutNGBlockFlow (positioned) DIV",
       "bounds": [100, 100],
       "contentsOpaque": true,
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/overflow/composited-overflow-local-background-removed-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/overflow/composited-overflow-local-background-removed-expected.txt
index e73b549..518c8e4 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/overflow/composited-overflow-local-background-removed-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/overflow/composited-overflow-local-background-removed-expected.txt
@@ -36,9 +36,10 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV id='scroller'",
-      "bounds": [185, 352],
+      "position": [0, 16],
+      "bounds": [185, 336],
       "invalidations": [
-        [0, 0, 185, 352]
+        [0, 0, 185, 336]
       ],
       "transform": 1
     }
diff --git a/third_party/blink/web_tests/platform/win/paint/invalidation/raster-under-invalidation-checking-expected.txt b/third_party/blink/web_tests/platform/win/paint/invalidation/raster-under-invalidation-checking-expected.txt
index af0cd403..6544ff4 100644
--- a/third_party/blink/web_tests/platform/win/paint/invalidation/raster-under-invalidation-checking-expected.txt
+++ b/third_party/blink/web_tests/platform/win/paint/invalidation/raster-under-invalidation-checking-expected.txt
@@ -970,7 +970,7 @@
     },
     {
       "name": "LayoutNGBlockFlow DIV id='scroller'",
-      "bounds": [4000, 20060],
+      "bounds": [4000, 20000],
       "invalidations": [
         [40, 10010, 60, 40]
       ],
diff --git a/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/fast/forms/color-scheme/autofill/autofilled-expected.png b/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/fast/forms/color-scheme/autofill/autofilled-expected.png
index b2e3be0..b5ca916 100644
--- a/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/fast/forms/color-scheme/autofill/autofilled-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/dark-color-scheme/fast/forms/color-scheme/autofill/autofilled-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/scrollbars/overlay-scrollbar-over-child-layer-nested.html b/third_party/blink/web_tests/scrollbars/overlay-scrollbar-over-child-layer-nested.html
index 3b3d1a97..7253fcb6 100644
--- a/third_party/blink/web_tests/scrollbars/overlay-scrollbar-over-child-layer-nested.html
+++ b/third_party/blink/web_tests/scrollbars/overlay-scrollbar-over-child-layer-nested.html
@@ -1,5 +1,4 @@
 <!DOCTYPE html>
-<meta name="fuzzy" content="maxDifference=0-255;totalPixels=0-3">
 <script>
 if (window.internals)
   internals.useMockOverlayScrollbars();
diff --git a/third_party/blink/web_tests/scrollbars/overlay-scrollbars-with-scrollbar-color-expected.txt b/third_party/blink/web_tests/scrollbars/overlay-scrollbars-with-scrollbar-color-expected.txt
index 3a82d86..1b0fb89 100644
--- a/third_party/blink/web_tests/scrollbars/overlay-scrollbars-with-scrollbar-color-expected.txt
+++ b/third_party/blink/web_tests/scrollbars/overlay-scrollbars-with-scrollbar-color-expected.txt
@@ -15,11 +15,17 @@
     {
       "name": "LayoutNGBlockFlow DIV id='outer'",
       "position": [1, 1],
-      "bounds": [400, 602],
+      "bounds": [400, 500],
+      "drawsContent": false,
       "transform": 1
     },
     {
       "name": "LayoutNGBlockFlow DIV id='inner'",
+      "bounds": [102, 102],
+      "transform": 2
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV id='inner'",
       "position": [1, 1],
       "bounds": [100, 100],
       "drawsContent": false,
diff --git a/third_party/blink/web_tests/virtual/prefer_compositing_to_lcd_text/scrollbars/overlay-scrollbars-within-overflow-scroll-expected.png b/third_party/blink/web_tests/virtual/prefer_compositing_to_lcd_text/scrollbars/overlay-scrollbars-within-overflow-scroll-expected.png
index f6dd657b..d7e5a72 100644
--- a/third_party/blink/web_tests/virtual/prefer_compositing_to_lcd_text/scrollbars/overlay-scrollbars-within-overflow-scroll-expected.png
+++ b/third_party/blink/web_tests/virtual/prefer_compositing_to_lcd_text/scrollbars/overlay-scrollbars-within-overflow-scroll-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/prefer_compositing_to_lcd_text/scrollbars/overlay-scrollbars-within-overflow-scroll-expected.txt b/third_party/blink/web_tests/virtual/prefer_compositing_to_lcd_text/scrollbars/overlay-scrollbars-within-overflow-scroll-expected.txt
index d9ce637..135e8c9 100644
--- a/third_party/blink/web_tests/virtual/prefer_compositing_to_lcd_text/scrollbars/overlay-scrollbars-within-overflow-scroll-expected.txt
+++ b/third_party/blink/web_tests/virtual/prefer_compositing_to_lcd_text/scrollbars/overlay-scrollbars-within-overflow-scroll-expected.txt
@@ -15,11 +15,17 @@
     {
       "name": "LayoutNGBlockFlow DIV id='outer'",
       "position": [1, 1],
-      "bounds": [400, 602],
+      "bounds": [400, 500],
+      "drawsContent": false,
       "transform": 1
     },
     {
       "name": "LayoutNGBlockFlow DIV id='inner'",
+      "bounds": [102, 102],
+      "transform": 2
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV id='inner'",
       "position": [1, 1],
       "bounds": [100, 100],
       "drawsContent": false,
diff --git a/third_party/catapult b/third_party/catapult
index d80f7d1..99cae58 160000
--- a/third_party/catapult
+++ b/third_party/catapult
@@ -1 +1 @@
-Subproject commit d80f7d1ae4c6c010ecc8e660588f33f5768cb177
+Subproject commit 99cae5876c51001792d8225f77579eb537188490
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index 1cdf052..d5d8b1b 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit 1cdf05204cf5365bcb63fd8b306750b054be5c73
+Subproject commit d5d8b1bb4ddbde9f4752ca8ae28543a31b64e211
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index cb3c970..f12c052 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit cb3c970179dfea108e67796c202f519068d1d48f
+Subproject commit f12c052fc715d18e765fe3f78fdb3584a14baf4b
diff --git a/third_party/metrics_proto/README.chromium b/third_party/metrics_proto/README.chromium
index ba8834932..eba6d72 100644
--- a/third_party/metrics_proto/README.chromium
+++ b/third_party/metrics_proto/README.chromium
@@ -1,8 +1,8 @@
 Name: Metrics Protos
 Short Name: metrics_proto
 URL: This is the canonical public repository
-Version: 600904906
-Date: 2024-01-23 UTC
+Version: 597734888
+Date: 2024-01-12 UTC
 License: BSD
 License File: LICENSE
 Shipped: yes
diff --git a/third_party/metrics_proto/ukm/source.proto b/third_party/metrics_proto/ukm/source.proto
index 2388d41f..0b4eaec8 100644
--- a/third_party/metrics_proto/ukm/source.proto
+++ b/third_party/metrics_proto/ukm/source.proto
@@ -27,6 +27,7 @@
   WEB_IDENTITY_ID = 10;
   CHROMEOS_WEBSITE_ID = 11;
   EXTENSION_ID = 12;
+  NOTIFICATION_ID = 13;
 }
 
 // Android Activity Type defined by
diff --git a/third_party/pdfium b/third_party/pdfium
index aad37f1..64571d5 160000
--- a/third_party/pdfium
+++ b/third_party/pdfium
@@ -1 +1 @@
-Subproject commit aad37f1a2976c5379b8a2aba60a330d7aba55164
+Subproject commit 64571d5d1f1eb7e91b7237b37d7bffecebb77981
diff --git a/third_party/skia b/third_party/skia
index c653705..d503bc9c 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit c653705482bc2a85d42e19cb441ce5e250d81e39
+Subproject commit d503bc9c6e46b399fe7f89057c21b7c393ccd845
diff --git a/third_party/vulkan-deps b/third_party/vulkan-deps
index d8fc430..52f57ac 160000
--- a/third_party/vulkan-deps
+++ b/third_party/vulkan-deps
@@ -1 +1 @@
-Subproject commit d8fc43000b3e5e924679432724228bf75695ca27
+Subproject commit 52f57acdf69e747b0cea3c458a6ec6467dcb6d8a
diff --git a/tools/codeql/queries/bad_message_no_return.ql b/tools/codeql/queries/bad_message_no_return.ql
new file mode 100644
index 0000000..eb859046
--- /dev/null
+++ b/tools/codeql/queries/bad_message_no_return.ql
@@ -0,0 +1,51 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import cpp
+import lib.Chromium
+
+/**
+ * @name potential ReceivedBadMessage call without return.
+ * @description Detects instances where bad_message::ReceivedBadMessage
+ *              is called and not immediately followed by a return statement.
+ * @kind problem
+ * @problem.severity warning
+ * @id cpp/report-bad-message-without-return
+ */
+
+class BadMessageCall extends FunctionCall {
+  BadMessageCall() {
+
+    // bad_message::ReceivedBadMessage
+    this.getTarget().hasQualifiedName("bad_message", "ReceivedBadMessage") or
+
+    // content::bad_message::ReceivedBadMessage
+    this.getTarget().hasQualifiedName("content::bad_message", "ReceivedBadMessage")
+  }
+}
+
+from BadMessageCall call, Function f
+where
+  Chromium::isChromiumCode(call) and
+
+  call.getEnclosingFunction() = f and
+
+  // Ignore any calls with a returnStatement in the same enclosing block.
+  not exists(ReturnStmt returnStmt |
+    call.getEnclosingBlock() = returnStmt.getEnclosingBlock()
+  )  and
+
+  // Ignore any calls with a returnStatement immediately after in the block
+  exists(Stmt stmtAfterCall |
+    stmtAfterCall.getEnclosingFunction() = f and
+    stmtAfterCall.getLocation().getStartLine() > call.getLocation().getStartLine() and
+    not stmtAfterCall instanceof ReturnStmt and
+    not exists(ReturnStmt returnBetween |
+      returnBetween.getEnclosingFunction() = f and
+      returnBetween.getLocation().getStartLine() > call.getLocation().getStartLine() and
+      returnBetween.getLocation().getStartLine() < stmtAfterCall.getLocation().getStartLine()
+    )
+  )
+select call,
+  call.getLocation().getFile().getRelativePath() + ":" + call.getLocation().getStartLine().toString()
diff --git a/tools/codeql/queries/lib/Chromium.qll b/tools/codeql/queries/lib/Chromium.qll
new file mode 100644
index 0000000..41bd981
--- /dev/null
+++ b/tools/codeql/queries/lib/Chromium.qll
@@ -0,0 +1,47 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import cpp
+import semmle.code.cpp.dataflow.DataFlow
+
+module Chromium {
+  predicate isChromiumCode(Element e) { isChromiumPath(e.getFile()) }
+
+  predicate isChromiumPath(Container c) {
+    exists(string path |
+        path = c.getAbsolutePath() and
+        not path.matches("%buildtools%") and
+        not path.matches("%include/c++%") and
+        not path.matches("/usr/include%") and
+        not path.matches("%native_client%")
+    )
+  }
+
+  predicate isUbiquitousChromiumPath(Container c) {
+    exists(string path |
+        path = c.getAbsolutePath() and
+        not path.matches("%ios_internal%") and
+        not path.matches("%ios%") and
+        not path.matches("%android_webview%") and
+        not path.matches("%chromecast%")
+    )
+  }
+
+  predicate isUbiquitousChromiumCode(Element e) {
+    isChromiumPath(e.getFile()) and
+    isUbiquitousChromiumPath(e.getFile())
+  }
+
+  predicate isBlinkCode(Element e) {
+    isChromiumCode(e.getFile()) and
+    isBlinkPath(e.getFile())
+  }
+
+  predicate isBlinkPath(Container c) {
+    exists(string path |
+      path = c.getAbsolutePath() and
+      path.matches("%third_party/blink%")
+    )
+  }
+}
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index 5b732a3..5ee9c00 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -841,6 +841,10 @@
     "META": {"join": 2, "sizes": {"includes": [20]}},
     "includes": [5880],
   },
+  "<(SHARED_INTERMEDIATE_DIR)/ash/webui/print_preview_cros/resources/resources.grd": {
+    "META": {"sizes": {"includes": [50]}},
+    "includes": [5885],
+  },
   "<(SHARED_INTERMEDIATE_DIR)/ash/webui/sample_system_web_app_ui/resources/trusted/resources.grd": {
     "META": {"sizes": {"includes": [50],}},
     "includes": [5900],
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index dc07b6cd..bdf212f 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -18600,6 +18600,7 @@
   <int value="-950411190"
       label="WebViewCpuAffinityRestrictToLittleCores:enabled"/>
   <int value="-950384924" label="OmniboxDisableInstantExtendedLimit:enabled"/>
+  <int value="-949956361" label="ControlledFrame:disabled"/>
   <int value="-949178861" label="enable-new-avatar-menu"/>
   <int value="-948930847" label="EnableOopPrintDrivers:enabled"/>
   <int value="-948684341" label="OmniboxBlurWithEscape:enabled"/>
@@ -20948,6 +20949,7 @@
   <int value="138995035" label="SafeBrowsingAsyncRealTimeCheck:enabled"/>
   <int value="139569991" label="SharingDeviceExpiration:disabled"/>
   <int value="139603247" label="AudioHFPMicSRToggle:disabled"/>
+  <int value="139974547" label="ControlledFrame:enabled"/>
   <int value="140257184" label="ChromeOSAmbientModeNewUrl:enabled"/>
   <int value="140427435" label="OmniboxReportSearchboxStats:enabled"/>
   <int value="140778694"
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index a9937d75..88db87b 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -974,7 +974,7 @@
   <owner>clank-app-team@google.com</owner>
   <summary>
     Records the primary display (screen) size in tens of inches with 0.2 inches
-    (2 units) granularity, during deferred startup. The display size is not
+    (2 units) granularity, during deferred startup. The re-enabled size is not
     affected by Android N multi-window mode.
   </summary>
 </histogram>
@@ -1028,27 +1028,35 @@
 </histogram>
 
 <histogram name="Android.DownloadManager.OpenSource.Audio"
-    enum="AndroidDownloadOpenSource" expires_after="2023-07-09">
+    enum="AndroidDownloadOpenSource" expires_after="2024-05-31">
   <owner>shaktisahu@chromium.org</owner>
   <owner>clank-downloads@google.com</owner>
-  <summary>Records how users open audio download files on Android.</summary>
+  <summary>
+    Records how users open audio download files on Android. This histogram was
+    previously expired on 2023-07-09, and was re-enabled on 2024-02-12.
+  </summary>
 </histogram>
 
 <histogram name="Android.DownloadManager.OpenSource.Other"
-    enum="AndroidDownloadOpenSource" expires_after="2023-06-25">
+    enum="AndroidDownloadOpenSource" expires_after="2024-05-31">
   <owner>shaktisahu@chromium.org</owner>
   <owner>clank-downloads@google.com</owner>
   <summary>
     Records the entry point for users opening downloaded files on Android, that
-    are not audio nor video.
+    are not audio nor video. Records how users open audio download files on
+    Android. This histogram was previously expired on 2023-06-25, and was
+    re-enabled on 2024-02-12.
   </summary>
 </histogram>
 
 <histogram name="Android.DownloadManager.OpenSource.Video"
-    enum="AndroidDownloadOpenSource" expires_after="2023-06-25">
+    enum="AndroidDownloadOpenSource" expires_after="2024-05-31">
   <owner>shaktisahu@chromium.org</owner>
   <owner>clank-downloads@google.com</owner>
-  <summary>Records how users open video download files on Android.</summary>
+  <summary>
+    Records how users open video download files on Android. This histogram was
+    previously expired on 2023-06-25, and was re-enabled on 2024-02-12.
+  </summary>
 </histogram>
 
 <histogram name="Android.DownloadManager.ServiceStopped.DownloadForeground"
@@ -1089,11 +1097,13 @@
 </histogram>
 
 <histogram name="Android.DownloadPage.OpenSource"
-    enum="AndroidDownloadOpenSource" expires_after="2023-04-23">
+    enum="AndroidDownloadOpenSource" expires_after="2024-05-31">
   <owner>qinmin@chromium.org</owner>
   <owner>clank-downloads@google.com</owner>
   <summary>
-    Records the entry point for users opening the download page on Android.
+    Records the entry point for users opening the download page on Android. This
+    histogram was previously expired on 2023-04-23, and was re-enabled on
+    2024-02-12.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml
index c6dd95b..7efc9cd1 100644
--- a/tools/metrics/histograms/metadata/arc/histograms.xml
+++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -1536,6 +1536,26 @@
   <summary>Records the style of an Arc notification when it's created.</summary>
 </histogram>
 
+<histogram
+    name="Arc.NotificationView.{ChildView}.{FadeType}.AnimationSmoothness"
+    units="%" expires_after="2024-06-01">
+  <owner>shuminghao@google.com</owner>
+  <owner>arc-framework@google.com</owner>
+  <summary>
+    Relative smoothness of the fade animation of {ChildView} inside a
+    notification view. 100% represents ideally smooth 60 frames per second.
+    Emitted when the fade in/out animation is completed.
+  </summary>
+  <token key="ChildView">
+    <variant name="CollapsedSummaryView"/>
+    <variant name="ContentView"/>
+  </token>
+  <token key="FadeType">
+    <variant name="FadeIn"/>
+    <variant name="FadeOut"/>
+  </token>
+</histogram>
+
 <histogram name="Arc.OptInAction" enum="ArcOptInAction"
     expires_after="2024-06-30">
   <owner>mhasank@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/ash/enums.xml b/tools/metrics/histograms/metadata/ash/enums.xml
index 3cf3042..500fdc6 100644
--- a/tools/metrics/histograms/metadata/ash/enums.xml
+++ b/tools/metrics/histograms/metadata/ash/enums.xml
@@ -441,6 +441,12 @@
   <int value="2" label="Delete file"/>
 </enum>
 
+<enum name="ClassroomUserAction">
+  <int value="0" label="Assignment list selected"/>
+  <int value="1" label="Header icon pressed"/>
+  <int value="2" label="Student assignment pressed"/>
+</enum>
+
 <enum name="DataFetchEventSource">
   <int value="0" label="PostLoginFullRestore"/>
   <int value="1" label="Overview"/>
@@ -1819,6 +1825,7 @@
   <int value="2" label="Session exit due to window destruction"/>
   <int value="3" label="Session exit due to shutdown"/>
   <int value="4" label="Session exit due to unspecified reasons"/>
+  <int value="5" label="Session exit due to converting to tablet mode"/>
 </enum>
 
 <enum name="SplitViewSwapWindowsSource">
@@ -1832,6 +1839,18 @@
   <int value="2" label="Went back on the home screen"/>
 </enum>
 
+<enum name="TasksUserAction">
+  <int value="0" label="Active task list changed"/>
+  <int value="1" label="Task marked complete"/>
+  <int value="2" label="Task marked incomplete"/>
+  <int value="3" label="Add task started"/>
+  <int value="4" label="Modify task started"/>
+  <int value="5" label="Header button clicked"/>
+  <int value="6" label="Add new task button clicked"/>
+  <int value="7" label="Footer button clicked"/>
+  <int value="8" label="Edit in Google Tasks button clicked"/>
+</enum>
+
 <enum name="TimeManagementGlanceablesFeatureStatus">
   <int value="0" label="Disabled"/>
   <int value="1" label="Enabled - trusted tester"/>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 05f1689..fc593a09 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -3664,6 +3664,16 @@
   </summary>
 </histogram>
 
+<histogram name="Ash.Glanceables.TimeManagement.Classroom.UserAction"
+    enum="ClassroomUserAction" expires_after="2024-12-31">
+  <owner>tbarzic@google.com</owner>
+  <owner>chromeos-launcher@google.com</owner>
+  <summary>
+    User actions performed in the Time Management Glanceables bubble for Google
+    Classroom
+  </summary>
+</histogram>
+
 <histogram name="Ash.Glanceables.TimeManagement.FeatureStatus"
     enum="TimeManagementGlanceablesFeatureStatus" expires_after="2024-12-31">
   <owner>amitrokhin@google.com</owner>
@@ -3759,6 +3769,16 @@
   </summary>
 </histogram>
 
+<histogram name="Ash.Glanceables.TimeManagement.Tasks.UserAction"
+    enum="TasksUserAction" expires_after="2024-12-31">
+  <owner>tbarzic@google.com</owner>
+  <owner>chromeos-launcher@google.com</owner>
+  <summary>
+    User actions performed in the Time Management Glanceables bubble for Google
+    Tasks
+  </summary>
+</histogram>
+
 <histogram name="Ash.Glanceables.TimeManagement.TasksCountInDefaultTaskList"
     units="tasks" expires_after="2024-12-31">
   <owner>amitrokhin@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/help_app/histograms.xml b/tools/metrics/histograms/metadata/help_app/histograms.xml
index 95cac56..d9e1ac7 100644
--- a/tools/metrics/histograms/metadata/help_app/histograms.xml
+++ b/tools/metrics/histograms/metadata/help_app/histograms.xml
@@ -23,7 +23,7 @@
 <histograms>
 
 <histogram name="Discover.SearchConcept.ReadStatus"
-    enum="HelpAppSearchConceptReadStatus" expires_after="2024-03-24">
+    enum="HelpAppSearchConceptReadStatus" expires_after="2025-03-24">
   <owner>chenjih@google.com</owner>
   <owner>tby@chromium.org</owner>
   <owner>showoff-eng@google.com</owner>
@@ -37,7 +37,7 @@
 </histogram>
 
 <histogram name="Discover.SearchConcept.WriteStatus"
-    enum="HelpAppSearchConceptWriteStatus" expires_after="2024-03-24">
+    enum="HelpAppSearchConceptWriteStatus" expires_after="2025-03-24">
   <owner>chenjih@google.com</owner>
   <owner>tby@chromium.org</owner>
   <owner>showoff-eng@google.com</owner>
@@ -62,7 +62,7 @@
 </histogram>
 
 <histogram name="Discover.SearchHandler.SearchResultStatus"
-    enum="HelpAppSearchHandlerSearchResultStatus" expires_after="2024-03-24">
+    enum="HelpAppSearchHandlerSearchResultStatus" expires_after="2025-03-24">
   <owner>chenjih@google.com</owner>
   <owner>tby@chromium.org</owner>
   <owner>zufeng@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
index d7cbe888..9f25bbdc 100644
--- a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
@@ -4525,6 +4525,7 @@
   <suffix name="OsUrlHandler" label="OsUrlHandler"/>
   <suffix name="Personalization" label="Personalization"/>
   <suffix name="PrintManagement" label="PrintManagement"/>
+  <suffix name="PrintPreviewCrOS" label="Print Preview CrOS"/>
   <suffix name="Projector" label="Projector"/>
   <suffix name="Sample" label="Sample"/>
   <suffix name="Scanning" label="Scanning"/>
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml
index 1cd4c9ce..27d3fe432 100644
--- a/tools/metrics/histograms/metadata/ios/histograms.xml
+++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -3810,7 +3810,7 @@
 </histogram>
 
 <histogram name="IOS.Variations.FirstRun.SeedApplicationStage"
-    enum="VariationsFirstRunSeedApplicationStage" expires_after="2024-07-01">
+    enum="VariationsFirstRunSeedApplicationStage" expires_after="2024-09-26">
   <owner>ginnyhuang@chromium.org</owner>
   <owner>huitingyu@google.com</owner>
   <owner>bling-get-set-up@google.com</owner>
@@ -3826,7 +3826,7 @@
 </histogram>
 
 <histogram name="IOS.Variations.FirstRun.SeedFetchResult"
-    enum="VariationsSeedFetchResult" expires_after="2024-03-26">
+    enum="VariationsSeedFetchResult" expires_after="2024-09-26">
   <owner>ginnyhuang@chromium.org</owner>
   <owner>bling-get-set-up@google.com</owner>
   <summary>
@@ -3837,7 +3837,7 @@
 </histogram>
 
 <histogram name="IOS.Variations.FirstRun.SeedFetchTime" units="ms"
-    expires_after="2024-08-04">
+    expires_after="2024-09-26">
   <owner>ginnyhuang@chromium.org</owner>
   <owner>bling-get-set-up@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index bec4441..287d5f1 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -341,7 +341,7 @@
 </histogram>
 
 <histogram name="HttpCache.AddTransactionToEntry" units="ms"
-    expires_after="2024-03-24">
+    expires_after="2024-08-13">
   <owner>bashi@chromium.org</owner>
   <owner>net-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml b/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml
index d0063f9..727ee4a7 100644
--- a/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml
+++ b/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml
@@ -130,7 +130,7 @@
 </variants>
 
 <histogram name="SegmentationPlatform.AdaptiveToolbar.SegmentSelected.Startup"
-    enum="AdaptiveToolbarButtonVariant" expires_after="2024-06-30">
+    enum="AdaptiveToolbarButtonVariant" expires_after="2025-06-30">
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -141,7 +141,7 @@
 
 <histogram
     name="SegmentationPlatform.AdaptiveToolbar.SegmentSelection.Computed"
-    enum="AdaptiveToolbarButtonVariant" expires_after="2024-04-28">
+    enum="AdaptiveToolbarButtonVariant" expires_after="2025-04-28">
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -151,7 +151,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.AdaptiveToolbar.SegmentSwitched"
-    enum="AdaptiveToolbarSegmentSwitch" expires_after="2024-06-30">
+    enum="AdaptiveToolbarSegmentSwitch" expires_after="2025-06-30">
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -163,7 +163,7 @@
 
 <histogram
     name="SegmentationPlatform.ClassificationRequest.TotalDuration.{SegmentationKey}"
-    units="ms" expires_after="2024-06-30">
+    units="ms" expires_after="2025-06-30">
   <owner>ritikagup@google.com</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -180,7 +180,7 @@
 
 <histogram
     name="SegmentationPlatform.DefaultModelDelivery.Metadata.FeatureCount.{SegmentationModel}"
-    units="features" expires_after="2024-08-14">
+    units="features" expires_after="2025-08-14">
   <owner>ritikagup@google.com</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -195,7 +195,7 @@
 
 <histogram
     name="SegmentationPlatform.DefaultModelDelivery.Metadata.Validation.{ValidationPhase}.{SegmentationModel}"
-    enum="SegmentationPlatformValidationResult" expires_after="2024-08-14">
+    enum="SegmentationPlatformValidationResult" expires_after="2025-08-14">
   <owner>ritikagup@google.com</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -211,7 +211,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.DefaultModelDelivery.Received"
-    enum="SegmentationPlatformSegmentationModel" expires_after="2024-08-14">
+    enum="SegmentationPlatformSegmentationModel" expires_after="2025-08-14">
   <owner>ritikagup@google.com</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -225,7 +225,7 @@
 
 <histogram
     name="SegmentationPlatform.DefaultModelDelivery.SaveResult.{SegmentationModel}"
-    enum="BooleanSuccess" expires_after="2024-08-14">
+    enum="BooleanSuccess" expires_after="2025-08-14">
   <owner>ritikagup@google.com</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -240,7 +240,7 @@
 
 <histogram
     name="SegmentationPlatform.DefaultModelDelivery.SegmentIdMatches.{SegmentationModel}"
-    enum="BooleanYesNo" expires_after="2024-08-14">
+    enum="BooleanYesNo" expires_after="2025-08-14">
   <owner>ritikagup@google.com</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -254,7 +254,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.DeviceCountByOsType.{OsType}"
-    units="devices" expires_after="2024-10-01">
+    units="devices" expires_after="2025-10-01">
   <owner>junzou@chromium.org</owner>
   <owner>ssid@chromium.org</owner>
   <owner>chrome-segmentation-team@google.com</owner>
@@ -282,7 +282,7 @@
 <histogram
     name="SegmentationPlatform.FeatureProcessing.Error.{SegmentationModel}"
     enum="SegmentationPlatformFeatureProcessingError"
-    expires_after="2024-10-01">
+    expires_after="2025-10-01">
   <owner>haileywang@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -295,7 +295,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.Init.CreationToInitializationLatency"
-    units="ms" expires_after="2024-08-04">
+    units="ms" expires_after="2025-08-04">
   <owner>ssid@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -306,7 +306,7 @@
 
 <histogram
     name="SegmentationPlatform.Init.ModelUpdatedTimeDifferenceInDays.{SegmentID}"
-    units="days" expires_after="2024-10-01">
+    units="days" expires_after="2025-10-01">
   <owner>haileywang@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -320,7 +320,7 @@
 
 <histogram
     name="SegmentationPlatform.Init.ProcessCreationToServiceCreationLatency"
-    units="ms" expires_after="2024-06-30">
+    units="ms" expires_after="2025-06-30">
   <owner>ssid@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -330,7 +330,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.Maintenance.CleanupSignalSuccessCount"
-    units="signals" expires_after="2024-10-01">
+    units="signals" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -344,7 +344,7 @@
 
 <histogram
     name="SegmentationPlatform.Maintenance.CompactionResult.{SignalType}"
-    enum="BooleanSuccess" expires_after="2024-10-01">
+    enum="BooleanSuccess" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -358,7 +358,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.Maintenance.SignalIdentifierCount"
-    units="ids" expires_after="2024-10-01">
+    units="ids" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -371,7 +371,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.ModelAvailability.{SegmentationModel}"
-    enum="SegmentationModelAvailability" expires_after="2024-10-01">
+    enum="SegmentationModelAvailability" expires_after="2025-10-01">
   <owner>ssid@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -390,7 +390,7 @@
 
 <histogram
     name="SegmentationPlatform.ModelDelivery.DeleteResult.{SegmentationModel}"
-    enum="BooleanSuccess" expires_after="2024-10-01">
+    enum="BooleanSuccess" expires_after="2025-10-01">
   <owner>salg@chromium.org</owner>
   <owner>ssid@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -406,7 +406,7 @@
 
 <histogram
     name="SegmentationPlatform.ModelDelivery.HasMetadata.{SegmentationModel}"
-    enum="BooleanYesNo" expires_after="2024-10-01">
+    enum="BooleanYesNo" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -422,7 +422,7 @@
 
 <histogram
     name="SegmentationPlatform.ModelDelivery.Metadata.FeatureCount.{SegmentationModel}"
-    units="features" expires_after="2024-10-01">
+    units="features" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -438,7 +438,7 @@
 
 <histogram
     name="SegmentationPlatform.ModelDelivery.Metadata.Validation.{ValidationPhase}.{SegmentationModel}"
-    enum="SegmentationPlatformValidationResult" expires_after="2024-10-01">
+    enum="SegmentationPlatformValidationResult" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -455,7 +455,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.ModelDelivery.Received"
-    enum="SegmentationPlatformSegmentationModel" expires_after="2024-06-30">
+    enum="SegmentationPlatformSegmentationModel" expires_after="2025-06-30">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -470,7 +470,7 @@
 
 <histogram
     name="SegmentationPlatform.ModelDelivery.SaveResult.{SegmentationModel}"
-    enum="BooleanSuccess" expires_after="2024-10-01">
+    enum="BooleanSuccess" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -486,7 +486,7 @@
 
 <histogram
     name="SegmentationPlatform.ModelDelivery.SegmentIdMatches.{SegmentationModel}"
-    enum="BooleanYesNo" expires_after="2024-10-01">
+    enum="BooleanYesNo" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -502,7 +502,7 @@
 
 <histogram
     name="SegmentationPlatform.ModelExecution.DefaultProvider.Status.{SegmentationModel}"
-    enum="SegmentationPlatformModelExecutionStatus" expires_after="2024-10-01">
+    enum="SegmentationPlatformModelExecutionStatus" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -516,7 +516,7 @@
 
 <histogram
     name="SegmentationPlatform.ModelExecution.Duration.FeatureProcessing.{SegmentationModel}"
-    units="ms" expires_after="2024-10-01">
+    units="ms" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -531,7 +531,7 @@
 
 <histogram
     name="SegmentationPlatform.ModelExecution.Duration.Model.{SegmentationModel}.{ModelExecutionStatus}"
-    units="ms" expires_after="2024-10-01">
+    units="ms" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -548,7 +548,7 @@
 
 <histogram
     name="SegmentationPlatform.ModelExecution.Duration.Total.{SegmentationModel}.{ModelExecutionStatus}"
-    units="ms" expires_after="2024-10-01">
+    units="ms" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -567,7 +567,7 @@
 
 <histogram
     name="SegmentationPlatform.ModelExecution.Result.{Index}.{SegmentationModel}"
-    units="score" expires_after="2024-10-01">
+    units="score" expires_after="2025-10-01">
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -583,7 +583,7 @@
 
 <histogram
     name="SegmentationPlatform.ModelExecution.Result.{SegmentationModel}"
-    units="score" expires_after="2024-10-01">
+    units="score" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -599,7 +599,7 @@
 
 <histogram
     name="SegmentationPlatform.ModelExecution.SaveResult.{SegmentationModel}"
-    enum="BooleanSuccess" expires_after="2024-10-01">
+    enum="BooleanSuccess" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -615,7 +615,7 @@
 
 <histogram
     name="SegmentationPlatform.ModelExecution.Status.{SegmentationModel}"
-    enum="SegmentationPlatformModelExecutionStatus" expires_after="2024-10-01">
+    enum="SegmentationPlatformModelExecutionStatus" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -642,7 +642,7 @@
 
 <histogram
     name="SegmentationPlatform.ModelExecution.ZeroValuePercent.{SegmentationModel}"
-    units="%" expires_after="2024-10-01">
+    units="%" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -657,7 +657,7 @@
 
 <histogram
     name="SegmentationPlatform.SegmentInfoDatabase.ProtoDBUpdateResult.{SegmentationKey}"
-    enum="BooleanSuccess" expires_after="2024-10-01">
+    enum="BooleanSuccess" expires_after="2025-10-01">
   <owner>salg@google.com</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -674,7 +674,7 @@
 
 <histogram
     name="SegmentationPlatform.SegmentSelectionOnDemand.Duration.{SegmentationKey}.{SelectedSegment}"
-    units="ms" expires_after="2024-10-01">
+    units="ms" expires_after="2025-10-01">
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -696,7 +696,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.SelectionFailedReason"
-    enum="SegmentationSelectionFailureReason" expires_after="2024-02-05">
+    enum="SegmentationSelectionFailureReason" expires_after="2025-02-05">
   <owner>ssid@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -707,7 +707,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.SelectionFailedReason.{SegmentationKey}"
-    enum="SegmentationSelectionFailureReason" expires_after="2024-10-01">
+    enum="SegmentationSelectionFailureReason" expires_after="2025-10-01">
   <owner>ssid@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -725,7 +725,7 @@
 
 <histogram
     name="SegmentationPlatform.SignalDatabase.GetSamples.DatabaseEntryCount"
-    units="entries" expires_after="2024-10-01">
+    units="entries" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -740,7 +740,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.SignalDatabase.GetSamples.Result"
-    enum="BooleanSuccess" expires_after="2024-05-26">
+    enum="BooleanSuccess" expires_after="2025-05-26">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -753,7 +753,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.SignalDatabase.GetSamples.SampleCount"
-    units="samples" expires_after="2024-10-01">
+    units="samples" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -768,7 +768,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.Signals.ListeningCount.{SignalType}"
-    units="signals" expires_after="2024-10-01">
+    units="signals" expires_after="2025-10-01">
   <owner>nyquist@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -781,7 +781,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.StructuredMetrics.TooManyTensors.Count"
-    units="tensors" expires_after="2024-10-01">
+    units="tensors" expires_after="2025-10-01">
   <owner>qinmin@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -792,7 +792,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.SyncSessions.RecordTabCountAtSyncUpdate"
-    units="tabs" expires_after="2024-06-30">
+    units="tabs" expires_after="2025-06-30">
   <owner>ritikagup@google.com</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -802,7 +802,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.SyncSessions.TabsCountAtStartup"
-    units="tabs" expires_after="2024-06-30">
+    units="tabs" expires_after="2025-06-30">
   <owner>ritikagup@google.com</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -813,7 +813,7 @@
 
 <histogram
     name="SegmentationPlatform.SyncSessions.TimeFromStartupToFirstSyncUpdate"
-    units="ms" expires_after="2024-06-30">
+    units="ms" expires_after="2025-06-30">
   <owner>ritikagup@google.com</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -825,7 +825,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.SyncSessions.TimeFromStartupToSyncUpdate"
-    units="ms" expires_after="2024-10-01">
+    units="ms" expires_after="2025-10-01">
   <owner>ritikagup@google.com</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -839,7 +839,7 @@
 
 <histogram
     name="SegmentationPlatform.SyncSessions.TimeFromTabLoadedToSyncUpdate"
-    units="ms" expires_after="2024-06-30">
+    units="ms" expires_after="2025-06-30">
   <owner>ritikagup@google.com</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -853,7 +853,7 @@
 
 <histogram
     name="SegmentationPlatform.SyncSessions.{TimeInterval}TabCountAtFirstSyncUpdate"
-    units="tabs" expires_after="2024-06-30">
+    units="tabs" expires_after="2025-06-30">
   <owner>ritikagup@google.com</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -874,7 +874,7 @@
 <histogram
     name="SegmentationPlatform.TrainingDataCollectionEvents.{SegmentationModel}"
     enum="SegmentationPlatformTrainingDataCollectionEvent"
-    expires_after="2024-10-01">
+    expires_after="2025-10-01">
   <owner>ssid@chromium.org</owner>
   <owner>qinmin@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
@@ -887,7 +887,7 @@
 </histogram>
 
 <histogram name="SegmentationPlatform.{BooleanModel}.SegmentSwitched"
-    enum="SegmentationBooleanSegmentSwitch" expires_after="2024-10-01">
+    enum="SegmentationBooleanSegmentSwitch" expires_after="2025-10-01">
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -900,7 +900,7 @@
 
 <histogram
     name="SegmentationPlatform.{SegmentationKey}.PostProcessing.TopLabel.Computed"
-    units="index" expires_after="2024-10-01">
+    units="index" expires_after="2025-10-01">
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -914,7 +914,7 @@
 
 <histogram
     name="SegmentationPlatform.{SegmentationKey}.PostProcessing.TopLabel.Switched"
-    units="index" expires_after="2024-10-01">
+    units="index" expires_after="2025-10-01">
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
@@ -928,7 +928,7 @@
 
 <histogram
     name="SegmentationPlatform.{SegmentationKey}.SegmentSelection.Computed2"
-    enum="SegmentationPlatformSegmentationModel" expires_after="2024-10-01">
+    enum="SegmentationPlatformSegmentationModel" expires_after="2025-10-01">
   <owner>shaktisahu@chromium.org</owner>
   <owner>chrome-segmentation-platform@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/signin/enums.xml b/tools/metrics/histograms/metadata/signin/enums.xml
index ede12f47..b45edb3 100644
--- a/tools/metrics/histograms/metadata/signin/enums.xml
+++ b/tools/metrics/histograms/metadata/signin/enums.xml
@@ -714,6 +714,11 @@
     reverted to the initial state: signed out in the profile but keeping the
     account on the web only.
   </int>
+  <int value="31" label="IdleTimeout policy triggered signout">
+    The enterprise policy IdleTimeout was set, and the IdleTimeoutActions list
+    value included `sign_out`. When the user has been idle for the time
+    specified in the policy value, they will be signed out.
+  </int>
 </enum>
 
 <enum name="SigninSSOIdentityListRequestCacheState">
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 2946154..06a4b1b 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -6,7 +6,7 @@
         },
         "win": {
             "hash": "a783ab3fdb019ab9f920723578ff14b5c3247ccf",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/958abadb3d3c8914fe0a50ae84b5ad7551a60cf9/trace_processor_shell.exe"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/e01c38d714f4d55c7ef67aa9414c69479b051b38/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "46739eeb4b8f2a65a8a0aac57743767e6407f7bb",
diff --git a/tools/search_engine_choice/search_engine_icons_utils.py b/tools/search_engine_choice/search_engine_icons_utils.py
index a09a604..14070af 100644
--- a/tools/search_engine_choice/search_engine_icons_utils.py
+++ b/tools/search_engine_choice/search_engine_icons_utils.py
@@ -47,7 +47,6 @@
   Returns the set of used engines. by checking which engines are used in
   `template_url_prepopulate_data.cc`.
   """
-  print('Populating used engines set')
   used_engines = set()
   SE_NAME_REGEX = re.compile(r'.*SearchEngineTier::[A-Za-z]+, &(.+)},')
   with open(src_dir + config_file_path, 'r',
diff --git a/tools/typescript/definitions/chrome_test.d.ts b/tools/typescript/definitions/chrome_test.d.ts
index 80042b2..79f45323 100644
--- a/tools/typescript/definitions/chrome_test.d.ts
+++ b/tools/typescript/definitions/chrome_test.d.ts
@@ -10,6 +10,7 @@
     export function assertEq<T>(expected: T, actual: T, message?: string): void;
     export function assertFalse(value: boolean, message?: string): void;
     export function assertTrue(value: boolean, message?: string): asserts value;
+    export function checkDeepEq<T>(value: T, actual: T): boolean;
     export function fail(message?: string): never;
     export function runTests(tests: Array<() => void>): void;
     export function runWithUserGesture(callback: () => void): void;
diff --git a/ui/compositor/debug_utils.cc b/ui/compositor/debug_utils.cc
index 95cd13e..330f425 100644
--- a/ui/compositor/debug_utils.cc
+++ b/ui/compositor/debug_utils.cc
@@ -91,7 +91,8 @@
 
   if (!layer->rounded_corner_radii().IsEmpty()) {
     *out << "\n" << property_indent_str;
-    *out << "rounded-corners-radii" << layer->rounded_corner_radii().ToString();
+    *out << "rounded-corners-radii: "
+         << layer->rounded_corner_radii().ToString();
   }
 
   const ui::Layer* mask = const_cast<ui::Layer*>(layer)->layer_mask_layer();
diff --git a/ui/file_manager/base/js/convert_to_ts.py b/ui/file_manager/base/js/convert_to_ts.py
index a632612..ebcfaba 100644
--- a/ui/file_manager/base/js/convert_to_ts.py
+++ b/ui/file_manager/base/js/convert_to_ts.py
@@ -26,6 +26,10 @@
     r'@private|@protected|@override'
 )
 
+TESTCASE_NAMESPACE = re.compile(r"testcase\.(.*) =( async)? \(\) => {")
+IMPORT_TESTCASE = re.compile(r"import {testcase} from '../testcase.js';")
+ARROW_FUNCTION_CLOSE_BRACE = re.compile(r"^};$")
+
 # Matching:` this.bla;``  or `private this.bla;`
 NON_INITIALIZED_PROP = re.compile(r'\s+this\.[\w_]+;')
 # Matching: this.bla = 'anything';
@@ -77,6 +81,10 @@
                 lines.append(line)
                 continue
 
+            # Don't include testcase import.
+            if IMPORT_TESTCASE.match(line):
+                continue
+
             # Fully commented out line, ignores inline comment.
             if is_comment(line):
                 if ts_ignoring:
@@ -96,6 +104,7 @@
             lines.append(line)
 
     processing_comment = False
+    processing_arrow_function = False
     idx = -1
     while (idx < len(lines) - 1):
         idx += 1
@@ -123,6 +132,19 @@
         if not is_jsdoc_star(line):
             processing_comment = False
 
+        # Arrow functions end with a ';' after their closing brace, remove it.
+        if processing_arrow_function and ARROW_FUNCTION_CLOSE_BRACE.match(
+                line):
+            lines[idx] = "}\n"
+            processing_arrow_function = False
+            continue
+
+        # Rewrite testcase imports to exports instead.
+        match = TESTCASE_NAMESPACE.search(line)
+        if match:
+            processing_arrow_function = True
+            lines[idx] = f"export async function {match.group(1)}() " "{\n"
+
     # Remove the lines marked for deletion.
     remove_lines(lines)
     return lines
@@ -636,8 +658,8 @@
             # Process as `file_names.gni` to remove the .js file and add it to
             # the `ts_files = ` section.
             if b == _INTEGRATION_TESTS_ROOT.joinpath('BUILD.gn'):
-              new_build_file = process_file_names_gni(js_path.absolute(), b)
-              replace_file(b, new_build_file)
+                new_build_file = process_file_names_gni(js_path.absolute(), b)
+                replace_file(b, new_build_file)
 
         # Only process file_names.gni for Files app and Image Loader.
         if js_path_abs_str.startswith((str(_FILES_APP_ROOT),
diff --git a/ui/file_manager/base/js/tests/testing_convert_to_ts.js b/ui/file_manager/base/js/tests/testing_convert_to_ts.js
index e97fe3e..3a80544 100644
--- a/ui/file_manager/base/js/tests/testing_convert_to_ts.js
+++ b/ui/file_manager/base/js/tests/testing_convert_to_ts.js
@@ -11,6 +11,7 @@
 import {ColorChangeUpdater} from 'chrome://resources/cr_components/color_change_listener/colors_css_updater.js';
 
 import {FileManagerBaseInterface} from '../../../file_manager/externs/background/file_manager_base.js';
+import {testcase} from '../testcase.js';
 
 
 // @ts-ignore: error TS2314: Generic type 'Array<T>' requires 1 type
@@ -146,3 +147,12 @@
     return show;
   }
 }
+
+testcase.functionShouldBeRewrittenAndAsyncAdded = () => {
+  // The function name should be rewritten as well as the trailing semicolon
+  // along with a new async specifier added.
+};
+
+testcase.functionShouldBeRewritten = async () => {
+  // The function name should be rewritten as well as the trailing semicolon.
+};
diff --git a/ui/file_manager/base/js/tests/testing_convert_to_ts_expected.js.txt b/ui/file_manager/base/js/tests/testing_convert_to_ts_expected.js.txt
index e5d9fd6..a57b275 100644
--- a/ui/file_manager/base/js/tests/testing_convert_to_ts_expected.js.txt
+++ b/ui/file_manager/base/js/tests/testing_convert_to_ts_expected.js.txt
@@ -120,3 +120,12 @@
     return show;
   }
 }
+
+export async function functionShouldBeRewrittenAndAsyncAdded() {
+  // The function name should be rewritten as well as the trailing semicolon
+  // along with a new async specifier added.
+}
+
+export async function functionShouldBeRewritten() {
+  // The function name should be rewritten as well as the trailing semicolon.
+}
diff --git a/ui/file_manager/integration_tests/BUILD.gn b/ui/file_manager/integration_tests/BUILD.gn
index 4af927c..34be576 100644
--- a/ui/file_manager/integration_tests/BUILD.gn
+++ b/ui/file_manager/integration_tests/BUILD.gn
@@ -13,9 +13,7 @@
 
   # File Manager.
   "file_manager/background.js",
-  "file_manager/breadcrumbs.js",
   "file_manager/context_menu.js",
-  "file_manager/copy_between_windows.js",
   "file_manager/create_new_folder.js",
   "file_manager/crostini.js",
   "file_manager/directory_tree_context_menu.js",
@@ -23,33 +21,18 @@
   "file_manager/dlp_enterprise_connectors.js",
   "file_manager/drive_specific.js",
   "file_manager/file_dialog.js",
-  "file_manager/file_display.js",
-  "file_manager/file_list.js",
   "file_manager/file_transfer_connector.js",
   "file_manager/folder_shortcuts.js",
-  "file_manager/format_dialog.js",
   "file_manager/gear_menu.js",
-  "file_manager/grid_view.js",
-  "file_manager/guest_os.js",
   "file_manager/holding_space.js",
   "file_manager/install_linux_package_dialog.js",
-  "file_manager/keyboard_operations.js",
-  "file_manager/metadata.js",
   "file_manager/metrics.js",
-  "file_manager/my_files.js",
-  "file_manager/office.js",
-  "file_manager/providers.js",
-  "file_manager/quick_view.js",
   "file_manager/recents.js",
   "file_manager/restore_prefs.js",
   "file_manager/search.js",
-  "file_manager/sort_columns.js",
   "file_manager/tab_index.js",
-  "file_manager/tasks.js",
   "file_manager/test_data.js",
-  "file_manager/toolbar.js",
   "file_manager/transfer.js",
-  "file_manager/zip_files.js",
 
   # Page Objects.
   "file_manager/page_objects/directory_tree.js",
@@ -67,14 +50,13 @@
 
   # "file_manager/background.ts",
 
-  # "file_manager/breadcrumbs.ts",
-
+  "file_manager/breadcrumbs.ts",
   "file_manager/choose_entry.ts",
   "file_manager/choose_entry_const.ts",
 
   # "file_manager/context_menu.ts",
 
-  # "file_manager/copy_between_windows.ts",
+  "file_manager/copy_between_windows.ts",
 
   # "file_manager/create_new_folder.ts",
 
@@ -92,9 +74,9 @@
 
   # "file_manager/file_dialog.ts",
 
-  # "file_manager/file_display.ts",
+  "file_manager/file_display.ts",
 
-  # "file_manager/file_list.ts",
+  "file_manager/file_list.ts",
 
   # "file_manager/file_transfer_connector.ts",
 
@@ -102,42 +84,35 @@
 
   # "file_manager/folder_shortcuts.ts",
 
-  # "file_manager/format_dialog.ts",
+  "file_manager/format_dialog.ts",
 
   # "file_manager/gear_menu.ts",
 
-  # "file_manager/grid_view.ts",
+  "file_manager/grid_view.ts",
 
-  # "file_manager/guest_os.ts",
+  "file_manager/guest_os.ts",
 
   # "file_manager/holding_space.ts",
 
   # "file_manager/install_linux_package_dialog.ts",
 
-  # "file_manager/keyboard_operations.ts",
-
+  "file_manager/keyboard_operations.ts",
   "file_manager/manage_dialog.ts",
-
-  # "file_manager/metadata.ts",
+  "file_manager/metadata.ts",
 
   # "file_manager/metrics.ts",
 
-  # "file_manager/my_files.ts",
-
+  "file_manager/my_files.ts",
   "file_manager/navigation.ts",
-
-  # "file_manager/office.ts",
-
+  "file_manager/office.ts",
   "file_manager/open_audio_media_app.ts",
   "file_manager/open_files_in_web_drive.ts",
   "file_manager/open_image_media_app.ts",
   "file_manager/open_media_app.ts",
   "file_manager/open_sniffed_files.ts",
   "file_manager/open_video_media_app.ts",
-
-  # "file_manager/providers.ts",
-
-  # "file_manager/quick_view.ts",
+  "file_manager/providers.ts",
+  "file_manager/quick_view.ts",
 
   # "file_manager/recents.ts",
 
@@ -146,24 +121,22 @@
   # "file_manager/search.ts",
 
   "file_manager/share.ts",
-
-  # "file_manager/sort_columns.ts",
+  "file_manager/sort_columns.ts",
 
   # "file_manager/tab_index.ts",
 
-  # "file_manager/tasks.ts",
+  "file_manager/tasks.ts",
 
   # "file_manager/test_data.ts",
 
-  # "file_manager/toolbar.ts",
+  "file_manager/toolbar.ts",
 
   # "file_manager/transfer.ts",
 
   "file_manager/trash.ts",
-
   "file_manager/traverse.ts",
 
-  # "file_manager/zip_files.ts",
+  "file_manager/zip_files.ts",
 
   # Page Objects.
   # "file_manager/page_objects/directory_tree.ts",
diff --git a/ui/file_manager/integration_tests/file_manager/breadcrumbs.js b/ui/file_manager/integration_tests/file_manager/breadcrumbs.ts
similarity index 82%
rename from ui/file_manager/integration_tests/file_manager/breadcrumbs.js
rename to ui/file_manager/integration_tests/file_manager/breadcrumbs.ts
index 9b604a9..c3581a0 100644
--- a/ui/file_manager/integration_tests/file_manager/breadcrumbs.js
+++ b/ui/file_manager/integration_tests/file_manager/breadcrumbs.ts
@@ -5,8 +5,8 @@
  * @fileoverview Tests that breadcrumbs work.
  */
 
+import type {ElementObject} from '../prod/file_manager/shared_types.js';
 import {createNestedTestFolders, ENTRIES, getCaller, getUserActionCount, pending, repeatUntil, RootPath, sendTestMessage, TestEntryInfo} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {remoteCall, setupAndWaitUntilReady} from './background.js';
 import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
@@ -15,12 +15,8 @@
   return 'xf-breadcrumb';
 }
 
-// @ts-ignore: error TS4111: Property 'breadcrumbsNavigate' comes from an index
-// signature, so it must be accessed with ['breadcrumbsNavigate'].
-testcase.breadcrumbsNavigate = async () => {
+export async function breadcrumbsNavigate() {
   const files = [ENTRIES.hello, ENTRIES.photos];
-  // @ts-ignore: error TS2345: Argument of type '(TestEntryInfo | undefined)[]'
-  // is not assignable to parameter of type 'TestEntryInfo[]'.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, files, []);
 
   const breadcrumbsTag = await getBreadcrumbTagName();
@@ -34,22 +30,17 @@
       appId, [breadcrumbsTag, 'button:nth-last-of-type(2)']);
 
   // Wait for the contents of Downloads to load again.
-  // @ts-ignore: error TS2345: Argument of type '(TestEntryInfo | undefined)[]'
-  // is not assignable to parameter of type 'TestEntryInfo[]'.
   await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows(files));
 
   // A user action should have been recorded for the breadcrumbs.
   chrome.test.assertEq(
       1, await getUserActionCount('FileBrowser.ClickBreadcrumbs'));
-};
+}
 
 /**
  * Tests that Downloads is translated in the breadcrumbs.
  */
-// @ts-ignore: error TS4111: Property 'breadcrumbsDownloadsTranslation' comes
-// from an index signature, so it must be accessed with
-// ['breadcrumbsDownloadsTranslation'].
-testcase.breadcrumbsDownloadsTranslation = async () => {
+export async function breadcrumbsDownloadsTranslation() {
   // Switch UI to Portuguese (Portugal).
   await sendTestMessage({name: 'switchLanguage', language: 'pt-PT'});
 
@@ -74,14 +65,12 @@
   // Wait and check breadcrumb translation.
   await remoteCall.waitUntilCurrentDirectoryIsChanged(
       appId, '/Os meus ficheiros/Transferências/photos');
-};
+}
 
 /**
  * Tests that the breadcrumbs correctly render a short (3 component) path.
  */
-// @ts-ignore: error TS4111: Property 'breadcrumbsRenderShortPath' comes from an
-// index signature, so it must be accessed with ['breadcrumbsRenderShortPath'].
-testcase.breadcrumbsRenderShortPath = async () => {
+export async function breadcrumbsRenderShortPath() {
   // Build an array of nested folder test entries.
   const nestedFolderTestEntries = createNestedTestFolders(1);
 
@@ -100,9 +89,7 @@
   const breadcrumbElement =
       await remoteCall.waitForElement(appId, [breadcrumbsTag]);
   const path = breadcrumb.slice(1);  // remove leading "/" char
-  // @ts-ignore: error TS4111: Property 'path' comes from an index signature, so
-  // it must be accessed with ['path'].
-  chrome.test.assertEq(path, breadcrumbElement.attributes.path);
+  chrome.test.assertEq(path, breadcrumbElement.attributes['path']);
 
   // Check: some of the main breadcrumb buttons should be visible.
   const buttons = [breadcrumbsTag, 'button'];
@@ -123,16 +110,13 @@
   // Check: the breadcrumb elider button should not exist.
   const eliderButton = [breadcrumbsTag, '[elider]'];
   await remoteCall.waitForElementLost(appId, eliderButton);
-};
+}
 
 /**
  * Tests that short breadcrumbs paths (of 4 or fewer components) should not
  * be rendered elided. The elider button not exist.
  */
-// @ts-ignore: error TS4111: Property 'breadcrumbsEliderButtonNotExist' comes
-// from an index signature, so it must be accessed with
-// ['breadcrumbsEliderButtonNotExist'].
-testcase.breadcrumbsEliderButtonNotExist = async () => {
+export async function breadcrumbsEliderButtonNotExist() {
   // Build an array of nested folder test entries.
   const nestedFolderTestEntries = createNestedTestFolders(2);
 
@@ -151,9 +135,7 @@
   const breadcrumbElement =
       await remoteCall.waitForElement(appId, [breadcrumbsTag]);
   const path = breadcrumb.slice(1);  // remove leading "/" char
-  // @ts-ignore: error TS4111: Property 'path' comes from an index signature, so
-  // it must be accessed with ['path'].
-  chrome.test.assertEq(path, breadcrumbElement.attributes.path);
+  chrome.test.assertEq(path, breadcrumbElement.attributes['path']);
 
   // Check: all of the main breadcrumb buttons should be visible.
   const buttons = [breadcrumbsTag, 'button'];
@@ -176,14 +158,12 @@
   // Check: the breadcrumb elider button should not exist.
   const eliderButton = [breadcrumbsTag, '[elider]'];
   await remoteCall.waitForElementLost(appId, eliderButton);
-};
+}
 
 /**
  * Tests that the breadcrumbs correctly render a long (5 component) path.
  */
-// @ts-ignore: error TS4111: Property 'breadcrumbsRenderLongPath' comes from an
-// index signature, so it must be accessed with ['breadcrumbsRenderLongPath'].
-testcase.breadcrumbsRenderLongPath = async () => {
+export async function breadcrumbsRenderLongPath() {
   // Build an array of nested folder test entries.
   const nestedFolderTestEntries = createNestedTestFolders(3);
 
@@ -202,9 +182,7 @@
   const breadcrumbElement =
       await remoteCall.waitForElement(appId, [breadcrumbsTag]);
   const path = breadcrumb.slice(1);  // remove leading "/" char
-  // @ts-ignore: error TS4111: Property 'path' comes from an index signature, so
-  // it must be accessed with ['path'].
-  chrome.test.assertEq(path, breadcrumbElement.attributes.path);
+  chrome.test.assertEq(path, breadcrumbElement.attributes['path']);
 
   // Check: some of the main breadcrumb buttons should be visible.
   const buttons = [breadcrumbsTag, 'button[id]'];
@@ -225,15 +203,13 @@
   // Check: the breadcrumb elider button should be shown.
   const eliderButton = [breadcrumbsTag, '[elider]'];
   await remoteCall.waitForElement(appId, eliderButton);
-};
+}
 
 /**
  * Tests that clicking a main breadcumb button makes the app navigate and
  * update the breadcrumb to that item.
  */
-// @ts-ignore: error TS4111: Property 'breadcrumbsMainButtonClick' comes from an
-// index signature, so it must be accessed with ['breadcrumbsMainButtonClick'].
-testcase.breadcrumbsMainButtonClick = async () => {
+export async function breadcrumbsMainButtonClick() {
   // Build an array of nested folder test entries.
   const nestedFolderTestEntries = createNestedTestFolders(2);
 
@@ -252,9 +228,7 @@
   const breadcrumbElement =
       await remoteCall.waitForElement(appId, [breadcrumbsTag]);
   const path = breadcrumb.slice(1);  // remove leading "/" char
-  // @ts-ignore: error TS4111: Property 'path' comes from an index signature, so
-  // it must be accessed with ['path'].
-  chrome.test.assertEq(path, breadcrumbElement.attributes.path);
+  chrome.test.assertEq(path, breadcrumbElement.attributes['path']);
 
   // Click the "second" main breadcrumb button (2nd path component).
   await remoteCall.waitAndClickElement(
@@ -263,16 +237,13 @@
   // Check: the breadcrumb path should be updated due to navigation.
   await remoteCall.waitForElement(
       appId, [`${breadcrumbsTag}[path="My files/Downloads"]`]);
-};
+}
 
 /**
  * Tests that an Enter key on a main breadcumb button item makes the app
  * navigate and update the breadcrumb to that item.
  */
-// @ts-ignore: error TS4111: Property 'breadcrumbsMainButtonEnterKey' comes from
-// an index signature, so it must be accessed with
-// ['breadcrumbsMainButtonEnterKey'].
-testcase.breadcrumbsMainButtonEnterKey = async () => {
+export async function breadcrumbsMainButtonEnterKey() {
   // Build an array of nested folder test entries.
   const nestedFolderTestEntries = createNestedTestFolders(2);
 
@@ -291,9 +262,7 @@
   const breadcrumbElement =
       await remoteCall.waitForElement(appId, [breadcrumbsTag]);
   const path = breadcrumb.slice(1);  // remove leading "/" char
-  // @ts-ignore: error TS4111: Property 'path' comes from an index signature, so
-  // it must be accessed with ['path'].
-  chrome.test.assertEq(path, breadcrumbElement.attributes.path);
+  chrome.test.assertEq(path, breadcrumbElement.attributes['path']);
 
   // Send an Enter key to the "second" main breadcrumb button.
   const secondButton = [breadcrumbsTag, 'button[id="second"]'];
@@ -304,16 +273,13 @@
   // Check: the breadcrumb path should be updated due to navigation.
   await remoteCall.waitForElement(
       appId, [`${breadcrumbsTag}[path="My files/Downloads"]`]);
-};
+}
 
 /**
  * Tests that a breadcrumbs elider button click opens its drop down menu and
  * that clicking the button again, closes the drop down menu.
  */
-// @ts-ignore: error TS4111: Property 'breadcrumbsEliderButtonClick' comes from
-// an index signature, so it must be accessed with
-// ['breadcrumbsEliderButtonClick'].
-testcase.breadcrumbsEliderButtonClick = async () => {
+export async function breadcrumbsEliderButtonClick() {
   // Build an array of nested folder test entries.
   const nestedFolderTestEntries = createNestedTestFolders(3);
 
@@ -358,16 +324,13 @@
 
   // Check: the focus should return to the elider button.
   await remoteCall.waitForElement(appId, eliderFocus);
-};
+}
 
 /**
  * Tests that pressing Enter key on the breadcrumbs elider button opens its
  * drop down menu, then pressing Escape key closes the drop down menu.
  */
-// @ts-ignore: error TS4111: Property 'breadcrumbsEliderButtonKeyboard' comes
-// from an index signature, so it must be accessed with
-// ['breadcrumbsEliderButtonKeyboard'].
-testcase.breadcrumbsEliderButtonKeyboard = async () => {
+export async function breadcrumbsEliderButtonKeyboard() {
   // Build an array of nested folder test entries.
   const nestedFolderTestEntries = createNestedTestFolders(4);
 
@@ -420,16 +383,13 @@
 
   // Check: the focus should return to the elider button.
   await remoteCall.waitForElement(appId, eliderFocus);
-};
+}
 
 /**
  * Tests that clicking outside the elider button drop down menu makes that
  * drop down menu close.
  */
-// @ts-ignore: error TS4111: Property 'breadcrumbsEliderMenuClickOutside' comes
-// from an index signature, so it must be accessed with
-// ['breadcrumbsEliderMenuClickOutside'].
-testcase.breadcrumbsEliderMenuClickOutside = async () => {
+export async function breadcrumbsEliderMenuClickOutside() {
   // Build an array of nested folder test entries.
   const nestedFolderTestEntries = createNestedTestFolders(5);
 
@@ -457,16 +417,13 @@
 
   // Check: the elider button drop-down menu should close.
   await remoteCall.waitForElementLost(appId, menu);
-};
+}
 
 /**
  * Tests that clicking an elider button drop down menu item makes the app
  * navigate and update the breadcrumb to that item.
  */
-// @ts-ignore: error TS4111: Property 'breadcrumbsEliderMenuItemClick' comes
-// from an index signature, so it must be accessed with
-// ['breadcrumbsEliderMenuItemClick'].
-testcase.breadcrumbsEliderMenuItemClick = async () => {
+export async function breadcrumbsEliderMenuItemClick() {
   // Build an array of nested folder test entries.
   const nestedFolderTestEntries = createNestedTestFolders(3);
 
@@ -485,9 +442,7 @@
   const breadcrumbElement =
       await remoteCall.waitForElement(appId, [breadcrumbsTag]);
   const path = breadcrumb.slice(1);  // remove leading "/" char
-  // @ts-ignore: error TS4111: Property 'path' comes from an index signature, so
-  // it must be accessed with ['path'].
-  chrome.test.assertEq(path, breadcrumbElement.attributes.path);
+  chrome.test.assertEq(path, breadcrumbElement.attributes['path']);
 
   // Click the breadcrumb elider button when it appears.
   const eliderButton = [breadcrumbsTag, '[elider]'];
@@ -517,16 +472,13 @@
   // Check: the breadcrumb path should be updated due to navigation.
   await remoteCall.waitForElement(
       appId, [`${breadcrumbsTag}[path="My files/Downloads"]`]);
-};
+}
 
 /**
  * Tests that a <shift>-Tab key on the elider button drop down menu closes
  * the menu and focuses button#first to the left of the elider button.
  */
-// @ts-ignore: error TS4111: Property 'breadcrumbsEliderMenuItemTabLeft' comes
-// from an index signature, so it must be accessed with
-// ['breadcrumbsEliderMenuItemTabLeft'].
-testcase.breadcrumbsEliderMenuItemTabLeft = async () => {
+export async function breadcrumbsEliderMenuItemTabLeft() {
   // Build an array of nested folder test entries.
   const nestedFolderTestEntries = createNestedTestFolders(3);
 
@@ -561,16 +513,13 @@
   // Check: the "first" main button should be focused.
   await remoteCall.waitForElement(
       appId, [breadcrumbsTag, 'button[id="first"]:focus']);
-};
+}
 
 /**
  * Tests that a Tab key on the elider button drop down menu closes the menu
  * and focuses button#second to the right of the elider button.
  */
-// @ts-ignore: error TS4111: Property 'breadcrumbsEliderMenuItemTabRight' comes
-// from an index signature, so it must be accessed with
-// ['breadcrumbsEliderMenuItemTabRight'].
-testcase.breadcrumbsEliderMenuItemTabRight = async () => {
+export async function breadcrumbsEliderMenuItemTabRight() {
   // Build an array of nested folder test entries.
   const nestedFolderTestEntries = createNestedTestFolders(3);
 
@@ -604,17 +553,14 @@
   // Check: the "second" main button should be focused.
   await remoteCall.waitForElement(
       appId, [breadcrumbsTag, 'button[id="second"]:focus']);
-};
+}
 
 /**
  * Tests that when the breadcrumbs + action bar buttons exceed the available
  * viewport width, their width is updated to fit the space instead of exceeding
  * the viewport and having some of the action bar buttons not visible.
  */
-// @ts-ignore: error TS4111: Property 'breadcrumbsDontExceedAvailableViewport'
-// comes from an index signature, so it must be accessed with
-// ['breadcrumbsDontExceedAvailableViewport'].
-testcase.breadcrumbsDontExceedAvailableViewport = async () => {
+export async function breadcrumbsDontExceedAvailableViewport() {
   const nestedFolderTestEntries = createNestedTestFolders(2);
 
   // Open FilesApp on Downloads containing the test entries.
@@ -628,14 +574,12 @@
   // Get the spacer with between breadcrumbs and action bar buttons, this
   // indicates the available space we can resize the window before the text in
   // the breadcrumb is clamped.
-  const spacerWidth = await remoteCall.waitForElementStyles(
+  const spacerWidth: ElementObject = await remoteCall.waitForElementStyles(
       appId, 'div.dialog-header > .spacer', ['width']);
 
   // Shrink the window by the available spacer width -10px to ensure the
   // breadcrumbs don't start clamping text.
-  // @ts-ignore: error TS18048: 'spacerWidth.renderedWidth' is possibly
-  // 'undefined'.
-  const newWindowWidth = outerWidth - spacerWidth.renderedWidth + 10;
+  const newWindowWidth = outerWidth - spacerWidth.renderedWidth! + 10;
   chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
       'resizeWindow', appId, [newWindowWidth, outerHeight]));
 
@@ -657,19 +601,19 @@
   // to the directory the below calculation occurs prior to the relayout
   // happening, repeat until the values agree with each other.
   const caller = getCaller();
-  // @ts-ignore: error TS7030: Not all code paths return a value.
   await repeatUntil(async () => {
     const actualDialogHeaderWidth = await remoteCall.waitForElementStyles(
         appId, 'div.dialog-header', ['width']);
-    if (expectedDialogHeaderWidth.renderedWidth !==
+    if (expectedDialogHeaderWidth.renderedWidth ===
         actualDialogHeaderWidth.renderedWidth) {
-      return pending(
-          caller, 'Expected dialog header width is %s, got %s',
-          expectedDialogHeaderWidth.renderedWidth,
-          actualDialogHeaderWidth.renderedWidth);
+      return;
     }
+    return pending(
+        caller, 'Expected dialog header width is %s, got %s',
+        expectedDialogHeaderWidth.renderedWidth,
+        actualDialogHeaderWidth.renderedWidth);
   });
-};
+}
 
 /**
  * Test that navigating back from a sub-folder in Google Drive> Shared With Me
@@ -677,10 +621,7 @@
  * Internally Shared With Me uses some of the Drive Search code, this confused
  * the DirectoryModel clearing the search state in the Store.
  */
-// @ts-ignore: error TS4111: Property 'breadcrumbNavigateBackToSharedWithMe'
-// comes from an index signature, so it must be accessed with
-// ['breadcrumbNavigateBackToSharedWithMe'].
-testcase.breadcrumbNavigateBackToSharedWithMe = async () => {
+export async function breadcrumbNavigateBackToSharedWithMe() {
   // Open Files app on Drive containing "Shared with me" file entries.
   const sharedSubFolderName = ENTRIES.sharedWithMeDirectory.nameText;
   const appId = await setupAndWaitUntilReady(
@@ -707,4 +648,4 @@
 
   // Wait until the breadcrumb path is updated.
   await remoteCall.waitUntilCurrentDirectoryIsChanged(appId, '/Shared with me');
-};
+}
diff --git a/ui/file_manager/integration_tests/file_manager/copy_between_windows.js b/ui/file_manager/integration_tests/file_manager/copy_between_windows.ts
similarity index 75%
rename from ui/file_manager/integration_tests/file_manager/copy_between_windows.js
rename to ui/file_manager/integration_tests/file_manager/copy_between_windows.ts
index f078876..614ca45a 100644
--- a/ui/file_manager/integration_tests/file_manager/copy_between_windows.js
+++ b/ui/file_manager/integration_tests/file_manager/copy_between_windows.ts
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import {addEntries, ENTRIES, RootPath, sendTestMessage, TestEntryInfo} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {openNewWindow, remoteCall} from './background.js';
 import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
@@ -15,12 +14,12 @@
 
 /**
  * Opens two window of given root paths.
- * @param {string} rootPath1 Root path of the first window.
- * @param {string} rootPath2 Root path of the second window.
- * @return {Promise<[string, string]>} Promise fulfilled with an array
- *     containing two window IDs.
+ * @param rootPath1 Root path of the first window.
+ * @param rootPath2 Root path of the second window.
+ * @return Promise fulfilled with an array containing two window IDs.
  */
-async function openTwoWindows(rootPath1, rootPath2) {
+async function openTwoWindows(
+    rootPath1: string, rootPath2: string): Promise<[string, string]> {
   const windowIds =
       await Promise.all([openNewWindow(rootPath1), openNewWindow(rootPath2)]);
 
@@ -33,15 +32,15 @@
 
 /**
  * Copies a file between two windows.
- * @param {string} window1 ID of the source window.
- * @param {string} window2 ID of the destination window.
- * @param {TestEntryInfo} file Test entry info to be copied.
- * @param {?TestEntryInfo} alreadyPresentFile Test entry info for file that
- *     should already exist.
- * @return {Promise<void>} Promise fulfilled on success.
+ * @param window1 ID of the source window.
+ * @param window2 ID of the destination window.
+ * @param file Test entry info to be copied.
+ * @param alreadyPresentFile Test entry info for file that should already exist.
+ * @return Promise fulfilled on success.
  */
 async function copyBetweenWindows(
-    window1, window2, file, alreadyPresentFile = null) {
+    window1: string, window2: string, file: TestEntryInfo,
+    alreadyPresentFile: null|TestEntryInfo = null): Promise<void> {
   if (!file || !file.nameText) {
     chrome.test.assertTrue(false, 'copyBetweenWindows invalid file name');
   }
@@ -61,20 +60,13 @@
   if (alreadyPresentFile) {
     expectedFiles.push(alreadyPresentFile.getExpectedRow());
   }
-  // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime:
-  // boolean; }' is not assignable to parameter of type '{ orderCheck: boolean |
-  // null | undefined; ignoreFileSize: boolean | null | undefined;
-  // ignoreLastModifiedTime: boolean | null | undefined; }'.
   await remoteCall.waitForFiles(window2, expectedFiles, flag);
 }
 
 /**
  * Tests file copy+paste from Drive to Downloads.
  */
-// @ts-ignore: error TS4111: Property 'copyBetweenWindowsDriveToLocal' comes
-// from an index signature, so it must be accessed with
-// ['copyBetweenWindowsDriveToLocal'].
-testcase.copyBetweenWindowsDriveToLocal = async () => {
+export async function copyBetweenWindowsDriveToLocal() {
   // Open two Files app windows.
   const [window1, window2] =
       await openTwoWindows(RootPath.DOWNLOADS, RootPath.DRIVE);
@@ -90,15 +82,12 @@
 
   // Copy Drive hello file to Downloads.
   await copyBetweenWindows(window2, window1, ENTRIES.hello, ENTRIES.photos);
-};
+}
 
 /**
  * Tests file copy+paste from Downloads to Drive.
  */
-// @ts-ignore: error TS4111: Property 'copyBetweenWindowsLocalToDrive' comes
-// from an index signature, so it must be accessed with
-// ['copyBetweenWindowsLocalToDrive'].
-testcase.copyBetweenWindowsLocalToDrive = async () => {
+export async function copyBetweenWindowsLocalToDrive() {
   // Open two Files app windows.
   const [window1, window2] =
       await openTwoWindows(RootPath.DOWNLOADS, RootPath.DRIVE);
@@ -114,15 +103,12 @@
 
   // Copy Downloads hello file to Drive.
   await copyBetweenWindows(window1, window2, ENTRIES.hello, ENTRIES.photos);
-};
+}
 
 /**
  * Tests file copy+paste from Drive to USB.
  */
-// @ts-ignore: error TS4111: Property 'copyBetweenWindowsDriveToUsb' comes from
-// an index signature, so it must be accessed with
-// ['copyBetweenWindowsDriveToUsb'].
-testcase.copyBetweenWindowsDriveToUsb = async () => {
+export async function copyBetweenWindowsDriveToUsb() {
   // Add photos to Downloads.
   await addEntries(['local'], [ENTRIES.photos]);
 
@@ -155,15 +141,12 @@
 
   // Check Drive hello file, copy it to USB.
   await copyBetweenWindows(window2, window1, ENTRIES.hello);
-};
+}
 
 /**
  * Tests file copy+paste from Downloads to USB.
  */
-// @ts-ignore: error TS4111: Property 'copyBetweenWindowsLocalToUsb' comes from
-// an index signature, so it must be accessed with
-// ['copyBetweenWindowsLocalToUsb'].
-testcase.copyBetweenWindowsLocalToUsb = async () => {
+export async function copyBetweenWindowsLocalToUsb() {
   // Add photos to Drive.
   await addEntries(['drive'], [ENTRIES.photos]);
 
@@ -196,15 +179,12 @@
 
   // Check Downloads hello file, copy it to USB.
   await copyBetweenWindows(window2, window1, ENTRIES.hello);
-};
+}
 
 /**
  * Tests file copy+paste from USB to Drive.
  */
-// @ts-ignore: error TS4111: Property 'copyBetweenWindowsUsbToDrive' comes from
-// an index signature, so it must be accessed with
-// ['copyBetweenWindowsUsbToDrive'].
-testcase.copyBetweenWindowsUsbToDrive = async () => {
+export async function copyBetweenWindowsUsbToDrive() {
   // Add photos to Downloads.
   await addEntries(['local'], [ENTRIES.photos]);
 
@@ -236,15 +216,12 @@
 
   // Check USB hello file, copy it to Drive.
   await copyBetweenWindows(window1, window2, ENTRIES.hello);
-};
+}
 
 /**
  * Tests file copy+paste from USB to Downloads.
  */
-// @ts-ignore: error TS4111: Property 'copyBetweenWindowsUsbToLocal' comes from
-// an index signature, so it must be accessed with
-// ['copyBetweenWindowsUsbToLocal'].
-testcase.copyBetweenWindowsUsbToLocal = async () => {
+export async function copyBetweenWindowsUsbToLocal() {
   // Add photos to Drive.
   await addEntries(['drive'], [ENTRIES.photos]);
 
@@ -277,4 +254,4 @@
 
   // Check USB hello file, copy it to Downloads.
   await copyBetweenWindows(window1, window2, ENTRIES.hello);
-};
+}
diff --git a/ui/file_manager/integration_tests/file_manager/file_display.js b/ui/file_manager/integration_tests/file_manager/file_display.ts
similarity index 69%
rename from ui/file_manager/integration_tests/file_manager/file_display.js
rename to ui/file_manager/integration_tests/file_manager/file_display.ts
index b9f9ad33..ed1a6807 100644
--- a/ui/file_manager/integration_tests/file_manager/file_display.js
+++ b/ui/file_manager/integration_tests/file_manager/file_display.ts
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {type ElementObject} from '../prod/file_manager/shared_types.js';
 import {addEntries, ENTRIES, RootPath, sendTestMessage, TestEntryInfo} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {IGNORE_APP_ERRORS, isSinglePartitionFormat, openNewWindow, remoteCall, setupAndWaitUntilReady} from './background.js';
 import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
@@ -13,10 +13,10 @@
  * Checks if the files initially added by the C++ side are displayed, and
  * that files subsequently added are also displayed.
  *
- * @param {string} path Path to be tested, Downloads or Drive.
- * @param {!Array<!TestEntryInfo>} defaultEntries Default file entries.
+ * @param path Path to be tested, Downloads or Drive.
+ * @param defaultEntries Default file entries.
  */
-async function fileDisplay(path, defaultEntries) {
+async function fileDisplay(path: string, defaultEntries: TestEntryInfo[]) {
   // Open Files app on the given |path| with default file entries.
   const appId = await setupAndWaitUntilReady(path);
 
@@ -36,21 +36,16 @@
 /**
  * Tests files display in Downloads.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayDownloads' comes from an index
-// signature, so it must be accessed with ['fileDisplayDownloads'].
-testcase.fileDisplayDownloads = () => {
+export async function fileDisplayDownloads() {
   return fileDisplay(RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET);
-};
+}
 
 /**
  * Tests opening the files app navigating to a local folder. Uses
  * platform_util::OpenItem, a call to an API distinct from the one commonly used
  * in other tests for the same operation.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayLaunchOnLocalFolder' comes
-// from an index signature, so it must be accessed with
-// ['fileDisplayLaunchOnLocalFolder'].
-testcase.fileDisplayLaunchOnLocalFolder = async () => {
+export async function fileDisplayLaunchOnLocalFolder() {
   // Add a file to Downloads.
   await addEntries(['local'], [ENTRIES.photos]);
 
@@ -68,17 +63,14 @@
   // The API used to launch the Files app does not set the IN_TEST flag to true:
   // error when attempting to retrieve Web Store access token.
   return IGNORE_APP_ERRORS;
-};
+}
 
 /**
  * Tests opening the files app navigating to a local folder. Uses
  * platform_util::OpenItem, a call to an API distinct from the one commonly used
  * in other tests for the same operation.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayLaunchOnLocalFile' comes from
-// an index signature, so it must be accessed with
-// ['fileDisplayLaunchOnLocalFile'].
-testcase.fileDisplayLaunchOnLocalFile = async () => {
+export async function fileDisplayLaunchOnLocalFile() {
   // Add a file to Downloads.
   await addEntries(['local'], [ENTRIES.hello, ENTRIES.world]);
 
@@ -100,16 +92,14 @@
   // The API used to launch the Files app does not set the IN_TEST flag to true:
   // error when attempting to retrieve Web Store access token.
   return IGNORE_APP_ERRORS;
-};
+}
 
 /**
  * Tests opening the files app navigating to the My Drive folder. Uses
  * platform_util::OpenItem, a call to an API distinct from the one commonly used
  * in other tests for the same operation.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayLaunchOnDrive' comes from an
-// index signature, so it must be accessed with ['fileDisplayLaunchOnDrive'].
-testcase.fileDisplayLaunchOnDrive = async () => {
+export async function fileDisplayLaunchOnDrive() {
   // Open Files app on the Drive directory.
   await sendTestMessage({name: 'launchAppOnDrive'});
 
@@ -123,23 +113,19 @@
   // The API used to launch the Files app does not set the IN_TEST flag to true:
   // error when attempting to retrieve Web Store access token.
   return IGNORE_APP_ERRORS;
-};
+}
 
 /**
  * Tests files display in Google Drive.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayDrive' comes from an index
-// signature, so it must be accessed with ['fileDisplayDrive'].
-testcase.fileDisplayDrive = () => {
+export async function fileDisplayDrive() {
   return fileDisplay(RootPath.DRIVE, BASIC_DRIVE_ENTRY_SET);
-};
+}
 
 /**
  * Tests file display rendering in offline Google Drive.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayDriveOffline' comes from an
-// index signature, so it must be accessed with ['fileDisplayDriveOffline'].
-testcase.fileDisplayDriveOffline = async () => {
+export async function fileDisplayDriveOffline() {
   const driveFiles =
       [ENTRIES.hello, ENTRIES.pinned, ENTRIES.photos, ENTRIES.testDocument];
 
@@ -156,10 +142,10 @@
 
   // Check: the hello.txt file only should be rendered 'offline'.
   chrome.test.assertEq(1, elements.length);
-  chrome.test.assertEq(0, elements[0].text.indexOf('hello.txt'));
+  chrome.test.assertEq(0, elements[0]!.text.indexOf('hello.txt'));
 
   // Check: hello.txt must have 'offline' CSS render style (opacity).
-  chrome.test.assertEq('0.38', elements[0].styles.opacity);
+  chrome.test.assertEq('0.38', elements[0]!.styles.opacity);
 
   // Retrieve file entries that are 'available offline' (not dimmed).
   // Use "first-child" here because opacity for offline only applies on the
@@ -172,11 +158,10 @@
   // Check: these files should have 'available offline' CSS style.
   chrome.test.assertEq(3, elements.length);
 
-  // @ts-ignore: error TS7006: Parameter 'fileName' implicitly has an 'any'
-  // type.
-  function checkRenderedInAvailableOfflineStyle(element, fileName) {
-    chrome.test.assertEq(0, element.text.indexOf(fileName));
-    chrome.test.assertEq('1', element.styles.opacity);
+  function checkRenderedInAvailableOfflineStyle(
+      element: ElementObject, fileName: string) {
+    chrome.test.assertEq(0, element.text!.indexOf(fileName));
+    chrome.test.assertEq('1', element.styles!['opacity']);
   }
 
   // Directories are shown as 'available offline'.
@@ -187,13 +172,13 @@
 
   // Pinned files are shown as 'available offline'.
   checkRenderedInAvailableOfflineStyle(elements[2], 'pinned');
-};
+}
 
 /**
  * Tests file display rendering in online Google Drive.
- * @param {string} appId the id for the window to check the file display.
+ * @param appId the id for the window to check the file display.
  */
-async function checkDriveOnlineDisplay(appId) {
+async function checkDriveOnlineDisplay(appId: string) {
   // Retrieve all file list row entries.
   const fileEntry = '#file-list .table-row';
   const elements = await remoteCall.callRemoteTestUtil(
@@ -209,24 +194,19 @@
 /**
  * Tests file display rendering in online Google Drive.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayDriveOnline' comes from an
-// index signature, so it must be accessed with ['fileDisplayDriveOnline'].
-testcase.fileDisplayDriveOnline = async () => {
+export async function fileDisplayDriveOnline() {
   // Open Files app on Drive.
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], BASIC_DRIVE_ENTRY_SET);
 
   await checkDriveOnlineDisplay(appId);
-};
+}
 
 /**
  * Tests file display rendering in online Google Drive when opening via OpenItem
  * function.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayDriveOnlineNewWindow' comes
-// from an index signature, so it must be accessed with
-// ['fileDisplayDriveOnlineNewWindow'].
-testcase.fileDisplayDriveOnlineNewWindow = async () => {
+export async function fileDisplayDriveOnlineNewWindow() {
   // Open Files app on the Drive directory.
   await addEntries(['drive'], BASIC_DRIVE_ENTRY_SET);
   await sendTestMessage({name: 'launchAppOnDrive'});
@@ -235,21 +215,17 @@
   const appId = await remoteCall.waitForWindow();
 
   // Wait for Files app to finish loading.
-  // @ts-ignore: error TS2345: Argument of type 'boolean' is not assignable to
-  // parameter of type '(arg0: Object) => boolean | Object'.
   await remoteCall.waitFor('isFileManagerLoaded', appId, true);
 
   await checkDriveOnlineDisplay(appId);
-};
+}
 
 /**
  * Tests files display in the "Computers" section of Google Drive. Testing that
  * we can navigate to folders inside /Computers also has the side effect of
  * testing that the breadcrumbs are working.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayComputers' comes from an index
-// signature, so it must be accessed with ['fileDisplayComputers'].
-testcase.fileDisplayComputers = async () => {
+export async function fileDisplayComputers() {
   // Open Files app on Drive with Computers registered.
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], COMPUTERS_ENTRY_SET);
@@ -263,15 +239,13 @@
 
   // Navigate to a subdirectory under a Computer Root.
   await directoryTree.navigateToPath('/Computers/Computer A/A');
-};
+}
 
 
 /**
  * Tests files display in an MTP volume.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayMtp' comes from an index
-// signature, so it must be accessed with ['fileDisplayMtp'].
-testcase.fileDisplayMtp = async () => {
+export async function fileDisplayMtp() {
   const MTP_VOLUME_TYPE = 'mtp';
 
   // Open Files app on local downloads.
@@ -286,19 +260,13 @@
 
   // Verify the MTP file list.
   const files = TestEntryInfo.getExpectedRows(BASIC_FAKE_ENTRY_SET);
-  // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime: true;
-  // }' is not assignable to parameter of type '{ orderCheck: boolean | null |
-  // undefined; ignoreFileSize: boolean | null | undefined;
-  // ignoreLastModifiedTime: boolean | null | undefined; }'.
   await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});
-};
+}
 
 /**
  * Tests files display in a removable USB volume.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayUsb' comes from an index
-// signature, so it must be accessed with ['fileDisplayUsb'].
-testcase.fileDisplayUsb = async () => {
+export async function fileDisplayUsb() {
   const USB_VOLUME_TYPE = 'removable';
 
   // Open Files app on local downloads.
@@ -313,19 +281,13 @@
 
   // Verify the USB file list.
   const files = TestEntryInfo.getExpectedRows(BASIC_FAKE_ENTRY_SET);
-  // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime: true;
-  // }' is not assignable to parameter of type '{ orderCheck: boolean | null |
-  // undefined; ignoreFileSize: boolean | null | undefined;
-  // ignoreLastModifiedTime: boolean | null | undefined; }'.
   await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});
-};
+}
 
 /**
  * Tests files display on a removable USB volume with and without partitions.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayUsbPartition' comes from an
-// index signature, so it must be accessed with ['fileDisplayUsbPartition'].
-testcase.fileDisplayUsbPartition = async () => {
+export async function fileDisplayUsbPartition() {
   // Open Files app on local downloads.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
 
@@ -380,17 +342,14 @@
     const childVolumeType = directoryTree.getItemVolumeType(itemEntries[0]);
     chrome.test.assertTrue('removable' !== childVolumeType);
   }
-};
+}
 
 /**
  * Tests that the file system type is properly displayed in the type
  * column. Checks that the entries can be properly sorted by type.
  * crbug.com/973743
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayUsbPartitionSort' comes from
-// an index signature, so it must be accessed with
-// ['fileDisplayUsbPartitionSort'].
-testcase.fileDisplayUsbPartitionSort = async () => {
+export async function fileDisplayUsbPartitionSort() {
   // Open Files app on local downloads.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
 
@@ -408,10 +367,6 @@
     ['partition-1', '--', 'ntfs'],
   ];
   const options = {orderCheck: true, ignoreLastModifiedTime: true};
-  // @ts-ignore: error TS2345: Argument of type '{ orderCheck: boolean;
-  // ignoreLastModifiedTime: boolean; }' is not assignable to parameter of type
-  // '{ orderCheck: boolean | null | undefined; ignoreFileSize: boolean | null |
-  // undefined; ignoreLastModifiedTime: boolean | null | undefined; }'.
   await remoteCall.waitForFiles(appId, expectedRows, options);
 
   // Sort by type in ascending order.
@@ -428,10 +383,6 @@
     ['partition-1', '--', 'ntfs'],
     ['partition-3', '--', 'vfat'],
   ];
-  // @ts-ignore: error TS2345: Argument of type '{ orderCheck: boolean;
-  // ignoreLastModifiedTime: boolean; }' is not assignable to parameter of type
-  // '{ orderCheck: boolean | null | undefined; ignoreFileSize: boolean | null |
-  // undefined; ignoreLastModifiedTime: boolean | null | undefined; }'.
   await remoteCall.waitForFiles(appId, expectedRows, options);
 
   // Sort by type in descending order.
@@ -448,21 +399,14 @@
     ['partition-1', '--', 'ntfs'],
     ['partition-2', '--', 'ext4'],
   ];
-  // @ts-ignore: error TS2345: Argument of type '{ orderCheck: boolean;
-  // ignoreLastModifiedTime: boolean; }' is not assignable to parameter of type
-  // '{ orderCheck: boolean | null | undefined; ignoreFileSize: boolean | null |
-  // undefined; ignoreLastModifiedTime: boolean | null | undefined; }'.
   await remoteCall.waitForFiles(appId, expectedRows, options);
-};
+}
 
 /**
  * Tests display of partitions in file list after mounting a removable USB
  * volume.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayPartitionFileTable' comes from
-// an index signature, so it must be accessed with
-// ['fileDisplayPartitionFileTable'].
-testcase.fileDisplayPartitionFileTable = async () => {
+export async function fileDisplayPartitionFileTable() {
   // Open Files app on local downloads.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
 
@@ -482,17 +426,18 @@
   const partitionTwo = await remoteCall.waitForElement(
       appId, '#file-list [file-name="partition-2"] .type');
   chrome.test.assertEq('ext4', partitionTwo.text);
-};
+}
 
 /**
  * Searches for a string in Downloads and checks that the correct results
  * are displayed.
  *
- * @param {string} searchTerm The string to search for.
- * @param {!Array<!TestEntryInfo>} expectedResults The results set.
+ * @param searchTerm The string to search for.
+ * @param expectedResults The results set.
  *
  */
-async function searchDownloads(searchTerm, expectedResults) {
+async function searchDownloads(
+    searchTerm: string, expectedResults: TestEntryInfo[]) {
   // Open Files app on local downloads.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
 
@@ -513,30 +458,24 @@
 }
 
 /**
- * Tests case-senstive search for an entry in Downloads.
+ * Tests case-sensitive search for an entry in Downloads.
  */
-// @ts-ignore: error TS4111: Property 'fileSearch' comes from an index
-// signature, so it must be accessed with ['fileSearch'].
-testcase.fileSearch = () => {
+export async function fileSearch() {
   return searchDownloads('hello', [ENTRIES.hello]);
-};
+}
 
 /**
  * Tests case-insenstive search for an entry in Downloads.
  */
-// @ts-ignore: error TS4111: Property 'fileSearchCaseInsensitive' comes from an
-// index signature, so it must be accessed with ['fileSearchCaseInsensitive'].
-testcase.fileSearchCaseInsensitive = () => {
+export async function fileSearchCaseInsensitive() {
   return searchDownloads('HELLO', [ENTRIES.hello]);
-};
+}
 
 /**
  * Tests searching for a string doesn't match anything in Downloads and that
  * there are no displayed items that match the search string.
  */
-// @ts-ignore: error TS4111: Property 'fileSearchNotFound' comes from an index
-// signature, so it must be accessed with ['fileSearchNotFound'].
-testcase.fileSearchNotFound = async () => {
+export async function fileSearchNotFound() {
   const searchTerm = 'blahblah';
 
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
@@ -553,16 +492,13 @@
       'fakeEvent', appId, ['#search-box cr-input', 'input']);
 
   await remoteCall.waitForFiles(appId, []);
-};
+}
 
 /**
  * Tests Files app opening without errors when there isn't Downloads which is
  * the default volume.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayWithoutDownloadsVolume' comes
-// from an index signature, so it must be accessed with
-// ['fileDisplayWithoutDownloadsVolume'].
-testcase.fileDisplayWithoutDownloadsVolume = async () => {
+export async function fileDisplayWithoutDownloadsVolume() {
   // Ensure no volumes are mounted.
   chrome.test.assertEq('0', await sendTestMessage({name: 'getVolumesCount'}));
 
@@ -576,17 +512,13 @@
   chrome.test.assertTrue(!!appId, 'failed to open new window');
 
   // Wait for Files app to finish loading.
-  // @ts-ignore: error TS2345: Argument of type 'boolean' is not assignable to
-  // parameter of type '(arg0: Object) => boolean | Object'.
   await remoteCall.waitFor('isFileManagerLoaded', appId, true);
-};
+}
 
 /**
  * Tests Files app opening without errors when there are no volumes at all.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayWithoutVolumes' comes from an
-// index signature, so it must be accessed with ['fileDisplayWithoutVolumes'].
-testcase.fileDisplayWithoutVolumes = async () => {
+export async function fileDisplayWithoutVolumes() {
   // Ensure no volumes are mounted.
   chrome.test.assertEq('0', await sendTestMessage({name: 'getVolumesCount'}));
 
@@ -595,20 +527,15 @@
   chrome.test.assertTrue(!!appId, 'failed to open new window');
 
   // Wait for Files app to finish loading.
-  // @ts-ignore: error TS2345: Argument of type 'boolean' is not assignable to
-  // parameter of type '(arg0: Object) => boolean | Object'.
   await remoteCall.waitFor('isFileManagerLoaded', appId, true);
-};
+}
 
 /**
  * Tests Files app opening without errors when there are no volumes at all and
  * then mounting Downloads volume which should appear and be able to display its
  * files.
  */
-// @ts-ignore: error TS4111: Property
-// 'fileDisplayWithoutVolumesThenMountDownloads' comes from an index signature,
-// so it must be accessed with ['fileDisplayWithoutVolumesThenMountDownloads'].
-testcase.fileDisplayWithoutVolumesThenMountDownloads = async () => {
+export async function fileDisplayWithoutVolumesThenMountDownloads() {
   // Ensure no volumes are mounted.
   chrome.test.assertEq('0', await sendTestMessage({name: 'getVolumesCount'}));
 
@@ -617,8 +544,6 @@
   chrome.test.assertTrue(!!appId, 'failed to open new window');
 
   // Wait for Files app to finish loading.
-  // @ts-ignore: error TS2345: Argument of type 'boolean' is not assignable to
-  // parameter of type '(arg0: Object) => boolean | Object'.
   await remoteCall.waitFor('isFileManagerLoaded', appId, true);
 
   // Mount Downloads.
@@ -634,23 +559,15 @@
       [['Downloads', '--', 'Folder'], ['Linux files', '--', 'Folder']];
   await remoteCall.waitForFiles(
       appId, expectedRows,
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreFileSize: true;
-      // ignoreLastModifiedTime: true; }' is not assignable to parameter of type
-      // '{ orderCheck: boolean | null | undefined; ignoreFileSize: boolean |
-      // null | undefined; ignoreLastModifiedTime: boolean | null | undefined;
-      // }'.
       {ignoreFileSize: true, ignoreLastModifiedTime: true});
-};
+}
 
 /**
  * Tests Files app opening without errors when there are no volumes at all and
  * then mounting Drive volume which should appear and be able to display its
  * files.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayWithoutVolumesThenMountDrive'
-// comes from an index signature, so it must be accessed with
-// ['fileDisplayWithoutVolumesThenMountDrive'].
-testcase.fileDisplayWithoutVolumesThenMountDrive = async () => {
+export async function fileDisplayWithoutVolumesThenMountDrive() {
   // Ensure no volumes are mounted.
   chrome.test.assertEq('0', await sendTestMessage({name: 'getVolumesCount'}));
 
@@ -659,8 +576,6 @@
   chrome.test.assertTrue(!!appId, 'failed to open new window');
 
   // Wait for Files app to finish loading.
-  // @ts-ignore: error TS2345: Argument of type 'boolean' is not assignable to
-  // parameter of type '(arg0: Object) => boolean | Object'.
   await remoteCall.waitFor('isFileManagerLoaded', appId, true);
 
   // Navigate to the Drive FakeItem.
@@ -678,22 +593,16 @@
   await remoteCall.waitFor('getVolumesCount', null, (count) => count === 1, []);
 
   // Add an entry to Drive.
-  // @ts-ignore: error TS4111: Property 'newlyAdded' comes from an index
-  // signature, so it must be accessed with ['newlyAdded'].
   await addEntries(['drive'], [ENTRIES.newlyAdded]);
 
   // Wait for "My Drive" files to display in the file list.
-  // @ts-ignore: error TS4111: Property 'newlyAdded' comes from an index
-  // signature, so it must be accessed with ['newlyAdded'].
   await remoteCall.waitForFiles(appId, [ENTRIES.newlyAdded.getExpectedRow()]);
-};
+}
 
 /**
  * Tests Files app opening without Drive mounted.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayWithoutDrive' comes from an
-// index signature, so it must be accessed with ['fileDisplayWithoutDrive'].
-testcase.fileDisplayWithoutDrive = async () => {
+export async function fileDisplayWithoutDrive() {
   // Ensure no volumes are mounted.
   chrome.test.assertEq('0', await sendTestMessage({name: 'getVolumesCount'}));
 
@@ -705,8 +614,6 @@
 
   // Open the files app.
   const appId = await setupAndWaitUntilReady(
-      // @ts-ignore: error TS4111: Property 'newlyAdded' comes from an index
-      // signature, so it must be accessed with ['newlyAdded'].
       RootPath.DOWNLOADS, [ENTRIES.newlyAdded], []);
 
   // Wait for the loading indicator blink to finish.
@@ -723,16 +630,13 @@
 
   // Check: the fake Google Drive should be empty.
   await remoteCall.waitForFiles(appId, []);
-};
+}
 
 /**
  * Tests Files app opening without Drive mounted and then disabling and
  * re-enabling Drive.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayWithoutDriveThenDisable' comes
-// from an index signature, so it must be accessed with
-// ['fileDisplayWithoutDriveThenDisable'].
-testcase.fileDisplayWithoutDriveThenDisable = async () => {
+export async function fileDisplayWithoutDriveThenDisable() {
   // Ensure no volumes are mounted.
   chrome.test.assertEq('0', await sendTestMessage({name: 'getVolumesCount'}));
 
@@ -740,8 +644,6 @@
   await sendTestMessage({name: 'mountDownloads'});
 
   // Add a file to Downloads.
-  // @ts-ignore: error TS4111: Property 'newlyAdded' comes from an index
-  // signature, so it must be accessed with ['newlyAdded'].
   await addEntries(['local'], [ENTRIES.newlyAdded]);
 
   // Wait until it mounts.
@@ -752,8 +654,6 @@
   chrome.test.assertTrue(!!appId, 'failed to open new window');
 
   // Wait for Files app to finish loading.
-  // @ts-ignore: error TS2345: Argument of type 'boolean' is not assignable to
-  // parameter of type '(arg0: Object) => boolean | Object'.
   await remoteCall.waitFor('isFileManagerLoaded', appId, true);
 
   // We should navigate to MyFiles.
@@ -762,10 +662,6 @@
     ['Linux files', '--', 'Folder'],
   ];
   await remoteCall.waitForFiles(
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime:
-      // true; }' is not assignable to parameter of type '{ orderCheck: boolean
-      // | null | undefined; ignoreFileSize: boolean | null | undefined;
-      // ignoreLastModifiedTime: boolean | null | undefined; }'.
       appId, expectedRows, {ignoreLastModifiedTime: true});
 
   // Navigate to Drive.
@@ -785,10 +681,6 @@
 
   // Ensure Downloads has loaded.
   await remoteCall.waitForFiles(
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime:
-      // true; }' is not assignable to parameter of type '{ orderCheck: boolean
-      // | null | undefined; ignoreFileSize: boolean | null | undefined;
-      // ignoreLastModifiedTime: boolean | null | undefined; }'.
       appId, expectedRows, {ignoreLastModifiedTime: true});
 
   // Re-enabled Drive.
@@ -796,21 +688,16 @@
 
   // Wait for the fake drive to reappear.
   await directoryTree.waitForItemByLabel('Google Drive');
-};
+}
 
 /**
  * Tests that mounting a hidden Volume does not mount the volume in file
  * manager.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayWithHiddenVolume' comes from
-// an index signature, so it must be accessed with
-// ['fileDisplayWithHiddenVolume'].
-testcase.fileDisplayWithHiddenVolume = async () => {
+export async function fileDisplayWithHiddenVolume() {
   const initialVolumeCount = await sendTestMessage({name: 'getVolumesCount'});
 
   const appId =
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
   // Get the directory tree elements.
@@ -828,24 +715,17 @@
   // The hidden volume should not be counted in the number of volumes.
   chrome.test.assertEq(
       initialVolumeCount, await sendTestMessage({name: 'getVolumesCount'}));
-};
+}
 
 /**
  * Tests Files app resisting the urge to switch to Downloads when mounts change.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayMountWithFakeItemSelected'
-// comes from an index signature, so it must be accessed with
-// ['fileDisplayMountWithFakeItemSelected'].
-testcase.fileDisplayMountWithFakeItemSelected = async () => {
+export async function fileDisplayMountWithFakeItemSelected() {
   // Open Files app on Drive with the given test files.
   const appId = await setupAndWaitUntilReady(
-      // @ts-ignore: error TS4111: Property 'newlyAdded' comes from an index
-      // signature, so it must be accessed with ['newlyAdded'].
       RootPath.DOWNLOADS, [ENTRIES.newlyAdded], []);
 
   // Ensure Downloads has loaded.
-  // @ts-ignore: error TS4111: Property 'newlyAdded' comes from an index
-  // signature, so it must be accessed with ['newlyAdded'].
   await remoteCall.waitForFiles(appId, [ENTRIES.newlyAdded.getExpectedRow()]);
 
   // Navigate to My files.
@@ -863,17 +743,13 @@
   chrome.test.assertEq(
       '/My files',
       await remoteCall.callRemoteTestUtil('getBreadcrumbPath', appId, []));
-};
+}
 
 /**
  * Tests Files app switching away from Drive virtual folders when Drive is
  * unmounted.
  */
-// @ts-ignore: error TS4111: Property
-// 'fileDisplayUnmountDriveWithSharedWithMeSelected' comes from an index
-// signature, so it must be accessed with
-// ['fileDisplayUnmountDriveWithSharedWithMeSelected'].
-testcase.fileDisplayUnmountDriveWithSharedWithMeSelected = async () => {
+export async function fileDisplayUnmountDriveWithSharedWithMeSelected() {
   // Open Files app on Drive with the given test files.
   const appId = await setupAndWaitUntilReady(
       RootPath.DRIVE, [ENTRIES.newlyAdded],
@@ -888,8 +764,6 @@
 
   // Check that the file is visible.
   await remoteCall.waitForFiles(
-      // @ts-ignore: error TS4111: Property 'testSharedDocument' comes from an
-      // index signature, so it must be accessed with ['testSharedDocument'].
       appId, [ENTRIES.testSharedDocument.getExpectedRow()]);
 
   // Unmount drive.
@@ -905,21 +779,17 @@
     ['Linux files', '--', 'Folder'],
   ];
   await remoteCall.waitForFiles(
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime:
-      // true; }' is not assignable to parameter of type '{ orderCheck: boolean
-      // | null | undefined; ignoreFileSize: boolean | null | undefined;
-      // ignoreLastModifiedTime: boolean | null | undefined; }'.
       appId, expectedRows, {ignoreLastModifiedTime: true});
-};
+}
 
 /**
  * Navigates to a removable volume, then unmounts it. Check to see whether
  * Files App switches away to the default Downloads directory.
  *
- * @param {string} removableDirectory The removable directory to be inside
+ * @param removableDirectory The removable directory to be inside
  *    before unmounting the USB.
  */
-async function unmountRemovableVolume(removableDirectory) {
+async function unmountRemovableVolume(removableDirectory: string) {
   // Open Files app on Downloads containing ENTRIES.photos.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.photos], []);
@@ -950,10 +820,6 @@
     await remoteCall.waitUntilCurrentDirectoryIsChanged(
         appId, `/Drive Label/${removableDirectory}`);
     await remoteCall.waitForFiles(
-        // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime:
-        // true; }' is not assignable to parameter of type '{ orderCheck:
-        // boolean | null | undefined; ignoreFileSize: boolean | null |
-        // undefined; ignoreLastModifiedTime: boolean | null | undefined; }'.
         appId, partitionFiles, {ignoreLastModifiedTime: true});
   }
 
@@ -970,10 +836,6 @@
     ['Linux files', '--', 'Folder'],
   ];
   await remoteCall.waitForFiles(
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime:
-      // true; }' is not assignable to parameter of type '{ orderCheck: boolean
-      // | null | undefined; ignoreFileSize: boolean | null | undefined;
-      // ignoreLastModifiedTime: boolean | null | undefined; }'.
       appId, expectedRows, {ignoreLastModifiedTime: true});
 }
 
@@ -981,56 +843,40 @@
  * Tests Files app switches away from a removable device root after the USB is
  * unmounted.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayUnmountRemovableRoot' comes
-// from an index signature, so it must be accessed with
-// ['fileDisplayUnmountRemovableRoot'].
-testcase.fileDisplayUnmountRemovableRoot = () => {
+export async function fileDisplayUnmountRemovableRoot() {
   return unmountRemovableVolume('Drive Label');
-};
+}
 
 /**
  * Tests Files app switches away from a partition inside the USB after the USB
  * is unmounted.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayUnmountFirstPartition' comes
-// from an index signature, so it must be accessed with
-// ['fileDisplayUnmountFirstPartition'].
-testcase.fileDisplayUnmountFirstPartition = () => {
+export async function fileDisplayUnmountFirstPartition() {
   return unmountRemovableVolume('partition-1');
-};
+}
 
 /**
  * Tests Files app switches away from a partition inside the USB after the USB
  * is unmounted. Partition-1 will be ejected first.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayUnmountLastPartition' comes
-// from an index signature, so it must be accessed with
-// ['fileDisplayUnmountLastPartition'].
-testcase.fileDisplayUnmountLastPartition = () => {
+export async function fileDisplayUnmountLastPartition() {
   return unmountRemovableVolume('partition-2');
-};
+}
 
 /**
  * Tests files display in Downloads while the default blocking file I/O task
  * runner is blocked.
  */
-// @ts-ignore: error TS4111: Property
-// 'fileDisplayDownloadsWithBlockedFileTaskRunner' comes from an index
-// signature, so it must be accessed with
-// ['fileDisplayDownloadsWithBlockedFileTaskRunner'].
-testcase.fileDisplayDownloadsWithBlockedFileTaskRunner = async () => {
+export async function fileDisplayDownloadsWithBlockedFileTaskRunner() {
   await sendTestMessage({name: 'blockFileTaskRunner'});
   await fileDisplay(RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET);
   await sendTestMessage({name: 'unblockFileTaskRunner'});
-};
+}
 
 /**
  * Tests to make sure check-select mode enables when selecting one item
  */
-// @ts-ignore: error TS4111: Property
-// 'fileDisplayCheckSelectWithFakeItemSelected' comes from an index signature,
-// so it must be accessed with ['fileDisplayCheckSelectWithFakeItemSelected'].
-testcase.fileDisplayCheckSelectWithFakeItemSelected = async () => {
+export async function fileDisplayCheckSelectWithFakeItemSelected() {
   // Open files app on Downloads containing ENTRIES.hello.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
@@ -1045,16 +891,13 @@
 
   // Make sure check-select is enabled.
   await remoteCall.waitForElement(appId, 'body.check-select');
-};
+}
 
 /**
  * Tests to make sure read-only indicator is visible when the current directory
  * is read-only.
  */
-// @ts-ignore: error TS4111: Property
-// 'fileDisplayCheckReadOnlyIconOnFakeDirectory' comes from an index signature,
-// so it must be accessed with ['fileDisplayCheckReadOnlyIconOnFakeDirectory'].
-testcase.fileDisplayCheckReadOnlyIconOnFakeDirectory = async () => {
+export async function fileDisplayCheckReadOnlyIconOnFakeDirectory() {
   // Open Files app on Drive with the given test files.
   const appId = await setupAndWaitUntilReady(
       RootPath.DRIVE, [ENTRIES.newlyAdded],
@@ -1069,32 +912,26 @@
 
   // Make sure read-only indicator on toolbar is visible.
   await remoteCall.waitForElement(appId, '#read-only-indicator:not([hidden])');
-};
+}
 
 /**
  * Tests to make sure read-only indicator is NOT visible when the current
  * is writable.
  */
-// @ts-ignore: error TS4111: Property
-// 'fileDisplayCheckNoReadOnlyIconOnDownloads' comes from an index signature, so
-// it must be accessed with ['fileDisplayCheckNoReadOnlyIconOnDownloads'].
-testcase.fileDisplayCheckNoReadOnlyIconOnDownloads = async () => {
+export async function fileDisplayCheckNoReadOnlyIconOnDownloads() {
   // Open files app on Downloads containing ENTRIES.hello.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
 
   // Make sure read-only indicator on toolbar is NOT visible.
   await remoteCall.waitForElement(appId, '#read-only-indicator[hidden]');
-};
+}
 
 /**
  * Tests to make sure read-only indicator is NOT visible when the current
  * directory is the "Linux files" fake root.
  */
-// @ts-ignore: error TS4111: Property
-// 'fileDisplayCheckNoReadOnlyIconOnLinuxFiles' comes from an index signature,
-// so it must be accessed with ['fileDisplayCheckNoReadOnlyIconOnLinuxFiles'].
-testcase.fileDisplayCheckNoReadOnlyIconOnLinuxFiles = async () => {
+export async function fileDisplayCheckNoReadOnlyIconOnLinuxFiles() {
   // Block mounts from progressing. This should cause the file manager to always
   // show the loading bar for linux files.
   await sendTestMessage({name: 'blockMounts'});
@@ -1113,16 +950,13 @@
 
   // Check: the toolbar read-only indicator should not be visible.
   await remoteCall.waitForElement(appId, '#read-only-indicator[hidden]');
-};
+}
 
 /**
  * Tests to make sure read-only indicator is NOT visible when the current
  * directory is a "GuestOs" fake root.
  */
-// @ts-ignore: error TS4111: Property 'fileDisplayCheckNoReadOnlyIconOnGuestOs'
-// comes from an index signature, so it must be accessed with
-// ['fileDisplayCheckNoReadOnlyIconOnGuestOs'].
-testcase.fileDisplayCheckNoReadOnlyIconOnGuestOs = async () => {
+export async function fileDisplayCheckNoReadOnlyIconOnGuestOs() {
   // Create a Bruschetta guest for this test.
   await sendTestMessage({
     name: 'registerMountableGuest',
@@ -1149,4 +983,4 @@
 
   // Check: the toolbar read-only indicator should not be visible.
   await remoteCall.waitForElement(appId, '#read-only-indicator[hidden]');
-};
+}
diff --git a/ui/file_manager/integration_tests/file_manager/file_list.js b/ui/file_manager/integration_tests/file_manager/file_list.ts
similarity index 80%
rename from ui/file_manager/integration_tests/file_manager/file_list.js
rename to ui/file_manager/integration_tests/file_manager/file_list.ts
index e414624c..1c21d570 100644
--- a/ui/file_manager/integration_tests/file_manager/file_list.js
+++ b/ui/file_manager/integration_tests/file_manager/file_list.ts
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import type {ElementObject} from '../prod/file_manager/shared_types.js';
 import {ENTRIES, RootPath, sendTestMessage} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {remoteCall, setupAndWaitUntilReady} from './background.js';
 import {BASIC_LOCAL_ENTRY_SET} from './test_data.js';
@@ -19,8 +19,7 @@
  *   updating the selected item which is async can be detected after an extra
  *   Tab has been sent.
  */
-// @ts-ignore: error TS7006: Parameter 'appId' implicitly has an 'any' type.
-async function tabUntilFileSelected(appId) {
+async function tabUntilFileSelected(appId: string) {
   // Check: the file-list should have nothing selected.
   const selectedRows = await remoteCall.callRemoteTestUtil(
       'deepQueryAllElements', appId, ['#file-list li[selected]']);
@@ -46,12 +45,8 @@
 /**
  * Tests that file list column header have ARIA attributes.
  */
-// @ts-ignore: error TS4111: Property 'fileListAriaAttributes' comes from an
-// index signature, so it must be accessed with ['fileListAriaAttributes'].
-testcase.fileListAriaAttributes = async () => {
+export async function fileListAriaAttributes() {
   const appId =
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
   // Fetch column header.
@@ -67,15 +62,13 @@
     // role button is used so users know that it's clickable.
     chrome.test.assertEq('button', header.attributes['role']);
   }
-};
+}
 
 /**
  * Tests using tab to focus the file list will select the first item, if
  * nothing is selected.
  */
-// @ts-ignore: error TS4111: Property 'fileListFocusFirstItem' comes from an
-// index signature, so it must be accessed with ['fileListFocusFirstItem'].
-testcase.fileListFocusFirstItem = async () => {
+export async function fileListFocusFirstItem() {
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
 
@@ -83,23 +76,19 @@
   await tabUntilFileSelected(appId);
 
   // Check: The first file only is selected in the file entry rows.
-  const fileRows = await remoteCall.callRemoteTestUtil(
+  const fileRows: ElementObject[] = await remoteCall.callRemoteTestUtil(
       'deepQueryAllElements', appId, ['#file-list li']);
   chrome.test.assertEq(5, fileRows.length);
-  // @ts-ignore: error TS7006: Parameter 'item' implicitly has an 'any' type.
   const selectedRows = fileRows.filter(item => 'selected' in item.attributes);
   chrome.test.assertEq(1, selectedRows.length);
-  chrome.test.assertEq(0, fileRows.indexOf(selectedRows[0]));
-};
+  chrome.test.assertEq(0, fileRows.indexOf(selectedRows[0]!));
+}
 
 /**
  * Tests that after a multiple selection, canceling the selection and using
  * Tab to focus the files list it selects the item that was last focused.
  */
-// @ts-ignore: error TS4111: Property 'fileListSelectLastFocusedItem' comes from
-// an index signature, so it must be accessed with
-// ['fileListSelectLastFocusedItem'].
-testcase.fileListSelectLastFocusedItem = async () => {
+export async function fileListSelectLastFocusedItem() {
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
 
@@ -137,8 +126,6 @@
   chrome.test.assertEq(2, selectedRows.length);
 
   // Cancel the selection.
-  // @ts-ignore: error TS6133: 'button' is declared but its value is never read.
-  const button = ['#cancel-selection-button'];
   await remoteCall.waitAndClickElement(appId, '#cancel-selection-button');
 
   // Wait until selection is removed.
@@ -148,22 +135,19 @@
   await tabUntilFileSelected(appId);
 
   // Check: The 3rd item only is selected.
-  const fileRows = await remoteCall.callRemoteTestUtil(
+  const fileRows: ElementObject[] = await remoteCall.callRemoteTestUtil(
       'deepQueryAllElements', appId, ['#file-list li']);
   chrome.test.assertEq(5, fileRows.length);
-  // @ts-ignore: error TS7006: Parameter 'item' implicitly has an 'any' type.
   selectedRows = fileRows.filter(item => 'selected' in item.attributes);
   chrome.test.assertEq(1, selectedRows.length);
   chrome.test.assertEq(2, fileRows.indexOf(selectedRows[0]));
-};
+}
 
 /**
  * Tests that after a multiple selection, canceling the selection and using
  * Tab to focus the files list it selects the item that was last focused.
  */
-// @ts-ignore: error TS4111: Property 'fileListSortWithKeyboard' comes from an
-// index signature, so it must be accessed with ['fileListSortWithKeyboard'].
-testcase.fileListSortWithKeyboard = async () => {
+export async function fileListSortWithKeyboard() {
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
 
@@ -207,19 +191,17 @@
   chrome.test.assertTrue(
       focusedElement['attributes']['aria-label'] ===
       'Click to sort the column in ascending order.');
-};
+}
 
 /**
  * Verifies the total number of a11y messages and asserts the latest message
  * is the expected one.
  *
- * @param {string} appId
- * @param {number} expectedCount
- * @param {?string=} expectedMessage
- * @return {!Promise<string>} Latest a11y message.
+ * @return Latest a11y message.
  */
 async function countAndCheckLatestA11yMessage(
-    appId, expectedCount, expectedMessage) {
+    appId: string, expectedCount: number,
+    expectedMessage?: null|string): Promise<string> {
   const a11yMessages =
       await remoteCall.callRemoteTestUtil('getA11yAnnounces', appId, []);
   if (expectedMessage === null || expectedMessage === undefined) {
@@ -240,9 +222,9 @@
  * messages.
  *
  * NOTE: Test shared with grid_view.js.
- * @param {boolean=} isGridView if the test is testing the grid view.
+ * @param isGridView if the test is testing the grid view.
  */
-export async function fileListKeyboardSelectionA11yImpl(isGridView) {
+export async function fileListKeyboardSelectionA11yImpl(isGridView?: boolean) {
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
 
@@ -257,16 +239,14 @@
   }
 
   // Keys used for keyboard navigation in the file list.
-  const homeKey = [viewSelector, 'Home', false, false, false];
-  const ctrlDownKey = [viewSelector, 'ArrowDown', true, false, false];
-  const ctrlSpaceKey = [viewSelector, ' ', true, false, false];
-  const shiftEndKey = [viewSelector, 'End', false, true, false];
-  const ctrlAKey = [viewSelector + ' li', 'a', true, false, false];
-  const escKey = [viewSelector, 'Escape', false, false, false];
+  const homeKey = [viewSelector, 'Home', false, false, false] as const;
+  const ctrlDownKey = [viewSelector, 'ArrowDown', true, false, false] as const;
+  const ctrlSpaceKey = [viewSelector, ' ', true, false, false] as const;
+  const shiftEndKey = [viewSelector, 'End', false, true, false] as const;
+  const ctrlAKey = [viewSelector + ' li', 'a', true, false, false] as const;
+  const escKey = [viewSelector, 'Escape', false, false, false] as const;
 
   // Select first item with Home key.
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
   await remoteCall.fakeKeyDown(appId, ...homeKey);
 
   // Navigating with Home key doesn't use aria-live message, it only uses the
@@ -274,11 +254,7 @@
   await countAndCheckLatestA11yMessage(appId, a11yMsgCount);
 
   // Ctrl+Down & Ctrl+Space to select second item: Beautiful Song.ogg
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
   await remoteCall.fakeKeyDown(appId, ...ctrlDownKey);
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
   await remoteCall.fakeKeyDown(appId, ...ctrlSpaceKey);
 
   // Check: Announced "Beautiful Song.add" added to selection.
@@ -286,8 +262,6 @@
       appId, ++a11yMsgCount, 'Added Beautiful Song.ogg to selection.');
 
   // Shift+End to select from 2nd item to the last item.
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
   await remoteCall.fakeKeyDown(appId, ...shiftEndKey);
 
   // Check: Announced range selection from "Beautiful Song.add" to hello.txt.
@@ -296,8 +270,6 @@
       'Selected a range of 4 entries from Beautiful Song.ogg to hello.txt.');
 
   // Ctrl+Space to de-select currently focused item (last item).
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
   await remoteCall.fakeKeyDown(appId, ...ctrlSpaceKey);
 
   // Check: Announced de-selecting hello.txt
@@ -305,8 +277,6 @@
       appId, ++a11yMsgCount, 'Removed hello.txt from selection.');
 
   // Ctrl+A to select all items.
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
   await remoteCall.fakeKeyDown(appId, ...ctrlAKey);
 
   // Check: Announced selecting all entries.
@@ -314,8 +284,6 @@
       appId, ++a11yMsgCount, 'Selected all entries.');
 
   // Esc key to deselect all.
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
   await remoteCall.fakeKeyDown(appId, ...escKey);
 
   // Check: Announced deselecting all entries.
@@ -323,20 +291,17 @@
       appId, ++a11yMsgCount, 'Removed all entries from selection.');
 }
 
-// @ts-ignore: error TS4111: Property 'fileListKeyboardSelectionA11y' comes from
-// an index signature, so it must be accessed with
-// ['fileListKeyboardSelectionA11y'].
-testcase.fileListKeyboardSelectionA11y = async () => {
+export async function fileListKeyboardSelectionA11y() {
   return fileListKeyboardSelectionA11yImpl(/*isGridView*/ false);
-};
+}
 
 /**
  * Tests that selecting/de-selecting files with mouse produces a11y messages.
  *
  * NOTE: Test shared with grid_view.js.
- * @param {boolean=} isGridView if the test is testing the grid view.
+ * @param isGridView if the test is testing the grid view.
  */
-export async function fileListMouseSelectionA11yImpl(isGridView) {
+export async function fileListMouseSelectionA11yImpl(isGridView?: boolean) {
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
 
@@ -390,22 +355,16 @@
       appId, ++a11yMsgCount, 'Removed all entries from selection.');
 }
 
-// @ts-ignore: error ts4111: property 'filelistmouseselectiona11y ' comes from
-// an index signature, so it must be accessed with
-// ['filelistmouseselectiona11y '].
-testcase.fileListMouseSelectionA11y = async () => {
+export async function fileListMouseSelectionA11y() {
   return fileListMouseSelectionA11yImpl(/*isGridView*/ false);
-};
+}
 
 /**
  * Tests the deletion of one or multiple items. After deletion, one of the
  * remaining items should have the lead, but shouldn't be in check-select
  * mode.
  */
-// @ts-ignore: error TS4111: Property 'fileListDeleteMultipleFiles' comes from
-// an index signature, so it must be accessed with
-// ['fileListDeleteMultipleFiles'].
-testcase.fileListDeleteMultipleFiles = async () => {
+export async function fileListDeleteMultipleFiles() {
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
 
@@ -453,7 +412,7 @@
 
   // Check: selected state of first item.
   chrome.test.assertTrue('selected' in item.attributes);
-};
+}
 
 /**
  * Tests that in selection mode, the rename operation is applied to the
@@ -461,9 +420,7 @@
  * The lead and the selected item(s) are different when we deselect a file
  * list item in selection mode. crbug.com/1094260
  */
-// @ts-ignore: error TS4111: Property 'fileListRenameSelectedItem' comes from an
-// index signature, so it must be accessed with ['fileListRenameSelectedItem'].
-testcase.fileListRenameSelectedItem = async () => {
+export async function fileListRenameSelectedItem() {
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
 
@@ -508,19 +465,14 @@
       appId, '#file-list [file-name="world.ogv"]');
   await remoteCall.waitForElement(
       appId, '#file-list li[selected][file-name="New File Name.txt"]');
-};
+}
 
 /**
  * Tests that user can rename a file/folder after using "select all" without
  * having selected any file previously.
  */
-// @ts-ignore: error TS4111: Property 'fileListRenameFromSelectAll' comes from
-// an index signature, so it must be accessed with
-// ['fileListRenameFromSelectAll'].
-testcase.fileListRenameFromSelectAll = async () => {
+export async function fileListRenameFromSelectAll() {
   const appId =
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
   // Select all the files.
@@ -541,4 +493,4 @@
   // Check: the renaming text input should be shown in the file list.
   const textInput = '#file-list .table-row[renaming] input.rename';
   await remoteCall.waitForElement(appId, textInput);
-};
+}
diff --git a/ui/file_manager/integration_tests/file_manager/format_dialog.js b/ui/file_manager/integration_tests/file_manager/format_dialog.ts
similarity index 83%
rename from ui/file_manager/integration_tests/file_manager/format_dialog.js
rename to ui/file_manager/integration_tests/file_manager/format_dialog.ts
index d7592795..bbef6fd 100644
--- a/ui/file_manager/integration_tests/file_manager/format_dialog.js
+++ b/ui/file_manager/integration_tests/file_manager/format_dialog.ts
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import {ENTRIES, RootPath, sendTestMessage} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {isSinglePartitionFormat, remoteCall, setupAndWaitUntilReady} from './background.js';
 import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
@@ -11,9 +10,9 @@
 /**
  * Lanuches file manager and stubs out the formatVolume private api.
  *
- * @return {!Promise<string>} Files app window ID.
+ * @return Files app window ID.
  */
-async function setupFormatDialogTest() {
+async function setupFormatDialogTest(): Promise<string> {
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
   await remoteCall.callRemoteTestUtil('overrideFormat', appId, []);
   return appId;
@@ -22,10 +21,10 @@
 /**
  * Opens a format dialog for the USB with label |usbLabel|.
  *
- * @param {string} appId Files app window ID.
- * @param {string} usbLabel Label of usb to format.
+ * @param appId Files app window ID.
+ * @param usbLabel Label of usb to format.
  */
-async function openFormatDialog(appId, usbLabel) {
+async function openFormatDialog(appId: string, usbLabel: string) {
   if (await isSinglePartitionFormat(appId)) {
     await openFormatDialogWithSinglePartitionFormat(appId, usbLabel, 'FAKEUSB');
     return;
@@ -52,12 +51,12 @@
  * Opens a format dialog for the USB with label |usbLabel| and device with
  * label |deviceLabel|.
  *
- * @param {string} appId Files app window ID.
- * @param {string} usbLabel Label of usb to format.
- * @param {string} deviceLabel Label of the parent device of usb.
+ * @param appId Files app window ID.
+ * @param usbLabel Label of usb to format.
+ * @param deviceLabel Label of the parent device of usb.
  */
 async function openFormatDialogWithSinglePartitionFormat(
-    appId, usbLabel, deviceLabel) {
+    appId: string, usbLabel: string, deviceLabel: string) {
   // Focus the directory tree.
   const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
   await directoryTree.focusTree();
@@ -81,9 +80,7 @@
 /**
  * Tests the format dialog for a sample USB with files on it.
  */
-// @ts-ignore: error TS4111: Property 'formatDialog' comes from an index
-// signature, so it must be accessed with ['formatDialog'].
-testcase.formatDialog = async () => {
+export async function formatDialog() {
   await sendTestMessage({name: 'mountFakeUsb'});
   const appId = await setupFormatDialogTest();
 
@@ -105,14 +102,12 @@
   // Check the dialog is closed.
   await remoteCall.waitForElement(
       appId, ['files-format-dialog', 'cr-dialog:not([open])']);
-};
+}
 
 /**
  * Tests the format dialog is a modal dialog.
  */
-// @ts-ignore: error TS4111: Property 'formatDialogIsModal' comes from an index
-// signature, so it must be accessed with ['formatDialogIsModal'].
-testcase.formatDialogIsModal = async () => {
+export async function formatDialogIsModal() {
   await sendTestMessage({name: 'mountFakeUsb'});
   const appId = await setupFormatDialogTest();
 
@@ -131,14 +126,12 @@
   const selectedRows = await remoteCall.callRemoteTestUtil(
       'deepQueryAllElements', appId, ['#file-list li[selected]']);
   chrome.test.assertEq(0, selectedRows.length);
-};
+}
 
 /**
  * Tests the format dialog for an empty USB.
  */
-// @ts-ignore: error TS4111: Property 'formatDialogEmpty' comes from an index
-// signature, so it must be accessed with ['formatDialogEmpty'].
-testcase.formatDialogEmpty = async () => {
+export async function formatDialogEmpty() {
   await sendTestMessage({name: 'mountFakeUsbEmpty'});
   const appId = await setupFormatDialogTest();
 
@@ -157,14 +150,12 @@
   // Check the dialog is closed.
   await remoteCall.waitForElement(
       appId, ['files-format-dialog', 'cr-dialog:not([open])']);
-};
+}
 
 /**
  * Tests cancelling out of the format dialog.
  */
-// @ts-ignore: error TS4111: Property 'formatDialogCancel' comes from an index
-// signature, so it must be accessed with ['formatDialogCancel'].
-testcase.formatDialogCancel = async () => {
+export async function formatDialogCancel() {
   await sendTestMessage({name: 'mountFakeUsb'});
   const appId = await setupFormatDialogTest();
 
@@ -178,18 +169,19 @@
   // Check the dialog is closed.
   await remoteCall.waitForElement(
       appId, ['files-format-dialog', 'cr-dialog:not([open])']);
-};
+}
 
 /**
  * Checks that formatting gives error |errorMessage| when given |label| and
  * |format|.
  *
- * @param {string} appId Files app window ID.
- * @param {string} label New label of usb drive.
- * @param {string} format New filesystem of drive.
- * @param {string} errorMessage Expected error message to be displayed.
+ * @param appId Files app window ID.
+ * @param label New label of usb drive.
+ * @param format New filesystem of drive.
+ * @param errorMessage Expected error message to be displayed.
  */
-async function checkError(appId, label, format, errorMessage) {
+async function checkError(
+    appId: string, label: string, format: string, errorMessage: string) {
   // Enter in a label.
   const driveNameQuery = ['files-format-dialog', 'cr-input#label'];
   await remoteCall.inputText(appId, driveNameQuery, label);
@@ -219,11 +211,11 @@
 /**
  * Checks that formatting succeeds when given |label| and |format|.
  *
- * @param {string} appId Files app window ID.
- * @param {string} label New label of usb drive.
- * @param {string} format New filesystem of drive.
+ * @param appId Files app window ID.
+ * @param label New label of usb drive.
+ * @param format New filesystem of drive.
  */
-async function checkSuccess(appId, label, format) {
+async function checkSuccess(appId: string, label: string, format: string) {
   // Enter in a label.
   const driveNameQuery = ['files-format-dialog', 'cr-input#label'];
   await remoteCall.inputText(appId, driveNameQuery, label);
@@ -250,9 +242,7 @@
 /**
  * Tests validations for drive name length.
  */
-// @ts-ignore: error TS4111: Property 'formatDialogNameLength' comes from an
-// index signature, so it must be accessed with ['formatDialogNameLength'].
-testcase.formatDialogNameLength = async () => {
+export async function formatDialogNameLength() {
   await sendTestMessage({name: 'mountFakeUsb'});
   const appId = await setupFormatDialogTest();
 
@@ -293,14 +283,12 @@
 
   // Check that a 32 character name succeeds on ntfs.
   await checkSuccess(appId, 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEF', 'ntfs');
-};
+}
 
 /**
  * Test validations for invalid characters.
  */
-// @ts-ignore: error TS4111: Property 'formatDialogNameInvalid' comes from an
-// index signature, so it must be accessed with ['formatDialogNameInvalid'].
-testcase.formatDialogNameInvalid = async () => {
+export async function formatDialogNameInvalid() {
   await sendTestMessage({name: 'mountFakeUsb'});
   const appId = await setupFormatDialogTest();
 
@@ -312,14 +300,12 @@
 
   // Check that a name without invalid characters succeeds.
   await checkSuccess(appId, 'Nice name', 'vfat');
-};
+}
 
 /**
  * Tests opening the format dialog from the gear menu.
  */
-// @ts-ignore: error TS4111: Property 'formatDialogGearMenu' comes from an index
-// signature, so it must be accessed with ['formatDialogGearMenu'].
-testcase.formatDialogGearMenu = async () => {
+export async function formatDialogGearMenu() {
   await sendTestMessage({name: 'mountFakeUsb'});
   const appId = await setupFormatDialogTest();
 
@@ -390,4 +376,4 @@
 
   // Ensure the format menu item has disappeared.
   await remoteCall.waitForElement(appId, '#gear-menu-format[disabled][hidden]');
-};
+}
diff --git a/ui/file_manager/integration_tests/file_manager/grid_view.js b/ui/file_manager/integration_tests/file_manager/grid_view.ts
similarity index 60%
rename from ui/file_manager/integration_tests/file_manager/grid_view.js
rename to ui/file_manager/integration_tests/file_manager/grid_view.ts
index e435554..f8870b3 100644
--- a/ui/file_manager/integration_tests/file_manager/grid_view.js
+++ b/ui/file_manager/integration_tests/file_manager/grid_view.ts
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import type {ElementObject} from '../prod/file_manager/shared_types.js';
 import {addEntries, ENTRIES, getCaller, pending, repeatUntil, RootPath, TestEntryInfo} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {openNewWindow, remoteCall, setupAndWaitUntilReady} from './background.js';
 import {fileListKeyboardSelectionA11yImpl, fileListMouseSelectionA11yImpl} from './file_list.js';
@@ -13,16 +13,16 @@
 /**
  * Shows the grid view and checks the label texts of entries.
  *
- * @param {string} rootPath Root path to be used as a default current directory
- *     during initialization. Can be null, for no default path.
- * @param {Array<TestEntryInfo>} expectedSet Set of entries that are expected
- *     to appear in the grid view.
- * @return {Promise<void>} Promise to be fulfilled or rejected depending on the
- *     test result.
+ * @param rootPath Root path to be used as a default current directory during
+ *     initialization. Can be null, for no default path.
+ * @param expectedSet Set of entries that are expected to appear in the grid
+ *     view.
+ * @return Promise to be fulfilled or rejected depending on the test result.
  */
-async function showGridView(rootPath, expectedSet) {
+async function showGridView(
+    rootPath: string, expectedSet: TestEntryInfo[]): Promise<string> {
   const caller = getCaller();
-  const expectedLabels =
+  const expectedLabels: string[] =
       expectedSet.map((entryInfo) => entryInfo.nameText).sort();
 
   // Open Files app on |rootPath|.
@@ -35,133 +35,97 @@
 
   // Compare the grid labels of the entries.
   await repeatUntil(async () => {
-    const labels = await remoteCall.callRemoteTestUtil(
+    const labels: ElementObject[] = await remoteCall.callRemoteTestUtil(
         'queryAllElements', appId,
         ['grid:not([hidden]) .thumbnail-item .entry-name']);
-    // @ts-ignore: error TS7006: Parameter 'label' implicitly has an 'any' type.
-    const actualLabels = labels.map((label) => label.text).sort();
+    const actualLabels = labels.map<string>((label) => label.text!).sort();
 
-    // @ts-ignore: error TS2339: Property 'checkDeepEq' does not exist on type
-    // 'typeof test'.
-    if (chrome.test.checkDeepEq(expectedLabels, actualLabels)) {
+    if (chrome.test.checkDeepEq<string[]>(expectedLabels, actualLabels)) {
       return true;
     }
     return pending(
         caller, 'Failed to compare the grid lables, expected: %j, actual %j.',
         expectedLabels, actualLabels);
   });
-  // @ts-ignore: error TS2322: Type 'string' is not assignable to type 'void'.
   return appId;
 }
 
 /**
  * Tests to show grid view on a local directory.
  */
-// @ts-ignore: error TS4111: Property 'showGridViewDownloads' comes from an
-// index signature, so it must be accessed with ['showGridViewDownloads'].
-testcase.showGridViewDownloads = () => {
+export async function showGridViewDownloads() {
   return showGridView(RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET);
-};
+}
 
 /**
  * Tests to show grid view on a drive directory.
  */
-// @ts-ignore: error TS4111: Property 'showGridViewDrive' comes from an index
-// signature, so it must be accessed with ['showGridViewDrive'].
-testcase.showGridViewDrive = () => {
+export async function showGridViewDrive() {
   return showGridView(RootPath.DRIVE, BASIC_DRIVE_ENTRY_SET);
-};
+}
 
 /**
  * Tests to view-button switches to thumbnail (grid) view and clicking again
  * switches back to detail (file list) view.
  */
-// @ts-ignore: error TS4111: Property 'showGridViewButtonSwitches' comes from an
-// index signature, so it must be accessed with ['showGridViewButtonSwitches'].
-testcase.showGridViewButtonSwitches = async () => {
+export async function showGridViewButtonSwitches() {
   const appId = await showGridView(RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET);
 
   // Check that a11y message for switching to grid view.
   let a11yMessages =
-      // @ts-ignore: error TS2345: Argument of type 'void' is not assignable to
-      // parameter of type 'string | null'.
       await remoteCall.callRemoteTestUtil('getA11yAnnounces', appId, []);
   chrome.test.assertEq(1, a11yMessages.length, 'Missing a11y message');
   chrome.test.assertEq(
       'File list has changed to thumbnail view.', a11yMessages[0]);
 
   // Click view-button again to switch to detail view.
-  // @ts-ignore: error TS2345: Argument of type 'void' is not assignable to
-  // parameter of type 'string'.
   await remoteCall.waitAndClickElement(appId, '#view-button');
 
   // Wait for detail-view to be visible and grid to be hidden.
-  // @ts-ignore: error TS2345: Argument of type 'void' is not assignable to
-  // parameter of type 'string'.
   await remoteCall.waitForElement(appId, '#detail-table:not([hidden])');
-  // @ts-ignore: error TS2345: Argument of type 'void' is not assignable to
-  // parameter of type 'string'.
   await remoteCall.waitForElement(appId, 'grid[hidden]');
 
   // Check that a11y message for switching to list view.
   a11yMessages =
-      // @ts-ignore: error TS2345: Argument of type 'void' is not assignable to
-      // parameter of type 'string | null'.
       await remoteCall.callRemoteTestUtil('getA11yAnnounces', appId, []);
   chrome.test.assertEq(2, a11yMessages.length, 'Missing a11y message');
   chrome.test.assertEq('File list has changed to list view.', a11yMessages[1]);
-};
+}
 
 /**
  * Tests that selecting/de-selecting files with keyboard produces a11y messages.
  */
-// @ts-ignore: error TS4111: Property 'showGridViewKeyboardSelectionA11y' comes
-// from an index signature, so it must be accessed with
-// ['showGridViewKeyboardSelectionA11y'].
-testcase.showGridViewKeyboardSelectionA11y = async () => {
+export async function showGridViewKeyboardSelectionA11y() {
   const isGridView = true;
   return fileListKeyboardSelectionA11yImpl(isGridView);
-};
+}
 
 /**
  * Tests that selecting/de-selecting files with mouse produces a11y messages.
  */
-// @ts-ignore: error TS4111: Property 'showGridViewMouseSelectionA11y' comes
-// from an index signature, so it must be accessed with
-// ['showGridViewMouseSelectionA11y'].
-testcase.showGridViewMouseSelectionA11y = async () => {
+export async function showGridViewMouseSelectionA11y() {
   const isGridView = true;
   return fileListMouseSelectionA11yImpl(isGridView);
-};
+}
 
 /**
  * Tests that Grid View shows "Folders" and "Files" titles before folders and
  * files respectively.
  */
-// @ts-ignore: error TS4111: Property 'showGridViewTitles' comes from an index
-// signature, so it must be accessed with ['showGridViewTitles'].
-testcase.showGridViewTitles = async () => {
+export async function showGridViewTitles() {
   const appId = await showGridView(RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET);
 
-  const titles = await remoteCall.callRemoteTestUtil(
-      // @ts-ignore: error TS2345: Argument of type 'void' is not assignable to
-      // parameter of type 'string | null'.
+  const titles: ElementObject[] = await remoteCall.callRemoteTestUtil(
       'queryAllElements', appId, ['.thumbnail-grid .grid-title']);
   chrome.test.assertEq(2, titles.length, 'Grid view should show 2 titles');
-  // @ts-ignore: error TS7006: Parameter 'title' implicitly has an 'any' type.
-  const titleTexts = titles.map((title) => title.text).sort();
-  // @ts-ignore: error TS2339: Property 'checkDeepEq' does not exist on type
-  // 'typeof test'.
-  chrome.test.checkDeepEq(['Files', 'Folders'], titleTexts);
-};
+  const titleTexts = titles.map<string>((title) => title.text!).sort();
+  chrome.test.checkDeepEq<string[]>(['Files', 'Folders'], titleTexts);
+}
 
 /**
  * Tests that Grid View shows DocumentsProvider thumbnails.
  */
-// @ts-ignore: error TS4111: Property 'showGridViewDocumentsProvider' comes from
-// an index signature, so it must be accessed with
-// ['showGridViewDocumentsProvider'].
-testcase.showGridViewDocumentsProvider = async () => {
+export async function showGridViewDocumentsProvider() {
   const caller = getCaller();
 
   // Add files to the DocumentsProvider volume.
@@ -200,8 +164,7 @@
       const item = await remoteCall.waitForElement(
           appId, `#file-list [file-name="${fname}"]`);
       const thumbnailLoaded =
-          // @ts-ignore: error TS2532: Object is possibly 'undefined'.
-          item.attributes['class'].split(/\s+/).includes('thumbnail-loaded');
+          item.attributes!['class']?.split(/\s+/).includes('thumbnail-loaded');
       if (thumbnailLoaded !== hasThumbnail) {
         return pending(
             caller, 'Unexpected thumbnail state for %j: %j', fname,
@@ -210,17 +173,13 @@
     }
     return true;
   });
-};
+}
 
 /**
  * Tests that an encrypted file will have a corresponding icon.
  */
-// @ts-ignore: error TS4111: Property 'showGridViewEncryptedFile' comes from an
-// index signature, so it must be accessed with ['showGridViewEncryptedFile'].
-testcase.showGridViewEncryptedFile = async () => {
+export async function showGridViewEncryptedFile() {
   const appId =
-      // @ts-ignore: error TS4111: Property 'testCSEFile' comes from an index
-      // signature, so it must be accessed with ['testCSEFile'].
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.testCSEFile]);
 
   // Click the grid view button.
@@ -230,8 +189,7 @@
   const icon = await remoteCall.waitForElementStyles(
       appId, '.thumbnail-grid .no-thumbnail', ['-webkit-mask-image']);
   chrome.test.assertTrue(
-      // @ts-ignore: error TS2532: Object is possibly 'undefined'.
-      icon.styles['-webkit-mask-image'].includes('encrypted.svg'),
+      icon.styles!['-webkit-mask-image']?.includes('encrypted.svg') ?? false,
       'Icon does not seem to be the encrypted one');
 
   // Move mouse out of the view change button, so we won't have its hover text.
@@ -246,4 +204,4 @@
   const label = await remoteCall.waitForElement(
       appId, ['files-tooltip[visible=true]', '#label']);
   chrome.test.assertEq('Encrypted file', label.text);
-};
+}
diff --git a/ui/file_manager/integration_tests/file_manager/guest_os.js b/ui/file_manager/integration_tests/file_manager/guest_os.ts
similarity index 86%
rename from ui/file_manager/integration_tests/file_manager/guest_os.js
rename to ui/file_manager/integration_tests/file_manager/guest_os.ts
index 561cd68..3150ffe 100644
--- a/ui/file_manager/integration_tests/file_manager/guest_os.js
+++ b/ui/file_manager/integration_tests/file_manager/guest_os.ts
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import {ENTRIES, RootPath, sendTestMessage} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {remoteCall, setupAndWaitUntilReady} from './background.js';
 import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
@@ -11,9 +10,7 @@
 /**
  * Tests that Guest OS entries show up in the sidebar at files app launch.
  */
-// @ts-ignore: error TS4111: Property 'fakesListed' comes from an index
-// signature, so it must be accessed with ['fakesListed'].
-testcase.fakesListed = async () => {
+export async function fakesListed() {
   // Prepopulate the list with a bunch of guests.
   const names = ['Electra', 'Etcetera', 'Jemima'];
   for (const name of names) {
@@ -30,16 +27,13 @@
   for (const name of names) {
     await directoryTree.waitForItemByLabel(name);
   }
-};
+}
 
 /**
  * Tests that the list of guests is updated when new guests are added or
  * removed.
  */
-// @ts-ignore: error TS4111: Property 'listUpdatedWhenGuestsChanged' comes from
-// an index signature, so it must be accessed with
-// ['listUpdatedWhenGuestsChanged'].
-testcase.listUpdatedWhenGuestsChanged = async () => {
+export async function listUpdatedWhenGuestsChanged() {
   // Open the files app.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
@@ -77,16 +71,14 @@
   }
   await directoryTree.waitForPlaceholderItemsCountByType(
       'android_files', names.length);
-};
+}
 
 /**
  * Tests that clicking on a Guest OS entry in the sidebar mounts the
  * corresponding volume, and that the UI is updated appropriately (volume in
  * sidebar and not fake, contents show up once done loading, etc).
  */
-// @ts-ignore: error TS4111: Property 'mountGuestSuccess' comes from an index
-// signature, so it must be accessed with ['mountGuestSuccess'].
-testcase.mountGuestSuccess = async () => {
+export async function mountGuestSuccess() {
   const guestName = 'JennyAnyDots';
   // Start off with one guest.
   const guestId = await sendTestMessage({
@@ -132,16 +124,14 @@
 
   // And no more volume.
   await directoryTree.waitForItemLostByType('bruschetta');
-};
+}
 
 /**
  * Tests that clicking on a Guest OS Android entry in the sidebar mounts the
  * corresponding volume, and that the UI is update appropriately (volume in
  * sidebar and not fake, contents show up once done loading, etc).
  */
-// @ts-ignore: error TS4111: Property 'mountAndroidVolumeSuccess' comes from an
-// index signature, so it must be accessed with ['mountAndroidVolumeSuccess'].
-testcase.mountAndroidVolumeSuccess = async () => {
+export async function mountAndroidVolumeSuccess() {
   await sendTestMessage({name: 'unmountPlayFiles'});
   const guestName = 'Play files';
   // Start off with one guest.
@@ -188,4 +178,4 @@
 
   // And no more volume.
   await directoryTree.waitForItemLostByType('android_files');
-};
+}
diff --git a/ui/file_manager/integration_tests/file_manager/keyboard_operations.js b/ui/file_manager/integration_tests/file_manager/keyboard_operations.ts
similarity index 72%
rename from ui/file_manager/integration_tests/file_manager/keyboard_operations.js
rename to ui/file_manager/integration_tests/file_manager/keyboard_operations.ts
index 93069a7..20395a2 100644
--- a/ui/file_manager/integration_tests/file_manager/keyboard_operations.js
+++ b/ui/file_manager/integration_tests/file_manager/keyboard_operations.ts
@@ -3,20 +3,18 @@
 // found in the LICENSE file.
 
 import {ENTRIES, getCaller, pending, repeatUntil, RootPath, sendTestMessage, TestEntryInfo} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {IGNORE_APP_ERRORS, remoteCall, setupAndWaitUntilReady} from './background.js';
-import {TREEITEM_DOWNLOADS, TREEITEM_DRIVE} from './create_new_folder.js';
 import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
 
 /**
  * Waits until a dialog with an OK button is shown, and accepts it by clicking
  * on the dialog's OK button.
  *
- * @param {string} appId The Files app windowId.
- * @return {Promise<void>} Promise to be fulfilled after clicking the OK button.
+ * @param appId The Files app windowId.
+ * @return Promise to be fulfilled after clicking the OK button.
  */
-export async function waitAndAcceptDialog(appId) {
+async function waitAndAcceptDialog(appId: string): Promise<void> {
   const okButton = '.cr-dialog-ok';
 
   // Wait until the dialog is shown.
@@ -32,12 +30,10 @@
 /**
  * Tests copying a file to the same file list.
  *
- * @param {string} path The path to be tested, Downloads or Drive.
+ * @param path The path to be tested, Downloads or Drive.
  */
-async function keyboardCopy(path) {
+async function keyboardCopy(path: string) {
   const appId =
-      // @ts-ignore: error TS4111: Property 'world' comes from an index
-      // signature, so it must be accessed with ['world'].
       await setupAndWaitUntilReady(path, [ENTRIES.world], [ENTRIES.world]);
 
   // Copy the file into the same file list.
@@ -45,15 +41,9 @@
       await remoteCall.callRemoteTestUtil('copyFile', appId, ['world.ogv']),
       'copyFile failed');
   // Check: the copied file should appear in the file list.
-  // @ts-ignore: error TS4111: Property 'world' comes from an index signature,
-  // so it must be accessed with ['world'].
   const expectedEntryRows = [ENTRIES.world.getExpectedRow()].concat(
       [['world (1).ogv', '56 KB', 'OGG video']]);
   await remoteCall.waitForFiles(
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime:
-      // true; }' is not assignable to parameter of type '{ orderCheck: boolean
-      // | null | undefined; ignoreFileSize: boolean | null | undefined;
-      // ignoreLastModifiedTime: boolean | null | undefined; }'.
       appId, expectedEntryRows, {ignoreLastModifiedTime: true});
   const files = await remoteCall.callRemoteTestUtil('getFileList', appId, []);
   if (path === RootPath.DRIVE) {
@@ -68,11 +58,11 @@
 /**
  * Tests deleting a file from the file list.
  *
- * @param {string} path The path to be tested, Downloads or Drive.
- * @param {boolean=} confirmDeletion If the file system doesn't support trash,
- *     need to confirm the deletion.
+ * @param path The path to be tested, Downloads or Drive.
+ * @param confirmDeletion If the file system doesn't support trash, need to
+ *     confirm the deletion.
  */
-async function keyboardDelete(path, confirmDeletion = false) {
+async function keyboardDelete(path: string, confirmDeletion: boolean = false) {
   const appId =
       await setupAndWaitUntilReady(path, [ENTRIES.hello], [ENTRIES.hello]);
 
@@ -93,13 +83,13 @@
  * Tests deleting a folder from the file list. The folder is also shown in the
  * Files app directory tree, and should not be shown there when deleted.
  *
- * @param {string} path The path to be tested, Downloads or Drive.
- * @param {string} parentLabel The directory tree item label.
- * @param {boolean=} confirmDeletion If the file system doesn't support trash,
- *     need to confirm the deletion.
+ * @param path The path to be tested, Downloads or Drive.
+ * @param parentLabel The directory tree item label.
+ * @param confirmDeletion If the file system doesn't support trash, need to
+ *     confirm the deletion.
  */
 async function keyboardDeleteFolder(
-    path, parentLabel, confirmDeletion = false) {
+    path: string, parentLabel: string, confirmDeletion: boolean = false) {
   const appId =
       await setupAndWaitUntilReady(path, [ENTRIES.photos], [ENTRIES.photos]);
 
@@ -129,12 +119,13 @@
 /**
  * Renames a file.
  *
- * @param {string} appId The Files app windowId.
- * @param {string} oldName Old name of a file.
- * @param {string} newName New name of a file.
- * @return {Promise<void>} Promise to be fulfilled on success.
+ * @param appId The Files app windowId.
+ * @param oldName Old name of a file.
+ * @param newName New name of a file.
+ * @return Promise to be fulfilled on success.
  */
-async function renameFile(appId, oldName, newName) {
+async function renameFile(
+    appId: string, oldName: string, newName: string): Promise<void> {
   const textInput = '#file-list .table-row[renaming] input.rename';
 
   // Select the file.
@@ -161,11 +152,12 @@
  * Tests renaming a folder. An extra enter key is sent to the file list during
  * renaming to check the folder cannot be entered while it is being renamed.
  *
- * @param {string} path Initial path (Downloads or Drive).
- * @param {string} parentLabel The directory tree item label.
- * @return {Promise<void>} Promise to be fulfilled on success.
+ * @param path Initial path (Downloads or Drive).
+ * @param parentLabel The directory tree item label.
+ * @return Promise to be fulfilled on success.
  */
-async function testRenameFolder(path, parentLabel) {
+async function testRenameFolder(
+    path: string, parentLabel: string): Promise<void> {
   const textInput = '#file-list .table-row[renaming] input.rename';
   const appId =
       await setupAndWaitUntilReady(path, [ENTRIES.photos], [ENTRIES.photos]);
@@ -225,10 +217,10 @@
 /**
  * Tests renaming a file.
  *
- * @param {string} path Initial path (Downloads or Drive).
- * @return {Promise<void>} Promise to be fulfilled on success.
+ * @param path Initial path (Downloads or Drive).
+ * @return Promise to be fulfilled on success.
  */
-async function testRenameFile(path) {
+async function testRenameFile(path: string): Promise<void> {
   const newFile = [['New File Name.txt', '51 bytes', 'Plain text', '']];
 
   const appId =
@@ -253,79 +245,53 @@
   await remoteCall.waitForFiles(appId, newFile, {ignoreLastModifiedTime: true});
 }
 
-// @ts-ignore: error TS4111: Property 'keyboardCopyDownloads' comes from an
-// index signature, so it must be accessed with ['keyboardCopyDownloads'].
-testcase.keyboardCopyDownloads = () => {
+export function keyboardCopyDownloads() {
   return keyboardCopy(RootPath.DOWNLOADS);
-};
+}
 
-// @ts-ignore: error TS4111: Property 'keyboardCopyDrive' comes from an index
-// signature, so it must be accessed with ['keyboardCopyDrive'].
-testcase.keyboardCopyDrive = () => {
+export function keyboardCopyDrive() {
   return keyboardCopy(RootPath.DRIVE);
-};
+}
 
-// @ts-ignore: error TS4111: Property 'keyboardDeleteDownloads' comes from an
-// index signature, so it must be accessed with ['keyboardDeleteDownloads'].
-testcase.keyboardDeleteDownloads = () => {
+export function keyboardDeleteDownloads() {
   return keyboardDelete(RootPath.DOWNLOADS);
-};
+}
 
-// @ts-ignore: error TS4111: Property 'keyboardDeleteDrive' comes from an index
-// signature, so it must be accessed with ['keyboardDeleteDrive'].
-testcase.keyboardDeleteDrive = () => {
+export function keyboardDeleteDrive() {
   return keyboardDelete(RootPath.DRIVE, /*confirmDeletion=*/ true);
-};
+}
 
-// @ts-ignore: error TS4111: Property 'keyboardDeleteFolderDownloads' comes from
-// an index signature, so it must be accessed with
-// ['keyboardDeleteFolderDownloads'].
-testcase.keyboardDeleteFolderDownloads = () => {
-  return keyboardDeleteFolder(RootPath.DOWNLOADS, TREEITEM_DOWNLOADS);
-};
+export function keyboardDeleteFolderDownloads() {
+  return keyboardDeleteFolder(RootPath.DOWNLOADS, 'Downloads');
+}
 
-// @ts-ignore: error TS4111: Property 'keyboardDeleteFolderDrive' comes from an
-// index signature, so it must be accessed with ['keyboardDeleteFolderDrive'].
-testcase.keyboardDeleteFolderDrive = () => {
+export function keyboardDeleteFolderDrive() {
   return keyboardDeleteFolder(
-      RootPath.DRIVE, TREEITEM_DRIVE, /*confirmDeletion=*/ true);
-};
+      RootPath.DRIVE, 'My Drive', /*confirmDeletion=*/ true);
+}
 
-// @ts-ignore: error TS4111: Property 'renameFileDownloads' comes from an index
-// signature, so it must be accessed with ['renameFileDownloads'].
-testcase.renameFileDownloads = () => {
+export function renameFileDownloads() {
   return testRenameFile(RootPath.DOWNLOADS);
-};
+}
 
-// @ts-ignore: error TS4111: Property 'renameFileDrive' comes from an index
-// signature, so it must be accessed with ['renameFileDrive'].
-testcase.renameFileDrive = () => {
+export function renameFileDrive() {
   return testRenameFile(RootPath.DRIVE);
-};
+}
 
-// @ts-ignore: error TS4111: Property 'renameNewFolderDownloads' comes from an
-// index signature, so it must be accessed with ['renameNewFolderDownloads'].
-testcase.renameNewFolderDownloads = () => {
-  return testRenameFolder(RootPath.DOWNLOADS, TREEITEM_DOWNLOADS);
-};
+export function renameNewFolderDownloads() {
+  return testRenameFolder(RootPath.DOWNLOADS, 'Downloads');
+}
 
-// @ts-ignore: error TS4111: Property 'renameNewFolderDrive' comes from an index
-// signature, so it must be accessed with ['renameNewFolderDrive'].
-testcase.renameNewFolderDrive = () => {
-  return testRenameFolder(RootPath.DRIVE, TREEITEM_DRIVE);
-};
+export function renameNewFolderDrive() {
+  return testRenameFolder(RootPath.DRIVE, 'My Drive');
+}
 
 /**
  * Tests renaming partitions with the keyboard on the file list.
  */
-// @ts-ignore: error TS4111: Property 'renameRemovableWithKeyboardOnFileList'
-// comes from an index signature, so it must be accessed with
-// ['renameRemovableWithKeyboardOnFileList'].
-testcase.renameRemovableWithKeyboardOnFileList = async () => {
+export async function renameRemovableWithKeyboardOnFileList() {
   // Open Files app on local downloads.
   const appId =
-      // @ts-ignore: error TS4111: Property 'world' comes from an index
-      // signature, so it must be accessed with ['world'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.world]);
 
   // Mount removable device with partitions.
@@ -375,29 +341,21 @@
   await remoteCall.waitForElementLost(appId, textInput);
 
   // verify the partition was successfully renamed.
-  // @ts-ignore: error TS2532: Object is possibly 'undefined'.
-  expectedRows[2][0] = smallerPartitionName;
+  expectedRows[2]![0] = smallerPartitionName;
   await remoteCall.waitForFiles(
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime:
-      // true; }' is not assignable to parameter of type '{ orderCheck: boolean
-      // | null | undefined; ignoreFileSize: boolean | null | undefined;
-      // ignoreLastModifiedTime: boolean | null | undefined; }'.
       appId, expectedRows, {ignoreLastModifiedTime: true});
 
   // Even though the Files app rename flow worked, the background.js page
   // console errors about not being able to 'mount' the older volume name
   // due to a disk_mount_manager.cc error: user/fake-usb not found.
   return IGNORE_APP_ERRORS;
-};
+}
 
 /**
  * Tests that the root html element .focus-outline-visible class appears for
  * keyboard interaction and is removed on mouse interaction.
  */
-// @ts-ignore: error TS4111: Property 'keyboardFocusOutlineVisible' comes from
-// an index signature, so it must be accessed with
-// ['keyboardFocusOutlineVisible'].
-testcase.keyboardFocusOutlineVisible = async () => {
+export async function keyboardFocusOutlineVisible() {
   // Open Files app.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
@@ -412,16 +370,13 @@
 
   // Check: the html element should not have focus-outline-visible class.
   await remoteCall.waitForElementLost(appId, htmlFocusOutlineVisible);
-};
+}
 
 /**
  * Tests that the root html element .pointer-active class is added and removed
  * for mouse interaction.
  */
-// @ts-ignore: error TS4111: Property 'keyboardFocusOutlineVisibleMouse' comes
-// from an index signature, so it must be accessed with
-// ['keyboardFocusOutlineVisibleMouse'].
-testcase.keyboardFocusOutlineVisibleMouse = async () => {
+export async function keyboardFocusOutlineVisibleMouse() {
   // Open Files app.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
@@ -443,16 +398,13 @@
 
   // Check: the html element should not have pointer-active class.
   await remoteCall.waitForElementLost(appId, htmlPointerActive);
-};
+}
 
 /**
  * Tests that the root html element .pointer-active class will be removed with
  * pointerup event triggered by touch.
  */
-// @ts-ignore: error TS4111: Property 'pointerActiveRemovedByTouch' comes from
-// an index signature, so it must be accessed with
-// ['pointerActiveRemovedByTouch'].
-testcase.pointerActiveRemovedByTouch = async () => {
+export async function pointerActiveRemovedByTouch() {
   // Open Files app.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
@@ -472,15 +424,13 @@
 
   // Check: the html element should not have pointer-active class.
   await remoteCall.waitForElementLost(appId, htmlPointerActive);
-};
+}
 
 /**
  * Tests that the root html element .pointer-active class should not be added if
  * the PointerDown event is triggered by touch.
  */
-// @ts-ignore: error TS4111: Property 'noPointerActiveOnTouch' comes from an
-// index signature, so it must be accessed with ['noPointerActiveOnTouch'].
-testcase.noPointerActiveOnTouch = async () => {
+export async function noPointerActiveOnTouch() {
   // Open Files app.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
@@ -493,16 +443,13 @@
   // Check: the html element should not have pointer-active class.
   const htmlPointerActive = ['html.pointer-active'];
   await remoteCall.waitForElementLost(appId, htmlPointerActive);
-};
+}
 
 /**
  * Test that selecting "Google Drive" in the directory tree with the keyboard
  * expands it and selects "My Drive".
  */
-// @ts-ignore: error TS4111: Property 'keyboardSelectDriveDirectoryTree' comes
-// from an index signature, so it must be accessed with
-// ['keyboardSelectDriveDirectoryTree'].
-testcase.keyboardSelectDriveDirectoryTree = async () => {
+export async function keyboardSelectDriveDirectoryTree() {
   // Open Files app.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, [ENTRIES.world], [ENTRIES.hello]);
@@ -539,16 +486,13 @@
 
   // My Drive should be selected.
   await directoryTree.waitForSelectedItemByLabel('My Drive');
-};
+}
 
 /**
  * Tests that while the delete dialog is displayed, it is not possible to press
  * CONTROL-C to copy a file.
  */
-// @ts-ignore: error TS4111: Property 'keyboardDisableCopyWhenDialogDisplayed'
-// comes from an index signature, so it must be accessed with
-// ['keyboardDisableCopyWhenDialogDisplayed'].
-testcase.keyboardDisableCopyWhenDialogDisplayed = async () => {
+export async function keyboardDisableCopyWhenDialogDisplayed() {
   // Open Files app.
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.hello]);
@@ -588,14 +532,12 @@
   // Check: no files should be pasted.
   const files = TestEntryInfo.getExpectedRows([ENTRIES.hello]);
   await remoteCall.waitForFiles(appId, files);
-};
+}
 
 /**
  * Tests Ctrl+N opens a new windows crbug.com/933302.
  */
-// @ts-ignore: error TS4111: Property 'keyboardOpenNewWindow' comes from an
-// index signature, so it must be accessed with ['keyboardOpenNewWindow'].
-testcase.keyboardOpenNewWindow = async () => {
+export async function keyboardOpenNewWindow() {
   // Open Files app.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
@@ -622,4 +564,4 @@
         'Waiting for new window to open, current windows: ' +
             currentWindowsIds);
   });
-};
+}
diff --git a/ui/file_manager/integration_tests/file_manager/metadata.js b/ui/file_manager/integration_tests/file_manager/metadata.ts
similarity index 78%
rename from ui/file_manager/integration_tests/file_manager/metadata.js
rename to ui/file_manager/integration_tests/file_manager/metadata.ts
index bbfb9d7..195ed4f 100644
--- a/ui/file_manager/integration_tests/file_manager/metadata.js
+++ b/ui/file_manager/integration_tests/file_manager/metadata.ts
@@ -2,9 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {MetadataStats} from '../prod/file_manager/shared_types.js';
+import type {MetadataStats} from '../prod/file_manager/shared_types.js';
 import {addEntries, createTestFile, ENTRIES, EntryType, RootPath, TestEntryInfo} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {openNewWindow, remoteCall, setupAndWaitUntilReady} from './background.js';
 import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
@@ -12,11 +11,10 @@
 
 /**
  * Check if |value| equals the |desiredValue| within 1% margin of tolerance.
- * @param {number} value The variable value.
- * @param {number} desiredValue The desired value.
- * @return {boolean}
+ * @param value The variable value.
+ * @param desiredValue The desired value.
  */
-function equal1PercentMargin(value, desiredValue) {
+function equal1PercentMargin(value: number, desiredValue: number): boolean {
   // floor and ceil to account to at least +/-1 unit.
   const minValue = Math.floor(desiredValue * 0.99);
   const maxValue = Math.ceil(desiredValue * 1.01);
@@ -35,10 +33,9 @@
 /**
  * Creates a test file, which can be inside another folder, however parent
  * folders have to be created by the caller.
- * @param {string} path Folder path to be created,
- * @return {TestEntryInfo}
+ * @param path Folder path to be created,
  */
-function createTestFolder(path) {
+function createTestFolder(path: string): TestEntryInfo {
   const name = path.split('/').pop();
   return new TestEntryInfo({
     targetPath: path,
@@ -52,17 +49,13 @@
 
 /**
  * Creates a Shared Drive.
- * @param {string} name Shared Drive name.
- * @return {TestEntryInfo}
+ * @param name Shared Drive name.
  */
-function createTestTeamDrive(name) {
+function createTestTeamDrive(name: string): TestEntryInfo {
   return new TestEntryInfo({
     teamDriveName: name,
     type: EntryType.SHARED_DRIVE,
-    // @ts-ignore: error TS2561: Object literal may only specify known
-    // properties, but 'capabilites' does not exist in type
-    // 'TestEntryInfoOptions'. Did you mean to write 'capabilities'?
-    capabilites: {
+    capabilities: {
       canCopy: true,
       canDelete: true,
       canRename: true,
@@ -74,7 +67,6 @@
 
 /**
  * Entries used by Drive and Downloads tests.
- * @type {!Array<TestEntryInfo>}
  */
 const testEntries = [
   createTestFile('file1.txt'),
@@ -100,9 +92,7 @@
  *  - Opening Files app in My Drive with 8 files and 3 folders.
  *  - Navigate to My Drive > photos1 > folder2, which is empty.
  */
-// @ts-ignore: error TS4111: Property 'metadataDrive' comes from an index
-// signature, so it must be accessed with ['metadataDrive'].
-testcase.metadataDrive = async () => {
+export async function metadataDrive() {
   // Open Files app on Drive.
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, testEntries, testEntries);
@@ -113,8 +103,8 @@
   await directoryTree.navigateToPath('/My Drive/photos1/folder1');
 
   // Fetch the metadata stats.
-  const metadataStats = /** @type {!MetadataStats} */
-      (await remoteCall.callRemoteTestUtil('getMetadataStats', appId, []));
+  const metadataStats =
+      await remoteCall.callRemoteTestUtil('getMetadataStats', appId, []);
 
   // Verify the number of metadata operations generated by the whole
   // navigation above.
@@ -134,16 +124,14 @@
   chrome.test.assertEq(11, metadataStats.clearCacheCount);
   chrome.test.assertEq(0, metadataStats.clearAllCount);
   chrome.test.assertEq(0, metadataStats.invalidateCount);
-};
+}
 
 /**
  * Measures the number of metadata operations generated for:
  *  - Opening Files app in Downloads with 8 files and 3 folders.
  *  - Navigate to Downloads > photos1 > folder1 which is empty.
  */
-// @ts-ignore: error TS4111: Property 'metadataDownloads' comes from an index
-// signature, so it must be accessed with ['metadataDownloads'].
-testcase.metadataDownloads = async () => {
+export async function metadataDownloads() {
   // Open Files app on Downloads.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, testEntries, testEntries);
@@ -179,7 +167,7 @@
   chrome.test.assertEq(11, metadataStats.clearCacheCount);
   chrome.test.assertEq(0, metadataStats.clearAllCount);
   chrome.test.assertEq(0, metadataStats.invalidateCount);
-};
+}
 
 /**
  * Measures the number of metadata operations generated for:
@@ -189,9 +177,7 @@
  * Using 50 files and 50 folders because in the Drive backend it has a
  * throttle for max of 20 concurrent operations.
  */
-// @ts-ignore: error TS4111: Property 'metadataLargeDrive' comes from an index
-// signature, so it must be accessed with ['metadataLargeDrive'].
-testcase.metadataLargeDrive = async () => {
+export async function metadataLargeDrive() {
   const entries = [createTestFolder('folder1')];
 
   const folder1ExpectedRows = [];
@@ -222,41 +208,38 @@
   // to wait until the metadata stats to have the expected count.
   // If the asserts below fail, check if your change has increased the number
   // of metadata operations, because they impact the overall app performance.
-  // @ts-ignore: error TS7006: Parameter 'metadataStats' implicitly has an 'any'
-  // type.
-  const checkMetadata = (metadataStats) => {
-    let result = true;
+  const checkMetadata = (metadataStats: MetadataStats) => {
     // Full fetch tally:
     //    51 files in My Drive.
     // +  50 files in My Drive>folder1.
     // +   1 My Drive root.
     // +   1 read again folder1 when naviated to it.
     // = 103
-    // @ts-ignore: error TS2447: The '&=' operator is not allowed for boolean
-    // types. Consider using '&&' instead.
-    result &= equal1PercentMargin(metadataStats.fullFetch, 103);
+    if (!equal1PercentMargin(metadataStats.fullFetch, 103)) {
+      return false;
+    }
 
     // 50 team drives cached, reading from file list when navigating to
     // /team_drives, then read cached when expanding directory tree.
-    // @ts-ignore: error TS2447: The '&=' operator is not allowed for boolean
-    // types. Consider using '&&' instead.
-    result &= metadataStats.fromCache < 70;
+    if (metadataStats.fromCache >= 70) {
+      return false;
+    }
 
     // Cleared 51 folders when navigated out of My Drive and clearing file
     // list.
-    // @ts-ignore: error TS2447: The '&=' operator is not allowed for boolean
-    // types. Consider using '&&' instead.
-    result &= equal1PercentMargin(metadataStats.clearCacheCount, 51);
-    // @ts-ignore: error TS2447: The '&=' operator is not allowed for boolean
-    // types. Consider using '&&' instead.
-    result &= metadataStats.clearAllCount === 0;
-    // @ts-ignore: error TS2447: The '&=' operator is not allowed for boolean
-    // types. Consider using '&&' instead.
-    result &= metadataStats.invalidateCount === 0;
-    return result;
+    if (!equal1PercentMargin(metadataStats.clearCacheCount, 51)) {
+      return false;
+    }
+    if (metadataStats.clearAllCount !== 0) {
+      return false;
+    }
+    if (metadataStats.invalidateCount !== 0) {
+      return false;
+    }
+    return true;
   };
-  await remoteCall.waitFor('getMetadataStats', appId, checkMetadata);
-};
+  await remoteCall.waitFor('getMetadataStats', appId, checkMetadata as any);
+}
 
 /**
  * Measures the number of metadata operations generated for:
@@ -264,9 +247,7 @@
  *  - Navigate to Shared Drives, with 50 team drives.
  *  - Expand Shared Drives to display the 50 team drives..
  */
-// @ts-ignore: error TS4111: Property 'metadataTeamDrives' comes from an index
-// signature, so it must be accessed with ['metadataTeamDrives'].
-testcase.metadataTeamDrives = async () => {
+export async function metadataTeamDrives() {
   const entries = [];
   const driveEntries = [];
 
@@ -334,14 +315,12 @@
   chrome.test.assertEq(100, metadataStats.clearCacheCount);
   chrome.test.assertEq(0, metadataStats.clearAllCount);
   chrome.test.assertEq(0, metadataStats.invalidateCount);
-};
+}
 
 /**
  *  Tests that fetching content metadata from a DocumentsProvider completes.
  */
-// @ts-ignore: error TS4111: Property 'metadataDocumentsProvider' comes from an
-// index signature, so it must be accessed with ['metadataDocumentsProvider'].
-testcase.metadataDocumentsProvider = async () => {
+export async function metadataDocumentsProvider() {
   // Add files to the DocumentsProvider volume.
   await addEntries(['documents_provider'], BASIC_LOCAL_ENTRY_SET);
 
@@ -365,7 +344,5 @@
       'getContentMetadata', appId, [['mediaMimeType']]);
 
   // Check nothing in the result was returned.
-  // @ts-ignore: error TS2339: Property 'checkDeepEq' does not exist on type
-  // 'typeof test'.
-  chrome.test.checkDeepEq([], result);
-};
+  chrome.test.checkDeepEq<string[]>([], result);
+}
diff --git a/ui/file_manager/integration_tests/file_manager/my_files.js b/ui/file_manager/integration_tests/file_manager/my_files.ts
similarity index 67%
rename from ui/file_manager/integration_tests/file_manager/my_files.js
rename to ui/file_manager/integration_tests/file_manager/my_files.ts
index 45e98ad..c328518e 100644
--- a/ui/file_manager/integration_tests/file_manager/my_files.js
+++ b/ui/file_manager/integration_tests/file_manager/my_files.ts
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import {addEntries, ENTRIES, EntryType, RootPath, sendTestMessage, TestEntryInfo} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {mountCrostini, remoteCall, setupAndWaitUntilReady} from './background.js';
 import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
@@ -11,9 +10,9 @@
 /**
  * Select My files in directory tree and wait for load.
  *
- * @param {string} appId ID of the app window.
+ * @param appId ID of the app window.
  */
-async function selectMyFiles(appId) {
+async function selectMyFiles(appId: string) {
   // Select My Files folder.
   const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
   await directoryTree.selectItemByLabel('My files');
@@ -24,20 +23,13 @@
   const crostiniRow = ['Linux files', '--', 'Folder'];
   await remoteCall.waitForFiles(
       appId, [downloadsRow, playFilesRow, crostiniRow],
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreFileSize: true;
-      // ignoreLastModifiedTime: true; }' is not assignable to parameter of type
-      // '{ orderCheck: boolean | null | undefined; ignoreFileSize: boolean |
-      // null | undefined; ignoreLastModifiedTime: boolean | null | undefined;
-      // }'.
       {ignoreFileSize: true, ignoreLastModifiedTime: true});
 }
 
 /**
  * Tests if MyFiles is displayed when flag is true.
  */
-// @ts-ignore: error TS4111: Property 'showMyFiles' comes from an index
-// signature, so it must be accessed with ['showMyFiles'].
-testcase.showMyFiles = async () => {
+export async function showMyFiles() {
   const expectedElementLabels = [
     'Recent',
     'My files',
@@ -53,8 +45,6 @@
 
   // Open Files app on local Downloads.
   const appId =
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
   const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
@@ -68,7 +58,7 @@
   // Check that My Files is displayed on breadcrumbs.
   const expectedBreadcrumbs = '/My files/Downloads';
   remoteCall.waitUntilCurrentDirectoryIsChanged(appId, expectedBreadcrumbs);
-};
+}
 
 /**
  * Tests directory tree refresh doesn't hide Downloads folder.
@@ -78,13 +68,9 @@
  * DirectoryTree expects NavigationModelItem to be the same instance through
  * updates.
  */
-// @ts-ignore: error TS4111: Property 'directoryTreeRefresh' comes from an index
-// signature, so it must be accessed with ['directoryTreeRefresh'].
-testcase.directoryTreeRefresh = async () => {
+export async function directoryTreeRefresh() {
   // Open Files app on local Downloads.
   const appId =
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
   // Mount a USB volume.
@@ -96,20 +82,15 @@
 
   // Select Downloads folder.
   await directoryTree.selectItemByLabel('Downloads');
-};
+}
 
 /**
  * Tests My Files displaying Downloads on file list (RHS) and opening Downloads
  * from file list.
  */
-// @ts-ignore: error TS4111: Property 'myFilesDisplaysAndOpensEntries' comes
-// from an index signature, so it must be accessed with
-// ['myFilesDisplaysAndOpensEntries'].
-testcase.myFilesDisplaysAndOpensEntries = async () => {
+export async function myFilesDisplaysAndOpensEntries() {
   // Open Files app on local Downloads.
   const appId =
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
   // Select My files in directory tree.
@@ -122,20 +103,13 @@
 
   // Wait for file list to Downloads' content.
   await remoteCall.waitForFiles(
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       appId, [ENTRIES.beautiful.getExpectedRow()],
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreFileSize: true;
-      // ignoreLastModifiedTime: true; }' is not assignable to parameter of type
-      // '{ orderCheck: boolean | null | undefined; ignoreFileSize: boolean |
-      // null | undefined; ignoreLastModifiedTime: boolean | null | undefined;
-      // }'.
       {ignoreFileSize: true, ignoreLastModifiedTime: true});
 
   // Get the selected navigation tree item.
   const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
   await directoryTree.waitForSelectedItemByLabel('Downloads');
-};
+}
 
 /**
  * Tests My files updating its children recursively.
@@ -143,9 +117,7 @@
  * If it doesn't update its children recursively it can cause directory tree to
  * not show or hide sub-folders crbug.com/864453.
  */
-// @ts-ignore: error TS4111: Property 'myFilesUpdatesChildren' comes from an
-// index signature, so it must be accessed with ['myFilesUpdatesChildren'].
-testcase.myFilesUpdatesChildren = async () => {
+export async function myFilesUpdatesChildren() {
   const hiddenFolder = new TestEntryInfo({
     type: EntryType.DIRECTORY,
     targetPath: '.hidden-folder',
@@ -162,8 +134,6 @@
 
   // Open Files app on local Downloads.
   const appId =
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
   // Select Downloads folder.
@@ -194,14 +164,7 @@
 
   // Check the hidden folder to be displayed in RHS.
   await remoteCall.waitForFiles(
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       appId, TestEntryInfo.getExpectedRows([hiddenFolder, ENTRIES.beautiful]),
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreFileSize: true;
-      // ignoreLastModifiedTime: true; }' is not assignable to parameter of type
-      // '{ orderCheck: boolean | null | undefined; ignoreFileSize: boolean |
-      // null | undefined; ignoreLastModifiedTime: boolean | null | undefined;
-      // }'.
       {ignoreFileSize: true, ignoreLastModifiedTime: true});
 
   // Wait for Downloads folder to have the expand icon because of hidden folder.
@@ -214,15 +177,13 @@
   // Check the hidden folder to be displayed in LHS.
   // Children of Downloads and named ".hidden-folder".
   await directoryTree.waitForChildItemByLabel('Downloads', '.hidden-folder');
-};
+}
 
 /**
  * Check naming a folder after navigating inside MyFiles using file list (RHS).
  * crbug.com/889636.
  */
-// @ts-ignore: error TS4111: Property 'myFilesFolderRename' comes from an index
-// signature, so it must be accessed with ['myFilesFolderRename'].
-testcase.myFilesFolderRename = async () => {
+export async function myFilesFolderRename() {
   const textInput = '#file-list .table-row[renaming] input.rename';
 
   // Open Files app on local Downloads.
@@ -271,24 +232,15 @@
   const expectedRows2 = [['new name', '--', 'Folder', '']];
   await remoteCall.waitForFiles(
       appId, expectedRows2,
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreFileSize: true;
-      // ignoreLastModifiedTime: true; }' is not assignable to parameter of type
-      // '{ orderCheck: boolean | null | undefined; ignoreFileSize: boolean |
-      // null | undefined; ignoreLastModifiedTime: boolean | null | undefined;
-      // }'.
       {ignoreFileSize: true, ignoreLastModifiedTime: true});
-};
+}
 
 /**
  * Tests that MyFiles only auto expands once.
  */
-// @ts-ignore: error TS4111: Property 'myFilesAutoExpandOnce' comes from an
-// index signature, so it must be accessed with ['myFilesAutoExpandOnce'].
-testcase.myFilesAutoExpandOnce = async () => {
+export async function myFilesAutoExpandOnce() {
   // Open Files app on local Downloads.
   const appId = await setupAndWaitUntilReady(
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       RootPath.DOWNLOADS, [ENTRIES.photos], [ENTRIES.beautiful]);
   const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
 
@@ -304,28 +256,18 @@
 
   // Wait for My Drive to selected.
   await remoteCall.waitForFiles(
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       appId, [ENTRIES.beautiful.getExpectedRow()],
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreFileSize: true;
-      // ignoreLastModifiedTime: true; }' is not assignable to parameter of type
-      // '{ orderCheck: boolean | null | undefined; ignoreFileSize: boolean |
-      // null | undefined; ignoreLastModifiedTime: boolean | null | undefined;
-      // }'.
       {ignoreFileSize: true, ignoreLastModifiedTime: true});
 
   // Check that MyFiles is still collapsed.
   await directoryTree.waitForItemToCollapseByLabel('My files');
-};
+}
 
 /**
  * Tests that My files refreshes its contents when PlayFiles is mounted.
  * crbug.com/946972.
  */
-// @ts-ignore: error TS4111: Property 'myFilesUpdatesWhenAndroidVolumeMounts'
-// comes from an index signature, so it must be accessed with
-// ['myFilesUpdatesWhenAndroidVolumeMounts'].
-testcase.myFilesUpdatesWhenAndroidVolumeMounts = async () => {
+export async function myFilesUpdatesWhenAndroidVolumeMounts() {
   // Mount Downloads.
   await sendTestMessage({name: 'mountDownloads'});
 
@@ -334,8 +276,6 @@
 
   // Open Files app on local Downloads.
   const appId =
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
   const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
 
@@ -346,11 +286,6 @@
   await directoryTree.selectItemByLabel('My files');
   await remoteCall.waitForFiles(
       appId, [downloadsRow, crostiniRow],
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreFileSize: true;
-      // ignoreLastModifiedTime: true; }' is not assignable to parameter of type
-      // '{ orderCheck: boolean | null | undefined; ignoreFileSize: boolean |
-      // null | undefined; ignoreLastModifiedTime: boolean | null | undefined;
-      // }'.
       {ignoreFileSize: true, ignoreLastModifiedTime: true});
 
   // Mount Play files volume.
@@ -363,11 +298,6 @@
   await directoryTree.waitForItemByLabel('Play files');
   await remoteCall.waitForFiles(
       appId, [downloadsRow, playFilesRow, crostiniRow],
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreFileSize: true;
-      // ignoreLastModifiedTime: true; }' is not assignable to parameter of type
-      // '{ orderCheck: boolean | null | undefined; ignoreFileSize: boolean |
-      // null | undefined; ignoreLastModifiedTime: boolean | null | undefined;
-      // }'.
       {ignoreFileSize: true, ignoreLastModifiedTime: true});
 
   // Un-mount Play files volume.
@@ -379,27 +309,18 @@
   // Check: Play files should disappear from file list.
   await remoteCall.waitForFiles(
       appId, [downloadsRow, crostiniRow],
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreFileSize: true;
-      // ignoreLastModifiedTime: true; }' is not assignable to parameter of type
-      // '{ orderCheck: boolean | null | undefined; ignoreFileSize: boolean |
-      // null | undefined; ignoreLastModifiedTime: boolean | null | undefined;
-      // }'.
       {ignoreFileSize: true, ignoreLastModifiedTime: true});
 
   // Check: Play files should disappear from directory tree.
   await directoryTree.waitForItemLostByLabel('Play files');
-};
+}
 
 /**
  * Tests that toolbar delete is not shown for Downloads, or Linux files.
  */
-// @ts-ignore: error TS4111: Property 'myFilesToolbarDelete' comes from an index
-// signature, so it must be accessed with ['myFilesToolbarDelete'].
-testcase.myFilesToolbarDelete = async () => {
+export async function myFilesToolbarDelete() {
   // Open Files app on local Downloads.
   const appId =
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
   // Select My files in directory tree.
@@ -429,4 +350,4 @@
 
   // Test that the delete button isn't visible.
   await remoteCall.waitForElement(appId, hiddenDeleteButton);
-};
+}
diff --git a/ui/file_manager/integration_tests/file_manager/office.js b/ui/file_manager/integration_tests/file_manager/office.ts
similarity index 65%
rename from ui/file_manager/integration_tests/file_manager/office.js
rename to ui/file_manager/integration_tests/file_manager/office.ts
index 659c51a9a..8ff756e 100644
--- a/ui/file_manager/integration_tests/file_manager/office.js
+++ b/ui/file_manager/integration_tests/file_manager/office.ts
@@ -3,17 +3,14 @@
 // found in the LICENSE file.
 
 import {ENTRIES, getCaller, pending, repeatUntil, RootPath, sendTestMessage} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {remoteCall, setupAndWaitUntilReady} from './background.js';
 import {FILE_MANAGER_SWA_APP_ID, FILE_SWA_BASE_URL} from './test_data.js';
 
 /**
  * Returns 'Open in Google Docs' task descriptor.
- *
- * @return {!FileTaskDescriptor}
  */
-function openDocWithDriveDescriptor() {
+function openDocWithDriveDescriptor(): FileTaskDescriptor {
   const filesAppId = FILE_MANAGER_SWA_APP_ID;
   const filesTaskType = 'web';
   const actionId = `${FILE_SWA_BASE_URL}?open-web-drive-office-word`;
@@ -23,10 +20,8 @@
 
 /**
  * Returns 'Open with Excel' task descriptor.
- *
- * @return {!FileTaskDescriptor}
  */
-function openExcelWithDriveDescriptor() {
+function openExcelWithDriveDescriptor(): FileTaskDescriptor {
   const filesAppId = FILE_MANAGER_SWA_APP_ID;
   const filesTaskType = 'web';
   const actionId = `${FILE_SWA_BASE_URL}?open-web-drive-office-excel`;
@@ -36,10 +31,8 @@
 
 /**
  * Returns 'Open in PowerPoint' task descriptor.
- *
- * @return {!FileTaskDescriptor}
  */
-function openPowerPointWithDriveDescriptor() {
+function openPowerPointWithDriveDescriptor(): FileTaskDescriptor {
   const filesAppId = FILE_MANAGER_SWA_APP_ID;
   const filesTaskType = 'web';
   const actionId = `${FILE_SWA_BASE_URL}?open-web-drive-office-powerpoint`;
@@ -52,11 +45,10 @@
  * Waits for the expected number of tasks executions, and returns the descriptor
  * of the last executed task.
  *
- * @param {string} appId Window ID.
- * @param {number} expectedCount
- * @return {!Promise<!FileTaskDescriptor>}
+ * @param appId Window ID.
  */
-async function getExecutedTask(appId, expectedCount = 1) {
+async function getExecutedTask(
+    appId: string, expectedCount: number = 1): Promise<FileTaskDescriptor> {
   const caller = getCaller();
 
   // Wait until a task has been executed.
@@ -79,20 +71,14 @@
   return executeTaskArgs[0];
 }
 
-// @ts-ignore: error TS4111: Property 'openOfficeWordFile' comes from an index
-// signature, so it must be accessed with ['openOfficeWordFile'].
-testcase.openOfficeWordFile = async () => {
+export async function openOfficeWordFile() {
   await sendTestMessage({
     name: 'expectFileTask',
-    // @ts-ignore: error TS4111: Property 'smallDocxHosted' comes from an index
-    // signature, so it must be accessed with ['smallDocxHosted'].
     fileNames: [ENTRIES.smallDocxHosted.targetPath],
     openType: 'launch',
   });
 
   const appId = await setupAndWaitUntilReady(
-      // @ts-ignore: error TS4111: Property 'smallDocxHosted' comes from an
-      // index signature, so it must be accessed with ['smallDocxHosted'].
       RootPath.DRIVE, [], [ENTRIES.smallDocxHosted]);
 
   // Disable office setup flow so the dialog doesn't open when the file is
@@ -101,8 +87,6 @@
 
   // Open file.
   chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
-      // @ts-ignore: error TS4111: Property 'smallDocxHosted' comes from an
-      // index signature, so it must be accessed with ['smallDocxHosted'].
       'openFile', appId, [ENTRIES.smallDocxHosted.nameText]));
 
   // Check that the Word file's alternate URL has been opened in a browser
@@ -110,17 +94,11 @@
   // opened from drive have this query parameter added
   // (https://crrev.com/c/3867338).
   await remoteCall.waitForLastOpenedBrowserTabUrl(
-      // @ts-ignore: error TS4111: Property 'smallDocxHosted' comes from an
-      // index signature, so it must be accessed with ['smallDocxHosted'].
       ENTRIES.smallDocxHosted.alternateUrl.concat('&cros_files=true'));
-};
+}
 
-// @ts-ignore: error TS4111: Property 'openOfficeWordFromMyFiles' comes from an
-// index signature, so it must be accessed with ['openOfficeWordFromMyFiles'].
-testcase.openOfficeWordFromMyFiles = async () => {
+export async function openOfficeWordFromMyFiles() {
   const appId =
-      // @ts-ignore: error TS4111: Property 'smallDocx' comes from an index
-      // signature, so it must be accessed with ['smallDocx'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.smallDocx]);
 
   // Fake chrome.fileManagerPrivate.executeTask to return
@@ -132,8 +110,6 @@
 
   // Open file.
   chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
-      // @ts-ignore: error TS4111: Property 'smallDocx' comes from an index
-      // signature, so it must be accessed with ['smallDocx'].
       'openFile', appId, [ENTRIES.smallDocx.nameText]));
 
   // The available Office task should be "Upload to Drive".
@@ -145,18 +121,12 @@
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
   chrome.test.assertEq(1, removedCount);
-};
+}
 
 // Tests that "Upload to Drive" cannot be enabled if the "Upload Office To
 // Cloud" flag is disabled (test setup similar to `openOfficeWordFromMyFiles`).
-// @ts-ignore: error TS4111: Property
-// 'uploadToDriveRequiresUploadOfficeToCloudEnabled' comes from an index
-// signature, so it must be accessed with
-// ['uploadToDriveRequiresUploadOfficeToCloudEnabled'].
-testcase.uploadToDriveRequiresUploadOfficeToCloudEnabled = async () => {
+export async function uploadToDriveRequiresUploadOfficeToCloudEnabled() {
   const appId =
-      // @ts-ignore: error TS4111: Property 'smallDocx' comes from an index
-      // signature, so it must be accessed with ['smallDocx'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.smallDocx]);
   // Fake chrome.fileManagerPrivate.executeTask to return
   // chrome.fileManagerPrivate.TaskResult.EMPTY.
@@ -167,8 +137,6 @@
 
   // Open file.
   chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
-      // @ts-ignore: error TS4111: Property 'smallDocx' comes from an index
-      // signature, so it must be accessed with ['smallDocx'].
       'openFile', appId, [ENTRIES.smallDocx.nameText]));
 
   // Since the Upload Office To Cloud flag isn't enabled, the Upload to Drive
@@ -184,14 +152,10 @@
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
   chrome.test.assertEq(1, removedCount);
-};
+}
 
-// @ts-ignore: error TS4111: Property 'openOfficeWordFromDrive' comes from an
-// index signature, so it must be accessed with ['openOfficeWordFromDrive'].
-testcase.openOfficeWordFromDrive = async () => {
+export async function openOfficeWordFromDrive() {
   const appId = await setupAndWaitUntilReady(
-      // @ts-ignore: error TS4111: Property 'smallDocxHosted' comes from an
-      // index signature, so it must be accessed with ['smallDocxHosted'].
       RootPath.DRIVE, [], [ENTRIES.smallDocxHosted]);
 
   // Fake chrome.fileManagerPrivate.executeTask to return
@@ -203,8 +167,6 @@
 
   // Open file.
   chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
-      // @ts-ignore: error TS4111: Property 'smallDocxHosted' comes from an
-      // index signature, so it must be accessed with ['smallDocxHosted'].
       'openFile', appId, [ENTRIES.smallDocxHosted.nameText]));
 
   // The Drive/Docs task should be available and executed.
@@ -215,14 +177,10 @@
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
   chrome.test.assertEq(1, removedCount);
-};
+}
 
-// @ts-ignore: error TS4111: Property 'openOfficeExcelFromDrive' comes from an
-// index signature, so it must be accessed with ['openOfficeExcelFromDrive'].
-testcase.openOfficeExcelFromDrive = async () => {
+export async function openOfficeExcelFromDrive() {
   const appId = await setupAndWaitUntilReady(
-      // @ts-ignore: error TS4111: Property 'smallXlsxPinned' comes from an
-      // index signature, so it must be accessed with ['smallXlsxPinned'].
       RootPath.DRIVE, [], [ENTRIES.smallXlsxPinned]);
 
   // Fake chrome.fileManagerPrivate.executeTask to return
@@ -234,8 +192,6 @@
 
   // Open file.
   chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
-      // @ts-ignore: error TS4111: Property 'smallXlsxPinned' comes from an
-      // index signature, so it must be accessed with ['smallXlsxPinned'].
       'openFile', appId, [ENTRIES.smallXlsxPinned.nameText]));
 
   // The Web Drive Office Excel task should be available and executed.
@@ -246,15 +202,10 @@
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
   chrome.test.assertEq(1, removedCount);
-};
+}
 
-// @ts-ignore: error TS4111: Property 'openOfficePowerPointFromDrive' comes from
-// an index signature, so it must be accessed with
-// ['openOfficePowerPointFromDrive'].
-testcase.openOfficePowerPointFromDrive = async () => {
+export async function openOfficePowerPointFromDrive() {
   const appId = await setupAndWaitUntilReady(
-      // @ts-ignore: error TS4111: Property 'smallPptxPinned' comes from an
-      // index signature, so it must be accessed with ['smallPptxPinned'].
       RootPath.DRIVE, [], [ENTRIES.smallPptxPinned]);
 
   // Fake chrome.fileManagerPrivate.executeTask to return
@@ -266,8 +217,6 @@
 
   // Open file.
   chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
-      // @ts-ignore: error TS4111: Property 'smallPptxPinned' comes from an
-      // index signature, so it must be accessed with ['smallPptxPinned'].
       'openFile', appId, [ENTRIES.smallPptxPinned.nameText]));
 
   // The Web Drive Office PowerPoint task should be available and executed.
@@ -278,19 +227,14 @@
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
   chrome.test.assertEq(1, removedCount);
-};
+}
 
-// @ts-ignore: error TS4111: Property 'openMultipleOfficeWordFromDrive' comes
-// from an index signature, so it must be accessed with
-// ['openMultipleOfficeWordFromDrive'].
-testcase.openMultipleOfficeWordFromDrive = async () => {
+export async function openMultipleOfficeWordFromDrive() {
   const appId = await setupAndWaitUntilReady(
       RootPath.DRIVE, [],
-      // @ts-ignore: error TS4111: Property 'smallDocxHosted' comes from an
-      // index signature, so it must be accessed with ['smallDocxHosted'].
       [ENTRIES.smallDocx, ENTRIES.smallDocxPinned, ENTRIES.smallDocxHosted]);
 
-  const enterKey = ['#file-list', 'Enter', false, false, false];
+  const enterKey = ['#file-list', 'Enter', false, false, false] as const;
 
   // Fake chrome.fileManagerPrivate.executeTask to return
   // chrome.fileManagerPrivate.TaskResult.OPENED.
@@ -300,28 +244,23 @@
   await remoteCall.callRemoteTestUtil('foregroundFake', appId, [fakeData]);
 
   // Select all the files.
-  const ctrlA = ['#file-list', 'a', true, false, false];
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
+  const ctrlA = ['#file-list', 'a', true, false, false] as const;
   await remoteCall.fakeKeyDown(appId, ...ctrlA);
 
   // Check: the file-list should show 3 selected files.
   const caller = getCaller();
-  // @ts-ignore: error TS7030: Not all code paths return a value.
   await repeatUntil(async () => {
     const element = await remoteCall.waitForElement(
         appId, '.check-select #files-selected-label');
-    if (element.text !== '3 files selected') {
-      return pending(
-          caller, `Waiting for files to be selected, got: ${element.text}`);
+    if (element.text === '3 files selected') {
+      return;
     }
+    return pending(
+        caller, `Waiting for files to be selected, got: ${element.text}`);
   });
 
   let taskDescriptor;
   let expectedExecuteTaskCount = 0;
-  // @ts-ignore: error TS6133: 'histogramCount' is declared but its value is
-  // never read.
-  let histogramCount;
 
   // Wait for the tasks calculation to complete, updating the "Open" button.
   await remoteCall.waitForElement(appId, '#tasks[get-tasks-completed]');
@@ -334,8 +273,6 @@
   // "docs.google.com" alternate URL.
 
   // Press Enter to execute the task.
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
   remoteCall.fakeKeyDown(appId, ...enterKey);
 
   // Check that it's the Docs task.
@@ -346,20 +283,14 @@
 
   // Unselect the file that doesn't have an alternate URL.
   await remoteCall.waitAndClickElement(
-      // @ts-ignore: error TS4111: Property 'smallDocx' comes from an index
-      // signature, so it must be accessed with ['smallDocx'].
       appId, `#file-list [file-name="${ENTRIES.smallDocx.nameText}"]`,
       {ctrl: true});
 
   // Wait for the file to be unselected.
   await remoteCall.waitForElement(
-      // @ts-ignore: error TS4111: Property 'smallDocx' comes from an index
-      // signature, so it must be accessed with ['smallDocx'].
       appId, `[file-name="${ENTRIES.smallDocx.nameText}"]:not([selected])`);
 
   // Press Enter.
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
   remoteCall.fakeKeyDown(appId, ...enterKey);
 
   // The Drive/Docs task should be available and executed.
@@ -371,15 +302,10 @@
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
   chrome.test.assertEq(1, removedCount);
-};
+}
 
-// @ts-ignore: error TS4111: Property 'openOfficeWordFromDriveNotSynced' comes
-// from an index signature, so it must be accessed with
-// ['openOfficeWordFromDriveNotSynced'].
-testcase.openOfficeWordFromDriveNotSynced = async () => {
+export async function openOfficeWordFromDriveNotSynced() {
   const appId =
-      // @ts-ignore: error TS4111: Property 'smallDocx' comes from an index
-      // signature, so it must be accessed with ['smallDocx'].
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.smallDocx]);
 
   // Fake chrome.fileManagerPrivate.executeTask to return
@@ -391,8 +317,6 @@
 
   // Open file.
   chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
-      // @ts-ignore: error TS4111: Property 'smallDocx' comes from an index
-      // signature, so it must be accessed with ['smallDocx'].
       'openFile', appId, [ENTRIES.smallDocx.nameText]));
 
   // The Drive/Docs task should be available and executed.
@@ -404,15 +328,10 @@
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
   chrome.test.assertEq(1, removedCount);
-};
+}
 
-// @ts-ignore: error TS4111: Property 'openOfficeWordFromMyFilesOffline' comes
-// from an index signature, so it must be accessed with
-// ['openOfficeWordFromMyFilesOffline'].
-testcase.openOfficeWordFromMyFilesOffline = async () => {
+export async function openOfficeWordFromMyFilesOffline() {
   const appId =
-      // @ts-ignore: error TS4111: Property 'smallDocx' comes from an index
-      // signature, so it must be accessed with ['smallDocx'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.smallDocx]);
 
   // Fake chrome.fileManagerPrivate.executeTask to return
@@ -424,8 +343,6 @@
 
   // Open file.
   chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
-      // @ts-ignore: error TS4111: Property 'smallDocx' comes from an index
-      // signature, so it must be accessed with ['smallDocx'].
       'openFile', appId, [ENTRIES.smallDocx.nameText]));
 
   // The Drive/Docs task should be executed, but it will fall back to
@@ -438,15 +355,10 @@
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
   chrome.test.assertEq(1, removedCount);
-};
+}
 
-// @ts-ignore: error TS4111: Property 'openOfficeWordFromDriveOffline' comes
-// from an index signature, so it must be accessed with
-// ['openOfficeWordFromDriveOffline'].
-testcase.openOfficeWordFromDriveOffline = async () => {
+export async function openOfficeWordFromDriveOffline() {
   const appId = await setupAndWaitUntilReady(
-      // @ts-ignore: error TS4111: Property 'smallDocxPinned' comes from an
-      // index signature, so it must be accessed with ['smallDocxPinned'].
       RootPath.DRIVE, [], [ENTRIES.smallDocxPinned]);
 
   // Fake chrome.fileManagerPrivate.executeTask to return
@@ -458,8 +370,6 @@
 
   // Open file.
   chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
-      // @ts-ignore: error TS4111: Property 'smallDocxPinned' comes from an
-      // index signature, so it must be accessed with ['smallDocxPinned'].
       'openFile', appId, [ENTRIES.smallDocxPinned.nameText]));
 
   // The Drive/Docs task should be executed, but it will fall back to
@@ -472,12 +382,10 @@
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
   chrome.test.assertEq(1, removedCount);
-};
+}
 
 /** Tests that the educational nudge is displayed when the preference is set. */
-// @ts-ignore: error TS4111: Property 'officeShowNudgeGoogleDrive' comes from an
-// index signature, so it must be accessed with ['officeShowNudgeGoogleDrive'].
-testcase.officeShowNudgeGoogleDrive = async () => {
+export async function officeShowNudgeGoogleDrive() {
   // Set the pref emulating that the user has moved a file.
   await sendTestMessage({
     name: 'setPrefOfficeFileMovedToGoogleDrive',
@@ -486,11 +394,9 @@
 
   // Open the Files app.
   const appId = await setupAndWaitUntilReady(
-      // @ts-ignore: error TS4111: Property 'smallDocxPinned' comes from an
-      // index signature, so it must be accessed with ['smallDocxPinned'].
       RootPath.DRIVE, [], [ENTRIES.smallDocxPinned]);
 
   // Check that the nudge and its text is visible.
   await remoteCall.waitNudge(
       appId, 'Recently opened Microsoft files have moved to Google Drive');
-};
+}
diff --git a/ui/file_manager/integration_tests/file_manager/providers.js b/ui/file_manager/integration_tests/file_manager/providers.ts
similarity index 79%
rename from ui/file_manager/integration_tests/file_manager/providers.js
rename to ui/file_manager/integration_tests/file_manager/providers.ts
index adf2fdb..0194aff 100644
--- a/ui/file_manager/integration_tests/file_manager/providers.js
+++ b/ui/file_manager/integration_tests/file_manager/providers.ts
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import {getHistogramCount, RootPath, sendTestMessage} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {IGNORE_APP_ERRORS, remoteCall, setupAndWaitUntilReady} from './background.js';
 import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
@@ -11,10 +10,10 @@
 /**
  * Returns provider name of the given testing provider manifest viz., the
  * the value of the name field in the |manifest| file.
- * @param {string} manifest Testing provider manifest file name.
- * @return {string} Testing provider name.
+ * @param manifest Testing provider manifest file name.
+ * @return Testing provider name.
  */
-function getProviderNameForTest(manifest) {
+function getProviderNameForTest(manifest: string): string {
   if (manifest === 'manifest.json') {
     return 'Files Testing Provider test extension';
   }
@@ -33,10 +32,10 @@
 
 /**
  * Initializes the provider extension.
- * @param {string} manifest The manifest name of testing provider extension
- *     to launch for the test case.
+ * @param manifest The manifest name of testing provider extension to launch for
+ *     the test case.
  */
-async function setUpProvider(manifest) {
+async function setUpProvider(manifest: string) {
   await sendTestMessage({name: 'launchProviderExtension', manifest: manifest});
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
   return appId;
@@ -45,8 +44,7 @@
 /**
  * Clicks on the "Services" menu button.
  */
-// @ts-ignore: error TS7006: Parameter 'appId' implicitly has an 'any' type.
-async function showProvidersMenu(appId) {
+async function showProvidersMenu(appId: string) {
   const providersMenuItem = '#gear-menu-providers:not([hidden])';
 
   // Open the gear menu by clicking the gear button.
@@ -68,9 +66,7 @@
 /**
  * Confirms that a provided volume is mounted.
  */
-// @ts-ignore: error TS7006: Parameter 'ejectExpected' implicitly has an 'any'
-// type.
-async function confirmVolume(appId, ejectExpected) {
+async function confirmVolume(appId: string, ejectExpected: boolean) {
   const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
   await directoryTree.selectItemByType('provided');
 
@@ -86,12 +82,11 @@
  * Tests that a provided extension with |manifest| is mountable via the menu
  * button.
  *
- * @param {boolean} multipleMounts Whether multiple mounts are supported by
- *     the providing extension.
- * @param {string} manifest Name of the manifest file for the providing
+ * @param multipleMounts Whether multiple mounts are supported by the providing
  *     extension.
+ * @param manifest Name of the manifest file for the providing extension.
  */
-async function requestMountInternal(multipleMounts, manifest) {
+async function requestMountInternal(multipleMounts: boolean, manifest: string) {
   const providerName = getProviderNameForTest(manifest);
   const appId = await setUpProvider(manifest);
   await showProvidersMenu(appId);
@@ -139,10 +134,9 @@
  * Tests that a provided extension with |manifest| is not available in the
  * providers menu, but it's mounted automatically.
  *
- * @param {string} manifest Name of the manifest file for the providing
- *     extension.
+ * @param manifest Name of the manifest file for the providing extension.
  */
-async function requestMountNotInMenuInternal(manifest) {
+async function requestMountNotInMenuInternal(manifest: string) {
   const appId = await setUpProvider(manifest);
   await confirmVolume(appId, true /* ejectExpected */);
 
@@ -190,48 +184,38 @@
 /**
  * Tests mounting a single mount point in the button menu.
  */
-// @ts-ignore: error TS4111: Property 'requestMount' comes from an index
-// signature, so it must be accessed with ['requestMount'].
-testcase.requestMount = () => {
+export async function requestMount() {
   const multipleMounts = false;
   return requestMountInternal(multipleMounts, 'manifest.json');
-};
+}
 
 /**
  * Tests mounting multiple mount points in the button menu.
  */
-// @ts-ignore: error TS4111: Property 'requestMountMultipleMounts' comes from an
-// index signature, so it must be accessed with ['requestMountMultipleMounts'].
-testcase.requestMountMultipleMounts = () => {
+export async function requestMountMultipleMounts() {
   const multipleMounts = true;
   return requestMountInternal(multipleMounts, 'manifest_multiple_mounts.json');
-};
+}
 
 /**
  * Tests mounting a device not present in the button menu.
  */
-// @ts-ignore: error TS4111: Property 'requestMountSourceDevice' comes from an
-// index signature, so it must be accessed with ['requestMountSourceDevice'].
-testcase.requestMountSourceDevice = () => {
+export async function requestMountSourceDevice() {
   return requestMountNotInMenuInternal('manifest_source_device.json');
-};
+}
 
 /**
  * Tests mounting a file not present in the button menu.
  */
-// @ts-ignore: error TS4111: Property 'requestMountSourceFile' comes from an
-// index signature, so it must be accessed with ['requestMountSourceFile'].
-testcase.requestMountSourceFile = () => {
+export async function requestMountSourceFile() {
   return requestMountNotInMenuInternal('manifest_source_file.json');
-};
+}
 
 /**
  * Tests that pressing the eject button on a FSP adds a message to screen
  * reader.
  */
-// @ts-ignore: error TS4111: Property 'providerEject' comes from an index
-// signature, so it must be accessed with ['providerEject'].
-testcase.providerEject = async () => {
+export async function providerEject() {
   const manifest = 'manifest_source_file.json';
   const appId = await setUpProvider(manifest);
 
@@ -255,16 +239,13 @@
   // JS errors due to volume related actions performed while volume is
   // ejected.
   return IGNORE_APP_ERRORS;
-};
+}
 
 /**
  * Tests mounting a file system provider emits only a single UMA when running
  * from either the SWA or Chrome app.
  */
-// @ts-ignore: error TS4111: Property
-// 'deduplicatedUmaMetricForFileSystemProviders' comes from an index signature,
-// so it must be accessed with ['deduplicatedUmaMetricForFileSystemProviders'].
-testcase.deduplicatedUmaMetricForFileSystemProviders = async () => {
+export async function deduplicatedUmaMetricForFileSystemProviders() {
   const umaMetricName = 'FileBrowser.FileSystemProviderMounted';
   const testProviderMetricEnumValue = 0;  // UNKNOWN = 0.
 
@@ -290,4 +271,4 @@
       await getHistogramCount(umaMetricName, testProviderMetricEnumValue);
   chrome.test.assertEq(
       1, mountedVolumeCount, 'Unexpected value in UMA metric for mounted FSPs');
-};
+}
diff --git a/ui/file_manager/integration_tests/file_manager/quick_view.js b/ui/file_manager/integration_tests/file_manager/quick_view.ts
similarity index 69%
rename from ui/file_manager/integration_tests/file_manager/quick_view.js
rename to ui/file_manager/integration_tests/file_manager/quick_view.ts
index f203b17..00b378b9 100644
--- a/ui/file_manager/integration_tests/file_manager/quick_view.js
+++ b/ui/file_manager/integration_tests/file_manager/quick_view.ts
@@ -2,10 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {DialogType} from '../prod/file_manager/shared_types.js';
+import {DialogType, type ElementObject} from '../prod/file_manager/shared_types.js';
 import {ExecuteScriptError} from '../remote_call.js';
-import {addEntries, ENTRIES, EntryType, getCaller, getHistogramCount, pending, repeatUntil, RootPath, sanitizeDate, sendTestMessage, TestEntryInfo, wait} from '../test_util.js';
-import {testcase} from '../testcase.js';
+import {ENTRIES, EntryType, RootPath, TestEntryInfo, addEntries, getCaller, getHistogramCount, pending, repeatUntil, sanitizeDate, sendTestMessage, wait} from '../test_util.js';
 
 import {mountCrostini, mountGuestOs, openNewWindow, remoteCall, setupAndWaitUntilReady} from './background.js';
 import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
@@ -41,9 +40,9 @@
 /**
  * Checks if dark mode has been turned on or not.
  *
- * @return {!Promise<boolean>} enabled or not.
+ * @return enabled or not.
  */
-async function isDarkModeEnabled() {
+async function isDarkModeEnabled(): Promise<boolean> {
   const isDarkModeEnabled = await sendTestMessage({name: 'isDarkModeEnabled'});
   return isDarkModeEnabled === 'true';
 }
@@ -51,16 +50,14 @@
 /**
  * Waits for Quick View dialog to be open.
  *
- * @param {string} appId Files app windowId.
+ * @param appId Files app windowId.
  */
-async function waitQuickViewOpen(appId) {
+async function waitQuickViewOpen(appId: string) {
   const caller = getCaller();
 
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkQuickViewElementsDisplayBlock(elements) {
+  function checkQuickViewElementsDisplayBlock(elements: ElementObject[]) {
     const haveElements = Array.isArray(elements) && elements.length !== 0;
-    if (!haveElements || elements[0].styles.display !== 'block') {
+    if (!haveElements || elements[0]!.styles!['display'] !== 'block') {
       return pending(caller, 'Waiting for Quick View to open.');
     }
     return;
@@ -77,18 +74,18 @@
 /**
  * Waits for Quick View dialog to be closed.
  *
- * @param {string} appId Files app windowId.
+ * @param appId Files app windowId.
  */
-async function waitQuickViewClose(appId) {
+async function waitQuickViewClose(appId: string) {
   const caller = getCaller();
 
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkQuickViewElementsDisplayNone(elements) {
+  function checkQuickViewElementsDisplayNone(elements: ElementObject[]) {
     chrome.test.assertTrue(Array.isArray(elements));
-    if (elements.length === 0 || elements[0].styles.display !== 'none') {
+    if (elements.length === 0 || elements[0]!.styles!['display'] !== 'none') {
       return pending(caller, 'Waiting for Quick View to close.');
     }
+
+    return;
   }
 
   // Check: the Quick View dialog should not be shown.
@@ -104,10 +101,10 @@
  * Opens the Quick View dialog on a given file |name|. The file must be
  * present in the Files app file list.
  *
- * @param {string} appId Files app windowId.
- * @param {string} name File name.
+ * @param appId Files app windowId.
+ * @param name File name.
  */
-async function openQuickView(appId, name) {
+async function openQuickViewEx(appId: string, name: string) {
   // Select file |name| in the file list.
   await remoteCall.waitUntilSelected(appId, name);
 
@@ -125,10 +122,10 @@
  * Opens the Quick View dialog by right clicking on the file |name| and
  * using the "Get Info" command from the context menu.
  *
- * @param {string} appId Files app windowId.
- * @param {string} name File name.
+ * @param appId Files app windowId.
+ * @param name File name.
  */
-async function openQuickViewViaContextMenu(appId, name) {
+async function openQuickViewViaContextMenu(appId: string, name: string) {
   // Right-click the file in the file-list.
   const query = `#file-list [file-name="${name}"]`;
   await remoteCall.waitAndRightClick(appId, query);
@@ -150,10 +147,10 @@
  * Opens the Quick View dialog with given file |names|. The files must be
  * present and check-selected in the Files app file list.
  *
- * @param {string} appId Files app windowId.
- * @param {Array<string>} names File names.
+ * @param appId Files app windowId.
+ * @param names File names.
  */
-async function openQuickViewMultipleSelection(appId, names) {
+async function openQuickViewMultipleSelection(appId: string, names: string[]) {
   // Get the file-list rows that are check-selected (multi-selected).
   const selectedRows = await remoteCall.callRemoteTestUtil(
       'deepQueryAllElements', appId, ['#file-list li[selected]']);
@@ -176,9 +173,9 @@
 /**
  * Mount and select USB.
  *
- * @param {string} appId Files app windowId.
+ * @param appId Files app windowId.
  */
-async function mountAndSelectUsb(appId) {
+async function mountAndSelectUsb(appId: string) {
   // Mount a USB volume.
   await sendTestMessage({name: 'mountFakeUsb'});
 
@@ -195,9 +192,9 @@
  * Assuming that Quick View is currently open per openQuickView above, closes
  * the Quick View dialog.
  *
- * @param {string} appId Files app windowId.
+ * @param appId Files app windowId.
  */
-async function closeQuickView(appId) {
+async function closeQuickViewEx(appId: string) {
   // Click on Quick View to close it.
   const panelElements = ['#quick-view', '#contentPanel'];
   chrome.test.assertTrue(
@@ -214,13 +211,14 @@
  * the text shown in the QuickView Metadata Box field |name|. If the optional
  * |hidden| is 'hidden', the field |name| should not be visible.
  *
- * @param {string} appId Files app windowId.
- * @param {string} name QuickView Metadata Box field name.
- * @param {string} hidden Whether the field name should be visible.
+ * @param appId Files app windowId.
+ * @param name QuickView Metadata Box field name.
+ * @param hidden Whether the field name should be visible.
  *
- * @return {!Promise<string>} text Text value in the field name.
+ * @return text Text value in the field name.
  */
-async function getQuickViewMetadataBoxField(appId, name, hidden = '') {
+async function getQuickViewMetadataBoxField(
+    appId: string, name: string, hidden: string = ''): Promise<string> {
   let filesMetadataBox = 'files-metadata-box';
 
   /**
@@ -280,14 +278,14 @@
  * Executes a script in the context of a <preview-tag> element and returns its
  * output. Returns undefined when ExecuteScriptError is caught.
  *
- * @param {string} appId App window Id.
- * @param {!Array<string>} query Query to the <preview-tag> element (this is
+ * @param appId App window Id.
+ * @param query Query to the <preview-tag> element (this is
  *     ignored for SWA).
- * @param {string} statement Javascript statement to be executed within the
+ * @param statement Javascript statement to be executed within the
  *     <preview-tag>.
- * @return {!Promise<*>}
  */
-async function executeJsInPreviewTagAndCatchErrors(appId, query, statement) {
+async function executeJsInPreviewTagAndCatchErrors(
+    appId: string, query: string[], statement: string): Promise<unknown> {
   try {
     return await remoteCall.executeJsInPreviewTag(appId, query, statement);
   } catch (e) {
@@ -302,49 +300,40 @@
 /**
  * Tests opening Quick View on a local downloads file.
  */
-// @ts-ignore: error TS4111: Property 'openQuickView' comes from an index
-// signature, so it must be accessed with ['openQuickView'].
-testcase.openQuickView = async () => {
+export async function openQuickView() {
   // Open Files app on Downloads containing ENTRIES.hello.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
 
   // Open the file in Quick View.
-  await openQuickView(appId, ENTRIES.hello.nameText);
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
 
   // Check the open button is shown.
   await remoteCall.waitForElement(
       appId, ['#quick-view', '#open-button:not([hidden])']);
-};
+}
 
 /**
  * Tests opening Quick View on a local downloads file in an open file dialog.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewDialog' comes from an index
-// signature, so it must be accessed with ['openQuickViewDialog'].
-testcase.openQuickViewDialog = async () => {
+export async function openQuickViewDialog() {
   // Open Files app on Downloads containing ENTRIES.hello.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, [ENTRIES.hello], [],
-      // @ts-ignore: error TS2345: Argument of type '{ type: string; }' is not
-      // assignable to parameter of type 'FilesAppState'.
       {type: DialogType.SELECT_OPEN_FILE});
 
   // Open the file in Quick View.
-  await openQuickView(appId, ENTRIES.hello.nameText);
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
 
   // Check the open button is not shown as we're in an open file dialog.
   await remoteCall.waitForElement(
       appId, ['#quick-view', '#open-button[hidden]']);
-};
+}
 
 /**
  * Tests that Quick View opens via the context menu with a single selection.
  */
-// @ts-ignore: error TS4111: Property
-// 'openQuickViewViaContextMenuSingleSelection' comes from an index signature,
-// so it must be accessed with ['openQuickViewViaContextMenuSingleSelection'].
-testcase.openQuickViewViaContextMenuSingleSelection = async () => {
+export async function openQuickViewViaContextMenuSingleSelection() {
   // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
@@ -354,59 +343,49 @@
 
   // Check: clicking the context menu "Get Info" should open Quick View.
   await openQuickViewViaContextMenu(appId, ENTRIES.hello.nameText);
-};
+}
 
 /**
  * Tests that Quick View opens via the context menu when multiple files
  * are selected (file-list check-select mode).
  */
-// @ts-ignore: error TS4111: Property
-// 'openQuickViewViaContextMenuCheckSelections' comes from an index signature,
-// so it must be accessed with ['openQuickViewViaContextMenuCheckSelections'].
-testcase.openQuickViewViaContextMenuCheckSelections = async () => {
+export async function openQuickViewViaContextMenuCheckSelections() {
   // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
 
   // Ctrl+A to select all files in the file-list.
-  const ctrlA = ['#file-list', 'a', true, false, false];
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
-  await remoteCall.fakeKeyDown(appId, ...ctrlA);
+  await remoteCall.fakeKeyDown(appId, '#file-list', 'a', true, false, false);
 
   // Check: clicking the context menu "Get Info" should open Quick View.
   await openQuickViewViaContextMenu(appId, ENTRIES.hello.nameText);
-};
+}
 
 /**
  * Tests opening then closing Quick View on a local downloads file.
  */
-// @ts-ignore: error TS4111: Property 'closeQuickView' comes from an index
-// signature, so it must be accessed with ['closeQuickView'].
-testcase.closeQuickView = async () => {
+export async function closeQuickView() {
   // Open Files app on Downloads containing ENTRIES.hello.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
 
   // Open the file in Quick View.
-  await openQuickView(appId, ENTRIES.hello.nameText);
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
 
   // Close Quick View.
-  await closeQuickView(appId);
-};
+  await closeQuickViewEx(appId);
+}
 
 /**
  * Tests opening Quick View on a Drive file.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewDrive' comes from an index
-// signature, so it must be accessed with ['openQuickViewDrive'].
-testcase.openQuickViewDrive = async () => {
+export async function openQuickViewDrive() {
   // Open Files app on Drive containing ENTRIES.hello.
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.hello]);
 
   // Open the file in Quick View.
-  await openQuickView(appId, ENTRIES.hello.nameText);
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
 
   // Check: the correct mimeType should be displayed (see crbug.com/1067499
   // for details on mimeType differences between Drive and local filesystem).
@@ -416,14 +395,12 @@
   // Check: the correct file location should be displayed in Drive.
   const location = await getQuickViewMetadataBoxField(appId, 'File location');
   chrome.test.assertEq('My Drive/hello.txt', location);
-};
+}
 
 /**
  * Tests opening Quick View on a Smbfs file.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewSmbfs' comes from an index
-// signature, so it must be accessed with ['openQuickViewSmbfs'].
-testcase.openQuickViewSmbfs = async () => {
+export async function openQuickViewSmbfs() {
   // Open Files app on Downloads containing ENTRIES.photos.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.photos], []);
@@ -442,31 +419,26 @@
   await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});
 
   // Open the file in Quick View.
-  await openQuickView(appId, ENTRIES.hello.nameText);
-};
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
+}
 
 /**
  * Tests opening Quick View on a USB file.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewUsb' comes from an index
-// signature, so it must be accessed with ['openQuickViewUsb'].
-testcase.openQuickViewUsb = async () => {
+export async function openQuickViewUsb() {
   // Open Files app on Downloads containing ENTRIES.photos.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.photos], []);
 
   // Open a USB file in Quick View.
   await mountAndSelectUsb(appId);
-  await openQuickView(appId, ENTRIES.hello.nameText);
-};
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
+}
 
 /**
  * Tests opening Quick View on a removable partition.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewRemovablePartitions' comes
-// from an index signature, so it must be accessed with
-// ['openQuickViewRemovablePartitions'].
-testcase.openQuickViewRemovablePartitions = async () => {
+export async function openQuickViewRemovablePartitions() {
   // Open Files app on Downloads containing ENTRIES.photos.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.photos], []);
@@ -488,23 +460,17 @@
 
   // Check: the USB files should appear in the file list.
   const files = TestEntryInfo.getExpectedRows(BASIC_FAKE_ENTRY_SET);
-  // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime: true;
-  // }' is not assignable to parameter of type '{ orderCheck: boolean | null |
-  // undefined; ignoreFileSize: boolean | null | undefined;
-  // ignoreLastModifiedTime: boolean | null | undefined; }'.
   await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});
 
   // Open the file in Quick View.
-  await openQuickView(appId, ENTRIES.hello.nameText);
-};
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
+}
 
 /**
  * Tests opening Quick View on an item that was Trashed shows original location
  * instead of the current file location.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewTrash' comes from an index
-// signature, so it must be accessed with ['openQuickViewTrash'].
-testcase.openQuickViewTrash = async () => {
+export async function openQuickViewTrash() {
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
 
@@ -524,22 +490,19 @@
       appId, '#file-list [file-name="hello.txt"]');
 
   // Open the file in Quick View.
-  await openQuickView(appId, 'hello.txt');
+  await openQuickViewEx(appId, 'hello.txt');
 
   // Check: the original location should be shown instead of the actual file
   // location.
   const location =
       await getQuickViewMetadataBoxField(appId, 'Original location');
   chrome.test.assertEq('My files/Downloads/hello.txt', location);
-};
+}
 
 /**
  * Tests seeing dashes for an empty last_modified for DocumentsProvider.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewLastModifiedMetaData' comes
-// from an index signature, so it must be accessed with
-// ['openQuickViewLastModifiedMetaData'].
-testcase.openQuickViewLastModifiedMetaData = async () => {
+export async function openQuickViewLastModifiedMetaData() {
   const documentsProviderVolumeType = 'documents_provider';
 
   // Add files to the DocumentsProvider volume.
@@ -557,38 +520,30 @@
 
   // Check: the DocumentsProvider files should appear in the file list.
   const files = TestEntryInfo.getExpectedRows(MODIFIED_ENTRY_SET);
-  // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime: true;
-  // }' is not assignable to parameter of type '{ orderCheck: boolean | null |
-  // undefined; ignoreFileSize: boolean | null | undefined;
-  // ignoreLastModifiedTime: boolean | null | undefined; }'.
   await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});
 
   // Open a DocumentsProvider file in Quick View.
-  await openQuickView(appId, ENTRIES.hello.nameText);
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
 
   const lastValidModifiedText =
       await getQuickViewMetadataBoxField(appId, 'Date modified');
   chrome.test.assertEq(ENTRIES.hello.lastModifiedTime, lastValidModifiedText);
 
-  await closeQuickView(appId);
+  await closeQuickViewEx(appId);
 
   // Open a DocumentsProvider file in Quick View.
-  // @ts-ignore: error TS4111: Property 'invalidLastModifiedDate' comes from an
-  // index signature, so it must be accessed with ['invalidLastModifiedDate'].
-  await openQuickView(appId, ENTRIES.invalidLastModifiedDate.nameText);
+  await openQuickViewEx(appId, ENTRIES.invalidLastModifiedDate.nameText);
 
   // Modified time should be displayed as "--" when it's absent.
   const lastInvalidModifiedText =
       await getQuickViewMetadataBoxField(appId, 'Date modified');
   chrome.test.assertEq('--', lastInvalidModifiedText);
-};
+}
 
 /**
  * Tests opening Quick View on an MTP file.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewMtp' comes from an index
-// signature, so it must be accessed with ['openQuickViewMtp'].
-testcase.openQuickViewMtp = async () => {
+export async function openQuickViewMtp() {
   // Open Files app on Downloads containing ENTRIES.photos.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.photos], []);
@@ -602,58 +557,46 @@
 
   // Check: the MTP files should appear in the file list.
   const files = TestEntryInfo.getExpectedRows(BASIC_FAKE_ENTRY_SET);
-  // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime: true;
-  // }' is not assignable to parameter of type '{ orderCheck: boolean | null |
-  // undefined; ignoreFileSize: boolean | null | undefined;
-  // ignoreLastModifiedTime: boolean | null | undefined; }'.
   await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});
 
   // Open an MTP file in Quick View.
-  await openQuickView(appId, ENTRIES.hello.nameText);
-};
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
+}
 
 /**
  * Tests opening Quick View on a Crostini file.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewCrostini' comes from an
-// index signature, so it must be accessed with ['openQuickViewCrostini'].
-testcase.openQuickViewCrostini = async () => {
+export async function openQuickViewCrostini() {
   // Open Files app on Downloads containing ENTRIES.photos.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.photos], []);
 
   // Open a Crostini file in Quick View.
   await mountCrostini(appId);
-  await openQuickView(appId, ENTRIES.hello.nameText);
-};
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
+}
 
 /**
  * Tests opening Quick View on a GuestOS file.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewGuestOs' comes from an index
-// signature, so it must be accessed with ['openQuickViewGuestOs'].
-testcase.openQuickViewGuestOs = async () => {
+export async function openQuickViewGuestOs() {
   // Open Files app on Downloads containing ENTRIES.photos.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.photos], []);
 
   // Open a GuestOS file in Quick View.
   await mountGuestOs(appId, BASIC_LOCAL_ENTRY_SET);
-  await openQuickView(appId, ENTRIES.hello.nameText);
-};
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
+}
 
 /**
  * Tests opening Quick View on an Android file.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewAndroid' comes from an index
-// signature, so it must be accessed with ['openQuickViewAndroid'].
-testcase.openQuickViewAndroid = async () => {
+export async function openQuickViewAndroid() {
   // Open Files app on Android files.
   const appId = await openNewWindow(RootPath.ANDROID_FILES);
 
   // Add files to the Android files volume.
-  // @ts-ignore: error TS4111: Property 'documentsText' comes from an index
-  // signature, so it must be accessed with ['documentsText'].
   const entrySet = BASIC_ANDROID_ENTRY_SET.concat([ENTRIES.documentsText]);
   await addEntries(['android_files'], entrySet);
 
@@ -662,10 +605,6 @@
 
   // Check: the basic Android file set should appear in the file list.
   let files = TestEntryInfo.getExpectedRows(BASIC_ANDROID_ENTRY_SET);
-  // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime: true;
-  // }' is not assignable to parameter of type '{ orderCheck: boolean | null |
-  // undefined; ignoreFileSize: boolean | null | undefined;
-  // ignoreLastModifiedTime: boolean | null | undefined; }'.
   await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});
 
   // Navigate to the Android files '/Documents' directory.
@@ -673,35 +612,22 @@
   await directoryTree.navigateToPath('/My files/Play files/Documents');
 
   // Check: the 'android.txt' file should appear in the file list.
-  // @ts-ignore: error TS4111: Property 'documentsText' comes from an index
-  // signature, so it must be accessed with ['documentsText'].
   files = [ENTRIES.documentsText.getExpectedRow()];
-  // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime: true;
-  // }' is not assignable to parameter of type '{ orderCheck: boolean | null |
-  // undefined; ignoreFileSize: boolean | null | undefined;
-  // ignoreLastModifiedTime: boolean | null | undefined; }'.
   await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});
 
   // Open the Android file in Quick View.
-  // @ts-ignore: error TS4111: Property 'documentsText' comes from an index
-  // signature, so it must be accessed with ['documentsText'].
   const documentsFileName = ENTRIES.documentsText.nameText;
-  await openQuickView(appId, documentsFileName);
-};
+  await openQuickViewEx(appId, documentsFileName);
+}
 
 /**
  * Tests opening Quick View on an Android file on GuestOS.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewAndroidGuestOs' comes from
-// an index signature, so it must be accessed with
-// ['openQuickViewAndroidGuestOs'].
-testcase.openQuickViewAndroidGuestOs = async () => {
+export async function openQuickViewAndroidGuestOs() {
   // Open Files app on Android files.
   const appId = await openNewWindow(RootPath.ANDROID_FILES);
 
   // Add files to the Android files volume.
-  // @ts-ignore: error TS4111: Property 'documentsText' comes from an index
-  // signature, so it must be accessed with ['documentsText'].
   const entrySet = BASIC_ANDROID_ENTRY_SET.concat([ENTRIES.documentsText]);
   await addEntries(['android_files'], entrySet);
 
@@ -710,10 +636,6 @@
 
   // Check: the basic Android file set should appear in the file list.
   let files = TestEntryInfo.getExpectedRows(BASIC_ANDROID_ENTRY_SET);
-  // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime: true;
-  // }' is not assignable to parameter of type '{ orderCheck: boolean | null |
-  // undefined; ignoreFileSize: boolean | null | undefined;
-  // ignoreLastModifiedTime: boolean | null | undefined; }'.
   await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});
 
   // Navigate to the Android files '/Documents' directory.
@@ -721,29 +643,18 @@
   await directoryTree.navigateToPath('/My files/Play files/Documents');
 
   // Check: the 'android.txt' file should appear in the file list.
-  // @ts-ignore: error TS4111: Property 'documentsText' comes from an index
-  // signature, so it must be accessed with ['documentsText'].
   files = [ENTRIES.documentsText.getExpectedRow()];
-  // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime: true;
-  // }' is not assignable to parameter of type '{ orderCheck: boolean | null |
-  // undefined; ignoreFileSize: boolean | null | undefined;
-  // ignoreLastModifiedTime: boolean | null | undefined; }'.
   await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});
 
   // Open the Android file in Quick View.
-  // @ts-ignore: error TS4111: Property 'documentsText' comes from an index
-  // signature, so it must be accessed with ['documentsText'].
   const documentsFileName = ENTRIES.documentsText.nameText;
-  await openQuickView(appId, documentsFileName);
-};
+  await openQuickViewEx(appId, documentsFileName);
+}
 
 /**
  * Tests opening Quick View on a DocumentsProvider root.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewDocumentsProvider' comes
-// from an index signature, so it must be accessed with
-// ['openQuickViewDocumentsProvider'].
-testcase.openQuickViewDocumentsProvider = async () => {
+export async function openQuickViewDocumentsProvider() {
   const DOCUMENTS_PROVIDER_VOLUME_TYPE = 'documents_provider';
 
   // Add files to the DocumentsProvider volume.
@@ -765,7 +676,7 @@
   await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});
 
   // Open a DocumentsProvider file in Quick View.
-  await openQuickView(appId, ENTRIES.hello.nameText);
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
 
   /**
    * The text preview resides in the #quick-view shadow DOM, as a child of
@@ -775,14 +686,12 @@
 
   // Wait for the Quick View preview to load and display its content.
   const caller = getCaller();
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewTextLoaded(elements) {
+  function checkPreviewTextLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || !elements[0].attributes.src) {
+    if (!haveElements || !elements[0]!.attributes['src']) {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -793,15 +702,15 @@
   });
 
   // Wait until the preview displays the file's content.
-  // @ts-ignore: error TS7030: Not all code paths return a value.
   await repeatUntil(async () => {
     const getTextContent = contentWindowQuery + '.document.body.textContent';
     const text = await executeJsInPreviewTagAndCatchErrors(
-        appId, preview, getTextContent);
+                     appId, preview, getTextContent) as string[];
     // Check: the content of text file should be shown.
     if (!text || !text[0] || !text[0].includes('chocolate and chips')) {
       return pending(caller, `Waiting for ${previewTag} content.`);
     }
+    return;
   });
 
   // Check: the correct size and date modified values should be displayed.
@@ -810,15 +719,13 @@
   const lastModifiedText =
       await getQuickViewMetadataBoxField(appId, 'Date modified');
   chrome.test.assertEq(ENTRIES.hello.lastModifiedTime, lastModifiedText);
-};
+}
 
 /**
  * Tests opening Quick View with a local text document identified as text from
  * file sniffing (the first word of the file is "From ", note trailing space).
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewSniffedText' comes from an
-// index signature, so it must be accessed with ['openQuickViewSniffedText'].
-testcase.openQuickViewSniffedText = async () => {
+export async function openQuickViewSniffedText() {
   const caller = getCaller();
 
   /**
@@ -829,24 +736,18 @@
 
   // Open Files app on Downloads containing ENTRIES.plainText.
   const appId =
-      // @ts-ignore: error TS4111: Property 'plainText' comes from an index
-      // signature, so it must be accessed with ['plainText'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.plainText], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'plainText' comes from an index
-  // signature, so it must be accessed with ['plainText'].
-  await openQuickView(appId, ENTRIES.plainText.nameText);
+  await openQuickViewEx(appId, ENTRIES.plainText.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewTextLoaded(elements) {
+  function checkPreviewTextLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || !elements[0].attributes.src) {
+    if (!haveElements || !elements[0]!.attributes['src']) {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -859,16 +760,13 @@
   // Check: the correct mimeType should be displayed.
   const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
   chrome.test.assertEq('text/plain', mimeType);
-};
+}
 
 /**
  * Tests opening Quick View with a local text document whose MIME type cannot
  * be identified by MIME type sniffing.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewTextFileWithUnknownMimeType'
-// comes from an index signature, so it must be accessed with
-// ['openQuickViewTextFileWithUnknownMimeType'].
-testcase.openQuickViewTextFileWithUnknownMimeType = async () => {
+export async function openQuickViewTextFileWithUnknownMimeType() {
   const caller = getCaller();
 
   /**
@@ -882,17 +780,15 @@
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
 
   // Open the file in Quick View.
-  await openQuickView(appId, ENTRIES.hello.nameText);
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewTextLoaded(elements) {
+  function checkPreviewTextLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || !elements[0].attributes.src) {
+    if (!haveElements || !elements[0]!.attributes['src']) {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -904,15 +800,13 @@
 
   // Check: the mimeType field should not be displayed.
   await getQuickViewMetadataBoxField(appId, 'Type', 'hidden');
-};
+}
 
 /**
  * Tests opening Quick View with a text file containing some UTF-8 encoded
  * characters: crbug.com/1064855
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewUtf8Text' comes from an
-// index signature, so it must be accessed with ['openQuickViewUtf8Text'].
-testcase.openQuickViewUtf8Text = async () => {
+export async function openQuickViewUtf8Text() {
   const caller = getCaller();
 
   /**
@@ -926,17 +820,15 @@
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.utf8Text], []);
 
   // Open the file in Quick View.
-  await openQuickView(appId, ENTRIES.utf8Text.nameText);
+  await openQuickViewEx(appId, ENTRIES.utf8Text.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewTextLoaded(elements) {
+  function checkPreviewTextLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || !elements[0].attributes.src) {
+    if (!haveElements || !elements[0]!.attributes['src']) {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -947,30 +839,29 @@
   });
 
   // Wait until the preview displays the file's content.
-  // @ts-ignore: error TS7030: Not all code paths return a value.
   await repeatUntil(async () => {
     const getTextContent = contentWindowQuery + '.document.body.textContent';
     const text = await executeJsInPreviewTagAndCatchErrors(
-        appId, preview, getTextContent);
+                     appId, preview, getTextContent) as string[];
 
     // Check: the content of ENTRIES.utf8Text should be shown.
     if (!text || !text[0] || !text[0].includes('їсти मुझे |∊☀✌✂♁ 🙂\n')) {
       return pending(caller, 'Waiting for preview content.');
     }
+
+    return;
   });
 
   // Check: the correct file size should be displayed.
   const size = await getQuickViewMetadataBoxField(appId, 'Size');
   chrome.test.assertEq('191 bytes', size);
-};
+}
 
 /**
  * Tests opening Quick View and scrolling its preview contents which contains a
  * tall text document.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewScrollText' comes from an
-// index signature, so it must be accessed with ['openQuickViewScrollText'].
-testcase.openQuickViewScrollText = async () => {
+export async function openQuickViewScrollText() {
   const caller = getCaller();
 
   /**
@@ -979,40 +870,33 @@
    */
   const preview = ['#quick-view', `#dialog[open] ${previewTag}.text-content`];
 
-  // @ts-ignore: error TS7006: Parameter 'y' implicitly has an 'any' type.
-  function scrollQuickViewTextBy(y) {
+  function scrollQuickViewTextBy(y: number) {
     const doScrollBy = `${contentWindowQuery}.scrollBy(0,${y})`;
     return remoteCall.executeJsInPreviewTag(appId, preview, doScrollBy);
   }
 
-  // @ts-ignore: error TS7006: Parameter 'scrollY' implicitly has an 'any' type.
-  async function checkQuickViewTextScrollY(scrollY) {
+  async function checkQuickViewTextScrollY(scrollY: {toString: () => string}) {
     if (!scrollY || Number(scrollY.toString()) <= 150) {
       await scrollQuickViewTextBy(100);
       return pending(caller, 'Waiting for Quick View to scroll.');
     }
+    return;
   }
 
   // Open Files app on Downloads containing ENTRIES.tallText.
   const appId =
-      // @ts-ignore: error TS4111: Property 'tallText' comes from an index
-      // signature, so it must be accessed with ['tallText'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.tallText], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'tallText' comes from an index
-  // signature, so it must be accessed with ['tallText'].
-  await openQuickView(appId, ENTRIES.tallText.nameText);
+  await openQuickViewEx(appId, ENTRIES.tallText.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewTextLoaded(elements) {
+  function checkPreviewTextLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || !elements[0].attributes.src) {
+    if (!haveElements || !elements[0]!.attributes['src']) {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -1026,13 +910,13 @@
   const getScrollY = `${contentWindowQuery}.scrollY`;
 
   // The initial preview scrollY should be 0.
-  // @ts-ignore: error TS7030: Not all code paths return a value.
   await repeatUntil(async () => {
     const scrollY =
         await executeJsInPreviewTagAndCatchErrors(appId, preview, getScrollY);
     if (String(scrollY) !== '0') {
       return pending(caller, 'Waiting for preview text to load.');
     }
+    return;
   });
 
   // Scroll the preview and verify that it scrolled.
@@ -1041,14 +925,12 @@
         await remoteCall.executeJsInPreviewTag(appId, preview, getScrollY);
     return checkQuickViewTextScrollY(scrollY);
   });
-};
+}
 
 /**
  * Tests opening Quick View containing a PDF document.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewPdf' comes from an index
-// signature, so it must be accessed with ['openQuickViewPdf'].
-testcase.openQuickViewPdf = async () => {
+export async function openQuickViewPdf() {
   const caller = getCaller();
 
   /**
@@ -1059,24 +941,18 @@
 
   // Open Files app on Downloads containing ENTRIES.tallPdf.
   const appId =
-      // @ts-ignore: error TS4111: Property 'tallPdf' comes from an index
-      // signature, so it must be accessed with ['tallPdf'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.tallPdf], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'tallPdf' comes from an index signature,
-  // so it must be accessed with ['tallPdf'].
-  await openQuickView(appId, ENTRIES.tallPdf.nameText);
+  await openQuickViewEx(appId, ENTRIES.tallPdf.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewPdfLoaded(elements) {
+  function checkPreviewPdfLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || !elements[0].attributes.src) {
+    if (!haveElements || !elements[0]!.attributes['src']) {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -1087,19 +963,18 @@
   });
 
   // Get the preview embed type attribute.
-  // @ts-ignore: error TS7006: Parameter 'type' implicitly has an 'any' type.
-  function checkPdfEmbedType(type) {
+  function checkPdfEmbedType(type: ElementObject[]) {
     const haveElements = Array.isArray(type) && type.length === 1;
-    if (!haveElements || !type[0].toString().includes('pdf')) {
+    if (!haveElements || !type[0]!.toString().includes('pdf')) {
       return pending(caller, 'Waiting for plugin <embed> type.');
     }
-    return type[0];
+    return type[0]!;
   }
   const type = await repeatUntil(async () => {
     const getType =
         contentWindowQuery + '.document.querySelector("embed").type';
-    const type =
-        await executeJsInPreviewTagAndCatchErrors(appId, preview, getType);
+    const type = await executeJsInPreviewTagAndCatchErrors(
+                     appId, preview, getType) as ElementObject[];
     return checkPdfEmbedType(type);
   });
 
@@ -1109,14 +984,12 @@
   // Check: the correct mimeType should be displayed.
   const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
   chrome.test.assertEq('application/pdf', mimeType);
-};
+}
 
 /**
  * Tests opening Quick View on a PDF document that opens a popup JS dialog.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewPdfPopup' comes from an
-// index signature, so it must be accessed with ['openQuickViewPdfPopup'].
-testcase.openQuickViewPdfPopup = async () => {
+export async function openQuickViewPdfPopup() {
   const caller = getCaller();
 
   /**
@@ -1127,24 +1000,18 @@
 
   // Open Files app on Downloads containing ENTRIES.popupPdf.
   const appId =
-      // @ts-ignore: error TS4111: Property 'popupPdf' comes from an index
-      // signature, so it must be accessed with ['popupPdf'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.popupPdf], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'popupPdf' comes from an index
-  // signature, so it must be accessed with ['popupPdf'].
-  await openQuickView(appId, ENTRIES.popupPdf.nameText);
+  await openQuickViewEx(appId, ENTRIES.popupPdf.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewPdfLoaded(elements) {
+  function checkPreviewPdfLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || !elements[0].attributes.src) {
+    if (!haveElements || !elements[0]!.attributes['src']) {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -1155,8 +1022,7 @@
   });
 
   // Get the preview embed type attribute.
-  // @ts-ignore: error TS7006: Parameter 'type' implicitly has an 'any' type.
-  function checkPdfEmbedType(type) {
+  function checkPdfEmbedType(type: unknown) {
     const haveElements = Array.isArray(type) && type.length === 1;
     if (!haveElements || !type[0].toString().includes('pdf')) {
       return pending(caller, 'Waiting for plugin <embed> type.');
@@ -1177,16 +1043,13 @@
   // Check: the correct mimeType should be displayed.
   const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
   chrome.test.assertEq('application/pdf', mimeType);
-};
+}
 
 /**
  * Tests that Quick View does not display a PDF file preview when that is
  * disabled by system settings (preferences).
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewPdfPreviewsDisabled' comes
-// from an index signature, so it must be accessed with
-// ['openQuickViewPdfPreviewsDisabled'].
-testcase.openQuickViewPdfPreviewsDisabled = async () => {
+export async function openQuickViewPdfPreviewsDisabled() {
   const caller = getCaller();
 
   /**
@@ -1200,25 +1063,19 @@
 
   // Open Files app on Downloads containing ENTRIES.tallPdf.
   const appId =
-      // @ts-ignore: error TS4111: Property 'tallPdf' comes from an index
-      // signature, so it must be accessed with ['tallPdf'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.tallPdf], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'tallPdf' comes from an index signature,
-  // so it must be accessed with ['tallPdf'].
-  await openQuickView(appId, ENTRIES.tallPdf.nameText);
+  await openQuickViewEx(appId, ENTRIES.tallPdf.nameText);
 
   // Wait for the innerContentPanel to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkInnerContentPanel(elements) {
+  function checkInnerContentPanel(elements: ElementObject[]) {
     const haveElements = Array.isArray(elements) && elements.length === 1;
-    if (!haveElements || elements[0].styles.display !== 'flex') {
+    if (!haveElements || elements[0]!.styles!['display'] !== 'flex') {
       return pending(caller, 'Waiting for inner content panel to load.');
     }
     // Check: the PDF preview should not be shown.
-    chrome.test.assertEq('No preview available', elements[0].text);
+    chrome.test.assertEq('No preview available', elements[0]!.text);
     return;
   }
   await repeatUntil(async () => {
@@ -1229,14 +1086,12 @@
   // Check: the correct file mimeType should be displayed.
   const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
   chrome.test.assertEq('application/pdf', mimeType);
-};
+}
 
 /**
  * Tests opening Quick View with a '.mhtml' filename extension.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewMhtml' comes from an index
-// signature, so it must be accessed with ['openQuickViewMhtml'].
-testcase.openQuickViewMhtml = async () => {
+export async function openQuickViewMhtml() {
   const caller = getCaller();
 
   /**
@@ -1247,24 +1102,18 @@
 
   // Open Files app on Downloads containing ENTRIES.plainText.
   const appId =
-      // @ts-ignore: error TS4111: Property 'mHtml' comes from an index
-      // signature, so it must be accessed with ['mHtml'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.mHtml], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'mHtml' comes from an index signature,
-  // so it must be accessed with ['mHtml'].
-  await openQuickView(appId, ENTRIES.mHtml.nameText);
+  await openQuickViewEx(appId, ENTRIES.mHtml.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewTextLoaded(elements) {
+  function checkPreviewTextLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || !elements[0].attributes.src) {
+    if (!haveElements || !elements[0]!.attributes['src']) {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -1281,15 +1130,13 @@
   // Check: the correct file location should be displayed.
   const location = await getQuickViewMetadataBoxField(appId, 'File location');
   chrome.test.assertEq('My files/Downloads/page.mhtml', location);
-};
+}
 
 /**
  * Tests opening Quick View and scrolling its preview contents which contains a
  * tall html document.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewScrollHtml' comes from an
-// index signature, so it must be accessed with ['openQuickViewScrollHtml'].
-testcase.openQuickViewScrollHtml = async () => {
+export async function openQuickViewScrollHtml() {
   const caller = getCaller();
 
   /**
@@ -1298,40 +1145,33 @@
    */
   const preview = ['#quick-view', 'files-safe-media[type="html"]', previewTag];
 
-  // @ts-ignore: error TS7006: Parameter 'y' implicitly has an 'any' type.
-  function scrollQuickViewHtmlBy(y) {
+  function scrollQuickViewHtmlBy(y: number) {
     const doScrollBy = `window.scrollBy(0,${y})`;
     return remoteCall.executeJsInPreviewTag(appId, preview, doScrollBy);
   }
 
-  // @ts-ignore: error TS7006: Parameter 'scrollY' implicitly has an 'any' type.
-  async function checkQuickViewHtmlScrollY(scrollY) {
+  async function checkQuickViewHtmlScrollY(scrollY: {toString: () => any}) {
     if (!scrollY || Number(scrollY.toString()) <= 200) {
       await scrollQuickViewHtmlBy(100);
       return pending(caller, 'Waiting for Quick View to scroll.');
     }
+    return;
   }
 
   // Open Files app on Downloads containing ENTRIES.tallHtml.
   const appId =
-      // @ts-ignore: error TS4111: Property 'tallHtml' comes from an index
-      // signature, so it must be accessed with ['tallHtml'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.tallHtml], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'tallHtml' comes from an index
-  // signature, so it must be accessed with ['tallHtml'].
-  await openQuickView(appId, ENTRIES.tallHtml.nameText);
+  await openQuickViewEx(appId, ENTRIES.tallHtml.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewHtmlLoaded(elements) {
+  function checkPreviewHtmlLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -1345,13 +1185,13 @@
   const getScrollY = 'window.scrollY';
 
   // The initial preview scrollY should be 0.
-  // @ts-ignore: error TS7030: Not all code paths return a value.
   await repeatUntil(async () => {
     const scrollY =
         await executeJsInPreviewTagAndCatchErrors(appId, preview, getScrollY);
     if (String(scrollY) !== '0') {
       return pending(caller, `Waiting for preview text to load.`);
     }
+    return;
   });
 
   // Scroll the preview and verify that it scrolled.
@@ -1363,17 +1203,14 @@
 
   // Check: the mimeType field should not be displayed.
   await getQuickViewMetadataBoxField(appId, 'Type', 'hidden');
-};
+}
 
 /**
  * Tests opening Quick View on an html document to verify that the background
  * color of the <files-safe-media type="html"> that contains the preview is
  * solid white.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewBackgroundColorHtml' comes
-// from an index signature, so it must be accessed with
-// ['openQuickViewBackgroundColorHtml'].
-testcase.openQuickViewBackgroundColorHtml = async () => {
+export async function openQuickViewBackgroundColorHtml() {
   const caller = getCaller();
 
   /**
@@ -1385,24 +1222,18 @@
 
   // Open Files app on Downloads containing ENTRIES.tallHtml.
   const appId =
-      // @ts-ignore: error TS4111: Property 'tallHtml' comes from an index
-      // signature, so it must be accessed with ['tallHtml'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.tallHtml], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'tallHtml' comes from an index
-  // signature, so it must be accessed with ['tallHtml'].
-  await openQuickView(appId, ENTRIES.tallHtml.nameText);
+  await openQuickViewEx(appId, ENTRIES.tallHtml.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewHtmlLoaded(elements) {
+  function checkPreviewHtmlLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -1419,14 +1250,12 @@
       appId, preview, getBackgroundStyle);
 
   chrome.test.assertEq('rgba(0, 0, 0, 0)', backgroundColor[0]);
-};
+}
 
 /**
  * Tests opening Quick View containing an audio file without album preview.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewAudio' comes from an index
-// signature, so it must be accessed with ['openQuickViewAudio'].
-testcase.openQuickViewAudio = async () => {
+export async function openQuickViewAudio() {
   const caller = getCaller();
 
   /**
@@ -1443,24 +1272,18 @@
 
   // Open Files app on Downloads containing ENTRIES.beautiful song.
   const appId =
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-  // signature, so it must be accessed with ['beautiful'].
-  await openQuickView(appId, ENTRIES.beautiful.nameText);
+  await openQuickViewEx(appId, ENTRIES.beautiful.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewAudioLoaded(elements) {
+  function checkPreviewAudioLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -1495,14 +1318,12 @@
   // Check: the correct mimeType should be displayed.
   const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
   chrome.test.assertEq('audio/ogg', mimeType);
-};
+}
 
 /**
  * Tests opening Quick View containing an audio file on Drive.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewAudioOnDrive' comes from an
-// index signature, so it must be accessed with ['openQuickViewAudioOnDrive'].
-testcase.openQuickViewAudioOnDrive = async () => {
+export async function openQuickViewAudioOnDrive() {
   const caller = getCaller();
 
   /**
@@ -1513,24 +1334,18 @@
 
   // Open Files app on Downloads containing ENTRIES.beautiful song.
   const appId =
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.beautiful]);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-  // signature, so it must be accessed with ['beautiful'].
-  await openQuickView(appId, ENTRIES.beautiful.nameText);
+  await openQuickViewEx(appId, ENTRIES.beautiful.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewAudioLoaded(elements) {
+  function checkPreviewAudioLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -1553,16 +1368,13 @@
     // Check: the preview body backgroundColor should be transparent black.
     chrome.test.assertEq('rgba(0, 0, 0, 0)', backgroundColor[0]);
   }
-};
+}
 
 /**
  * Tests opening Quick View containing an audio file that has an album art
  * image in its metadata.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewAudioWithImageMetadata'
-// comes from an index signature, so it must be accessed with
-// ['openQuickViewAudioWithImageMetadata'].
-testcase.openQuickViewAudioWithImageMetadata = async () => {
+export async function openQuickViewAudioWithImageMetadata() {
   const caller = getCaller();
 
   // Define a test file containing audio file with metadata.
@@ -1588,17 +1400,15 @@
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [id3Audio], []);
 
   // Open the file in Quick View.
-  await openQuickView(appId, id3Audio.nameText);
+  await openQuickViewEx(appId, id3Audio.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewImageLoaded(elements) {
+  function checkPreviewImageLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -1617,14 +1427,12 @@
   // Check: the audio album metadata should also be displayed.
   const album = await getQuickViewMetadataBoxField(appId, 'Album');
   chrome.test.assertEq(album, 'OK Computer');
-};
+}
 
 /**
  * Tests opening Quick View containing an image with extension 'jpg'.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewImageJpg' comes from an
-// index signature, so it must be accessed with ['openQuickViewImageJpg'].
-testcase.openQuickViewImageJpg = async () => {
+export async function openQuickViewImageJpg() {
   const caller = getCaller();
 
   /**
@@ -1635,24 +1443,18 @@
 
   // Open Files app on Downloads containing ENTRIES.smallJpeg.
   const appId =
-      // @ts-ignore: error TS4111: Property 'smallJpeg' comes from an index
-      // signature, so it must be accessed with ['smallJpeg'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.smallJpeg], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'smallJpeg' comes from an index
-  // signature, so it must be accessed with ['smallJpeg'].
-  await openQuickView(appId, ENTRIES.smallJpeg.nameText);
+  await openQuickViewEx(appId, ENTRIES.smallJpeg.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewImageLoaded(elements) {
+  function checkPreviewImageLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -1679,14 +1481,12 @@
   // Check: the correct mimeType should be displayed.
   const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
   chrome.test.assertEq('image/jpeg', mimeType);
-};
+}
 
 /**
  * Tests opening Quick View containing an image with extension 'jpeg'.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewImageJpeg' comes from an
-// index signature, so it must be accessed with ['openQuickViewImageJpeg'].
-testcase.openQuickViewImageJpeg = async () => {
+export async function openQuickViewImageJpeg() {
   const caller = getCaller();
 
   /**
@@ -1697,24 +1497,18 @@
 
   // Open Files app on Downloads containing ENTRIES.sampleJpeg.
   const appId = await setupAndWaitUntilReady(
-      // @ts-ignore: error TS4111: Property 'sampleJpeg' comes from an index
-      // signature, so it must be accessed with ['sampleJpeg'].
       RootPath.DOWNLOADS, [ENTRIES.sampleJpeg], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'sampleJpeg' comes from an index
-  // signature, so it must be accessed with ['sampleJpeg'].
-  await openQuickView(appId, ENTRIES.sampleJpeg.nameText);
+  await openQuickViewEx(appId, ENTRIES.sampleJpeg.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewImageLoaded(elements) {
+  function checkPreviewImageLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -1741,15 +1535,13 @@
   // Check: the correct mimeType should be displayed.
   const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
   chrome.test.assertEq('image/jpeg', mimeType);
-};
+}
 
 /**
  * Tests that opening Quick View on a JPEG image with EXIF displays the EXIF
  * information in the QuickView Metadata Box.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewImageExif' comes from an
-// index signature, so it must be accessed with ['openQuickViewImageExif'].
-testcase.openQuickViewImageExif = async () => {
+export async function openQuickViewImageExif() {
   const caller = getCaller();
 
   /**
@@ -1760,24 +1552,18 @@
 
   // Open Files app on Downloads containing ENTRIES.exifImage.
   const appId =
-      // @ts-ignore: error TS4111: Property 'exifImage' comes from an index
-      // signature, so it must be accessed with ['exifImage'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.exifImage], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'exifImage' comes from an index
-  // signature, so it must be accessed with ['exifImage'].
-  await openQuickView(appId, ENTRIES.exifImage.nameText);
+  await openQuickViewEx(appId, ENTRIES.exifImage.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewImageLoaded(elements) {
+  function checkPreviewImageLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -1802,15 +1588,13 @@
   chrome.test.assertEq('FinePix S5000', model);
   const film = await getQuickViewMetadataBoxField(appId, 'Device settings');
   chrome.test.assertEq('f/2.8 0.004 5.7mm ISO200', film);
-};
+}
 
 /**
  * Tests opening Quick View on an RAW image. The RAW image has EXIF and that
  * information should be displayed in the QuickView metadata box.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewImageRaw' comes from an
-// index signature, so it must be accessed with ['openQuickViewImageRaw'].
-testcase.openQuickViewImageRaw = async () => {
+export async function openQuickViewImageRaw() {
   const caller = getCaller();
 
   /**
@@ -1821,24 +1605,18 @@
 
   // Open Files app on Downloads containing ENTRIES.rawImage.
   const appId =
-      // @ts-ignore: error TS4111: Property 'rawImage' comes from an index
-      // signature, so it must be accessed with ['rawImage'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.rawImage], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'rawImage' comes from an index
-  // signature, so it must be accessed with ['rawImage'].
-  await openQuickView(appId, ENTRIES.rawImage.nameText);
+  await openQuickViewEx(appId, ENTRIES.rawImage.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewImageLoaded(elements) {
+  function checkPreviewImageLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -1859,16 +1637,13 @@
   chrome.test.assertEq('E-M1', model);
   const film = await getQuickViewMetadataBoxField(appId, 'Device settings');
   chrome.test.assertEq('f/8 0.002 12mm ISO200', film);
-};
+}
 
 /**
  * Tests opening Quick View on an RAW .NEF image and that the dimensions
  * shown in the metadata box respect the image EXIF orientation.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewImageRawWithOrientation'
-// comes from an index signature, so it must be accessed with
-// ['openQuickViewImageRawWithOrientation'].
-testcase.openQuickViewImageRawWithOrientation = async () => {
+export async function openQuickViewImageRawWithOrientation() {
   const caller = getCaller();
 
   /**
@@ -1880,24 +1655,18 @@
 
   // Open Files app on Downloads containing ENTRIES.rawNef.
   const appId =
-      // @ts-ignore: error TS4111: Property 'nefImage' comes from an index
-      // signature, so it must be accessed with ['nefImage'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.nefImage], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'nefImage' comes from an index
-  // signature, so it must be accessed with ['nefImage'].
-  await openQuickView(appId, ENTRIES.nefImage.nameText);
+  await openQuickViewEx(appId, ENTRIES.nefImage.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewImageLoaded(elements) {
+  function checkPreviewImageLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -1919,37 +1688,30 @@
   // Get the fileSafeMedia element preview thumbnail image size.
   const element = await remoteCall.waitForElement(appId, filesSafeMedia);
   const image = new Image();
+  let imageSize = '';
   image.onload = () => {
-    // @ts-ignore: error TS2339: Property 'imageSize' does not exist on type
-    // 'HTMLImageElement'.
-    image.imageSize = `${image.naturalWidth} x ${image.naturalHeight}`;
+    imageSize = `${image.naturalWidth} x ${image.naturalHeight}`;
   };
 
-  const sourceContent =
-      // @ts-ignore: error TS4111: Property 'src' comes from an index signature,
-      // so it must be accessed with ['src'].
-      /** @type {FilePreviewContent} */ (JSON.parse(element.attributes.src));
+  const sourceContent = JSON.parse(element.attributes['src']!);
 
   chrome.test.assertTrue(!!sourceContent.data);
-  image.src = sourceContent.data;
+  image.src = sourceContent.data as string;
 
-  // Check: the preview thumbnail should have an orientiated size.
-  // @ts-ignore: error TS7030: Not all code paths return a value.
+  // Check: the preview thumbnail should have an orientated size.
   await repeatUntil(async () => {
-    // @ts-ignore: error TS2339: Property 'imageSize' does not exist on type
-    // 'HTMLImageElement'.
-    if (!image.complete || image.imageSize !== '120 x 160') {
+    if (!image.complete || imageSize !== '120 x 160') {
       return pending(caller, 'Waiting for preview thumbnail size.');
     }
+
+    return;
   });
-};
+}
 
 /**
  * Tests opening Quick View with a VP8X format WEBP image.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewImageWebp' comes from an
-// index signature, so it must be accessed with ['openQuickViewImageWebp'].
-testcase.openQuickViewImageWebp = async () => {
+export async function openQuickViewImageWebp() {
   const caller = getCaller();
 
   /**
@@ -1960,24 +1722,18 @@
 
   // Open Files app on Downloads containing ENTRIES.rawImage.
   const appId =
-      // @ts-ignore: error TS4111: Property 'webpImage' comes from an index
-      // signature, so it must be accessed with ['webpImage'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.webpImage], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'webpImage' comes from an index
-  // signature, so it must be accessed with ['webpImage'].
-  await openQuickView(appId, ENTRIES.webpImage.nameText);
+  await openQuickViewEx(appId, ENTRIES.webpImage.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewImageLoaded(elements) {
+  function checkPreviewImageLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -1994,16 +1750,14 @@
   // Check: the correct dimensions should be displayed.
   const size = await getQuickViewMetadataBoxField(appId, 'Dimensions');
   chrome.test.assertEq('400 x 175', size);
-};
+}
 
 /**
  * Tests that opening Quick View on an image and clicking the image does not
  * focus the image. Instead, the user should still be able to cycle through
  * file list items in Quick View: crbug.com/1038835.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewImageClick' comes from an
-// index signature, so it must be accessed with ['openQuickViewImageClick'].
-testcase.openQuickViewImageClick = async () => {
+export async function openQuickViewImageClick() {
   const caller = getCaller();
 
   /**
@@ -2014,24 +1768,18 @@
 
   // Open Files app on Downloads containing two images.
   const appId = await setupAndWaitUntilReady(
-      // @ts-ignore: error TS4111: Property 'image3' comes from an index
-      // signature, so it must be accessed with ['image3'].
       RootPath.DOWNLOADS, [ENTRIES.desktop, ENTRIES.image3], []);
 
   // Open the first image in Quick View.
-  // @ts-ignore: error TS4111: Property 'desktop' comes from an index signature,
-  // so it must be accessed with ['desktop'].
-  await openQuickView(appId, ENTRIES.desktop.nameText);
+  await openQuickViewEx(appId, ENTRIES.desktop.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewImageLoaded(elements) {
+  function checkPreviewImageLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -2064,16 +1812,14 @@
   chrome.test.assertEq('image/jpeg', mimeType);
 
   // Check: Quick View should be able to close.
-  await closeQuickView(appId);
-};
+  await closeQuickViewEx(appId);
+}
 
 /**
  * Tests that opening a broken image in Quick View displays the "no-preview
  * available" generic icon and has a [load-error] attribute.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewBrokenImage' comes from an
-// index signature, so it must be accessed with ['openQuickViewBrokenImage'].
-testcase.openQuickViewBrokenImage = async () => {
+export async function openQuickViewBrokenImage() {
   const caller = getCaller();
 
   /**
@@ -2088,24 +1834,18 @@
 
   // Open Files app on Downloads containing ENTRIES.brokenJpeg.
   const appId = await setupAndWaitUntilReady(
-      // @ts-ignore: error TS4111: Property 'brokenJpeg' comes from an index
-      // signature, so it must be accessed with ['brokenJpeg'].
       RootPath.DOWNLOADS, [ENTRIES.brokenJpeg], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'brokenJpeg' comes from an index
-  // signature, so it must be accessed with ['brokenJpeg'].
-  await openQuickView(appId, ENTRIES.brokenJpeg.nameText);
+  await openQuickViewEx(appId, ENTRIES.brokenJpeg.nameText);
 
   // Check: the quick view element should have a 'load-error' attribute.
   await remoteCall.waitForElement(appId, '#quick-view[load-error]');
 
   // Wait for the generic thumbnail to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkForGenericThumbnail(elements) {
+  function checkForGenericThumbnail(elements: ElementObject[]) {
     const haveElements = Array.isArray(elements) && elements.length === 1;
-    if (!haveElements || elements[0].styles.display !== 'block') {
+    if (!haveElements || elements[0]!.styles!['display'] !== 'block') {
       return pending(caller, 'Waiting for generic thumbnail to load.');
     }
     return;
@@ -2116,14 +1856,12 @@
     return checkForGenericThumbnail(await remoteCall.callRemoteTestUtil(
         'deepQueryAllElements', appId, [genericThumbnail, ['display']]));
   });
-};
+}
 
 /**
  * Tests opening Quick View containing a video.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewVideo' comes from an index
-// signature, so it must be accessed with ['openQuickViewVideo'].
-testcase.openQuickViewVideo = async () => {
+export async function openQuickViewVideo() {
   const caller = getCaller();
 
   /**
@@ -2134,24 +1872,18 @@
 
   // Open Files app on Downloads containing ENTRIES.webm video.
   const appId =
-      // @ts-ignore: error TS4111: Property 'webm' comes from an index
-      // signature, so it must be accessed with ['webm'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.webm], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'webm' comes from an index signature, so
-  // it must be accessed with ['webm'].
-  await openQuickView(appId, ENTRIES.webm.nameText);
+  await openQuickViewEx(appId, ENTRIES.webm.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewVideoLoaded(elements) {
+  function checkPreviewVideoLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -2180,19 +1912,17 @@
   chrome.test.assertEq('video/webm', mimeType);
 
   // Close Quick View.
-  await closeQuickView(appId);
+  await closeQuickViewEx(appId);
 
   // Check: closing Quick View should remove the video <files-safe-media>
   // preview element, so it stops playing the video. crbug.com/970192
   await remoteCall.waitForElementLost(appId, preview);
-};
+}
 
 /**
  * Tests opening Quick View containing a video on Drive.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewVideoOnDrive' comes from an
-// index signature, so it must be accessed with ['openQuickViewVideoOnDrive'].
-testcase.openQuickViewVideoOnDrive = async () => {
+export async function openQuickViewVideoOnDrive() {
   const caller = getCaller();
 
   /**
@@ -2203,24 +1933,18 @@
 
   // Open Files app on Downloads containing ENTRIES.webm video.
   const appId =
-      // @ts-ignore: error TS4111: Property 'webm' comes from an index
-      // signature, so it must be accessed with ['webm'].
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.webm]);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'webm' comes from an index signature, so
-  // it must be accessed with ['webm'].
-  await openQuickView(appId, ENTRIES.webm.nameText);
+  await openQuickViewEx(appId, ENTRIES.webm.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewVideoLoaded(elements) {
+  function checkPreviewVideoLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -2249,21 +1973,18 @@
   chrome.test.assertEq('video/webm', mimeType);
 
   // Close Quick View.
-  await closeQuickView(appId);
+  await closeQuickViewEx(appId);
 
   // Check: closing Quick View should remove the video <files-safe-media>
   // preview element, so it stops playing the video. crbug.com/970192
   await remoteCall.waitForElementLost(appId, preview);
-};
+}
 
 /**
  * Tests opening Quick View with multiple files and using the up/down arrow
  * keys to select and view their content.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewKeyboardUpDownChangesView'
-// comes from an index signature, so it must be accessed with
-// ['openQuickViewKeyboardUpDownChangesView'].
-testcase.openQuickViewKeyboardUpDownChangesView = async () => {
+export async function openQuickViewKeyboardUpDownChangesView() {
   const caller = getCaller();
 
   /**
@@ -2273,27 +1994,19 @@
   const preview = ['#quick-view', `#dialog[open] ${previewTag}.text-content`];
 
   // Open Files app on Downloads containing two text files.
-  // @ts-ignore: error TS4111: Property 'tallText' comes from an index
-  // signature, so it must be accessed with ['tallText'].
   const files = [ENTRIES.hello, ENTRIES.tallText];
-  // @ts-ignore: error TS2345: Argument of type '(TestEntryInfo | undefined)[]'
-  // is not assignable to parameter of type 'TestEntryInfo[]'.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, files, []);
 
   // Open the last file in Quick View.
-  // @ts-ignore: error TS4111: Property 'tallText' comes from an index
-  // signature, so it must be accessed with ['tallText'].
-  await openQuickView(appId, ENTRIES.tallText.nameText);
+  await openQuickViewEx(appId, ENTRIES.tallText.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewTextLoaded(elements) {
+  function checkPreviewTextLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || !elements[0].attributes.src) {
+    if (!haveElements || !elements[0]!.attributes['src']) {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -2309,14 +2022,14 @@
       await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downArrow));
 
   // Wait until the preview displays that file's content.
-  // @ts-ignore: error TS7030: Not all code paths return a value.
   await repeatUntil(async () => {
     const getTextContent = contentWindowQuery + '.document.body.textContent';
     const text = await executeJsInPreviewTagAndCatchErrors(
-        appId, preview, getTextContent);
+                     appId, preview, getTextContent) as string[];
     if (!text || !text[0] || !text[0].includes('This is a sample file')) {
       return pending(caller, 'Waiting for preview content.');
     }
+    return;
   });
 
   // Press the up arrow key to select the previous file.
@@ -2325,25 +2038,22 @@
       await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, upArrow));
 
   // Wait until the preview displays that file's content.
-  // @ts-ignore: error TS7030: Not all code paths return a value.
   await repeatUntil(async () => {
     const getTextContent = contentWindowQuery + '.document.body.textContent';
     const text = await executeJsInPreviewTagAndCatchErrors(
-        appId, preview, getTextContent);
+                     appId, preview, getTextContent) as string[];
     if (!text || !text[0] || !text[0].includes('42 tall text')) {
       return pending(caller, 'Waiting for preview content.');
     }
+    return;
   });
-};
+}
 
 /**
  * Tests opening Quick View with multiple files and using the left/right arrow
  * keys to select and view their content.
  */
-// @ts-ignore: error TS4111: Property
-// 'openQuickViewKeyboardLeftRightChangesView' comes from an index signature, so
-// it must be accessed with ['openQuickViewKeyboardLeftRightChangesView'].
-testcase.openQuickViewKeyboardLeftRightChangesView = async () => {
+export async function openQuickViewKeyboardLeftRightChangesView() {
   const caller = getCaller();
 
   /**
@@ -2353,27 +2063,19 @@
   const preview = ['#quick-view', `#dialog[open] ${previewTag}.text-content`];
 
   // Open Files app on Downloads containing two text files.
-  // @ts-ignore: error TS4111: Property 'tallText' comes from an index
-  // signature, so it must be accessed with ['tallText'].
   const files = [ENTRIES.hello, ENTRIES.tallText];
-  // @ts-ignore: error TS2345: Argument of type '(TestEntryInfo | undefined)[]'
-  // is not assignable to parameter of type 'TestEntryInfo[]'.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, files, []);
 
   // Open the last file in Quick View.
-  // @ts-ignore: error TS4111: Property 'tallText' comes from an index
-  // signature, so it must be accessed with ['tallText'].
-  await openQuickView(appId, ENTRIES.tallText.nameText);
+  await openQuickViewEx(appId, ENTRIES.tallText.nameText);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewTextLoaded(elements) {
+  function checkPreviewTextLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || !elements[0].attributes.src) {
+    if (!haveElements || !elements[0]!.attributes['src']) {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -2389,14 +2091,14 @@
       await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, rightArrow));
 
   // Wait until the preview displays that file's content.
-  // @ts-ignore: error TS7030: Not all code paths return a value.
   await repeatUntil(async () => {
     const getTextContent = contentWindowQuery + '.document.body.textContent';
     const text = await executeJsInPreviewTagAndCatchErrors(
-        appId, preview, getTextContent);
+                     appId, preview, getTextContent) as string[];
     if (!text || !text[0] || !text[0].includes('This is a sample file')) {
       return pending(caller, 'Waiting for preview content.');
     }
+    return;
   });
 
   // Press the left arrow key to select the previous file item.
@@ -2405,31 +2107,28 @@
       await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, leftArrow));
 
   // Wait until the preview displays that file's content.
-  // @ts-ignore: error TS7030: Not all code paths return a value.
   await repeatUntil(async () => {
     const getTextContent = contentWindowQuery + '.document.body.textContent';
     const text = await executeJsInPreviewTagAndCatchErrors(
-        appId, preview, getTextContent);
+                     appId, preview, getTextContent) as string[];
     if (!text || !text[0] || !text[0].includes('42 tall text')) {
       return pending(caller, 'Waiting for preview content.');
     }
+    return;
   });
-};
+}
 
 /**
  * Tests that the metadatabox can be toggled opened/closed by pressing the
  * Enter key on the Quick View toolbar info button.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewToggleInfoButtonKeyboard'
-// comes from an index signature, so it must be accessed with
-// ['openQuickViewToggleInfoButtonKeyboard'].
-testcase.openQuickViewToggleInfoButtonKeyboard = async () => {
+export async function openQuickViewToggleInfoButtonKeyboard() {
   // Open Files app on Downloads containing ENTRIES.hello.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
 
   // Open the file in Quick View.
-  await openQuickView(appId, ENTRIES.hello.nameText);
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
 
   // Check: the metadatabox should be open.
   const metaShown = ['#quick-view', '#contentPanel[metadata-box-active]'];
@@ -2454,22 +2153,19 @@
 
   // Check: the metadatabox should open.
   await remoteCall.waitForElement(appId, metaShown);
-};
+}
 
 /**
  * Tests that the metadatabox can be toggled opened/closed by clicking the
  * the Quick View toolbar info button.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewToggleInfoButtonClick' comes
-// from an index signature, so it must be accessed with
-// ['openQuickViewToggleInfoButtonClick'].
-testcase.openQuickViewToggleInfoButtonClick = async () => {
+export async function openQuickViewToggleInfoButtonClick() {
   // Open Files app on Downloads containing ENTRIES.hello.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
 
   // Open the file in Quick View.
-  await openQuickView(appId, ENTRIES.hello.nameText);
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
 
   // Check: the metadatabox should be open.
   const metaShown = ['#quick-view', '#contentPanel[metadata-box-active]'];
@@ -2493,15 +2189,12 @@
 
   // Check: the metadatabox should open.
   await remoteCall.waitForElement(appId, metaShown);
-};
+}
 
 /**
  * Tests that Quick View opens with multiple files selected.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewWithMultipleFiles' comes
-// from an index signature, so it must be accessed with
-// ['openQuickViewWithMultipleFiles'].
-testcase.openQuickViewWithMultipleFiles = async () => {
+export async function openQuickViewWithMultipleFiles() {
   const caller = getCaller();
 
   /**
@@ -2541,14 +2234,12 @@
   await openQuickViewMultipleSelection(appId, ['Desktop', 'hello']);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewImageLoaded(elements) {
+  function checkPreviewImageLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -2563,16 +2254,13 @@
   // Check: the correct file mimeType should be displayed.
   const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
   chrome.test.assertEq('image/png', mimeType);
-};
+}
 
 /**
  * Tests that Quick View displays text files when multiple files are
  * selected.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewWithMultipleFilesText' comes
-// from an index signature, so it must be accessed with
-// ['openQuickViewWithMultipleFilesText'].
-testcase.openQuickViewWithMultipleFilesText = async () => {
+export async function openQuickViewWithMultipleFilesText() {
   const caller = getCaller();
 
   /**
@@ -2581,11 +2269,7 @@
    */
   const preview = ['#quick-view', 'files-safe-media[type="image"]', previewTag];
 
-  // @ts-ignore: error TS4111: Property 'smallJpeg' comes from an index
-  // signature, so it must be accessed with ['smallJpeg'].
   const files = [ENTRIES.tallText, ENTRIES.hello, ENTRIES.smallJpeg];
-  // @ts-ignore: error TS2345: Argument of type '(TestEntryInfo | undefined)[]'
-  // is not assignable to parameter of type 'TestEntryInfo[]'.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, files, []);
 
   // Add item 1 to the check-selection, ENTRIES.smallJpeg.
@@ -2613,14 +2297,12 @@
   await openQuickViewMultipleSelection(appId, ['small', 'hello']);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewImageLoaded(elements) {
+  function checkPreviewImageLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -2644,14 +2326,12 @@
       await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downArrow));
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewTextLoaded(elements) {
+  function checkPreviewTextLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || !elements[0].attributes.src) {
+    if (!haveElements || !elements[0]!.attributes['src']) {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -2666,16 +2346,13 @@
   // Check: the open button should be displayed.
   await remoteCall.waitForElement(
       appId, ['#quick-view', '#open-button:not([hidden])']);
-};
+}
 
 /**
  * Tests that Quick View displays pdf files when multiple files are
  * selected.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewWithMultipleFilesPdf' comes
-// from an index signature, so it must be accessed with
-// ['openQuickViewWithMultipleFilesPdf'].
-testcase.openQuickViewWithMultipleFilesPdf = async () => {
+export async function openQuickViewWithMultipleFilesPdf() {
   const caller = getCaller();
 
   /**
@@ -2684,11 +2361,7 @@
    */
   const preview = ['#quick-view', 'files-safe-media[type="image"]', previewTag];
 
-  // @ts-ignore: error TS4111: Property 'smallJpeg' comes from an index
-  // signature, so it must be accessed with ['smallJpeg'].
   const files = [ENTRIES.tallPdf, ENTRIES.desktop, ENTRIES.smallJpeg];
-  // @ts-ignore: error TS2345: Argument of type '(TestEntryInfo | undefined)[]'
-  // is not assignable to parameter of type 'TestEntryInfo[]'.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, files, []);
 
   // Add item 1 to the check-selection, ENTRIES.smallJpeg.
@@ -2716,14 +2389,12 @@
   await openQuickViewMultipleSelection(appId, ['small', 'tall']);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewImageLoaded(elements) {
+  function checkPreviewImageLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -2747,14 +2418,12 @@
       await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downArrow));
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewPdfLoaded(elements) {
+  function checkPreviewPdfLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || !elements[0].attributes.src) {
+    if (!haveElements || !elements[0]!.attributes['src']) {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -2769,16 +2438,13 @@
   // Check: the open button should be displayed.
   await remoteCall.waitForElement(
       appId, ['#quick-view', '#open-button:not([hidden])']);
-};
+}
 
 /**
  * Tests that the content panel changes when using the up/down arrow keys
  * when multiple files are selected.
  */
-// @ts-ignore: error TS4111: Property
-// 'openQuickViewWithMultipleFilesKeyboardUpDown' comes from an index signature,
-// so it must be accessed with ['openQuickViewWithMultipleFilesKeyboardUpDown'].
-testcase.openQuickViewWithMultipleFilesKeyboardUpDown = async () => {
+export async function openQuickViewWithMultipleFilesKeyboardUpDown() {
   const caller = getCaller();
 
   /**
@@ -2788,11 +2454,7 @@
   const preview = ['#quick-view', `#dialog[open] ${previewTag}.text-content`];
 
   // Open Files app on Downloads containing three text files.
-  // @ts-ignore: error TS4111: Property 'plainText' comes from an index
-  // signature, so it must be accessed with ['plainText'].
   const files = [ENTRIES.hello, ENTRIES.tallText, ENTRIES.plainText];
-  // @ts-ignore: error TS2345: Argument of type '(TestEntryInfo | undefined)[]'
-  // is not assignable to parameter of type 'TestEntryInfo[]'.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, files, []);
 
   // Add item 1 to the check-selection, ENTRIES.tallText.
@@ -2820,14 +2482,12 @@
   await openQuickViewMultipleSelection(appId, ['tall', 'hello']);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewTextLoaded(elements) {
+  function checkPreviewTextLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || !elements[0].attributes.src) {
+    if (!haveElements || !elements[0]!.attributes['src']) {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -2843,15 +2503,15 @@
       await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downArrow));
 
   // Wait until the preview displays that file's content.
-  // @ts-ignore: error TS7030: Not all code paths return a value.
   await repeatUntil(async () => {
     const getTextContent = contentWindowQuery + '.document.body.textContent';
     const text = await executeJsInPreviewTagAndCatchErrors(
-        appId, preview, getTextContent);
+                     appId, preview, getTextContent) as string[];
     // Check: the content of ENTRIES.hello should be shown.
     if (!text || !text[0] || !text[0].includes('This is a sample file')) {
       return pending(caller, 'Waiting for preview content.');
     }
+    return;
   });
 
   // Press the up arrow key to select the previous file.
@@ -2860,27 +2520,23 @@
       await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, upArrow));
 
   // Wait until the preview displays that file's content.
-  // @ts-ignore: error TS7030: Not all code paths return a value.
   await repeatUntil(async () => {
     const getTextContent = contentWindowQuery + '.document.body.textContent';
     const text = await executeJsInPreviewTagAndCatchErrors(
-        appId, preview, getTextContent);
+                     appId, preview, getTextContent) as string[];
     // Check: the content of ENTRIES.tallText should be shown.
     if (!text || !text[0] || !text[0].includes('42 tall text')) {
       return pending(caller, 'Waiting for preview content.');
     }
+    return;
   });
-};
+}
 
 /**
  * Tests that the content panel changes when using the left/right arrow keys
  * when multiple files are selected.
  */
-// @ts-ignore: error TS4111: Property
-// 'openQuickViewWithMultipleFilesKeyboardLeftRight' comes from an index
-// signature, so it must be accessed with
-// ['openQuickViewWithMultipleFilesKeyboardLeftRight'].
-testcase.openQuickViewWithMultipleFilesKeyboardLeftRight = async () => {
+export async function openQuickViewWithMultipleFilesKeyboardLeftRight() {
   const caller = getCaller();
 
   /**
@@ -2890,11 +2546,7 @@
   const preview = ['#quick-view', `#dialog[open] ${previewTag}.text-content`];
 
   // Open Files app on Downloads containing three text files.
-  // @ts-ignore: error TS4111: Property 'plainText' comes from an index
-  // signature, so it must be accessed with ['plainText'].
   const files = [ENTRIES.hello, ENTRIES.tallText, ENTRIES.plainText];
-  // @ts-ignore: error TS2345: Argument of type '(TestEntryInfo | undefined)[]'
-  // is not assignable to parameter of type 'TestEntryInfo[]'.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, files, []);
 
   // Add item 1 to the check-selection, ENTRIES.tallText.
@@ -2922,14 +2574,12 @@
   await openQuickViewMultipleSelection(appId, ['tall', 'hello']);
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewTextLoaded(elements) {
+  function checkPreviewTextLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || !elements[0].attributes.src) {
+    if (!haveElements || !elements[0]!.attributes['src']) {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -2945,15 +2595,15 @@
       await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, rightArrow));
 
   // Wait until the preview displays that file's content.
-  // @ts-ignore: error TS7030: Not all code paths return a value.
   await repeatUntil(async () => {
     const getTextContent = contentWindowQuery + '.document.body.textContent';
     const text = await executeJsInPreviewTagAndCatchErrors(
-        appId, preview, getTextContent);
+                     appId, preview, getTextContent) as string[];
     // Check: the content of ENTRIES.hello should be shown.
     if (!text || !text[0] || !text[0].includes('This is a sample file')) {
       return pending(caller, 'Waiting for preview content.');
     }
+    return;
   });
 
   // Press the left arrow key to select the previous file item.
@@ -2962,31 +2612,29 @@
       await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, leftArrow));
 
   // Wait until the preview displays that file's content.
-  // @ts-ignore: error TS7030: Not all code paths return a value.
   await repeatUntil(async () => {
     const getTextContent = contentWindowQuery + '.document.body.textContent';
     const text = await executeJsInPreviewTagAndCatchErrors(
-        appId, preview, getTextContent);
+                     appId, preview, getTextContent) as string[];
     // Check: the content of ENTRIES.tallText should be shown.
     if (!text || !text[0] || !text[0].includes('42 tall text')) {
       return pending(caller, 'Waiting for preview content.');
     }
+    return;
   });
-};
+}
 
 /**
  * Tests opening Quick View and closing with Escape key returns focus to file
  * list.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewAndEscape' comes from an
-// index signature, so it must be accessed with ['openQuickViewAndEscape'].
-testcase.openQuickViewAndEscape = async () => {
+export async function openQuickViewAndEscape() {
   // Open Files app on Downloads containing ENTRIES.hello.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
 
   // Open the file in Quick View.
-  await openQuickView(appId, ENTRIES.hello.nameText);
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
 
   // Hit Escape key to close Quick View.
   const panelElements = ['#quick-view', '#contentPanel'];
@@ -3002,16 +2650,13 @@
   const element = await remoteCall.waitForElement(appId, '#file-list:focus');
   chrome.test.assertEq(
       'file-list', element.attributes['id'], '#file-list should be focused');
-};
+}
 
 /**
  * Test opening Quick View when Directory Tree is focused it should display if
  * there is only 1 file/folder selected in the file list.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewFromDirectoryTree' comes
-// from an index signature, so it must be accessed with
-// ['openQuickViewFromDirectoryTree'].
-testcase.openQuickViewFromDirectoryTree = async () => {
+export async function openQuickViewFromDirectoryTree() {
   // Open Files app on Downloads containing ENTRIES.hello.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
@@ -3021,9 +2666,7 @@
   await directoryTree.focusTree();
 
   // Ctrl+A to select the only file.
-  const ctrlA = [directoryTree.rootSelector, 'a', true, false, false];
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
+  const ctrlA = [directoryTree.rootSelector, 'a', true, false, false] as const;
   await remoteCall.fakeKeyDown(appId, ...ctrlA);
 
   // Use selection menu button to open Quick View.
@@ -3046,20 +2689,18 @@
     const elements = await remoteCall.callRemoteTestUtil(
         'deepQueryAllElements', appId, [query, ['display']]);
     const haveElements = Array.isArray(elements) && elements.length !== 0;
-    if (!haveElements || elements[0].styles.display !== 'block') {
+    if (!haveElements || elements[0]!.styles!['display'] !== 'block') {
       return pending(caller, 'Waiting for Quick View to open.');
     }
     return true;
   });
-};
+}
 
 /**
  * Tests the tab-index focus order when sending tab keys when an image file is
  * shown in Quick View.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewTabIndexImage' comes from an
-// index signature, so it must be accessed with ['openQuickViewTabIndexImage'].
-testcase.openQuickViewTabIndexImage = async () => {
+export async function openQuickViewTabIndexImage() {
   // Prepare a list of tab-index focus queries.
   const tabQueries = [
     {'query': ['#quick-view', `[aria-label="Back"]:focus`]},
@@ -3070,21 +2711,15 @@
 
   // Open Files app on Downloads containing ENTRIES.smallJpeg.
   const appId =
-      // @ts-ignore: error TS4111: Property 'smallJpeg' comes from an index
-      // signature, so it must be accessed with ['smallJpeg'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.smallJpeg], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'smallJpeg' comes from an index
-  // signature, so it must be accessed with ['smallJpeg'].
-  await openQuickView(appId, ENTRIES.smallJpeg.nameText);
+  await openQuickViewEx(appId, ENTRIES.smallJpeg.nameText);
 
   for (const query of tabQueries) {
     // Make the browser dispatch a tab key event to FilesApp.
-    const result = await sendTestMessage(
-        // @ts-ignore: error TS2339: Property 'shift' does not exist on type '{
-        // query: string[]; }'.
-        {name: 'dispatchTabKey', shift: query.shift || false});
+    const result =
+        await sendTestMessage({name: 'dispatchTabKey', shift: false});
     chrome.test.assertEq(
         'tabKeyDispatched', result, 'Tab key dispatch failure');
 
@@ -3095,15 +2730,13 @@
     // Check: the queried element should gain the focus.
     await remoteCall.waitForElement(appId, query.query);
   }
-};
+}
 
 /**
  * Tests the tab-index focus order when sending tab keys when a text file is
  * shown in Quick View.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewTabIndexText' comes from an
-// index signature, so it must be accessed with ['openQuickViewTabIndexText'].
-testcase.openQuickViewTabIndexText = async () => {
+export async function openQuickViewTabIndexText() {
   // Prepare a list of tab-index focus queries.
   const tabQueries = [
     {'query': ['#quick-view', `[aria-label="Back"]:focus`]},
@@ -3116,21 +2749,15 @@
 
   // Open Files app on Downloads containing ENTRIES.tallText.
   const appId =
-      // @ts-ignore: error TS4111: Property 'tallText' comes from an index
-      // signature, so it must be accessed with ['tallText'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.tallText], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'tallText' comes from an index
-  // signature, so it must be accessed with ['tallText'].
-  await openQuickView(appId, ENTRIES.tallText.nameText);
+  await openQuickViewEx(appId, ENTRIES.tallText.nameText);
 
   for (const query of tabQueries) {
     // Make the browser dispatch a tab key event to FilesApp.
-    const result = await sendTestMessage(
-        // @ts-ignore: error TS2339: Property 'shift' does not exist on type '{
-        // query: string[]; }'.
-        {name: 'dispatchTabKey', shift: query.shift || false});
+    const result =
+        await sendTestMessage({name: 'dispatchTabKey', shift: false});
     chrome.test.assertEq(
         'tabKeyDispatched', result, 'Tab key dispatch failure');
 
@@ -3141,15 +2768,13 @@
     // Check: the queried element should gain the focus.
     await remoteCall.waitForElement(appId, query.query);
   }
-};
+}
 
 /**
  * Tests the tab-index focus order when sending tab keys when an HTML file is
  * shown in Quick View.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewTabIndexHtml' comes from an
-// index signature, so it must be accessed with ['openQuickViewTabIndexHtml'].
-testcase.openQuickViewTabIndexHtml = async () => {
+export async function openQuickViewTabIndexHtml() {
   // Prepare a list of tab-index focus queries.
   const tabQueries = [
     {'query': ['#quick-view', `[aria-label="Back"]:focus`]},
@@ -3160,21 +2785,15 @@
 
   // Open Files app on Downloads containing ENTRIES.tallHtml.
   const appId =
-      // @ts-ignore: error TS4111: Property 'tallHtml' comes from an index
-      // signature, so it must be accessed with ['tallHtml'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.tallHtml], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'tallHtml' comes from an index
-  // signature, so it must be accessed with ['tallHtml'].
-  await openQuickView(appId, ENTRIES.tallHtml.nameText);
+  await openQuickViewEx(appId, ENTRIES.tallHtml.nameText);
 
   for (const query of tabQueries) {
     // Make the browser dispatch a tab key event to FilesApp.
-    const result = await sendTestMessage(
-        // @ts-ignore: error TS2339: Property 'shift' does not exist on type '{
-        // query: string[]; }'.
-        {name: 'dispatchTabKey', shift: query.shift || false});
+    const result =
+        await sendTestMessage({name: 'dispatchTabKey', shift: false});
     chrome.test.assertEq(
         'tabKeyDispatched', result, 'Tab key dispatch failure');
 
@@ -3185,25 +2804,19 @@
     // Check: the queried element should gain the focus.
     await remoteCall.waitForElement(appId, query.query);
   }
-};
+}
 
 /**
  * Tests the tab-index focus order when sending tab keys when an audio file
  * is shown in Quick View.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewTabIndexAudio' comes from an
-// index signature, so it must be accessed with ['openQuickViewTabIndexAudio'].
-testcase.openQuickViewTabIndexAudio = async () => {
+export async function openQuickViewTabIndexAudio() {
   // Open Files app on Downloads containing ENTRIES.beautiful song.
   const appId =
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-  // signature, so it must be accessed with ['beautiful'].
-  await openQuickView(appId, ENTRIES.beautiful.nameText);
+  await openQuickViewEx(appId, ENTRIES.beautiful.nameText);
 
   // Prepare a list of tab-index focus queries.
   const tabQueries = [
@@ -3215,10 +2828,8 @@
 
   for (const query of tabQueries) {
     // Make the browser dispatch a tab key event to FilesApp.
-    const result = await sendTestMessage(
-        // @ts-ignore: error TS2339: Property 'shift' does not exist on type '{
-        // query: string[]; }'.
-        {name: 'dispatchTabKey', shift: query.shift || false});
+    const result =
+        await sendTestMessage({name: 'dispatchTabKey', shift: false});
     chrome.test.assertEq(
         'tabKeyDispatched', result, 'Tab key dispatch failure');
 
@@ -3249,25 +2860,19 @@
       break;
     }
   }
-};
+}
 
 /**
  * Tests the tab-index focus order when sending tab keys when a video file is
  * shown in Quick View.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewTabIndexVideo' comes from an
-// index signature, so it must be accessed with ['openQuickViewTabIndexVideo'].
-testcase.openQuickViewTabIndexVideo = async () => {
+export async function openQuickViewTabIndexVideo() {
   // Open Files app on Downloads containing ENTRIES.webm video.
   const appId =
-      // @ts-ignore: error TS4111: Property 'webm' comes from an index
-      // signature, so it must be accessed with ['webm'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.webm], []);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'webm' comes from an index signature, so
-  // it must be accessed with ['webm'].
-  await openQuickView(appId, ENTRIES.webm.nameText);
+  await openQuickViewEx(appId, ENTRIES.webm.nameText);
 
   // Prepare a list of tab-index focus queries.
   const tabQueries = [
@@ -3279,10 +2884,8 @@
 
   for (const query of tabQueries) {
     // Make the browser dispatch a tab key event to FilesApp.
-    const result = await sendTestMessage(
-        // @ts-ignore: error TS2339: Property 'shift' does not exist on type '{
-        // query: string[]; }'.
-        {name: 'dispatchTabKey', shift: query.shift || false});
+    const result =
+        await sendTestMessage({name: 'dispatchTabKey', shift: false});
     chrome.test.assertEq(
         'tabKeyDispatched', result, 'Tab key dispatch failure');
 
@@ -3313,15 +2916,12 @@
       break;
     }
   }
-};
+}
 
 /**
  * Tests that the tab-index focus stays within the delete confirm dialog.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewTabIndexDeleteDialog' comes
-// from an index signature, so it must be accessed with
-// ['openQuickViewTabIndexDeleteDialog'].
-testcase.openQuickViewTabIndexDeleteDialog = async () => {
+export async function openQuickViewTabIndexDeleteDialog() {
   // Open Files app.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.photos], []);
@@ -3329,7 +2929,7 @@
   // Open a USB file in Quick View. USB delete never uses trash and always
   // shows the delete dialog.
   await mountAndSelectUsb(appId);
-  await openQuickView(appId, ENTRIES.hello.nameText);
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
 
   // Open the Quick View delete confirm dialog.
   const deleteKey = ['#quick-view', 'Delete', false, false, false];
@@ -3350,10 +2950,8 @@
 
   for (const query of tabQueries) {
     // Make the browser dispatch a tab key event to FilesApp.
-    const result = await sendTestMessage(
-        // @ts-ignore: error TS2339: Property 'shift' does not exist on type '{
-        // query: string[]; }'.
-        {name: 'dispatchTabKey', shift: query.shift || false});
+    const result =
+        await sendTestMessage({name: 'dispatchTabKey', shift: false});
     chrome.test.assertEq(
         'tabKeyDispatched', result, 'Tab key dispatch failure');
 
@@ -3364,22 +2962,19 @@
     // Check: the queried element should gain the focus.
     await remoteCall.waitForElement(appId, query.query);
   }
-};
+}
 
 /**
  * Tests deleting an item from Quick View when in single select mode, and
  * that Quick View closes when there are no more items to view.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewAndDeleteSingleSelection'
-// comes from an index signature, so it must be accessed with
-// ['openQuickViewAndDeleteSingleSelection'].
-testcase.openQuickViewAndDeleteSingleSelection = async () => {
+export async function openQuickViewAndDeleteSingleSelection() {
   // Open Files app on Downloads containing ENTRIES.hello.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
 
   // Open the file in Quick View.
-  await openQuickView(appId, ENTRIES.hello.nameText);
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
 
   // Press delete key.
   const deleteKey = ['#quick-view', 'Delete', false, false, false];
@@ -3393,7 +2988,7 @@
 
   // Check: the Quick View dialog should close.
   await waitQuickViewClose(appId);
-};
+}
 
 /**
  * Tests deleting an item from Quick View while in check-selection mode.
@@ -3401,10 +2996,7 @@
  * the item below the item deleted is shown in Quick View after the item's
  * deletion.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewAndDeleteCheckSelection'
-// comes from an index signature, so it must be accessed with
-// ['openQuickViewAndDeleteCheckSelection'].
-testcase.openQuickViewAndDeleteCheckSelection = async () => {
+export async function openQuickViewAndDeleteCheckSelection() {
   // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
@@ -3441,14 +3033,12 @@
 
   // Check: Quick View should display the entry below |hello.txt|,
   // which is |world.ogv|.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewVideoLoaded(elements) {
+  function checkPreviewVideoLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -3464,15 +3054,12 @@
   // Check: the mimeType of |world.ogv| should be 'video/ogg'.
   const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
   chrome.test.assertEq('video/ogg', mimeType);
-};
+}
 
 /**
  * Tests that deleting all items in a check-selection closes the Quick View.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewDeleteEntireCheckSelection'
-// comes from an index signature, so it must be accessed with
-// ['openQuickViewDeleteEntireCheckSelection'].
-testcase.openQuickViewDeleteEntireCheckSelection = async () => {
+export async function openQuickViewDeleteEntireCheckSelection() {
   const caller = getCaller();
 
   // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
@@ -3513,14 +3100,12 @@
       ['#quick-view', 'files-safe-media[type="audio"]', previewTag];
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewAudioLoaded(elements) {
+  function checkPreviewAudioLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -3548,14 +3133,12 @@
       ['#quick-view', 'files-safe-media[type="image"]', previewTag];
 
   // Wait for the Quick View preview to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkPreviewImageLoaded(elements) {
+  function checkPreviewImageLoaded(elements: ElementObject[]) {
     let haveElements = Array.isArray(elements) && elements.length === 1;
     if (haveElements) {
-      haveElements = elements[0].styles.display.includes('block');
+      haveElements = elements[0]!.styles!['display']!.includes('block');
     }
-    if (!haveElements || elements[0].attributes.loaded !== '') {
+    if (!haveElements || elements[0]!.attributes['loaded'] !== '') {
       return pending(caller, `Waiting for ${previewTag} to load.`);
     }
     return;
@@ -3576,21 +3159,18 @@
 
   // Check: the Quick View dialog should close.
   await waitQuickViewClose(appId);
-};
+}
 
 /**
  * Tests that an item can be deleted using the Quick View delete button.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewClickDeleteButton' comes
-// from an index signature, so it must be accessed with
-// ['openQuickViewClickDeleteButton'].
-testcase.openQuickViewClickDeleteButton = async () => {
+export async function openQuickViewClickDeleteButton() {
   // Open Files app on Downloads containing ENTRIES.hello.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
 
   // Open the file in Quick View.
-  await openQuickView(appId, ENTRIES.hello.nameText);
+  await openQuickViewEx(appId, ENTRIES.hello.nameText);
 
   // Click the Quick View delete button.
   const quickViewDeleteButton = ['#quick-view', '#delete-button:not([hidden])'];
@@ -3602,16 +3182,13 @@
 
   // Check: the Quick View dialog should close.
   await waitQuickViewClose(appId);
-};
+}
 
 /**
  * Tests that the delete button is not shown if the file displayed in Quick
  * View cannot be deleted.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewDeleteButtonNotShown' comes
-// from an index signature, so it must be accessed with
-// ['openQuickViewDeleteButtonNotShown'].
-testcase.openQuickViewDeleteButtonNotShown = async () => {
+export async function openQuickViewDeleteButtonNotShown() {
   // Open Files app on My Files
   const appId = await openNewWindow('');
 
@@ -3625,28 +3202,21 @@
     ['Linux files', '--', 'Folder'],
   ];
   await remoteCall.waitForFiles(
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime:
-      // true; }' is not assignable to parameter of type '{ orderCheck: boolean
-      // | null | undefined; ignoreFileSize: boolean | null | undefined;
-      // ignoreLastModifiedTime: boolean | null | undefined; }'.
       appId, expectedRows, {ignoreLastModifiedTime: true});
 
   // Open Play files in Quick View, which cannot be deleted.
-  await openQuickView(appId, 'Play files');
+  await openQuickViewEx(appId, 'Play files');
 
   // Check: the delete button should not be shown.
   const quickViewDeleteButton = ['#quick-view', '#delete-button[hidden]'];
   await remoteCall.waitForElement(appId, quickViewDeleteButton);
-};
+}
 
 /**
  * Tests that the correct WayToOpen UMA histogram is recorded when opening
  * a single file via Quick View using "Get Info" from the context menu.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewUmaViaContextMenu' comes
-// from an index signature, so it must be accessed with
-// ['openQuickViewUmaViaContextMenu'].
-testcase.openQuickViewUmaViaContextMenu = async () => {
+export async function openQuickViewUmaViaContextMenu() {
   // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
@@ -3676,17 +3246,14 @@
       contextMenuUMAValueAfterOpening, contextMenuUMAValueBeforeOpening + 1);
   chrome.test.assertEq(
       selectionMenuUMAValueAfterOpening, selectionMenuUMAValueBeforeOpening);
-};
+}
 
 /**
  * Tests that the correct WayToOpen UMA histogram is recorded when using
  * Quick View in check-select mode using "Get Info" from the context
  * menu.
  */
-// @ts-ignore: error TS4111: Property
-// 'openQuickViewUmaForCheckSelectViaContextMenu' comes from an index signature,
-// so it must be accessed with ['openQuickViewUmaForCheckSelectViaContextMenu'].
-testcase.openQuickViewUmaForCheckSelectViaContextMenu = async () => {
+export async function openQuickViewUmaForCheckSelectViaContextMenu() {
   // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
@@ -3701,9 +3268,7 @@
       QuickViewUmaWayToOpenHistogramValues.SELECTION_MENU);
 
   // Ctrl+A to select all files in the file-list.
-  const ctrlA = ['#file-list', 'a', true, false, false];
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
+  const ctrlA = ['#file-list', 'a', true, false, false] as const;
   await remoteCall.fakeKeyDown(appId, ...ctrlA);
 
   // Open Quick View using the context menu.
@@ -3722,37 +3287,31 @@
       contextMenuUMAValueAfterOpening, contextMenuUMAValueBeforeOpening + 1);
   chrome.test.assertEq(
       selectionMenuUMAValueAfterOpening, selectionMenuUMAValueBeforeOpening);
-};
+}
 
 /**
  * Tests that the correct WayToOpen UMA histogram is recorded when using
  * Quick View in check-select mode using "Get Info" from the Selection
  * menu.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewUmaViaSelectionMenu' comes
-// from an index signature, so it must be accessed with
-// ['openQuickViewUmaViaSelectionMenu'].
-testcase.openQuickViewUmaViaSelectionMenu = async () => {
+export async function openQuickViewUmaViaSelectionMenu() {
   // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
 
   // Ctrl+A to select all files in the file-list.
-  const ctrlA = ['#file-list', 'a', true, false, false];
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
+  const ctrlA = ['#file-list', 'a', true, false, false] as const;
   await remoteCall.fakeKeyDown(appId, ...ctrlA);
 
   const caller = getCaller();
 
   // Wait until the selection menu is visible.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkElementsDisplayVisible(elements) {
+  function checkElementsDisplayVisible(elements: ElementObject[]) {
     chrome.test.assertTrue(Array.isArray(elements));
-    if (elements.length === 0 || elements[0].styles.display === 'none') {
+    if (elements.length === 0 || elements[0]!.styles!['display'] === 'none') {
       return pending(caller, 'Waiting for Selection Menu to be visible.');
     }
+    return;
   }
 
   await repeatUntil(async () => {
@@ -3791,7 +3350,7 @@
     const elements = await remoteCall.callRemoteTestUtil(
         'deepQueryAllElements', appId, [query, ['display']]);
     const haveElements = Array.isArray(elements) && elements.length !== 0;
-    if (!haveElements || elements[0].styles.display !== 'block') {
+    if (!haveElements || elements[0]!.styles!['display'] !== 'block') {
       return pending(caller, 'Waiting for Quick View to open.');
     }
     return true;
@@ -3811,17 +3370,14 @@
   chrome.test.assertEq(
       selectionMenuUMAValueAfterOpening,
       selectionMenuUMAValueBeforeOpening + 1);
-};
+}
 
 /**
  * Tests that the correct WayToOpen UMA histogram is recorded when using
  * Quick View in check-select mode using "Get Info" from the context
  * menu opened via keyboard tabbing (not mouse).
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewUmaViaSelectionMenuKeyboard'
-// comes from an index signature, so it must be accessed with
-// ['openQuickViewUmaViaSelectionMenuKeyboard'].
-testcase.openQuickViewUmaViaSelectionMenuKeyboard = async () => {
+export async function openQuickViewUmaViaSelectionMenuKeyboard() {
   const caller = getCaller();
 
   // Open Files app on Downloads containing BASIC_LOCAL_ENTRY_SET.
@@ -3829,19 +3385,16 @@
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
 
   // Ctrl+A to select all files in the file-list.
-  const ctrlA = ['#file-list', 'a', true, false, false];
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
+  const ctrlA = ['#file-list', 'a', true, false, false] as const;
   await remoteCall.fakeKeyDown(appId, ...ctrlA);
 
   // Wait until the selection menu is visible.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkElementsDisplayVisible(elements) {
+  function checkElementsDisplayVisible(elements: ElementObject[]) {
     chrome.test.assertTrue(Array.isArray(elements));
-    if (elements.length === 0 || elements[0].styles.display === 'none') {
+    if (elements.length === 0 || elements[0]!.styles!['display'] === 'none') {
       return pending(caller, 'Waiting for Selection Menu to be visible.');
     }
+    return;
   }
 
   await repeatUntil(async () => {
@@ -3902,7 +3455,7 @@
     const elements = await remoteCall.callRemoteTestUtil(
         'deepQueryAllElements', appId, [query, ['display']]);
     const haveElements = Array.isArray(elements) && elements.length !== 0;
-    if (!haveElements || elements[0].styles.display !== 'block') {
+    if (!haveElements || elements[0]!.styles!['display'] !== 'block') {
       return pending(caller, 'Waiting for Quick View to open.');
     }
     return true;
@@ -3922,14 +3475,12 @@
   chrome.test.assertEq(
       selectionMenuUMAValueAfterOpening,
       selectionMenuUMAValueBeforeOpening + 1);
-};
+}
 
 /**
  * Tests that Quick View does not display a CSE file preview.
  */
-// @ts-ignore: error TS4111: Property 'openQuickViewEncryptedFile' comes from an
-// index signature, so it must be accessed with ['openQuickViewEncryptedFile'].
-testcase.openQuickViewEncryptedFile = async () => {
+export async function openQuickViewEncryptedFile() {
   const caller = getCaller();
 
   /**
@@ -3939,25 +3490,19 @@
   const contentPanel = ['#quick-view', '#dialog[open] #innerContentPanel'];
 
   const appId =
-      // @ts-ignore: error TS4111: Property 'testCSEFile' comes from an index
-      // signature, so it must be accessed with ['testCSEFile'].
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.testCSEFile]);
 
   // Open the file in Quick View.
-  // @ts-ignore: error TS4111: Property 'testCSEFile' comes from an index
-  // signature, so it must be accessed with ['testCSEFile'].
-  await openQuickView(appId, ENTRIES.testCSEFile.nameText);
+  await openQuickViewEx(appId, ENTRIES.testCSEFile.nameText);
 
   // Wait for the innerContentPanel to load and display its content.
-  // @ts-ignore: error TS7006: Parameter 'elements' implicitly has an 'any'
-  // type.
-  function checkInnerContentPanel(elements) {
+  function checkInnerContentPanel(elements: ElementObject[]) {
     const haveElements = Array.isArray(elements) && elements.length === 1;
-    if (!haveElements || elements[0].styles.display !== 'flex') {
+    if (!haveElements || elements[0]!.styles!['display'] !== 'flex') {
       return pending(caller, 'Waiting for inner content panel to load.');
     }
     // Check: the preview should not be shown.
-    chrome.test.assertEq('No preview available', elements[0].innerText);
+    chrome.test.assertEq('No preview available', elements[0]!.innerText);
     return;
   }
   await repeatUntil(async () => {
@@ -3968,4 +3513,4 @@
   // Check: the correct file mimeType should be displayed.
   const mimeType = await getQuickViewMetadataBoxField(appId, 'Type');
   chrome.test.assertEq('Encrypted text/plain', mimeType);
-};
+}
diff --git a/ui/file_manager/integration_tests/file_manager/sort_columns.js b/ui/file_manager/integration_tests/file_manager/sort_columns.ts
similarity index 72%
rename from ui/file_manager/integration_tests/file_manager/sort_columns.js
rename to ui/file_manager/integration_tests/file_manager/sort_columns.ts
index d2bfd0cd..f4a4a81 100644
--- a/ui/file_manager/integration_tests/file_manager/sort_columns.js
+++ b/ui/file_manager/integration_tests/file_manager/sort_columns.ts
@@ -3,16 +3,13 @@
 // found in the LICENSE file.
 
 import {ENTRIES, RootPath, TestEntryInfo} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {remoteCall, setupAndWaitUntilReady} from './background.js';
 
 /**
  * Tests the order is sorted correctly for each of the columns.
  */
-// @ts-ignore: error TS4111: Property 'sortColumns' comes from an index
-// signature, so it must be accessed with ['sortColumns'].
-testcase.sortColumns = async () => {
+export async function sortColumns() {
   const NAME_ASC = TestEntryInfo.getExpectedRows([
     ENTRIES.photos,
     ENTRIES.beautiful,
@@ -90,10 +87,6 @@
   await remoteCall.callRemoteTestUtil(
       'fakeMouseClick', appId, ['.table-header-cell:nth-of-type(1)']);
   await remoteCall.waitForElement(appId, iconSortedAsc);
-  // @ts-ignore: error TS2345: Argument of type '{ orderCheck: true; }' is not
-  // assignable to parameter of type '{ orderCheck: boolean | null | undefined;
-  // ignoreFileSize: boolean | null | undefined; ignoreLastModifiedTime: boolean
-  // | null | undefined; }'.
   await remoteCall.waitForFiles(appId, NAME_ASC, {orderCheck: true});
 
   // Fetch A11y messages.
@@ -110,10 +103,6 @@
   await remoteCall.callRemoteTestUtil(
       'fakeMouseClick', appId, ['.table-header-cell:nth-of-type(1)']);
   await remoteCall.waitForElement(appId, iconSortedDesc);
-  // @ts-ignore: error TS2345: Argument of type '{ orderCheck: true; }' is not
-  // assignable to parameter of type '{ orderCheck: boolean | null | undefined;
-  // ignoreFileSize: boolean | null | undefined; ignoreLastModifiedTime: boolean
-  // | null | undefined; }'.
   await remoteCall.waitForFiles(appId, NAME_DESC, {orderCheck: true});
 
   // Fetch A11y messages.
@@ -125,10 +114,6 @@
   await remoteCall.callRemoteTestUtil(
       'fakeMouseClick', appId, ['.table-header-cell:nth-of-type(2)']);
   await remoteCall.waitForElement(appId, iconSortedDesc);
-  // @ts-ignore: error TS2345: Argument of type '{ orderCheck: true; }' is not
-  // assignable to parameter of type '{ orderCheck: boolean | null | undefined;
-  // ignoreFileSize: boolean | null | undefined; ignoreLastModifiedTime: boolean
-  // | null | undefined; }'.
   await remoteCall.waitForFiles(appId, SIZE_DESC, {orderCheck: true});
 
   // Fetch A11y messages.
@@ -145,10 +130,6 @@
   await remoteCall.callRemoteTestUtil(
       'fakeMouseClick', appId, ['.table-header-cell:nth-of-type(2)']);
   await remoteCall.waitForElement(appId, iconSortedAsc);
-  // @ts-ignore: error TS2345: Argument of type '{ orderCheck: true; }' is not
-  // assignable to parameter of type '{ orderCheck: boolean | null | undefined;
-  // ignoreFileSize: boolean | null | undefined; ignoreLastModifiedTime: boolean
-  // | null | undefined; }'.
   await remoteCall.waitForFiles(appId, SIZE_ASC, {orderCheck: true});
 
   // Fetch A11y messages.
@@ -166,10 +147,6 @@
   await remoteCall.callRemoteTestUtil(
       'fakeMouseClick', appId, ['.table-header-cell:nth-of-type(3)']);
   await remoteCall.waitForElement(appId, iconSortedAsc);
-  // @ts-ignore: error TS2345: Argument of type '{ orderCheck: true; }' is not
-  // assignable to parameter of type '{ orderCheck: boolean | null | undefined;
-  // ignoreFileSize: boolean | null | undefined; ignoreLastModifiedTime: boolean
-  // | null | undefined; }'.
   await remoteCall.waitForFiles(appId, TYPE_ASC, {orderCheck: true});
 
   // Fetch A11y messages.
@@ -181,10 +158,6 @@
   await remoteCall.callRemoteTestUtil(
       'fakeMouseClick', appId, ['.table-header-cell:nth-of-type(3)']);
   await remoteCall.waitForElement(appId, iconSortedDesc);
-  // @ts-ignore: error TS2345: Argument of type '{ orderCheck: true; }' is not
-  // assignable to parameter of type '{ orderCheck: boolean | null | undefined;
-  // ignoreFileSize: boolean | null | undefined; ignoreLastModifiedTime: boolean
-  // | null | undefined; }'.
   await remoteCall.waitForFiles(appId, TYPE_DESC, {orderCheck: true});
 
   // Fetch A11y messages.
@@ -202,10 +175,6 @@
   await remoteCall.callRemoteTestUtil(
       'fakeMouseClick', appId, ['.table-header-cell:nth-of-type(4)']);
   await remoteCall.waitForElement(appId, iconSortedDesc);
-  // @ts-ignore: error TS2345: Argument of type '{ orderCheck: true; }' is not
-  // assignable to parameter of type '{ orderCheck: boolean | null | undefined;
-  // ignoreFileSize: boolean | null | undefined; ignoreLastModifiedTime: boolean
-  // | null | undefined; }'.
   await remoteCall.waitForFiles(appId, DATE_DESC, {orderCheck: true});
 
   // Fetch A11y messages.
@@ -217,10 +186,6 @@
   await remoteCall.callRemoteTestUtil(
       'fakeMouseClick', appId, ['.table-header-cell:nth-of-type(4)']);
   await remoteCall.waitForElement(appId, iconSortedAsc);
-  // @ts-ignore: error TS2345: Argument of type '{ orderCheck: true; }' is not
-  // assignable to parameter of type '{ orderCheck: boolean | null | undefined;
-  // ignoreFileSize: boolean | null | undefined; ignoreLastModifiedTime: boolean
-  // | null | undefined; }'.
   await remoteCall.waitForFiles(appId, DATE_ASC, {orderCheck: true});
 
   // Fetch A11y messages.
@@ -237,10 +202,6 @@
   await remoteCall.callRemoteTestUtil(
       'fakeMouseClick', appId, ['#sort-menu-sort-by-name']);
   await remoteCall.waitForElement(appId, iconSortedAsc);
-  // @ts-ignore: error TS2345: Argument of type '{ orderCheck: true; }' is not
-  // assignable to parameter of type '{ orderCheck: boolean | null | undefined;
-  // ignoreFileSize: boolean | null | undefined; ignoreLastModifiedTime: boolean
-  // | null | undefined; }'.
   await remoteCall.waitForFiles(appId, NAME_ASC, {orderCheck: true});
 
   // Fetch A11y messages.
@@ -252,10 +213,6 @@
   await remoteCall.callRemoteTestUtil(
       'fakeMouseClick', appId, ['.table-header-cell:nth-of-type(1)']);
   await remoteCall.waitForElement(appId, iconSortedDesc);
-  // @ts-ignore: error TS2345: Argument of type '{ orderCheck: true; }' is not
-  // assignable to parameter of type '{ orderCheck: boolean | null | undefined;
-  // ignoreFileSize: boolean | null | undefined; ignoreLastModifiedTime: boolean
-  // | null | undefined; }'.
   await remoteCall.waitForFiles(appId, NAME_DESC, {orderCheck: true});
 
   // Fetch A11y messages.
@@ -268,14 +225,10 @@
   await remoteCall.callRemoteTestUtil(
       'fakeMouseClick', appId, ['#sort-menu-sort-by-name']);
   await remoteCall.waitForElement(appId, iconSortedAsc);
-  // @ts-ignore: error TS2345: Argument of type '{ orderCheck: true; }' is not
-  // assignable to parameter of type '{ orderCheck: boolean | null | undefined;
-  // ignoreFileSize: boolean | null | undefined; ignoreLastModifiedTime: boolean
-  // | null | undefined; }'.
   await remoteCall.waitForFiles(appId, NAME_ASC, {orderCheck: true});
 
   // Fetch A11y messages.
   a11yMessages =
       await remoteCall.callRemoteTestUtil('getA11yAnnounces', appId, []);
   chrome.test.assertEq(11, a11yMessages.length, 'Missing a11y message');
-};
+}
diff --git a/ui/file_manager/integration_tests/file_manager/tasks.js b/ui/file_manager/integration_tests/file_manager/tasks.ts
similarity index 71%
rename from ui/file_manager/integration_tests/file_manager/tasks.js
rename to ui/file_manager/integration_tests/file_manager/tasks.ts
index 403a2ae..8fb4aed 100644
--- a/ui/file_manager/integration_tests/file_manager/tasks.js
+++ b/ui/file_manager/integration_tests/file_manager/tasks.ts
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import type {ElementObject} from '../prod/file_manager/shared_types.js';
 import {getCaller, pending, repeatUntil, RootPath} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {remoteCall, setupAndWaitUntilReady} from './background.js';
 import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
@@ -11,9 +11,6 @@
 
 /**
  * Fake tasks for a local volume opening in browser.
- *
- * @type {Array<FakeTask>}
- * @const
  */
 const DOWNLOADS_FAKE_TEXT = [
   new FakeTask(true, {
@@ -25,9 +22,6 @@
 
 /**
  * Fake tasks for a PDF file opening in browser.
- *
- * @type {Array<FakeTask>}
- * @const
  */
 const DOWNLOADS_FAKE_PDF = [
   new FakeTask(true, {
@@ -39,9 +33,6 @@
 
 /**
  * Fake tasks for a drive volume.
- *
- * @type {Array<FakeTask>}
- * @const
  */
 const DRIVE_FAKE_TASKS = [
   new FakeTask(
@@ -55,10 +46,10 @@
 /**
  * Sets up task tests.
  *
- * @param {string} rootPath Root path.
- * @param {Array<FakeTask>} fakeTasks Fake tasks.
+ * @param rootPath Root path.
+ * @param fakeTasks Fake tasks.
  */
-async function setupTaskTest(rootPath, fakeTasks) {
+async function setupTaskTest(rootPath: string, fakeTasks: FakeTask[]) {
   const appId = await setupAndWaitUntilReady(rootPath);
   await remoteCall.callRemoteTestUtil('overrideTasks', appId, [fakeTasks]);
   return appId;
@@ -67,11 +58,11 @@
 /**
  * Tests executing the default task when there is only one task.
  *
- * @param {string} appId Window ID.
- * @param {!FileTaskDescriptor} descriptor Task
- *     descriptor.
+ * @param appId Window ID.
+ * @param descriptor Task descriptor.
  */
-async function executeDefaultTask(appId, descriptor) {
+async function executeDefaultTask(
+    appId: string, descriptor: FileTaskDescriptor) {
   // Select file.
   await remoteCall.waitUntilSelected(appId, 'hello.txt');
 
@@ -87,13 +78,13 @@
 /**
  * Tests to specify default task via the default task dialog.
  *
- * @param {string} appId Window ID.
- * @param {!FileTaskDescriptor} descriptor Task
- *     descriptor of the task expected to be newly specified as default.
- * @return {Promise<void>} Promise to be fulfilled/rejected depends on the test
- *     result.
+ * @param appId Window ID.
+ * @param descriptor Task descriptor of the task expected to be newly specified
+ *     as default.
+ * @return Promise to be fulfilled/rejected depends on the test result.
  */
-async function defaultTaskDialog(appId, descriptor) {
+async function defaultTaskDialog(
+    appId: string, descriptor: FileTaskDescriptor): Promise<void> {
   // Prepare expected labels.
   const expectedLabels = [
     'DummyTask1 (default)',
@@ -115,15 +106,12 @@
   // Wait for the list of menu item is added as expected.
   await repeatUntil(async () => {
     // Obtains menu items.
-    const items = await remoteCall.callRemoteTestUtil(
+    const items: ElementObject[] = await remoteCall.callRemoteTestUtil(
         'queryAllElements', appId,
         ['#default-task-dialog #default-tasks-list li']);
 
     // Compare the contents of items.
-    // @ts-ignore: error TS7006: Parameter 'item' implicitly has an 'any' type.
     const actualLabels = items.map((item) => item.text);
-    // @ts-ignore: error TS2339: Property 'checkDeepEq' does not exist on type
-    // 'typeof test'.
     if (chrome.test.checkDeepEq(expectedLabels, actualLabels)) {
       return true;
     }
@@ -169,63 +157,41 @@
   await remoteCall.waitUntilTaskExecutes(appId, descriptor, ['hello.txt']);
 }
 
-// @ts-ignore: error TS4111: Property 'executeDefaultTaskDrive' comes from an
-// index signature, so it must be accessed with ['executeDefaultTaskDrive'].
-testcase.executeDefaultTaskDrive = async () => {
+export async function executeDefaultTaskDrive() {
   const appId = await setupTaskTest(RootPath.DRIVE, DRIVE_FAKE_TASKS);
-  // @ts-ignore: error TS2532: Object is possibly 'undefined'.
-  await executeDefaultTask(appId, DRIVE_FAKE_TASKS[0].descriptor);
-};
+  await executeDefaultTask(appId, DRIVE_FAKE_TASKS[0]!.descriptor);
+}
 
-// @ts-ignore: error TS4111: Property 'executeDefaultTaskDownloads' comes from
-// an index signature, so it must be accessed with
-// ['executeDefaultTaskDownloads'].
-testcase.executeDefaultTaskDownloads = async () => {
+export async function executeDefaultTaskDownloads() {
   const appId = await setupTaskTest(RootPath.DOWNLOADS, DOWNLOADS_FAKE_TASKS);
-  // @ts-ignore: error TS2532: Object is possibly 'undefined'.
-  await executeDefaultTask(appId, DOWNLOADS_FAKE_TASKS[0].descriptor);
-};
+  await executeDefaultTask(appId, DOWNLOADS_FAKE_TASKS[0]!.descriptor);
+}
 
-// @ts-ignore: error TS4111: Property 'defaultTaskForTextPlain' comes from an
-// index signature, so it must be accessed with ['defaultTaskForTextPlain'].
-testcase.defaultTaskForTextPlain = async () => {
+export async function defaultTaskForTextPlain() {
   const appId = await setupTaskTest(RootPath.DOWNLOADS, DOWNLOADS_FAKE_TEXT);
-  // @ts-ignore: error TS2532: Object is possibly 'undefined'.
-  await executeDefaultTask(appId, DOWNLOADS_FAKE_TEXT[0].descriptor);
-};
+  await executeDefaultTask(appId, DOWNLOADS_FAKE_TEXT[0]!.descriptor);
+}
 
-// @ts-ignore: error TS4111: Property 'defaultTaskForPdf' comes from an index
-// signature, so it must be accessed with ['defaultTaskForPdf'].
-testcase.defaultTaskForPdf = async () => {
+export async function defaultTaskForPdf() {
   const appId = await setupTaskTest(RootPath.DOWNLOADS, DOWNLOADS_FAKE_PDF);
-  // @ts-ignore: error TS2532: Object is possibly 'undefined'.
-  await executeDefaultTask(appId, DOWNLOADS_FAKE_PDF[0].descriptor);
-};
+  await executeDefaultTask(appId, DOWNLOADS_FAKE_PDF[0]!.descriptor);
+}
 
-// @ts-ignore: error TS4111: Property 'defaultTaskDialogDrive' comes from an
-// index signature, so it must be accessed with ['defaultTaskDialogDrive'].
-testcase.defaultTaskDialogDrive = async () => {
+export async function defaultTaskDialogDrive() {
   const appId = await setupTaskTest(RootPath.DRIVE, DRIVE_FAKE_TASKS);
-  // @ts-ignore: error TS2532: Object is possibly 'undefined'.
-  await defaultTaskDialog(appId, DRIVE_FAKE_TASKS[1].descriptor);
-};
+  await defaultTaskDialog(appId, DRIVE_FAKE_TASKS[1]!.descriptor);
+}
 
-// @ts-ignore: error TS4111: Property 'defaultTaskDialogDownloads' comes from an
-// index signature, so it must be accessed with ['defaultTaskDialogDownloads'].
-testcase.defaultTaskDialogDownloads = async () => {
+export async function defaultTaskDialogDownloads() {
   const appId = await setupTaskTest(RootPath.DOWNLOADS, DOWNLOADS_FAKE_TASKS);
-  // @ts-ignore: error TS2532: Object is possibly 'undefined'.
-  await defaultTaskDialog(appId, DOWNLOADS_FAKE_TASKS[1].descriptor);
-};
+  await defaultTaskDialog(appId, DOWNLOADS_FAKE_TASKS[1]!.descriptor);
+}
 
 
 /**
  * Tests that the Change Default Task dialog has a scrollable list.
  */
-// @ts-ignore: error TS4111: Property 'changeDefaultDialogScrollList' comes from
-// an index signature, so it must be accessed with
-// ['changeDefaultDialogScrollList'].
-testcase.changeDefaultDialogScrollList = async () => {
+export async function changeDefaultDialogScrollList() {
   const tasks = [
     new FakeTask(
         true,
@@ -284,11 +250,9 @@
   // Check: CSS class bottom-shadow should be removed.
   await remoteCall.waitForElementLost(
       appId, '#default-task-dialog.bottom-shadow');
-};
+}
 
-// @ts-ignore: error TS4111: Property 'genericTaskIsNotExecuted' comes from an
-// index signature, so it must be accessed with ['genericTaskIsNotExecuted'].
-testcase.genericTaskIsNotExecuted = async () => {
+export async function genericTaskIsNotExecuted() {
   const tasks = [new FakeTask(
       false,
       {appId: 'dummytaskid', taskType: 'fake-type', actionId: 'open-with'},
@@ -305,12 +269,9 @@
     taskType: 'file',
     actionId: 'view-in-browser',
   });
-};
+}
 
-// @ts-ignore: error TS4111: Property 'genericTaskAndNonGenericTask' comes from
-// an index signature, so it must be accessed with
-// ['genericTaskAndNonGenericTask'].
-testcase.genericTaskAndNonGenericTask = async () => {
+export async function genericTaskAndNonGenericTask() {
   const tasks = [
     new FakeTask(
         false,
@@ -327,14 +288,10 @@
   ];
 
   const appId = await setupTaskTest(RootPath.DOWNLOADS, tasks);
-  // @ts-ignore: error TS2532: Object is possibly 'undefined'.
-  await executeDefaultTask(appId, tasks[1].descriptor);
-};
+  await executeDefaultTask(appId, tasks[1]!.descriptor);
+}
 
-// @ts-ignore: error TS4111: Property 'noActionBarOpenForDirectories' comes from
-// an index signature, so it must be accessed with
-// ['noActionBarOpenForDirectories'].
-testcase.noActionBarOpenForDirectories = async () => {
+export async function noActionBarOpenForDirectories() {
   const fileTasks = [new FakeTask(
       true,
       {appId: 'dummytaskid', taskType: 'fake-type', actionId: 'open-with'},
@@ -379,11 +336,9 @@
   chrome.test.assertEq('DirTask1 (default)', appOptions[0].text);
   chrome.test.assertEq('DirTask2', appOptions[1].text);
   chrome.test.assertEq('Change default…', appOptions[2].text);
-};
+}
 
-// @ts-ignore: error TS4111: Property 'executeViaDblClick' comes from an index
-// signature, so it must be accessed with ['executeViaDblClick'].
-testcase.executeViaDblClick = async () => {
+export async function executeViaDblClick() {
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
   await remoteCall.callRemoteTestUtil(
       'overrideTasks', appId, [DOWNLOADS_FAKE_TASKS]);
@@ -393,8 +348,7 @@
       ['#file-list li[file-name="hello.txt"] .filename-label span']));
 
   // Wait until the task is executed.
-  // @ts-ignore: error TS2532: Object is possibly 'undefined'.
-  const descriptor = DOWNLOADS_FAKE_TASKS[0].descriptor;
+  const descriptor = DOWNLOADS_FAKE_TASKS[0]!.descriptor;
   await remoteCall.waitUntilTaskExecutes(appId, descriptor, ['hello.txt']);
 
   // Reset the overridden tasks.
@@ -412,4 +366,4 @@
 
   // Check the tasks again.
   await remoteCall.waitUntilTaskExecutes(appId, descriptor, ['world.ogv']);
-};
+}
diff --git a/ui/file_manager/integration_tests/file_manager/toolbar.js b/ui/file_manager/integration_tests/file_manager/toolbar.ts
similarity index 72%
rename from ui/file_manager/integration_tests/file_manager/toolbar.js
rename to ui/file_manager/integration_tests/file_manager/toolbar.ts
index f61cf8823..f63ba4c 100644
--- a/ui/file_manager/integration_tests/file_manager/toolbar.js
+++ b/ui/file_manager/integration_tests/file_manager/toolbar.ts
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import {addEntries, ENTRIES, getCaller, pending, repeatUntil, RootPath, sendTestMessage, TestEntryInfo} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {openNewWindow, remoteCall, setupAndWaitUntilReady} from './background.js';
 import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
@@ -12,10 +11,7 @@
 /**
  * Tests that the Delete menu item is disabled if no entry is selected.
  */
-// @ts-ignore: error TS4111: Property 'toolbarDeleteWithMenuItemNoEntrySelected'
-// comes from an index signature, so it must be accessed with
-// ['toolbarDeleteWithMenuItemNoEntrySelected'].
-testcase.toolbarDeleteWithMenuItemNoEntrySelected = async () => {
+export async function toolbarDeleteWithMenuItemNoEntrySelected() {
   const contextMenu = '#file-context-menu:not([hidden])';
 
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
@@ -32,25 +28,18 @@
   // Assert the menu delete command is disabled.
   const deleteDisabled = '[command="#delete"][disabled="disabled"]';
   await remoteCall.waitForElement(appId, contextMenu + ' ' + deleteDisabled);
-};
+}
 
 /**
  * Tests that the toolbar Delete button opens the delete confirm dialog and
  * that the dialog cancel button has the focus by default.
  */
-// @ts-ignore: error TS4111: Property
-// 'toolbarDeleteButtonOpensDeleteConfirmDialog' comes from an index signature,
-// so it must be accessed with ['toolbarDeleteButtonOpensDeleteConfirmDialog'].
-testcase.toolbarDeleteButtonOpensDeleteConfirmDialog = async () => {
+export async function toolbarDeleteButtonOpensDeleteConfirmDialog() {
   // Open Files app.
   const appId =
-      // @ts-ignore: error TS4111: Property 'desktop' comes from an index
-      // signature, so it must be accessed with ['desktop'].
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.desktop]);
 
   // Select My Desktop Background.png
-  // @ts-ignore: error TS4111: Property 'desktop' comes from an index signature,
-  // so it must be accessed with ['desktop'].
   await remoteCall.waitUntilSelected(appId, ENTRIES.desktop.nameText);
 
   // Click the toolbar Delete button.
@@ -63,16 +52,13 @@
   const defaultDialogButton =
       await remoteCall.waitForElement(appId, '.cr-dialog-cancel:focus');
   chrome.test.assertEq('Cancel', defaultDialogButton.text);
-};
+}
 
 /**
  * Tests that the toolbar Delete button keeps focus after the delete confirm
  * dialog is closed.
  */
-// @ts-ignore: error TS4111: Property 'toolbarDeleteButtonKeepFocus' comes from
-// an index signature, so it must be accessed with
-// ['toolbarDeleteButtonKeepFocus'].
-testcase.toolbarDeleteButtonKeepFocus = async () => {
+export async function toolbarDeleteButtonKeepFocus() {
   // Open Files app.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.hello], []);
@@ -112,14 +98,12 @@
 
   // Check: the toolbar Delete button should be focused.
   await remoteCall.waitForElement(appId, '#delete-button:focus');
-};
+}
 
 /**
  * Tests deleting an entry using the toolbar.
  */
-// @ts-ignore: error TS4111: Property 'toolbarDeleteEntry' comes from an index
-// signature, so it must be accessed with ['toolbarDeleteEntry'].
-testcase.toolbarDeleteEntry = async () => {
+export async function toolbarDeleteEntry() {
   const beforeDeletion = TestEntryInfo.getExpectedRows([
     ENTRIES.photos,
     ENTRIES.hello,
@@ -140,10 +124,6 @@
 
   // Confirm entries in the directory before the deletion.
   await remoteCall.waitForFiles(
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime:
-      // true; }' is not assignable to parameter of type '{ orderCheck: boolean
-      // | null | undefined; ignoreFileSize: boolean | null | undefined;
-      // ignoreLastModifiedTime: boolean | null | undefined; }'.
       appId, beforeDeletion, {ignoreLastModifiedTime: true});
 
   // Select My Desktop Background.png
@@ -155,12 +135,8 @@
 
   // Confirm the file is removed.
   await remoteCall.waitForFiles(
-      // @ts-ignore: error TS2345: Argument of type '{ ignoreLastModifiedTime:
-      // true; }' is not assignable to parameter of type '{ orderCheck: boolean
-      // | null | undefined; ignoreFileSize: boolean | null | undefined;
-      // ignoreLastModifiedTime: boolean | null | undefined; }'.
       appId, afterDeletion, {ignoreLastModifiedTime: true});
-};
+}
 
 /**
  * Tests that refresh button hides in selection mode.
@@ -170,10 +146,7 @@
  * button should be hidden when entering the selection mode.
  * crbug.com/978383
  */
-// @ts-ignore: error TS4111: Property 'toolbarRefreshButtonWithSelection' comes
-// from an index signature, so it must be accessed with
-// ['toolbarRefreshButtonWithSelection'].
-testcase.toolbarRefreshButtonWithSelection = async () => {
+export async function toolbarRefreshButtonWithSelection() {
   // Open files app.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
 
@@ -190,26 +163,19 @@
   await remoteCall.waitForElement(appId, '#refresh-button:not([hidden])');
 
   // Ctrl+A to enter selection mode.
-  const ctrlA = ['#file-list', 'a', true, false, false];
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
+  const ctrlA = ['#file-list', 'a', true, false, false] as const;
   await remoteCall.fakeKeyDown(appId, ...ctrlA);
 
   // Check that the button should be hidden.
   await remoteCall.waitForElement(appId, '#refresh-button[hidden]');
-};
+}
 
 /**
  * Tests that refresh button is not shown when the Recent view is selected.
  */
-// @ts-ignore: error TS4111: Property 'toolbarRefreshButtonHiddenInRecents'
-// comes from an index signature, so it must be accessed with
-// ['toolbarRefreshButtonHiddenInRecents'].
-testcase.toolbarRefreshButtonHiddenInRecents = async () => {
+export async function toolbarRefreshButtonHiddenInRecents() {
   // Open files app.
   const appId =
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
   // Navigate to Recent.
@@ -219,16 +185,12 @@
 
   // Check that the button should be hidden.
   await remoteCall.waitForElement(appId, '#refresh-button[hidden]');
-};
+}
 
 /**
  * Tests that refresh button is shown for non-watchable volumes.
  */
-// @ts-ignore: error TS4111: Property
-// 'toolbarRefreshButtonShownForNonWatchableVolume' comes from an index
-// signature, so it must be accessed with
-// ['toolbarRefreshButtonShownForNonWatchableVolume'].
-testcase.toolbarRefreshButtonShownForNonWatchableVolume = async () => {
+export async function toolbarRefreshButtonShownForNonWatchableVolume() {
   // Open files app.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
 
@@ -243,19 +205,14 @@
 
   // Check that refresh button is visible.
   await remoteCall.waitForElement(appId, '#refresh-button:not([hidden])');
-};
+}
 
 /**
  * Tests that refresh button is hidden for watchable volumes.
  */
-// @ts-ignore: error TS4111: Property
-// 'toolbarRefreshButtonHiddenForWatchableVolume' comes from an index signature,
-// so it must be accessed with ['toolbarRefreshButtonHiddenForWatchableVolume'].
-testcase.toolbarRefreshButtonHiddenForWatchableVolume = async () => {
+export async function toolbarRefreshButtonHiddenForWatchableVolume() {
   // Open Files app on local Downloads.
   const appId =
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
   // It should start in Downloads.
@@ -264,24 +221,18 @@
 
   // Check that the button should be hidden.
   await remoteCall.waitForElement(appId, '#refresh-button[hidden]');
-};
+}
 
 /**
  * Tests that command Alt+A focus the toolbar.
  */
-// @ts-ignore: error TS4111: Property 'toolbarAltACommand' comes from an index
-// signature, so it must be accessed with ['toolbarAltACommand'].
-testcase.toolbarAltACommand = async () => {
+export async function toolbarAltACommand() {
   // Open files app.
   const appId =
-      // @ts-ignore: error TS4111: Property 'beautiful' comes from an index
-      // signature, so it must be accessed with ['beautiful'].
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
   // Press Alt+A in the File List.
-  const altA = ['#file-list', 'a', false, false, true];
-  // @ts-ignore: error TS2556: A spread argument must either have a tuple type
-  // or be passed to a rest parameter.
+  const altA = ['#file-list', 'a', false, false, true] as const;
   await remoteCall.fakeKeyDown(appId, ...altA);
 
   // Check that a menu-button should be focused.
@@ -289,16 +240,13 @@
       await remoteCall.callRemoteTestUtil('getActiveElement', appId, []);
   const cssClasses = focusedElement.attributes['class'] || '';
   chrome.test.assertTrue(cssClasses.includes('menu-button'));
-};
+}
 
 /**
  * Tests that the menu drop down follows the button if the button moves. This
  * happens when the search box is expanded and then collapsed.
  */
-// @ts-ignore: error TS4111: Property 'toolbarMultiMenuFollowsButton' comes from
-// an index signature, so it must be accessed with
-// ['toolbarMultiMenuFollowsButton'].
-testcase.toolbarMultiMenuFollowsButton = async () => {
+export async function toolbarMultiMenuFollowsButton() {
   const entry = ENTRIES.hello;
 
   // Open Files app on Downloads.
@@ -325,29 +273,27 @@
 
   // Check that the dropdown menu and "Open" button are aligned.
   const caller = getCaller();
-  // @ts-ignore: error TS7030: Not all code paths return a value.
   await repeatUntil(async () => {
     const openButton =
         await remoteCall.waitForElementStyles(appId, '#tasks', ['width']);
     const menu =
         await remoteCall.waitForElementStyles(appId, '#tasks-menu', ['width']);
 
-    if (openButton.renderedLeft !== menu.renderedLeft) {
-      return pending(
-          caller,
-          `Waiting for the menu and button to be aligned: ` +
-              `${openButton.renderedLeft} !== ${menu.renderedLeft}`);
+    if (openButton.renderedLeft === menu.renderedLeft) {
+      return;
     }
+
+    return pending(
+        caller,
+        `Waiting for the menu and button to be aligned: ` +
+            `${openButton.renderedLeft} !== ${menu.renderedLeft}`);
   });
-};
+}
 
 /**
  * Tests that the sharesheet button is enabled and executable.
  */
-// @ts-ignore: error TS4111: Property 'toolbarSharesheetButtonWithSelection'
-// comes from an index signature, so it must be accessed with
-// ['toolbarSharesheetButtonWithSelection'].
-testcase.toolbarSharesheetButtonWithSelection = async () => {
+export async function toolbarSharesheetButtonWithSelection() {
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
 
   // Fake chrome.fileManagerPrivate.sharesheetHasTargets to return true.
@@ -358,18 +304,13 @@
 
   // Fake chrome.fileManagerPrivate.invokeSharesheet.
   fakeData = {
-    // @ts-ignore: error TS2353: Object literal may only specify known
-    // properties, and ''chrome.fileManagerPrivate.invokeSharesheet'' does not
-    // exist in type '{ 'chrome.fileManagerPrivate.sharesheetHasTargets':
-    // (string | boolean[])[]; }'.
     'chrome.fileManagerPrivate.invokeSharesheet': ['static_fake', []],
-  };
+  } as any;
   await remoteCall.callRemoteTestUtil('foregroundFake', appId, [fakeData]);
 
   const entry = ENTRIES.hello;
 
   // Select an entry in the file list.
-  // @ts-ignore: error TS18048: 'entry' is possibly 'undefined'.
   await remoteCall.waitUntilSelected(appId, entry.nameText);
 
   await remoteCall.waitAndClickElement(
@@ -385,15 +326,12 @@
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
   chrome.test.assertEq(2, removedCount);
-};
+}
 
 /**
  * Tests that the sharesheet command in context menu is enabled and executable.
  */
-// @ts-ignore: error TS4111: Property
-// 'toolbarSharesheetContextMenuWithSelection' comes from an index signature, so
-// it must be accessed with ['toolbarSharesheetContextMenuWithSelection'].
-testcase.toolbarSharesheetContextMenuWithSelection = async () => {
+export async function toolbarSharesheetContextMenuWithSelection() {
   const contextMenu = '#file-context-menu:not([hidden])';
 
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
@@ -406,12 +344,8 @@
 
   // Fake chrome.fileManagerPrivate.invokeSharesheet.
   fakeData = {
-    // @ts-ignore: error TS2353: Object literal may only specify known
-    // properties, and ''chrome.fileManagerPrivate.invokeSharesheet'' does not
-    // exist in type '{ 'chrome.fileManagerPrivate.sharesheetHasTargets':
-    // (string | boolean[])[]; }'.
     'chrome.fileManagerPrivate.invokeSharesheet': ['static_fake', []],
-  };
+  } as any;
   await remoteCall.callRemoteTestUtil('foregroundFake', appId, [fakeData]);
 
   const entry = ENTRIES.hello;
@@ -442,15 +376,12 @@
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
   chrome.test.assertEq(2, removedCount);
-};
+}
 
 /**
  * Tests that the sharesheet item is hidden if no entry is selected.
  */
-// @ts-ignore: error TS4111: Property 'toolbarSharesheetNoEntrySelected' comes
-// from an index signature, so it must be accessed with
-// ['toolbarSharesheetNoEntrySelected'].
-testcase.toolbarSharesheetNoEntrySelected = async () => {
+export async function toolbarSharesheetNoEntrySelected() {
   const contextMenu = '#file-context-menu:not([hidden])';
 
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
@@ -463,8 +394,6 @@
 
   // Right click the list without selecting an entry.
   chrome.test.assertTrue(
-      // @ts-ignore: error TS1345: An expression of type 'void' cannot be tested
-      // for truthiness.
       !!await remoteCall.waitAndRightClick(appId, 'list.list'));
 
   // Wait until the context menu is shown.
@@ -482,48 +411,36 @@
   const removedCount = await remoteCall.callRemoteTestUtil(
       'removeAllForegroundFakes', appId, []);
   chrome.test.assertEq(1, removedCount);
-};
+}
 
 /**
  * Tests that the cloud icon does not appear if bulk pinning is disabled.
  */
-// @ts-ignore: error TS4111: Property
-// 'toolbarCloudIconShouldNotShowWhenBulkPinningDisabled' comes from an index
-// signature, so it must be accessed with
-// ['toolbarCloudIconShouldNotShowWhenBulkPinningDisabled'].
-testcase.toolbarCloudIconShouldNotShowWhenBulkPinningDisabled = async () => {
+export async function toolbarCloudIconShouldNotShowWhenBulkPinningDisabled() {
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.hello]);
   await remoteCall.waitForElement(appId, '#cloud-button[hidden]');
-};
+}
 
 /**
  * Tests that the cloud icon does not appear if the bulk pinning preference is
  * disabled and the supplied Stage does not have a UI state in the progress
  * panel.
  */
-testcase
-    // @ts-ignore: error TS4111: Property
-    // 'toolbarCloudIconShouldNotShowIfPreferenceDisabledAndNoUIStateAvailable'
-    // comes from an index signature, so it must be accessed with
-    // ['toolbarCloudIconShouldNotShowIfPreferenceDisabledAndNoUIStateAvailable'].
-    .toolbarCloudIconShouldNotShowIfPreferenceDisabledAndNoUIStateAvailable =
-    async () => {
+export async function
+toolbarCloudIconShouldNotShowIfPreferenceDisabledAndNoUIStateAvailable() {
   await sendTestMessage({name: 'setBulkPinningEnabledPref', enabled: false});
 
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.hello]);
   await remoteCall.waitForElement(appId, '#cloud-button[hidden]');
-};
+}
 
 /**
  * Tests that the cloud icon should only show when the bulk pinning is in
  * progress.
  */
-// @ts-ignore: error TS4111: Property 'toolbarCloudIconShouldShowForInProgress'
-// comes from an index signature, so it must be accessed with
-// ['toolbarCloudIconShouldShowForInProgress'].
-testcase.toolbarCloudIconShouldShowForInProgress = async () => {
+export async function toolbarCloudIconShouldShowForInProgress() {
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.hello]);
   await remoteCall.waitForElement(appId, '#cloud-button[hidden]');
@@ -539,17 +456,13 @@
   await remoteCall.waitForElementLost(appId, '#cloud-button[hidden]');
   await remoteCall.waitForElement(
       appId, '#cloud-button > xf-icon[type="cloud_sync"]');
-};
+}
 
 /**
  * Tests that the cloud icon should show when there is not enough disk space
  * available to pin.
  */
-// @ts-ignore: error TS4111: Property
-// 'toolbarCloudIconShowsWhenNotEnoughDiskSpaceIsReturned' comes from an index
-// signature, so it must be accessed with
-// ['toolbarCloudIconShowsWhenNotEnoughDiskSpaceIsReturned'].
-testcase.toolbarCloudIconShowsWhenNotEnoughDiskSpaceIsReturned = async () => {
+export async function toolbarCloudIconShowsWhenNotEnoughDiskSpaceIsReturned() {
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.hello]);
   await remoteCall.waitForElement(appId, '#cloud-button[hidden]');
@@ -564,18 +477,14 @@
   await remoteCall.waitForElementLost(appId, '#cloud-button[hidden]');
   await remoteCall.waitForElement(
       appId, '#cloud-button > xf-icon[type="cloud_error"]');
-};
+}
 
 
 /**
  * Tests that the cloud icon should not show if an error state has been
  * returned (in this case `CannotGetFreeSpace`).
  */
-// @ts-ignore: error TS4111: Property
-// 'toolbarCloudIconShouldNotShowWhenCannotGetFreeSpace' comes from an index
-// signature, so it must be accessed with
-// ['toolbarCloudIconShouldNotShowWhenCannotGetFreeSpace'].
-testcase.toolbarCloudIconShouldNotShowWhenCannotGetFreeSpace = async () => {
+export async function toolbarCloudIconShouldNotShowWhenCannotGetFreeSpace() {
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.hello]);
   await remoteCall.waitForElement(appId, '#cloud-button[hidden]');
@@ -595,17 +504,13 @@
   // currently is done on a 60s poll).
   await sendTestMessage({name: 'forcePinningManagerSpaceCheck'});
   await remoteCall.waitForElement(appId, '#cloud-button[hidden]');
-};
+}
 
 /**
  * Tests that when the cloud icon is pressed the xf-cloud-panel moves into space
  * and resizes correctly.
  */
-// @ts-ignore: error TS4111: Property
-// 'toolbarCloudIconWhenPressedShouldOpenCloudPanel' comes from an index
-// signature, so it must be accessed with
-// ['toolbarCloudIconWhenPressedShouldOpenCloudPanel'].
-testcase.toolbarCloudIconWhenPressedShouldOpenCloudPanel = async () => {
+export async function toolbarCloudIconWhenPressedShouldOpenCloudPanel() {
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.hello]);
   await remoteCall.waitForElement(appId, '#cloud-button[hidden]');
@@ -629,17 +534,13 @@
   // Click the cloud icon and wait for the dialog to move into space.
   await remoteCall.waitAndClickElement(appId, '#cloud-button:not([hidden])');
   await remoteCall.waitForCloudPanelVisible(appId);
-};
+}
 
 /**
  * Tests that the cloud icon should not show if bulk pinning is paused (which
  * represents an offline state) and the user preference is disabled.
  */
-// @ts-ignore: error TS4111: Property
-// 'toolbarCloudIconShouldNotShowWhenPrefDisabled' comes from an index
-// signature, so it must be accessed with
-// ['toolbarCloudIconShouldNotShowWhenPrefDisabled'].
-testcase.toolbarCloudIconShouldNotShowWhenPrefDisabled = async () => {
+export async function toolbarCloudIconShouldNotShowWhenPrefDisabled() {
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], BASIC_DRIVE_ENTRY_SET);
   await remoteCall.waitForElement(appId, '#cloud-button[hidden]');
@@ -664,16 +565,13 @@
   // Assert the stage is `PAUSED` and the cloud button is still hidden.
   await remoteCall.waitForBulkPinningStage('PausedOffline');
   await remoteCall.waitForElement(appId, '#cloud-button[hidden]');
-};
+}
 
 /**
  * Tests that the cloud icon should show if bulk pinning is paused (which
  * represents an offline state) and the user preference is enabled.
  */
-// @ts-ignore: error TS4111: Property
-// 'toolbarCloudIconShouldShowWhenPausedState' comes from an index signature, so
-// it must be accessed with ['toolbarCloudIconShouldShowWhenPausedState'].
-testcase.toolbarCloudIconShouldShowWhenPausedState = async () => {
+export async function toolbarCloudIconShouldShowWhenPausedState() {
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], BASIC_DRIVE_ENTRY_SET);
   await remoteCall.waitForElement(appId, '#cloud-button[hidden]');
@@ -693,18 +591,14 @@
   await remoteCall.waitForElement(appId, '#cloud-button:not([hidden])');
   await remoteCall.waitForElement(
       appId, '#cloud-button > xf-icon[type="bulk_pinning_offline"]');
-};
+}
 
 /**
  * Tests that the cloud icon should show when a Files app window has started.
  * This mainly tests that on startup the bulk pin progress is fetched and
  * doesn't require an async event to show.
  */
-// @ts-ignore: error TS4111: Property
-// 'toolbarCloudIconShouldShowOnStartupEvenIfSyncing' comes from an index
-// signature, so it must be accessed with
-// ['toolbarCloudIconShouldShowOnStartupEvenIfSyncing'].
-testcase.toolbarCloudIconShouldShowOnStartupEvenIfSyncing = async () => {
+export async function toolbarCloudIconShouldShowOnStartupEvenIfSyncing() {
   await addEntries(['drive'], [ENTRIES.hello]);
 
   // Mock the free space returned by spaced to be 4 GB.
@@ -733,8 +627,6 @@
   // Open a new window to the Drive root and ensure the cloud button is not
   // hidden. The cloud button will show on startup as it relies on the bulk
   // pinning preference to be set.
-  // @ts-ignore: error TS2345: Argument of type '{}' is not assignable to
-  // parameter of type 'FilesAppState'.
   const appId = await openNewWindow(RootPath.DRIVE, /*appState=*/ {});
   await remoteCall.waitForElement(appId, '#detail-table');
   await remoteCall.waitForElement(appId, '#cloud-button:not([hidden])');
@@ -746,17 +638,13 @@
   // to ensure its done prior to the 60s free disk space check.
   await remoteCall.waitForCloudPanelState(
       appId, /*items=*/ 1, /*percentage=*/ 100);
-};
+}
 
 /**
  * Tests that the cloud icon should show if bulk pinning is paused due to being
  * on a metered network.
  */
-// @ts-ignore: error TS4111: Property
-// 'toolbarCloudIconShouldShowWhenOnMeteredNetwork' comes from an index
-// signature, so it must be accessed with
-// ['toolbarCloudIconShouldShowWhenOnMeteredNetwork'].
-testcase.toolbarCloudIconShouldShowWhenOnMeteredNetwork = async () => {
+export async function toolbarCloudIconShouldShowWhenOnMeteredNetwork() {
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.hello]);
   await remoteCall.waitForElement(appId, '#cloud-button[hidden]');
@@ -774,4 +662,4 @@
   await remoteCall.waitForElement(appId, '#cloud-button:not([hidden])');
   await remoteCall.waitForElement(
       appId, '#cloud-button > xf-icon[type="cloud_paused"]');
-};
+}
diff --git a/ui/file_manager/integration_tests/file_manager/zip_files.js b/ui/file_manager/integration_tests/file_manager/zip_files.ts
similarity index 85%
rename from ui/file_manager/integration_tests/file_manager/zip_files.js
rename to ui/file_manager/integration_tests/file_manager/zip_files.ts
index 2deb18b..c35a137 100644
--- a/ui/file_manager/integration_tests/file_manager/zip_files.js
+++ b/ui/file_manager/integration_tests/file_manager/zip_files.ts
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import {addEntries, ENTRIES, expectHistogramTotalCount, getCaller, pending, repeatUntil, RootPath, sendTestMessage} from '../test_util.js';
-import {testcase} from '../testcase.js';
 
 import {remoteCall, setupAndWaitUntilReady} from './background.js';
 import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
@@ -39,9 +38,7 @@
 /**
  * Tests ZIP mounting from Downloads.
  */
-// @ts-ignore: error TS4111: Property 'zipFileOpenDownloads' comes from an index
-// signature, so it must be accessed with ['zipFileOpenDownloads'].
-testcase.zipFileOpenDownloads = async () => {
+export async function zipFileOpenDownloads() {
   await sendTestMessage({
     name: 'expectFileTask',
     fileNames: [ENTRIES.zipArchive.targetPath],
@@ -64,14 +61,12 @@
   // Check: the zip file content should be shown (unzip).
   const files = getUnzippedFileListRowEntries();
   await remoteCall.waitForFiles(appId, files, {'ignoreLastModifiedTime': true});
-};
+}
 
 /**
  * Tests that Files app's ZIP mounting notifies FileTasks when mounted.
  */
-// @ts-ignore: error TS4111: Property 'zipNotifyFileTasks' comes from an index
-// signature, so it must be accessed with ['zipNotifyFileTasks'].
-testcase.zipNotifyFileTasks = async () => {
+export async function zipNotifyFileTasks() {
   await sendTestMessage({
     name: 'expectFileTask',
     fileNames: [ENTRIES.zipArchive.targetPath],
@@ -89,14 +84,12 @@
 
   // Wait for the zip archive to mount.
   await remoteCall.waitForElement(appId, `[scan-completed="archive.zip"]`);
-};
+}
 
 /**
  * Tests ZIP mounting from Google Drive.
  */
-// @ts-ignore: error TS4111: Property 'zipFileOpenDrive' comes from an index
-// signature, so it must be accessed with ['zipFileOpenDrive'].
-testcase.zipFileOpenDrive = async () => {
+export async function zipFileOpenDrive() {
   await sendTestMessage({
     name: 'expectFileTask',
     fileNames: [ENTRIES.zipArchive.targetPath],
@@ -119,14 +112,12 @@
   // Check: the zip file content should be shown (unzip).
   const files = getUnzippedFileListRowEntries();
   await remoteCall.waitForFiles(appId, files, {'ignoreLastModifiedTime': true});
-};
+}
 
 /**
  * Tests ZIP mounting from a removable USB volume.
  */
-// @ts-ignore: error TS4111: Property 'zipFileOpenUsb' comes from an index
-// signature, so it must be accessed with ['zipFileOpenUsb'].
-testcase.zipFileOpenUsb = async () => {
+export async function zipFileOpenUsb() {
   await sendTestMessage({
     name: 'expectFileTask',
     fileNames: [ENTRIES.zipArchive.targetPath],
@@ -163,7 +154,7 @@
   // Check: the zip file content should be shown (unzip).
   const files = getUnzippedFileListRowEntries();
   await remoteCall.waitForFiles(appId, files, {'ignoreLastModifiedTime': true});
-};
+}
 
 /**
  * Returns the expected file list rows after invoking the 'Zip selection' menu
@@ -179,9 +170,7 @@
 /**
  * Tests creating a ZIP file on Downloads.
  */
-// @ts-ignore: error TS4111: Property 'zipCreateFileDownloads' comes from an
-// index signature, so it must be accessed with ['zipCreateFileDownloads'].
-testcase.zipCreateFileDownloads = async () => {
+export async function zipCreateFileDownloads() {
   // Open Files app on Downloads containing ENTRIES.photos.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.photos], []);
@@ -210,14 +199,12 @@
 
   // Check: a zip time histogram value should have been recorded.
   await expectHistogramTotalCount(ZipCreationTimeHistogramName, 1);
-};
+}
 
 /**
  * Tests creating a ZIP file on Drive.
  */
-// @ts-ignore: error TS4111: Property 'zipCreateFileDrive' comes from an index
-// signature, so it must be accessed with ['zipCreateFileDrive'].
-testcase.zipCreateFileDrive = async () => {
+export async function zipCreateFileDrive() {
   // Open Files app on Drive containing ENTRIES.photos.
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.photos]);
@@ -246,14 +233,12 @@
 
   // Check: a zip time histogram value should have been recorded.
   await expectHistogramTotalCount(ZipCreationTimeHistogramName, 1);
-};
+}
 
 /**
  * Tests creating a ZIP file containing an Office file on Drive.
  */
-// @ts-ignore: error TS4111: Property 'zipCreateFileDriveOffice' comes from an
-// index signature, so it must be accessed with ['zipCreateFileDriveOffice'].
-testcase.zipCreateFileDriveOffice = async () => {
+export async function zipCreateFileDriveOffice() {
   // Open Files app on Drive containing ENTRIES.photos and ENTRIES.docxFile.
   const appId = await setupAndWaitUntilReady(
       RootPath.DRIVE, [], [ENTRIES.photos, ENTRIES.docxFile]);
@@ -286,15 +271,12 @@
 
   // Check: a zip time histogram value should have been recorded.
   await expectHistogramTotalCount(ZipCreationTimeHistogramName, 1);
-};
+}
 
 /**
  * Tests that creating a ZIP file containing an encrypted file is disabled.
  */
-// @ts-ignore: error TS4111: Property 'zipDoesntCreateFileEncrypted' comes from
-// an index signature, so it must be accessed with
-// ['zipDoesntCreateFileEncrypted'].
-testcase.zipDoesntCreateFileEncrypted = async () => {
+export async function zipDoesntCreateFileEncrypted() {
   // Open Files app on Drive containing a test CSE file.
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.testCSEFile]);
@@ -316,17 +298,13 @@
   const element =
       await remoteCall.waitForElement(appId, '[command="#zip-selection"]');
 
-  // @ts-ignore: error TS4111: Property 'disabled' comes from an index
-  // signature, so it must be accessed with ['disabled'].
-  chrome.test.assertEq('disabled', element.attributes.disabled);
-};
+  chrome.test.assertEq('disabled', element.attributes['disabled']);
+}
 
 /**
  * Tests creating a ZIP file on a removable USB volume.
  */
-// @ts-ignore: error TS4111: Property 'zipCreateFileUsb' comes from an index
-// signature, so it must be accessed with ['zipCreateFileUsb'].
-testcase.zipCreateFileUsb = async () => {
+export async function zipCreateFileUsb() {
   // Open Files app on Drive.
   const appId =
       await setupAndWaitUntilReady(RootPath.DRIVE, [], [ENTRIES.beautiful]);
@@ -369,14 +347,12 @@
 
   // Check: a zip time histogram value should have been recorded.
   await expectHistogramTotalCount(ZipCreationTimeHistogramName, 1);
-};
+}
 
 /**
  * Tests that extraction of a ZIP archive produces a feedback panel.
  */
-// @ts-ignore: error TS4111: Property 'zipExtractShowPanel' comes from an index
-// signature, so it must be accessed with ['zipExtractShowPanel'].
-testcase.zipExtractShowPanel = async () => {
+export async function zipExtractShowPanel() {
   const entry = ENTRIES.zipArchive;
   const targetDirectoryName = entry.nameText.split('.')[0];
 
@@ -391,7 +367,6 @@
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, [entry], []);
 
   // Select the file.
-  // @ts-ignore: error TS18048: 'entry' is possibly 'undefined'.
   await remoteCall.waitUntilSelected(appId, entry.nameText);
 
   // Right-click the selected file.
@@ -414,14 +389,11 @@
       'fakeMouseClick failed');
 
   // Check that the error appears in the feedback panel.
-  let element = {};
   const caller = getCaller();
   await repeatUntil(async () => {
-    element = await remoteCall.waitForElement(
+    const element = await remoteCall.waitForElement(
         appId, ['#progress-panel', 'xf-panel-item']);
     const expectedMsg = `Extracting ${entry.nameText} to Downloads`;
-    // @ts-ignore: error TS2339: Property 'attributes' does not exist on type
-    // '{}'.
     const actualMsg = element.attributes['primary-text'];
 
     if (actualMsg === expectedMsg) {
@@ -435,29 +407,25 @@
 
   // Check: a extract archive status histogram value should have been recorded.
   await expectHistogramTotalCount(ExtractArchiveStatusHistogramName, 1);
-};
+}
 
 /**
  * Tests that extraction of a multiple ZIP archives produces the correct
  * feedback panel string.
  */
-// @ts-ignore: error TS4111: Property 'zipExtractShowMultiPanel' comes from an
-// index signature, so it must be accessed with ['zipExtractShowMultiPanel'].
-testcase.zipExtractShowMultiPanel = async () => {
+export async function zipExtractShowMultiPanel() {
   const entries = COMPLEX_ZIP_ENTRY_SET;
 
   // Make sure the test extension handles the new window creation(s) properly.
-  let entry = entries[2];  // ENTRIES.zipArchive.
-  // @ts-ignore: error TS18048: 'entry' is possibly 'undefined'.
-  let targetDirectoryName = entry.nameText.split('.')[0];
+  let entry = entries[2]!;  // ENTRIES.zipArchive.
+  let targetDirectoryName = entry.nameText.split('.')[0]!;
   await sendTestMessage({
     name: 'expectFileTask',
     fileNames: [targetDirectoryName],
     openType: 'launch',
   });
-  entry = entries[3];  // ENTRIES.zipSJISArchive.
-  // @ts-ignore: error TS18048: 'entry' is possibly 'undefined'.
-  targetDirectoryName = entry.nameText.split('.')[0];
+  entry = entries[3]!;  // ENTRIES.zipSJISArchive.
+  targetDirectoryName = entry.nameText.split('.')[0]!;
   await sendTestMessage({
     name: 'expectFileTask',
     fileNames: [targetDirectoryName],
@@ -493,14 +461,11 @@
       'fakeMouseClick failed');
 
   // Check that the error appears in the feedback panel.
-  let element = {};
   const caller = getCaller();
   await repeatUntil(async () => {
-    element = await remoteCall.waitForElement(
+    const element = await remoteCall.waitForElement(
         appId, ['#progress-panel', 'xf-panel-item']);
     const expectedMsg = `Extracting 2 items…`;
-    // @ts-ignore: error TS2339: Property 'attributes' does not exist on type
-    // '{}'.
     const actualMsg = element.attributes['primary-text'];
 
     if (actualMsg === expectedMsg) {
@@ -514,22 +479,19 @@
 
   // Check: a extract archive status histogram value should have been recorded.
   await expectHistogramTotalCount(ExtractArchiveStatusHistogramName, 1);
-};
+}
 
 /**
  * Tests that various selections enable/hide the correct menu items.
  */
-// @ts-ignore: error TS4111: Property 'zipExtractSelectionMenus' comes from an
-// index signature, so it must be accessed with ['zipExtractSelectionMenus'].
-testcase.zipExtractSelectionMenus = async () => {
+export async function zipExtractSelectionMenus() {
   const entries = BASIC_ZIP_ENTRY_SET;
 
   // Open files app.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, entries, []);
 
   // Select the first file (ENTRIES.hello).
-  // @ts-ignore: error TS2532: Object is possibly 'undefined'.
-  await remoteCall.waitUntilSelected(appId, entries[0].nameText);
+  await remoteCall.waitUntilSelected(appId, entries[0]!.nameText);
 
   // Right-click the selected file.
   chrome.test.assertTrue(
@@ -554,8 +516,7 @@
       'fakeMouseClick failed');
 
   // Select the third file (ENTRIES.zipArchive).
-  // @ts-ignore: error TS2532: Object is possibly 'undefined'.
-  await remoteCall.waitUntilSelected(appId, entries[2].nameText);
+  await remoteCall.waitUntilSelected(appId, entries[2]!.nameText);
 
   // Right-click the selected file.
   chrome.test.assertTrue(
@@ -629,14 +590,12 @@
   // Check: the Zip selection menu item should be visible.
   await remoteCall.waitForElement(
       appId, '[command="#zip-selection"]:not([hidden])');
-};
+}
 
 /**
  * Tests that extraction of a ZIP archive generates correct output files.
  */
-// @ts-ignore: error TS4111: Property 'zipExtractCheckContent' comes from an
-// index signature, so it must be accessed with ['zipExtractCheckContent'].
-testcase.zipExtractCheckContent = async () => {
+export async function zipExtractCheckContent() {
   const entry = ENTRIES.zipArchive;
   const targetDirectoryName = entry.nameText.split('.')[0];
 
@@ -685,14 +644,12 @@
 
   // Check: a extract archive status histogram value should have been recorded.
   await expectHistogramTotalCount(ExtractArchiveStatusHistogramName, 1);
-};
+}
 
 /**
  * Tests that repeated extraction of a ZIP archive generates extra directories.
  */
-// @ts-ignore: error TS4111: Property 'zipExtractCheckDuplicates' comes from an
-// index signature, so it must be accessed with ['zipExtractCheckDuplicates'].
-testcase.zipExtractCheckDuplicates = async () => {
+export async function zipExtractCheckDuplicates() {
   const entry = ENTRIES.zipArchive;
   const directory = entry.nameText.split('.')[0];
 
@@ -760,14 +717,12 @@
 
   // Check: 2 extract archive status histogram value should have been recorded.
   await expectHistogramTotalCount(ExtractArchiveStatusHistogramName, 2);
-};
+}
 
 /**
  * Tests extraction of a ZIP archive can detect and unpack filename encodings.
  */
-// @ts-ignore: error TS4111: Property 'zipExtractCheckEncodings' comes from an
-// index signature, so it must be accessed with ['zipExtractCheckEncodings'].
-testcase.zipExtractCheckEncodings = async () => {
+export async function zipExtractCheckEncodings() {
   const entry = ENTRIES.zipSJISArchive;
   const targetDirectoryName = entry.nameText.split('.')[0];
 
@@ -815,14 +770,12 @@
 
   // Check: a extract archive status histogram value should have been recorded.
   await expectHistogramTotalCount(ExtractArchiveStatusHistogramName, 1);
-};
+}
 
 /**
  * Tests extract option menu item has proper a11y labels.
  */
-// @ts-ignore: error TS4111: Property 'zipExtractA11y' comes from an index
-// signature, so it must be accessed with ['zipExtractA11y'].
-testcase.zipExtractA11y = async () => {
+export async function zipExtractA11y() {
   const entry = ENTRIES.zipArchive;
 
   // Open files app.
@@ -844,14 +797,12 @@
   // NB: It's sufficient to check the ARIA role attribute is set correctly.
   await remoteCall.waitForElement(
       appId, '[command="#extract-all"][role="menuitem"]');
-};
+}
 
 /**
  * Tests extraction of a ZIP archive fails if there's not enough disk space.
  */
-// @ts-ignore: error TS4111: Property 'zipExtractNotEnoughSpace' comes from an
-// index signature, so it must be accessed with ['zipExtractNotEnoughSpace'].
-testcase.zipExtractNotEnoughSpace = async () => {
+export async function zipExtractNotEnoughSpace() {
   const entry = ENTRIES.zipExtArchive;  // 120TB fake archive.
 
   // Open files app.
@@ -876,14 +827,11 @@
       'fakeMouseClick failed');
 
   // Check: Error panel appears.
-  let element = {};
   const caller = getCaller();
   await repeatUntil(async () => {
-    element = await remoteCall.waitForElement(
+    const element = await remoteCall.waitForElement(
         appId, ['#progress-panel', 'xf-panel-item']);
     const expectedMsg = 'Extract operation failed. There is not enough space.';
-    // @ts-ignore: error TS2339: Property 'attributes' does not exist on type
-    // '{}'.
     const actualMsg = element.attributes['primary-text'];
 
     if (actualMsg === expectedMsg) {
@@ -897,14 +845,12 @@
 
   // Check: a extract archive status histogram value should have been recorded.
   await expectHistogramTotalCount(ExtractArchiveStatusHistogramName, 1);
-};
+}
 
 /**
  * Tests that extraction of a ZIP archive from a read only volume succeeds.
  */
-// @ts-ignore: error TS4111: Property 'zipExtractFromReadOnly' comes from an
-// index signature, so it must be accessed with ['zipExtractFromReadOnly'].
-testcase.zipExtractFromReadOnly = async () => {
+export async function zipExtractFromReadOnly() {
   const entry = ENTRIES.readOnlyZipFile;
   const targetDirectoryName = entry.nameText.split('.')[0];
 
@@ -966,4 +912,4 @@
   await remoteCall.waitForElement(appId, '#file-list [file-name="folder"]');
   await remoteCall.waitForElement(appId, '#file-list [file-name="text.txt"]');
   await remoteCall.waitForElement(appId, '#file-list [file-name="image.png"]');
-};
+}
diff --git a/ui/file_manager/integration_tests/remote_call.js b/ui/file_manager/integration_tests/remote_call.js
index 3cd222a8..1d4bfacd 100644
--- a/ui/file_manager/integration_tests/remote_call.js
+++ b/ui/file_manager/integration_tests/remote_call.js
@@ -268,8 +268,8 @@
    *
    * @param {string} funcName Name of remote test function to be executed.
    * @param {?string} appId App window Id.
-   * @param {function(Object):boolean|boolean|Object} expectedResult An value to
-   *     be checked against the return value of |funcName| or a callback that
+   * @param {(function(Object):boolean)|boolean|Object} expectedResult An value
+   *     to be checked against the return value of |funcName| or a callback that
    *     receives the return value of |funcName| and returns true if the result
    *     is the expected value.
    * @param {?Array<*>=} args Arguments to be provided to |funcName| when
diff --git a/ui/file_manager/integration_tests/testcase.ts b/ui/file_manager/integration_tests/testcase.ts
index a41e92e..351c4f1f 100644
--- a/ui/file_manager/integration_tests/testcase.ts
+++ b/ui/file_manager/integration_tests/testcase.ts
@@ -4,9 +4,9 @@
 
 import * as androidPhotosTests from './file_manager/android_photos.js';
 // clang-format off
-// import * as breadcrumbsTests from './file_manager/breadcrumbs.js';
+import * as breadcrumbsTests from './file_manager/breadcrumbs.js';
 // import * as contextMenuTests from './file_manager/context_menu.js';
-// import * as copyBetweenWindowsTests from './file_manager/copy_between_windows.js';
+import * as copyBetweenWindowsTests from './file_manager/copy_between_windows.js';
 // import * as createNewFolderTests from './file_manager/create_new_folder.js';
 // import * as crostiniTests from './file_manager/crostini.js';
 import * as directoryTreeTests from './file_manager/directory_tree.js';
@@ -15,43 +15,43 @@
 // import * as dlpEnterpriseConnectorsTests from './file_manager/dlp_enterprise_connectors.js';
 // import * as driveSpecificTests from './file_manager/drive_specific.js';
 // import * as fileDialogTests from './file_manager/file_dialog.js';
-// import * as fileDisplayTests from './file_manager/file_display.js';
-// import * as fileListTests from './file_manager/file_list.js';
+import * as fileDisplayTests from './file_manager/file_display.js';
+import * as fileListTests from './file_manager/file_list.js';
 // import * as fileTransferConnectorTests from './file_manager/file_transfer_connector.js';
 import * as filesTooltipTests from './file_manager/files_tooltip.js';
 // import * as folderShortcutsTests from './file_manager/folder_shortcuts.js';
-// import * as formatDialogTests from './file_manager/format_dialog.js';
+import * as formatDialogTests from './file_manager/format_dialog.js';
 // import * as gearMenuTests from './file_manager/gear_menu.js';
-// import * as gridViewTests from './file_manager/grid_view.js';
-// import * as guestOsTests from './file_manager/guest_os.js';
+import * as gridViewTests from './file_manager/grid_view.js';
+import * as guestOsTests from './file_manager/guest_os.js';
 // import * as holdingSpaceTests from './file_manager/holding_space.js';
 // import * as installLinuxPackageDialogTests from './file_manager/install_linux_package_dialog.js';
-// import * as keyboardOperationsTests from './file_manager/keyboard_operations.js';
+import * as keyboardOperationsTests from './file_manager/keyboard_operations.js';
 import * as manageDialogTests from './file_manager/manage_dialog.js';
-// import * as metadataTests from './file_manager/metadata.js';
+import * as metadataTests from './file_manager/metadata.js';
 // import * as metricsTests from './file_manager/metrics.js';
-// import * as myFilesTests from './file_manager/my_files.js';
+import * as myFilesTests from './file_manager/my_files.js';
 import * as navigationTests from './file_manager/navigation.js';
-// import * as officeTests from './file_manager/office.js';
+import * as officeTests from './file_manager/office.js';
 import * as openAudioMediaAppTests from './file_manager/open_audio_media_app.js';
 import * as openFilesInWebDriveTests from './file_manager/open_files_in_web_drive.js';
 import * as openImageMediaAppTests from './file_manager/open_image_media_app.js';
 import * as openSniffedFilesTests from './file_manager/open_sniffed_files.js';
 import * as openVideoMediaAppTests from './file_manager/open_video_media_app.js';
-// import * as providersTests from './file_manager/providers.js';
-// import * as quickViewTests from './file_manager/quick_view.js';
+import * as providersTests from './file_manager/providers.js';
+import * as quickViewTests from './file_manager/quick_view.js';
 // import * as recentsTests from './file_manager/recents.js';
 // import * as restorePrefsTests from './file_manager/restore_prefs.js';
 // import * as searchTests from './file_manager/search.js';
 import * as shareTests from './file_manager/share.js';
-// import * as sortColumnsTests from './file_manager/sort_columns.js';
+import * as sortColumnsTests from './file_manager/sort_columns.js';
 // import * as tabIndexTests from './file_manager/tab_index.js';
-// import * as tasksTests from './file_manager/tasks.js';
-// import * as toolbarTests from './file_manager/toolbar.js';
+import * as tasksTests from './file_manager/tasks.js';
+import * as toolbarTests from './file_manager/toolbar.js';
 // import * as transferTests from './file_manager/transfer.js';
 import * as trashTests from './file_manager/trash.js';
 import * as traverseTests from './file_manager/traverse.js';
-// import * as zipFilesTests from './file_manager/zip_files.js';
+import * as zipFilesTests from './file_manager/zip_files.js';
 // clang-format on
 
 export type TestFunctionName = string;
@@ -62,9 +62,9 @@
  */
 export const testcase: Record<TestFunctionName, TestFunction> = {
   ...androidPhotosTests,
-  // ...breadcrumbsTests,
+  ...breadcrumbsTests,
   // ...contextMenuTests,
-  // ...copyBetweenWindowsTests,
+  ...copyBetweenWindowsTests,
   // ...createNewFolderTests,
   // ...crostiniTests,
   ...directoryTreeTests,
@@ -73,41 +73,41 @@
   // ...dlpEnterpriseConnectorsTests,
   // ...driveSpecificTests,
   // ...fileDialogTests,
-  // ...fileDisplayTests,
-  // ...fileListTests,
+  ...fileDisplayTests,
+  ...fileListTests,
   // ...fileTransferConnectorTests,
   ...filesTooltipTests,
   // ...folderShortcutsTests,
-  // ...formatDialogTests,
+  ...formatDialogTests,
   // ...gearMenuTests,
-  // ...gridViewTests,
-  // ...guestOsTests,
+  ...gridViewTests,
+  ...guestOsTests,
   // ...holdingSpaceTests,
   // ...installLinuxPackageDialogTests,
-  // ...keyboardOperationsTests,
+  ...keyboardOperationsTests,
   ...manageDialogTests,
-  // ...metadataTests,
+  ...metadataTests,
   // ...metricsTests,
-  // ...myFilesTests,
+  ...myFilesTests,
   ...navigationTests,
-  // ...officeTests,
+  ...officeTests,
   ...openAudioMediaAppTests,
   ...openFilesInWebDriveTests,
   ...openImageMediaAppTests,
   ...openSniffedFilesTests,
   ...openVideoMediaAppTests,
-  // ...providersTests,
-  // ...quickViewTests,
+  ...providersTests,
+  ...quickViewTests,
   // ...recentsTests,
   // ...restorePrefsTests,
   // ...searchTests,
   ...shareTests,
-  // ...sortColumnsTests,
+  ...sortColumnsTests,
   // ...tabIndexTests,
-  // ...tasksTests,
-  // ...toolbarTests,
+  ...tasksTests,
+  ...toolbarTests,
   // ...transferTests,
   ...trashTests,
   ...traverseTests,
-  // ...zipFilesTests,
+  ...zipFilesTests,
 };
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index 358af34..9ea053f 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -1174,6 +1174,10 @@
     ]
   }
 
+  if (is_chromeos_lacros) {
+    deps += [ "//chromeos/startup" ]
+  }
+
   if (use_aura) {
     sources += [
       "corewm/tooltip_controller_test_helper.cc",
diff --git a/ui/views/test/DEPS b/ui/views/test/DEPS
index 220fb85d..38a40c7 100644
--- a/ui/views/test/DEPS
+++ b/ui/views/test/DEPS
@@ -4,6 +4,7 @@
 
 specific_include_rules = {
   "views_test_base\.cc": [
+    "+chromeos/startup",
     "+mojo/core/embedder",
     "+ui/gl",
   ],
diff --git a/ui/views/test/views_test_base.cc b/ui/views/test/views_test_base.cc
index c08f18d..6c93ddc2 100644
--- a/ui/views/test/views_test_base.cc
+++ b/ui/views/test/views_test_base.cc
@@ -30,6 +30,10 @@
 #include "ui/views/widget/native_widget_mac.h"
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chromeos/startup/browser_params_proxy.h"
+#endif
+
 namespace views {
 
 namespace {
@@ -42,7 +46,16 @@
 
 ViewsTestBase::ViewsTestBase(
     std::unique_ptr<base::test::TaskEnvironment> task_environment)
-    : task_environment_(std::move(task_environment)) {}
+    : task_environment_(std::move(task_environment)) {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  // ViewsTestBase is also used by a number of Lacros interactive_ui_tests.
+  // Lacros interactive_ui_tests are expected to process the
+  // --lacros-mojo-socket-for-testing command line switch in order to set up
+  // crosapi. However, ViewsTestBase doesn't implement that as it's also used by
+  // other tests and doesn't need crosapi. Hence disable crosapi explicitly.
+  chromeos::BrowserParamsProxy::DisableCrosapiForTesting();
+#endif
+}
 
 ViewsTestBase::~ViewsTestBase() {
   CHECK(setup_called_)
diff --git a/v8 b/v8
index fb8803a8..ee50bc8 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit fb8803a8ccd3ccbca07e40deb509505b665af175
+Subproject commit ee50bc8415cbf6756bfa4cb078155e498c89a4f7